Hi!

I finally finished my dateedit widget, it is not yet thoroughly tested
but all I tested worked fine.

It is not an exact port of the libgnomeui widget, since I think some
changes make more sense for a python widget (e.g. datetime as return
type instead of epoch).

I also hope to make this a bug free and documented widget, since I
couldn't find any simple but still feature complete custom gobject in
python. So I hope many people will comment on this so that it can be
used as reference for other developers.


Things which still bug me:

- constructor
The original gnome-dateedit has two constructors, unfortunately with
overlapping but different parameters, so they can't just be merged into
one python constructor with named parameters.
So far I only got one response on this problem.

- destruction
The original version has these additional functions:
free_resources, mnemonic_activate, destroy, finalize
I don't know what of this is needed for python, so some comments would
be great.

- show_all
if you add this widget to a window and then do a window.show_all() the
dateedit widget will show the time entry, even though it should be
hidden by the show_time = False flag.
I can't see the difference to the c widget so I need help here too!

- todos
I have several todos at various places in the source, most are just
spots which should be verified by other developers

Thanks a lot, Fabian

P.s. I will set up a webpage for the widget and announce it once more at
the list when the above stuff is resolved.
# Python GTK+ date and time entry widget.
# Copyright (C) 2005  Fabian Sturm
#
# ported from the libgnomeui/gnome-dateedit.c
# with changes where it makes sense for python (e.g. return types)
#
# This widget is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this widget; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA

import sys
import time
import datetime

import pygtk
pygtk.require('2.0')
import gobject
import gtk
from gtk import gdk



(DATE_EDIT_SHOW_TIME, 
 DATE_EDIT_24_HR, 
 DATE_EDIT_WEEK_STARTS_ON_MONDAY) = [1 << 0, 1 << 1, 1 << 2]
 
 # gnome_date_edit_new:
 # @the_time: date and time to be displayed on the widget
 # @show_time: whether time should be displayed
 # @use_24_format: whether 24-hour format is desired for the time display.
 #
 # Description: Creates a new #GnomeDateEdit widget which can be used
 # to provide an easy to use way for entering dates and times.
 # If @the_time is 0 then current time is used.
 #
 # Returns: a new #GnomeDateEdit widget.
 # Todo: missing the version with the flags in the constructor
class DateEdit(gtk.HBox):
    __gtype_name__ = 'DateEdit'
    
    __gproperties__ = {
        'time' : (gobject.TYPE_PYOBJECT,                     # type
                  'Time',                                    # nick name
                  'The time currently selected',             # description
                  gobject.PARAM_READWRITE),                  # flags
       
       # must use int as replacement for TYPE_FLAGS since not available in pygtk
        'dateedit_flags' : (gobject.TYPE_INT,
                  'DateEdit Flags',
                  'Flags for how DateEdit looks',
                  0,                                        # min value
                  sys.maxint,                               # max value
                  DATE_EDIT_SHOW_TIME,                      # default value
                  gobject.PARAM_READWRITE),
                  
        'lower_hour' : (gobject.TYPE_INT,
                  'Lower Hour',
                  'Lower hour in the time popup selector',
                  0,
                  24,
                  7,
                  gobject.PARAM_READWRITE),
                  
        'upper_hour' : (gobject.TYPE_INT,
                  'Upper Hour',
                  'Upper hour in the time popup selector',
                  0,
                  24,
                  19,
                  gobject.PARAM_READWRITE),
                  
        'initial_time' : (gobject.TYPE_PYOBJECT,
                  'Initial Time',
                  'The initial time',
                  gobject.PARAM_READABLE)
    }

    __gsignals__ = {
        'time_changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                          ()),
        'date_changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                          ()),                          
    
    }

    def __init__(self, the_time = None, show_time = True, use_24_format = True):
        gtk.HBox.__init__(self)

        # preset values
        self.__lower_hour = 7;
        self.__upper_hour = 19;
        self.__flags = DATE_EDIT_SHOW_TIME

        # the date entry
        self.__date_entry = gtk.Entry()
        self.__date_entry.set_size_request(90, -1)
        self.pack_start(self.__date_entry, True, True, 0)
        self.__date_entry.show()
        
        # the date button
        self.__date_button = gtk.Button()
        self.__date_button.connect('clicked', self.date_button_clicked)
        self.pack_start(self.__date_button, False, False, 0)
        hbox = gtk.HBox(False, 3)
        self.__date_button.add(hbox)
        hbox.show()
        # calendar label, only show if the date editor has a time field
        self.__cal_label = gtk.Label('Calendar')
        self.__cal_label.set_alignment(0.0, 0.5)
        hbox.pack_start(self.__cal_label, True, True, 0)
        self.__cal_label.show()
        # the down arrow
        arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_OUT)
        hbox.pack_start(arrow, True, False, 0)
        arrow.show()
        # finally show the button
        self.__date_button.show()
        
        # the time entry
        self.__time_entry = gtk.Entry()
        self.__time_entry.set_max_length(12)
        self.__time_entry.set_size_request(88, -1)
        self.pack_start(self.__time_entry, True, True, 0)
            
        # the time popup menu
        self.__time_popup = gtk.OptionMenu()
        self.pack_start(self.__time_popup, False, False, 0)
        self.connect('realize', self.fill_time_popup)
        
        if show_time == True:
            self.__time_entry.show()
            self.__time_popup.show()
        
        # the calendar popup
        self.__cal_popup = gtk.Window(gtk.WINDOW_POPUP)
        self.__cal_popup.set_events(self.__cal_popup.get_events() | gdk.KEY_PRESS_MASK)
        self.__cal_popup.connect('delete_event', self.delete_popup)
        self.__cal_popup.connect('key_press_event', self.key_press_popup)
        self.__cal_popup.connect('button_press_event', self.button_press_popup)
        self.__cal_popup.set_resizable(False) # Todo: Needed?
        frame = gtk.Frame()
        frame.set_shadow_type(gtk.SHADOW_OUT)
        self.__cal_popup.add(frame)
        frame.show()
        # the calendar
        self.__calendar = gtk.Calendar()
        self.__calendar.display_options(gtk.CALENDAR_SHOW_DAY_NAMES 
                                        | gtk.CALENDAR_SHOW_HEADING)
        self.__calendar.connect('day-selected', self.day_selected)
        self.__calendar.connect('day-selected-double-click', self.day_selected_double_click)
        frame.add(self.__calendar)
        self.__calendar.show()

        # set user provided flags, will toggle visibility of some widgets
        new_flags = 0
        if show_time is True:
            new_flags |= DATE_EDIT_SHOW_TIME
        if use_24_format is True:
            new_flags |= DATE_EDIT_24_HR
        self.set_flags(new_flags)

        # set provided date and time
        self.set_time(the_time)
        

    def popup_grab_on_window(self, window, activate_time):
        if gdk.pointer_grab(window, True, gdk.BUTTON_PRESS_MASK 
                                          | gdk.BUTTON_RELEASE_MASK
                                          | gdk.POINTER_MOTION_MASK, 
                            None, None, activate_time) == 0:
                if gdk.keyboard_grab (window, True, activate_time) == 0:
                    return True
                else:
                    gdk.pointer_ungrab(activate_time)
                    return False
        return False


    def position_popup(self):
        req = self.__cal_popup.size_request()
        (x,y) = gdk.Window.get_origin(self.__date_button.window)

        x += self.__date_button.allocation.x
        y += self.__date_button.allocation.y
        bwidth = self.__date_button.allocation.width
        bheight = self.__date_button.allocation.height

        x += bwidth - req[0]
        y += bheight

        if x < 0: x = 0
        if y < 0: y = 0
        
        self.__cal_popup.move(x,y)
        
        
    def date_button_clicked(self, widget, data=None):
        # Temporarily grab pointer and keyboard on a window we know exists        
        if not self.popup_grab_on_window(widget.window, gtk.get_current_event_time()):
            print 'error during grab'
            return
        
        # set calendar date
        str = self.__date_entry.get_text()
        mtime = time.strptime(str, '%x')
        self.__calendar.select_month(mtime.tm_mon - 1, mtime.tm_year)
        self.__calendar.select_day(mtime.tm_mday)        
        
        # position and show popup window
        self.position_popup()
        self.__cal_popup.grab_add()
        self.__cal_popup.show()
        self.__calendar.grab_focus()
        
        # Now transfer our grabs to the popup window, this should always succed
        self.popup_grab_on_window(self.__cal_popup.window, gtk.get_current_event_time())


    def hide_popup(self):
        self.__cal_popup.hide()
        self.__cal_popup.grab_remove()


    def day_selected(self, widget, data=None):
        (year, month, day) = self.__calendar.get_date()
        month += 1        
        the_time = datetime.date(year, month, day)
        self.__date_entry.set_text(the_time.strftime('%x'))
        self.emit('date-changed')
        
        
    def day_selected_double_click(self, widget, data=None):
        self.hide_popup()


    def key_press_popup(self, widget, data=None):        
        # Todo, Fixme: what is the name of gdk.Escape? missing?
        if data == None or data.keyval != 65307:
            return False

        # Todo: does not work and what does it do anyway?
        # widget.stop_emission_by_name('key_press_event')
        self.hide_popup()
        return True


    # Todo: is this correct?
    def button_press_popup(self, widget, data=None):
        # We don't ask for button press events on the grab widget, so
        # if an event is reported directly to the grab widget, it must
        # be on a window outside the application (and thus we remove
        # the popup window). Otherwise, we check if the widget is a child
        # of the grab widget, and only remove the popup window if it
        # is not.
        if data == None or data.window == None:
            return False
            
        child = data.window.get_user_data()
        if child != widget:
            while child:
                if child == widget:
                    return False
                child = child.parent
                
        self.hide_popup()
        return True


    def delete_popup(self, widget, data=None):
        # Todo: when is this ever called??
        print 'delete_popup'
        self.hide_popup();
        return TRUE;


    def fill_time_popup(self, widget, data=None):
        if self.__lower_hour > self.__upper_hour:
            return
        
        # create menu
        menu = gtk.Menu()
        for i in range(self.__lower_hour, self.__upper_hour + 1):
            the_time = datetime.time(i, 0)
            
            if self.__flags & DATE_EDIT_24_HR:
                label = the_time.strftime('%H:%M')
            else:
                label = the_time.strftime('%I:%M %p')
                
            item = gtk.MenuItem(label)
            menu.append(item)
            item.show()
            
            # create submenu
            submenu = gtk.Menu()
            item.set_submenu(submenu)
            for j in range(0,60,15):
                the_time = datetime.time(i,j)
                
                if self.__flags & DATE_EDIT_24_HR:
                    label = the_time.strftime('%H:%M')
                else:
                    label = the_time.strftime('%I:%M %p')
                # create submenu item
                submenu_item = gtk.MenuItem(label)
                submenu.append(submenu_item)
                # add event handler                
                submenu_item.connect('activate', self.time_selected, label)
                submenu_item.show()
                
        # finally replace current menu with this new one
        self.__time_popup.set_menu(menu)
            
            
    def time_selected(self, widget, data = None):
        self.__time_entry.set_text(data)
        self.emit('time-changed');
        
        
    # properties and their convenience functions    
        
    def get_time(self):
        # Todo: make this more fuzzy
        # get date
        str = self.__date_entry.get_text()
        mdate = time.strptime(str, '%x')
        # get time
        str = self.__time_entry.get_text()
        if self.__flags & DATE_EDIT_24_HR:
            mtime = time.strptime(str, '%H:%M')
        else:
            mtime = time.strptime(str, '%I:%M %p')
        # combine current date with curent times
        mdt = datetime.datetime(mdate.tm_year,
                                mdate.tm_mon,
                                mdate.tm_mday,
                                mtime.tm_hour,
                                mtime.tm_min,
                                mtime.tm_sec
                               )
        return mdt
        

    def set_time(self, the_time):      
        if the_time is None:
            the_time = datetime.datetime.today()
        assert isinstance(the_time, (datetime.datetime, datetime.date))
        # set the date
        self.__initial_time = the_time
        self.__date_entry.set_text(the_time.strftime('%x'))
        # set the time
        if self.__flags & DATE_EDIT_24_HR:
            self.__time_entry.set_text(the_time.strftime('%H:%M'))
        else:
            self.__time_entry.set_text(the_time.strftime('%I:%M %p'))


    # Changes the display flags on an existing date editor widget
    def set_flags(self, flags):
        old_flags = self.__flags
        self.__flags = flags
        
        if (flags & DATE_EDIT_SHOW_TIME) != (old_flags & DATE_EDIT_SHOW_TIME):
            if flags & DATE_EDIT_SHOW_TIME:
                self.__cal_label.show()
                self.__time_entry.show()
                self.__time_popup.show()
            else:
                self.__cal_label.hide()
                self.__time_entry.hide()
                self.__time_popup.hide()
            
        if (flags & DATE_EDIT_24_HR) != (old_flags & DATE_EDIT_24_HR):
            self.fill_time_popup(self)
        
        if (flags & DATE_EDIT_WEEK_STARTS_ON_MONDAY) is (old_flags & DATE_EDIT_WEEK_STARTS_ON_MONDAY):
            if flags & DATE_EDIT_WEEK_STARTS_ON_MONDAY:
                self.__calendar.set_display_options(self.__calendar.get_display_options() | gtk.CALENDAR_WEEK_START_MONDAY)
            else:
                self.__calendar.set_display_options(self.__calendar.get_display_options() & ~gtk.CALENDAR_WEEK_START_MONDAY)


    def set_lower_hour(self, value):
        if value < 0 or value > 24 or value > self.__upper_hour:
            return
        self.__lower_hour = value
        self.fill_time_popup(None)

    def set_upper_hour(self, value):
        if value < 0 or value > 24 or value < self.__lower_hour:
            return
        self.__upper_hour = value
        self.fill_time_popup(None)
        
        
    def get_initial_time(self):
        return self.__initial_time
        
        
        
    # get_properties
    def do_get_property(self, property): 
        if property.name == 'time':            
            return self.get_time()
        elif property.name == 'dateedit-flags':
            return self.__flags
        elif property.name == 'lower-hour': 
            return self.__lower_hour
        elif property.name == 'upper-hour':
            return self.__upper_hour
        elif property.name == 'initial-time':
            return self.get_initial_time()
        else:
            raise AttributeError, 'unknown property %s' % property.name
            
    # set_properties
    def do_set_property(self, property, value):
        if property.name == 'time':
            self.set_time(value)            
        elif property.name == 'dateedit-flags':
            self.set_flags(value)
        elif property.name == 'lower-hour': 
            self.set_lower_hour(value)
        elif property.name == 'upper-hour':
            self.set_upper_hour(value)
        else:
            raise AttributeError, 'unknown property %s' % property.name
            
        
# finally register our new Type        
# Warning, throws an error if a type_flags property is register 
# see bug number #323290
gobject.type_register(DateEdit)



# Test the dateedit widget

def time_changed_cb(widget, data=None):
    print 'time changed'

def clicked_cb(widget, data=None):
    print 'time: ', data.get_property('time')
    print 'date-edit flags: ', data.get_property('dateedit-flags')
    print 'initial-time: ', data.get_property('initial-time')
    print 'lower_hour:', data.get_property('lower-hour')
    print 'upper_hour:', data.get_property('upper-hour')
    data.set_property('lower-hour', 10)
    data.set_property('upper-hour', 23)
    data.set_property('time', datetime.datetime.today())

    
win = gtk.Window()
win.connect('delete-event', gtk.main_quit)

vbox = gtk.VBox()
win.add(vbox)
vbox.show()

d = DateEdit(show_time = False, use_24_format = True)
vbox.add(d)
d.show()

b = gtk.Button('Print and set Time')
vbox.add(b)
b.show()

d.connect('time-changed', time_changed_cb)
b.connect('clicked', clicked_cb, d)

win.show_all() # fixme show_all also shows hidden widgets, investgate widget.set_property("no-show-all", True)

gtk.main()

_______________________________________________
pygtk mailing list   [email protected]
http://www.daa.com.au/mailman/listinfo/pygtk
Read the PyGTK FAQ: http://www.async.com.br/faq/pygtk/

Reply via email to