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/