From c6203b282bc62e632c526275aec6be80a5af8528 Mon Sep 17 00:00:00 2001 From: Zsolt Foldvari Date: Sun, 11 May 2008 16:22:14 +0000 Subject: [PATCH] Reorganize custom widgets into a new 'widgets' package. 4. Blow up 'grampswidgets' module into several smaller modules. Also move 'objectentry' module out from 'widgets' to src/, otherwise there would be circular dependency between the 'Editors' and 'widgets' packages. svn: r10703 --- src/Editors/_EditEvent.py | 3 +- src/Editors/_EditEventRef.py | 4 +- src/Editors/_EditLdsOrd.py | 3 +- src/Editors/__init__.py | 1 - src/Makefile.am | 3 +- src/objectentries.py | 400 +++++ src/widgets/Makefile.am | 13 +- src/widgets/__init__.py | 12 +- src/widgets/buttons.py | 154 ++ src/widgets/expandcollapsearrow.py | 87 ++ src/widgets/labels.py | 205 +++ src/widgets/linkbox.py | 52 + src/widgets/monitoredwidgets.py | 478 ++++++ src/widgets/statusbar.py | 260 ++++ src/widgets/toolbarwidgets.py | 2 +- src/widgets/unused.py | 93 ++ ...ampswidgets.py => validatedmaskedentry.py} | 1292 +---------------- 17 files changed, 1778 insertions(+), 1284 deletions(-) create mode 100644 src/objectentries.py create mode 100644 src/widgets/buttons.py create mode 100644 src/widgets/expandcollapsearrow.py create mode 100644 src/widgets/labels.py create mode 100644 src/widgets/linkbox.py create mode 100644 src/widgets/monitoredwidgets.py create mode 100644 src/widgets/statusbar.py create mode 100644 src/widgets/unused.py rename src/widgets/{grampswidgets.py => validatedmaskedentry.py} (56%) diff --git a/src/Editors/_EditEvent.py b/src/Editors/_EditEvent.py index d8101d98a..ab0e04043 100644 --- a/src/Editors/_EditEvent.py +++ b/src/Editors/_EditEvent.py @@ -44,11 +44,12 @@ import Config import gen.lib import GrampsDisplay from Editors import EditPrimary +from objectentries import PlaceEntry from QuestionDialog import ErrorDialog from DisplayTabs import (SourceEmbedList, NoteTab, GalleryTab, EventBackRefList, AttrEmbedList) -from widgets import (MonitoredEntry, PlaceEntry, PrivacyButton, +from widgets import (MonitoredEntry, PrivacyButton, MonitoredDataType, MonitoredDate) #------------------------------------------------------------------------- diff --git a/src/Editors/_EditEventRef.py b/src/Editors/_EditEventRef.py index bf9c9b3a0..c19731525 100644 --- a/src/Editors/_EditEventRef.py +++ b/src/Editors/_EditEventRef.py @@ -45,10 +45,12 @@ import gen.lib from DisplayTabs import (SourceEmbedList, NoteTab, GalleryTab, EventBackRefList, AttrEmbedList) -from widgets import (PrivacyButton, MonitoredEntry, PlaceEntry, +from widgets import (PrivacyButton, MonitoredEntry, MonitoredDate, MonitoredDataType) from _EditReference import RefTab, EditReference +from objectentries import PlaceEntry + #------------------------------------------------------------------------- # # Constants diff --git a/src/Editors/_EditLdsOrd.py b/src/Editors/_EditLdsOrd.py index 3753cfe23..ac9ace473 100644 --- a/src/Editors/_EditLdsOrd.py +++ b/src/Editors/_EditLdsOrd.py @@ -52,9 +52,10 @@ from BasicUtils import name_displayer import LdsUtils from _EditSecondary import EditSecondary +from objectentries import PlaceEntry from DisplayTabs import SourceEmbedList,NoteTab -from widgets import (PrivacyButton, MonitoredDate, PlaceEntry, +from widgets import (PrivacyButton, MonitoredDate, MonitoredMenu, MonitoredStrMenu) _DATA_MAP = { diff --git a/src/Editors/__init__.py b/src/Editors/__init__.py index ace2ec81b..fb3719020 100644 --- a/src/Editors/__init__.py +++ b/src/Editors/__init__.py @@ -38,4 +38,3 @@ from _EditSourceRef import * from _EditUrl import * from _EditPersonRef import * from _EditChildRef import * - diff --git a/src/Makefile.am b/src/Makefile.am index aa6b9a2da..e3a0763ff 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -81,7 +81,8 @@ gdir_PYTHON = \ ViewManager.py\ UndoHistory.py\ PlaceUtils.py\ - ProgressDialog.py + ProgressDialog.py \ + objectentries.py # Clean up all the byte-compiled files MOSTLYCLEANFILES = *pyc *pyo diff --git a/src/objectentries.py b/src/objectentries.py new file mode 100644 index 000000000..7291cb121 --- /dev/null +++ b/src/objectentries.py @@ -0,0 +1,400 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2007-2008 The Gramps Developers +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import cPickle as pickle + +import logging +_LOG = logging.getLogger(".objectentries") + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +import gtk + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from gen.lib import (Place, MediaObject, Note) +from Editors._EditPlace import EditPlace +from Editors._EditMedia import EditMedia +from Editors._EditNote import EditNote +from Selectors import selector_factory +from DdTargets import DdTargets + + +class ObjEntry: + """ + Handles the selection of a existing or new Object. Supports Drag and Drop + to select the object. + This is the base class to create a real entry + """ + def __init__(self, dbstate, uistate, track, label, set_val, + get_val, add_edt, share): + """Pass the dbstate and uistate and present track. + label is a gtk.Label that shows the persent value + set_val is function that is called when handle changes, use it + to update the calling module + get_val is function that is called to obtain handle from calling + module + share is the gtk.Button to call the object selector or del connect + add_edt is the gtk.Button with add or edit value. Pass None if + this button should not be present. + """ + self.label = label + self.add_edt = add_edt + self.share = share + self.dbstate = dbstate + self.db = dbstate.db + self.get_val = get_val + self.set_val = set_val + self.uistate = uistate + self.track = track + self.tooltips = gtk.Tooltips() + + #connect drag and drop + self._init_dnd() + #set the object specific code + self._init_object() + + #check if valid object: + handle = self.get_val() + if handle: + obj = self.get_from_handle(handle) + if not obj: + #invalid val, set it to None + self.set_val(None) + if self.get_val(): + self.set_button(True) + obj = self.get_from_handle(self.get_val()) + name = self.get_label(obj) + else: + name = u"" + self.set_button(False) + + if self.db.readonly: + if self.add_edt is not None: + self.add_edt.set_sensitive(False) + self.share.set_sensitive(False) + else: + if self.add_edt is not None: + self.add_edt.set_sensitive(True) + self.share.set_sensitive(True) + + if self.add_edt is not None: + self.add_edt.connect('clicked', self.add_edt_clicked) + self.share.connect('clicked', self.share_clicked) + + if not self.db.readonly and not name: + if self.add_edt is None: + self.label.set_text(self.EMPTY_TEXT_RED) + else: + self.label.set_text(self.EMPTY_TEXT) + self.label.set_use_markup(True) + else: + self.label.set_text(name) + + def _init_dnd(self): + """inheriting objects must set this + """ + pass + + def _init_object(self): + """inheriting objects can use this to set extra variables + """ + pass + + def get_from_handle(self, handle): + """ return the object given the hande + inheriting objects must set this + """ + pass + + def get_label(self, object): + """ return the label + inheriting objects must set this + """ + pass + + def after_edit(self, obj): + name = self.get_label(obj) + self.label.set_text(name) + + def add_edt_clicked(self, obj): + """ if value, edit, if no value, call editor on new object + """ + if self.get_val(): + obj = self.get_from_handle(self.get_val()) + self.call_editor(obj) + else: + self.call_editor() + + def call_editor(self, obj): + """inheriting objects must set this + """ + pass + + def call_selector(self): + """inheriting objects must set this + """ + pass + + def drag_data_received(self, widget, context, x, y, selection, info, time): + (drag_type, idval, obj, val) = pickle.loads(selection.data) + + data = self.db.get_place_from_handle(obj) + self.obj_added(data) + + def obj_added(self, data): + """ callback from adding an object to the entry""" + self.set_val(data.handle) + self.label.set_text(self.get_label(data)) + self.set_button(True) + + def share_clicked(self, obj): + """ if value, delete connect, in no value, select existing object + """ + if self.get_val(): + self.set_val(None) + self.label.set_text(self.EMPTY_TEXT) + self.label.set_use_markup(True) + self.set_button(False) + else: + select = self.call_selector() + obj = select.run() + if obj: + self.obj_added(obj) + + def set_button(self, use_add): + """ This sets the correct image to the two buttons. + If False: select icon and add icon + If True: remove icon and edit icon + """ + if self.add_edt is not None: + for i in self.add_edt.get_children(): + self.add_edt.remove(i) + for i in self.share.get_children(): + self.share.remove(i) + + if use_add: + image = gtk.Image() + image.set_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_BUTTON) + image.show() + self.share.add(image) + self.tooltips.set_tip(self.share, self.DEL_STR) + if self.add_edt is not None: + image = gtk.Image() + image.set_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_BUTTON) + image.show() + self.add_edt.add(image) + self.tooltips.set_tip(self.add_edt, self.EDIT_STR) + else: + image = gtk.Image() + image.set_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON) + image.show() + self.share.add(image) + self.tooltips.set_tip(self.share, self.SHARE_STR) + if self.add_edt is not None: + image = gtk.Image() + image.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_BUTTON) + image.show() + self.add_edt.add(image) + self.tooltips.set_tip(self.add_edt, self.ADD_STR) + +class PlaceEntry(ObjEntry): + """ + Handles the selection of a existing or new Place. Supports Drag and Drop + to select a place. + """ + EMPTY_TEXT = "%s" % _('To select a place, use drag-and-drop ' + 'or use the buttons') + EMPTY_TEXT_RED = "%s" % _('No place given, click button to select one') + EDIT_STR = _('Edit place') + SHARE_STR = _('Select an existing place') + ADD_STR = _('Add a new place') + DEL_STR = _('Remove place') + + def __init__(self, dbstate, uistate, track, label, set_val, + get_val, add_edt, share): + ObjEntry.__init__(self, dbstate, uistate, track, label, set_val, + get_val, add_edt, share) + + def _init_dnd(self): + """connect drag and drop of places + """ + self.label.drag_dest_set(gtk.DEST_DEFAULT_ALL, [DdTargets.PLACE_LINK.target()], + gtk.gdk.ACTION_COPY) + self.label.connect('drag_data_received', self.drag_data_received) + + def get_from_handle(self, handle): + """ return the object given the hande + """ + return self.db.get_place_from_handle(handle) + + def get_label(self, place): + return "%s [%s]" % (place.get_title(), place.gramps_id) + + def call_editor(self, obj=None): + if obj is None: + place = Place() + func = self.obj_added + else: + place = obj + func = self.after_edit + try: + EditPlace(self.dbstate, self.uistate, self.track, + place, func) + except WindowActiveError: + pass + + def call_selector(self): + cls = selector_factory('Place') + return cls(self.dbstate, self.uistate, self.track) + +# FIXME isn't used anywhere +class MediaEntry(ObjEntry): + """ + Handles the selection of a existing or new media. Supports Drag and Drop + to select a media object. + """ + EMPTY_TEXT = "%s" % _('To select a media object, use drag-and-drop ' + 'or use the buttons') + EMPTY_TEXT_RED = "%s" % _('No image given, click button to select one') + EDIT_STR = _('Edit media object') + SHARE_STR = _('Select an existing media object') + ADD_STR = _('Add a new media object') + DEL_STR = _('Remove media object') + + def __init__(self, dbstate, uistate, track, label, set_val, + get_val, add_edt, share): + ObjEntry.__init__(self, dbstate, uistate, track, label, set_val, + get_val, add_edt, share) + + def _init_dnd(self): + """connect drag and drop of places + """ + self.label.drag_dest_set(gtk.DEST_DEFAULT_ALL, [DdTargets.MEDIAOBJ.target()], + gtk.gdk.ACTION_COPY) + self.label.connect('drag_data_received', self.drag_data_received) + + def get_from_handle(self, handle): + """ return the object given the hande + """ + return self.db.get_object_from_handle(handle) + + def get_label(self, object): + return "%s [%s]" % (object.get_description(), object.gramps_id) + + def call_editor(self, obj=None): + if obj is None: + object = MediaObject() + func = self.obj_added + else: + object = obj + func = self.after_edit + try: + EditMedia(self.dbstate, self.uistate, self.track, + object, func) + except WindowActiveError: + pass + + def call_selector(self): + from Selectors import selector_factory + cls = selector_factory('MediaObject') + return cls(self.dbstate, self.uistate, self.track) + +# FIXME isn't used anywhere +class NoteEntry(ObjEntry): + """ + Handles the selection of a existing or new Note. Supports Drag and Drop + to select a Note. + """ + EMPTY_TEXT = "%s" % _('To select a note, use drag-and-drop ' + 'or use the buttons') + EMPTY_TEXT_RED = "%s" % _('No note given, click button to select one') + EDIT_STR = _('Edit Note') + SHARE_STR = _('Select an existing note') + ADD_STR = _('Add a new note') + DEL_STR = _('Remove note') + + def __init__(self, dbstate, uistate, track, label, set_val, + get_val, add_edt, share): + ObjEntry.__init__(self, dbstate, uistate, track, label, set_val, + get_val, add_edt, share) + self.notetype = None + + def set_notetype(self, type): + """ set a notetype to use in new notes + """ + self.notetype = type + + def get_notetype(self): + """ return the set notetype + """ + return self.notetype + + def _init_dnd(self): + """connect drag and drop of places + """ + self.label.drag_dest_set(gtk.DEST_DEFAULT_ALL, [DdTargets.NOTE_LINK.target()], + gtk.gdk.ACTION_COPY) + self.label.connect('drag_data_received', self.drag_data_received) + + def get_from_handle(self, handle): + """ return the object given the hande + """ + return self.db.get_note_from_handle(handle) + + def get_label(self, note): + txt = " ".join(note.get().split()) + if len(txt) > 35: + txt = txt[:35]+"..." + else: + txt = txt + return "%s [%s]" % (txt, note.gramps_id) + + def call_editor(self, obj=None): + if obj is None: + note = Note() + note.set_type(self.get_notetype()) + func = self.obj_added + else: + note = obj + func = self.after_edit + try: + EditNote(self.dbstate, self.uistate, self.track, + note, func) + except WindowActiveError: + pass + + def call_selector(self): + cls = selector_factory('Note') + return cls(self.dbstate, self.uistate, self.track) diff --git a/src/widgets/Makefile.am b/src/widgets/Makefile.am index 46bfbbc7e..1be279555 100644 --- a/src/widgets/Makefile.am +++ b/src/widgets/Makefile.am @@ -7,11 +7,19 @@ pkgdatadir = $(datadir)/@PACKAGE@/widgets pkgdata_PYTHON = \ __init__.py \ + buttons.py \ + expandcollapsearrow.py \ grampswidgets.py \ + labels.py \ + linkbox.py \ + monitoredwidgets.py \ multitypecomboentry.py \ - toolbarwidgets.py \ + statusbar.py \ styledtextbuffer.py \ - styledtexteditor.py + styledtexteditor.py \ + toolbarwidgets.py \ + unused.py \ + validatedmaskedentry.py pkgpyexecdir = @pkgpyexecdir@/widgets pkgpythondir = @pkgpythondir@/widgets @@ -24,3 +32,4 @@ GRAMPS_PY_MODPATH = "../" pycheck: (export PYTHONPATH=$(GRAMPS_PY_MODPATH); \ pychecker $(pkgdata_PYTHON)); + diff --git a/src/widgets/__init__.py b/src/widgets/__init__.py index ef694e949..283beadc5 100644 --- a/src/widgets/__init__.py +++ b/src/widgets/__init__.py @@ -22,12 +22,22 @@ """Custom widgets.""" -from grampswidgets import * +from monitoredwidgets import * +from labels import * +from buttons import * +from expandcollapsearrow import ExpandCollapseArrow +from linkbox import LinkBox +from statusbar import Statusbar +from validatedmaskedentry import ValidatableMaskedEntry from multitypecomboentry import MultiTypeComboEntry from toolbarwidgets import * from styledtextbuffer import * from styledtexteditor import * +# moved to src/ because of circular dependency +#from objectentries import * +from unused import * +# Enabling custom widgets to be included in Glade from gtk.glade import set_custom_handler def get_custom_handler(glade, function_name, widget_name, diff --git a/src/widgets/buttons.py b/src/widgets/buttons.py new file mode 100644 index 000000000..1c71cd679 --- /dev/null +++ b/src/widgets/buttons.py @@ -0,0 +1,154 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2007-2008 The Gramps Developers +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import logging +_LOG = logging.getLogger(".widgets.buttons") + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +import gtk + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- +# STOCK_INFO was added only in Gtk 2.8 +try: + INFO_ICON = gtk.STOCK_INFO +except AttributeError: + INFO_ICON = gtk.STOCK_DIALOG_INFO + + +#------------------------------------------------------------------------- +# +# IconButton class +# +#------------------------------------------------------------------------- +class IconButton(gtk.Button): + + def __init__(self, func, handle, icon=gtk.STOCK_EDIT, + size=gtk.ICON_SIZE_MENU): + gtk.Button.__init__(self) + image = gtk.Image() + image.set_from_stock(icon, size) + image.show() + self.add(image) + self.set_relief(gtk.RELIEF_NONE) + self.show() + + if func: + self.connect('button-press-event', func, handle) + self.connect('key-press-event', func, handle) + +#------------------------------------------------------------------------- +# +# WarnButton class +# +#------------------------------------------------------------------------- +class WarnButton(gtk.Button): + def __init__(self): + gtk.Button.__init__(self) + + image = gtk.Image() + image.set_from_stock(INFO_ICON, gtk.ICON_SIZE_MENU) + image.show() + self.add(image) + + self.set_relief(gtk.RELIEF_NONE) + self.show() + self.func = None + self.hide() + + def on_clicked(self, func): + self.connect('button-press-event', self._button_press) + self.func = func + + def _button_press(self, obj, event): + if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1: + self.func(obj) + +#------------------------------------------------------------------------- +# +# SimpleButton class +# +#------------------------------------------------------------------------- +class SimpleButton(gtk.Button): + + def __init__(self, image, func): + gtk.Button.__init__(self) + self.set_relief(gtk.RELIEF_NONE) + self.add(gtk.image_new_from_stock(image, gtk.ICON_SIZE_BUTTON)) + self.connect('clicked', func) + self.show() + +#------------------------------------------------------------------------- +# +# PrivacyButton class +# +#------------------------------------------------------------------------- +class PrivacyButton: + + def __init__(self, button, obj, readonly=False): + self.button = button + self.button.connect('toggled', self._on_toggle) + self.tooltips = gtk.Tooltips() + self.obj = obj + self.set_active(obj.get_privacy()) + self.button.set_sensitive(not readonly) + + def set_sensitive(self, val): + self.button.set_sensitive(val) + + def set_active(self, val): + self.button.set_active(val) + self._on_toggle(self.button) + + def get_active(self): + return self.button.get_active() + + def _on_toggle(self, obj): + child = obj.child + if child: + obj.remove(child) + image = gtk.Image() + if obj.get_active(): +# image.set_from_icon_name('stock_lock', gtk.ICON_SIZE_MENU) + image.set_from_stock('gramps-lock', gtk.ICON_SIZE_MENU) + self.tooltips.set_tip(obj, _('Record is private')) + self.obj.set_privacy(True) + else: +# image.set_from_icon_name('stock_lock-open', gtk.ICON_SIZE_MENU) + image.set_from_stock('gramps-unlock', gtk.ICON_SIZE_MENU) + self.tooltips.set_tip(obj, _('Record is public')) + self.obj.set_privacy(False) + image.show() + obj.add(image) diff --git a/src/widgets/expandcollapsearrow.py b/src/widgets/expandcollapsearrow.py new file mode 100644 index 000000000..fad959ca0 --- /dev/null +++ b/src/widgets/expandcollapsearrow.py @@ -0,0 +1,87 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2007-2008 The Gramps Developers +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +from gettext import gettext as _ + +import logging +_LOG = logging.getLogger(".widgets.expandcollapsearrow") + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +import gtk + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- +HAND_CURSOR = gtk.gdk.Cursor(gtk.gdk.HAND2) + +#------------------------------------------------------------------------- +# +# Module functions +# +#------------------------------------------------------------------------- +def realize_cb(widget): + widget.window.set_cursor(HAND_CURSOR) + +#------------------------------------------------------------------------- +# +# ExpandCollapseArrow class +# +#------------------------------------------------------------------------- +class ExpandCollapseArrow(gtk.EventBox): + """ + Arrow to be used for expand/collapse of sections. + Note: shadow does not work, we indicate action with realize_cb + """ + def __init__(self, collapsed, onbuttonpress, pair): + """ + Constructor for the ExpandCollapseArrow class. + + @param collapsed: True if arrow must be shown collapsed, + False otherwise + @type collapsed: bool + @param onbuttonpress: The callback function for button press + @type onbuttonpress: callback + @param pair: user param for onbuttonpress function + """ + gtk.EventBox.__init__(self) + self.tooltips = gtk.Tooltips() + if collapsed : + self.arrow = gtk.Arrow(gtk.ARROW_RIGHT, gtk.SHADOW_OUT) + self.tooltips.set_tip(self, _("Expand this section")) + else: + self.arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_OUT) + self.tooltips.set_tip(self, _("Collapse this section")) + self.add(self.arrow) + self.connect('button-press-event', onbuttonpress, pair) + self.connect('realize', realize_cb) diff --git a/src/widgets/labels.py b/src/widgets/labels.py new file mode 100644 index 000000000..6594307fc --- /dev/null +++ b/src/widgets/labels.py @@ -0,0 +1,205 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2007-2008 The Gramps Developers +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import os +import cgi + +import logging +_LOG = logging.getLogger(".widgets.objectentries") + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +import gtk +import pango + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +import Config + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- +HAND_CURSOR = gtk.gdk.Cursor(gtk.gdk.HAND2) + +#------------------------------------------------------------------------- +# +# Module functions +# +#------------------------------------------------------------------------- +def realize_cb(widget): + widget.window.set_cursor(HAND_CURSOR) + +#------------------------------------------------------------------------- +# +# LinkLabel class +# +#------------------------------------------------------------------------- +class LinkLabel(gtk.EventBox): + + def __init__(self, label, func, handle, decoration=None): + if decoration == None: + relation_display_theme = Config.get(Config.RELATION_DISPLAY_THEME) + if relation_display_theme == "CLASSIC": + decoration = 'underline="single"' + elif relation_display_theme == "WEBPAGE": + decoration = 'foreground="blue"' + else: + raise AttributeError("invalid relation-display-theme: '%s'" % relation_display_theme) + + gtk.EventBox.__init__(self) + self.orig_text = cgi.escape(label[0]) + self.gender = label[1] + self.tooltips = gtk.Tooltips() + self.decoration = decoration + text = '%s' % (self.decoration, self.orig_text) + + if func: + msg = _('Click to make the active person\n' + 'Right click to display the edit menu') + if not Config.get(Config.RELEDITBTN): + msg += "\n" + _('Edit icons can be enabled in the Preferences dialog') + + self.tooltips.set_tip(self, msg) + + self.label = gtk.Label(text) + self.label.set_use_markup(True) + self.label.set_alignment(0, 0.5) + + hbox = gtk.HBox() + hbox.pack_start(self.label, False, False, 0) + if label[1]: + hbox.pack_start(GenderLabel(label[1]), False, False, 0) + hbox.set_spacing(4) + self.add(hbox) + + if func: + self.connect('button-press-event', func, handle) + self.connect('enter-notify-event', self.enter_text, handle) + self.connect('leave-notify-event', self.leave_text, handle) + self.connect('realize', realize_cb) + + def set_padding(self, x, y): + self.label.set_padding(x, y) + + def enter_text(self, obj, event, handle): + relation_display_theme = Config.get(Config.RELATION_DISPLAY_THEME) + if relation_display_theme == "CLASSIC": + text = '%s' % (self.decoration, self.orig_text) + elif relation_display_theme == "WEBPAGE": + text = '%s' % (self.decoration, self.orig_text) + else: + raise AttributeError("invalid relation-display-theme: '%s'" % relation_display_theme) + self.label.set_text(text) + self.label.set_use_markup(True) + + def leave_text(self, obj, event, handle): + text = '%s' % (self.decoration, self.orig_text) + self.label.set_text(text) + self.label.set_use_markup(True) + +#------------------------------------------------------------------------- +# +# EditLabel class +# +#------------------------------------------------------------------------- +class EditLabel(gtk.HBox): + def __init__(self, text): + gtk.HBox.__init__(self) + label = BasicLabel(text) + self.pack_start(label, False) + self.pack_start(gtk.image_new_from_stock(gtk.STOCK_EDIT, + gtk.ICON_SIZE_MENU), False) + self.set_spacing(4) + self.show_all() + +#------------------------------------------------------------------------- +# +# BasicLabel class +# +#------------------------------------------------------------------------- +class BasicLabel(gtk.Label): + + def __init__(self, text): + gtk.Label.__init__(self, text) + self.set_alignment(0, 0.5) + self.show() + +#------------------------------------------------------------------------- +# +# GenderLabel class +# +#------------------------------------------------------------------------- +class GenderLabel(gtk.Label): + + def __init__(self, text): + gtk.Label.__init__(self, text) + self.set_alignment(0, 0.5) + if os.sys.platform == "win32": + pangoFont = pango.FontDescription('Arial') + self.modify_font(pangoFont) + self.show() + +#------------------------------------------------------------------------- +# +# MarkupLabel class +# +#------------------------------------------------------------------------- +class MarkupLabel(gtk.Label): + + def __init__(self, text, x_align=0, y_align=0.5): + gtk.Label.__init__(self, text) + self.set_alignment(x_align, y_align) + self.set_use_markup(True) + self.show_all() + +#------------------------------------------------------------------------- +# +# DualMarkupLabel class +# +#------------------------------------------------------------------------- +class DualMarkupLabel(gtk.HBox): + + def __init__(self, text, alt, x_align=0, y_align=0.5): + gtk.HBox.__init__(self) + label = gtk.Label(text) + label.set_alignment(x_align, y_align) + label.set_use_markup(True) + + self.pack_start(label, False, False, 0) + b = GenderLabel(alt) + b.set_use_markup(True) + self.pack_start(b, False, False, 4) + self.show() diff --git a/src/widgets/linkbox.py b/src/widgets/linkbox.py new file mode 100644 index 000000000..e9bf49d61 --- /dev/null +++ b/src/widgets/linkbox.py @@ -0,0 +1,52 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2007-2008 The Gramps Developers +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import logging +_LOG = logging.getLogger(".widgets.linkbox") + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +import gtk + +#------------------------------------------------------------------------- +# +# LinkBox class +# +#------------------------------------------------------------------------- +class LinkBox(gtk.HBox): + + def __init__(self, link, button): + gtk.HBox.__init__(self) + self.set_spacing(6) + self.pack_start(link, False) + if button: + self.pack_start(button, False) + self.show() diff --git a/src/widgets/monitoredwidgets.py b/src/widgets/monitoredwidgets.py new file mode 100644 index 000000000..2d547e2b6 --- /dev/null +++ b/src/widgets/monitoredwidgets.py @@ -0,0 +1,478 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2007-2008 The Gramps Developers +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import logging +_LOG = logging.getLogger(".widgets.monitoredwidgets") + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +import gobject +import gtk + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +import AutoComp +import DateEdit + +#------------------------------------------------------------------------- +# +# MonitoredCheckbox class +# +#------------------------------------------------------------------------- +class MonitoredCheckbox: + + def __init__(self, obj, button, set_val, get_val, on_toggle=None, readonly = False): + self.button = button + self.button.connect('toggled', self._on_toggle) + self.on_toggle = on_toggle + self.obj = obj + self.set_val = set_val + self.get_val = get_val + self.button.set_active(get_val()) + self.button.set_sensitive(not readonly) + + def _on_toggle(self, obj): + self.set_val(obj.get_active()) + if self.on_toggle: + self.on_toggle(self.get_val()) + +#------------------------------------------------------------------------- +# +# MonitoredEntry class +# +#------------------------------------------------------------------------- +class MonitoredEntry: + + def __init__(self, obj, set_val, get_val, read_only=False, + autolist=None, changed=None): + self.obj = obj + self.set_val = set_val + self.get_val = get_val + self.changed = changed + + if get_val(): + self.obj.set_text(get_val()) + self.obj.connect('changed', self._on_change) + self.obj.set_editable(not read_only) + + if autolist: + AutoComp.fill_entry(obj, autolist) + + def reinit(self, set_val, get_val): + self.set_val = set_val + self.get_val = get_val + self.update() + + def set_text(self, text): + self.obj.set_text(text) + + def connect(self, signal, callback, *data): + self.obj.connect(signal, callback, *data) + + def _on_change(self, obj): + self.set_val(unicode(obj.get_text())) + if self.changed: + self.changed(obj) + + def force_value(self, value): + self.obj.set_text(value) + + def get_value(self, value): + return unicode(self.obj.get_text()) + + def enable(self, value): + self.obj.set_sensitive(value) + self.obj.set_editable(value) + + def grab_focus(self): + self.obj.grab_focus() + + def update(self): + if self.get_val() is not None: + self.obj.set_text(self.get_val()) + +#------------------------------------------------------------------------- +# +# MonitoredSpinButton class +# +#------------------------------------------------------------------------- +class MonitoredSpinButton: + """ + Class for signal handling of spinbuttons. + (Code is a modified copy of MonitoredEntry) + """ + + def __init__(self, obj, set_val, get_val, read_only=False, + autolist=None, changed=None): + """ + @param obj: widget to be monitored + @type obj: gtk.SpinButton + @param set_val: callback to be called when obj is changed + @param get_val: callback to be called to retrieve value for obj + @param read_only: If SpinButton is read only. + """ + + self.obj = obj + self.set_val = set_val + self.get_val = get_val + self.changed = changed + + if get_val(): + self.obj.set_value(get_val()) + self.obj.connect('value-changed', self._on_change) + self.obj.set_editable(not read_only) + + if autolist: + AutoComp.fill_entry(obj,autolist) + + def reinit(self, set_val, get_val): + """ + Reinitialize class with the specified callback functions. + + @param set_val: callback to be called when SpinButton is changed + @param get_val: callback to be called to retrieve value for SpinButton + """ + + self.set_val = set_val + self.get_val = get_val + self.update() + + def set_value(self, value): + """ + Set the value of the monitored widget to the specified value. + + @param value: Value to be set. + """ + + self.obj.set_value(value) + + def connect(self, signal, callback): + """ + Connect the signal of monitored widget to the specified callback. + + @param signal: Signal prototype for which a connection should be set up. + @param callback: Callback function to be called when signal is emitted. + """ + + self.obj.connect(signal, callback) + + def _on_change(self, obj): + """ + Event handler to be called when the monitored widget is changed. + + @param obj: Widget that has been changed. + @type obj: gtk.SpinButton + """ + + self.set_val(obj.get_value()) + if self.changed: + self.changed(obj) + + def force_value(self, value): + """ + Set the value of the monitored widget to the specified value. + + @param value: Value to be set. + """ + + self.obj.set_value(value) + + def get_value(self): + """ + Get the current value of the monitored widget. + + @returns: Current value of monitored widget. + """ + + return self.obj.get_value() + + def enable(self, value): + """ + Change the property editable and sensitive of the monitored widget to value. + + @param value: If widget should be editable or deactivated. + @type value: bool + """ + + self.obj.set_sensitive(value) + self.obj.set_editable(value) + + def grab_focus(self): + """ + Assign the keyboard focus to the monitored widget. + """ + + self.obj.grab_focus() + + def update(self): + """ + Updates value of monitored SpinButton with the value returned by the get_val callback. + """ + + if self.get_val(): + self.obj.set_value(self.get_val()) + +#------------------------------------------------------------------------- +# +# MonitoredText class +# +#------------------------------------------------------------------------- +class MonitoredText: + + def __init__(self, obj, set_val, get_val, read_only=False): + self.buf = obj.get_buffer() + self.set_val = set_val + self.get_val = get_val + + if get_val(): + self.buf.set_text(get_val()) + self.buf.connect('changed', self.on_change) + obj.set_editable(not read_only) + + def on_change(self, obj): + s, e = self.buf.get_bounds() + self.set_val(unicode(self.buf.get_text(s, e, False))) + +#------------------------------------------------------------------------- +# +# MonitoredType class +# +#------------------------------------------------------------------------- +class MonitoredType: + + def __init__(self, obj, set_val, get_val, mapping, custom, readonly=False, + custom_values=None): + self.set_val = set_val + self.get_val = get_val + + self.obj = obj + + val = get_val() + if val: + default = val[0] + else: + default = None + + self.sel = AutoComp.StandardCustomSelector( + mapping, obj, custom, default, additional=custom_values) + + self.set_val(self.sel.get_values()) + self.obj.set_sensitive(not readonly) + self.obj.connect('changed', self.on_change) + + def reinit(self, set_val, get_val): + self.set_val = set_val + self.get_val = get_val + self.update() + + def update(self): + if self.get_val(): + self.sel.set_values(self.get_val()) + + def on_change(self, obj): + self.set_val(self.sel.get_values()) + +#------------------------------------------------------------------------- +# +# MonitoredDataType class +# +#------------------------------------------------------------------------- +class MonitoredDataType: + + + def __init__(self, obj, set_val, get_val, readonly=False, + custom_values=None, ignore_values=None): + """ + Constructor for the MonitoredDataType class. + + @param obj: Existing ComboBoxEntry widget to use. + @type obj: gtk.ComboBoxEntry + @param set_val: The function that sets value of the type in the object + @type set_val: method + @param get_val: The function that gets value of the type in the object. + This returns a GrampsType, of which get_map returns all possible types + @type get_val: method + @param custom_values: Extra values to show in the combobox. These can be + text of custom type, tuple with type info or GrampsType class + @type : list of str, tuple or GrampsType + @ignore_values: list of values not to show in the combobox. If the result + of get_val is in these, it is not ignored + @type : list of int + """ + self.set_val = set_val + self.get_val = get_val + + self.obj = obj + + val = get_val() + + if val: + default = int(val) + else: + default = None + + map = get_val().get_map().copy() + if ignore_values : + for key in map.keys(): + try : + i = ignore_values.index(key) + except ValueError: + i = None + if (not i==None) and (not ignore_values[i] == default) : + del map[key] + + self.sel = AutoComp.StandardCustomSelector( + map, + obj, + get_val().get_custom(), + default, + additional=custom_values) + + self.sel.set_values((int(get_val()), str(get_val()))) + self.obj.set_sensitive(not readonly) + self.obj.connect('changed', self.on_change) + + def reinit(self, set_val, get_val): + self.set_val = set_val + self.get_val = get_val + self.update() + + def fix_value(self, value): + if value[0] == self.get_val().get_custom(): + return value + else: + return (value[0], '') + + def update(self): + val = self.get_val() + if type(val) == tuple : + self.sel.set_values(val) + else: + self.sel.set_values((int(val), str(val))) + + def on_change(self, obj): + value = self.fix_value(self.sel.get_values()) + self.set_val(value) + +#------------------------------------------------------------------------- +# +# MonitoredMenu class +# +#------------------------------------------------------------------------- +class MonitoredMenu: + + def __init__(self, obj, set_val, get_val, mapping, + readonly=False, changed=None): + self.set_val = set_val + self.get_val = get_val + + self.changed = changed + self.obj = obj + + self.change_menu(mapping) + self.obj.connect('changed', self.on_change) + self.obj.set_sensitive(not readonly) + + def force(self, value): + self.obj.set_active(value) + + def change_menu(self, mapping): + self.data = {} + self.model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_INT) + index = 0 + for t, v in mapping: + self.model.append(row=[t, v]) + self.data[v] = index + index += 1 + self.obj.set_model(self.model) + self.obj.set_active(self.data.get(self.get_val(), 0)) + + def on_change(self, obj): + self.set_val(self.model.get_value(obj.get_active_iter(), 1)) + if self.changed: + self.changed() + +#------------------------------------------------------------------------- +# +# MonitoredStrMenu class +# +#------------------------------------------------------------------------- +class MonitoredStrMenu: + + def __init__(self, obj, set_val, get_val, mapping, readonly=False): + self.set_val = set_val + self.get_val = get_val + + self.obj = obj + self.model = gtk.ListStore(gobject.TYPE_STRING) + + if len(mapping) > 20: + self.obj.set_wrap_width(3) + + self.model.append(row=['']) + index = 0 + self.data = [''] + + default = get_val() + active = 0 + + for t, v in mapping: + self.model.append(row=[v]) + self.data.append(t) + index += 1 + if t == default: + active = index + + self.obj.set_model(self.model) + self.obj.set_active(active) + self.obj.connect('changed', self.on_change) + self.obj.set_sensitive(not readonly) + + def on_change(self, obj): + self.set_val(self.data[obj.get_active()]) + +#------------------------------------------------------------------------- +# +# MonitoredDate class +# +#------------------------------------------------------------------------- +class MonitoredDate: + + def __init__(self, field, button, value, uistate, track, readonly=False): + self.date = value + self.date_check = DateEdit.DateEdit( + self.date, field, button, uistate, track) + field.set_editable(not readonly) + button.set_sensitive(not readonly) + diff --git a/src/widgets/statusbar.py b/src/widgets/statusbar.py new file mode 100644 index 000000000..f50e54aea --- /dev/null +++ b/src/widgets/statusbar.py @@ -0,0 +1,260 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2007-2008 The Gramps Developers +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import logging +_LOG = logging.getLogger(".widgets.statusbar") + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +import gobject +import gtk +import pango + +#------------------------------------------------------------------------- +# +# Statusbar class +# +#------------------------------------------------------------------------- +class Statusbar(gtk.HBox): + """Custom Statusbar with flexible number of "bars". + + Statusbar can have any number of fields included, each identified + by it's own bar id. It has by default one field with id = 0. This + defult field is used when no bar id is given in the relevant (push, pop, + etc.) methods, thus Statusbar behaves as a single gtk.Statusbar. + + To add a new field use the "insert" method. Using the received bar id + one can push, pop and remove messages to/from this newly inserted field. + + """ + __gtype_name__ = 'Statusbar' + + ##__gsignals__ = { + ##'text-popped': , + ##'text-pushed': , + ##} + + __gproperties__ = { + 'has-resize-grip': (gobject.TYPE_BOOLEAN, + 'Resize grip', + 'Whether resize grip is visible', + True, + gobject.PARAM_READWRITE), + } + + def __init__(self, min_width=30): + gtk.HBox.__init__(self) + + # initialize pixel/character scale + pl = pango.Layout(self.get_pango_context()) + pl.set_text("M") + (self._char_width, h) = pl.get_pixel_size() + + # initialize property values + self.__has_resize_grip = True + + # create the main statusbar with id #0 + main_bar = gtk.Statusbar() + main_bar.set_size_request(min_width*self._char_width, -1) + main_bar.show() + self.pack_start(main_bar) + self._bars = {0: main_bar} + self._set_resize_grip() + + # Virtual methods + + def do_get_property(self, prop): + """Return the gproperty's value. + """ + if prop.name == 'has-resize-grip': + return self.__has_resize_grip + else: + raise AttributeError, 'unknown property %s' % prop.name + + def do_set_property(self, prop, value): + """Set the property of writable properties. + """ + if prop.name == 'has-resize-grip': + self.__has_resize_grip = value + self._set_resize_grip() + else: + raise AttributeError, 'unknown or read only property %s' % prop.name + + # Private + + def _set_resize_grip(self): + """Set the resize grip for the statusbar. + + Resize grip is disabled for all statusbars except the last one, + which is set according to the "has-resize-grip" propery. + + """ + for bar in self.get_children(): + bar.set_has_resize_grip(False) + + bar.set_has_resize_grip(self.get_property('has-resize-grip')) + + def _set_packing(self): + """Set packing style of the statusbars. + + All bars are packed with "expand"=True, "fill"=True parameters, + except the last one, which is packed with "expand"=False, "fill"=False. + + """ + for bar in self.get_children(): + self.set_child_packing(bar, True, True, 0, gtk.PACK_START) + + self.set_child_packing(bar, False, False, 0, gtk.PACK_START) + + def _get_next_id(self): + """Get next unused statusbar id. + """ + id = 1 + while id in self._bars.keys(): + id = id + 1 + + return id + + # Public API + + def insert(self, index=-1, min_width=30, ralign=False): + """Insert a new statusbar. + + Create a new statusbar and insert it at the given index. Index starts + from '0'. If index is negative the new statusbar is appended. + The new bar_id is returned. + + """ + new_bar = gtk.Statusbar() + new_bar.set_size_request(min_width*self._char_width, -1) + new_bar.show() + self.pack_start(new_bar) + self.reorder_child(new_bar, index) + self._set_resize_grip() + self._set_packing() + + if ralign: + label = new_bar.get_children()[0].get_children()[0] + label.set_alignment(xalign=1.0, yalign=0.5) + + new_bar_id = self._get_next_id() + self._bars[new_bar_id] = new_bar + + return new_bar_id + + def get_context_id(self, context_description, bar_id=0): + """Return a new or existing context identifier. + + The target statusbar is identified by bar_id created when statusbar + was added. + Existence of the bar_id is not checked as giving a wrong id is + programming fault. + + """ + return self._bars[bar_id].get_context_id(context_description) + + def push(self, context_id, text, bar_id=0): + """Push message onto a statusbar's stack. + + The target statusbar is identified by bar_id created when statusbar + was added. + Existence of the bar_id is not checked as giving a wrong id is + programming fault. + + """ + return self._bars[bar_id].push(context_id, text) + + def pop(self, context_id, bar_id=0): + """Remove the top message from a statusbar's stack. + + The target statusbar is identified by bar_id created when statusbar + was added. + Existence of the bar_id is not checked as giving a wrong id is + programming fault. + + """ + self._bars[bar_id].pop(context_id) + + def remove(self, context_id, message_id, bar_id=0): + """Remove the message with the specified message_id. + + Remove the message with the specified message_id and context_id + from the statusbar's stack, which is identified by bar_id. + Existence of the bar_id is not checked as giving a wrong id is + programming fault. + + """ + self._bars[bar_id].remove(context_id, message_id) + + def set_has_resize_grip(self, setting): + """Mirror gtk.Statusbar functionaliy. + """ + self.set_property('has-resize-grip', setting) + + def get_has_resize_grip(self): + """Mirror gtk.Statusbar functionaliy. + """ + return self.get_property('has-resize-grip') + +def main(args): + win = gtk.Window() + win.set_title('Statusbar test window') + win.set_position(gtk.WIN_POS_CENTER) + def cb(window, event): + gtk.main_quit() + win.connect('delete-event', cb) + + vbox = gtk.VBox() + win.add(vbox) + + statusbar = Statusbar() + vbox.pack_end(statusbar, False) + + statusbar.push(1, "This is my statusbar...") + + my_statusbar = statusbar.insert(min_width=24) + statusbar.push(1, "Testing status bar width", my_statusbar) + + yet_another_statusbar = statusbar.insert(1, 11) + statusbar.push(1, "A short one", yet_another_statusbar) + + last_statusbar = statusbar.insert(min_width=41, ralign=True) + statusbar.push(1, "The last statusbar has always fixed width", + last_statusbar) + + win.show_all() + gtk.main() + +if __name__ == '__main__': + import sys + # fall back to root logger for testing + log = logging + sys.exit(main(sys.argv)) diff --git a/src/widgets/toolbarwidgets.py b/src/widgets/toolbarwidgets.py index 883cc1f90..2aab88628 100644 --- a/src/widgets/toolbarwidgets.py +++ b/src/widgets/toolbarwidgets.py @@ -43,7 +43,7 @@ import gtk # GRAMPS modules # #------------------------------------------------------------------------- -from widgets import MultiTypeComboEntry +from multitypecomboentry import MultiTypeComboEntry #------------------------------------------------------------------------- # diff --git a/src/widgets/unused.py b/src/widgets/unused.py new file mode 100644 index 000000000..3c84397a9 --- /dev/null +++ b/src/widgets/unused.py @@ -0,0 +1,93 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2007-2008 The Gramps Developers +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import logging +log = logging.getLogger(".widgets.unused") + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +import gobject +import gtk + +#------------------------------------------------------------------------- +# +# IntEdit class +# +#------------------------------------------------------------------------- +# FIXME isn't used anywhere +class IntEdit(gtk.Entry): + """An gtk.Edit widget that only allows integers.""" + + def __init__(self): + gtk.Entry.__init__(self) + + self._signal = self.connect("insert_text", self.insert_cb) + + def insert_cb(self, widget, text, length, *args): + # if you don't do this, garbage comes in with text + text = text[:length] + pos = widget.get_position() + # stop default emission + widget.emit_stop_by_name("insert_text") + gobject.idle_add(self.insert, widget, text, pos) + + def insert(self, widget, text, pos): + if len(text) > 0 and text.isdigit(): + # the next three lines set up the text. this is done because we + # can't use insert_text(): it always inserts at position zero. + orig_text = widget.get_text() + new_text = orig_text[:pos] + text + orig_text[pos:] + # avoid recursive calls triggered by set_text + widget.handler_block(self._signal) + # replace the text with some new text + widget.set_text(new_text) + widget.handler_unblock(self._signal) + # set the correct position in the widget + widget.set_position(pos + len(text)) + +#------------------------------------------------------------------------- +# +# TypeCellRenderer class +# +#------------------------------------------------------------------------- +# FIXME isn't used anywhere, though it is mentioned in _EditFamily.py +class TypeCellRenderer(gtk.CellRendererCombo): + + def __init__(self, values): + gtk.CellRendererCombo.__init__(self) + + model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_INT) + for key in values: + model.append(row=[values[key], key]) + self.set_property('editable', True) + self.set_property('model', model) + self.set_property('text-column', 0) + diff --git a/src/widgets/grampswidgets.py b/src/widgets/validatedmaskedentry.py similarity index 56% rename from src/widgets/grampswidgets.py rename to src/widgets/validatedmaskedentry.py index 01ded0da9..03ecfd5f8 100644 --- a/src/widgets/grampswidgets.py +++ b/src/widgets/validatedmaskedentry.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2007-2008 The Gramps Developers # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -25,19 +26,11 @@ # Standard python modules # #------------------------------------------------------------------------- -import cgi -import os -import cPickle as pickle from gettext import gettext as _ import string -#------------------------------------------------------------------------- -# -# set up logging -# -#------------------------------------------------------------------------- import logging -log = logging.getLogger(".GrampsWidget") +_LOG = logging.getLogger(".widgets.validatedmaskedentry") #------------------------------------------------------------------------- # @@ -46,7 +39,6 @@ log = logging.getLogger(".GrampsWidget") #------------------------------------------------------------------------- import gobject import gtk -import gtk.glade import pango #------------------------------------------------------------------------- @@ -54,1243 +46,19 @@ import pango # Gramps modules # #------------------------------------------------------------------------- -import AutoComp -import DateEdit -import const -import Config from Errors import MaskError, ValidationError, WindowActiveError -from DdTargets import DdTargets #------------------------------------------------------------------------- # # Constants # #------------------------------------------------------------------------- - - # STOCK_INFO was added only in Gtk 2.8 try: INFO_ICON = gtk.STOCK_INFO -except: +except AttributeError: INFO_ICON = gtk.STOCK_DIALOG_INFO -# Enabling custom widgets to be included in Glade -##def get_custom_handler(glade, function_name, widget_name, - ##str1, str2, int1, int2): - ##from Editors._StyledTextEditor import StyledTextEditor - ##if function_name == 'ValidatableMaskedEntry': - ##return ValidatableMaskedEntry() - ##if function_name == 'StyledTextEditor': - ##return StyledTextEditor() - -##gtk.glade.set_custom_handler(get_custom_handler) - - -hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2) -def realize_cb(widget): - widget.window.set_cursor(hand_cursor) - -class ExpandCollapseArrow(gtk.EventBox): - """ - Arrow to be used for expand/collapse of sections. - Note: shadow does not work, we indicate action with realize_cb - """ - def __init__(self, collapsed, onbuttonpress, pair): - """ - Constructor for the ExpandCollapseArrow class. - - @param collapsed: True if arrow must be shown collapsed, - False otherwise - @type collapsed: bool - @param onbuttonpress: The callback function for button press - @type onbuttonpress: callback - @param pair: user param for onbuttonpress function - """ - gtk.EventBox.__init__(self) - self.tooltips = gtk.Tooltips() - if collapsed : - self.arrow = gtk.Arrow(gtk.ARROW_RIGHT, gtk.SHADOW_OUT) - self.tooltips.set_tip(self, _("Expand this section")) - else: - self.arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_OUT) - self.tooltips.set_tip(self, _("Collapse this section")) - self.add(self.arrow) - self.connect('button-press-event', onbuttonpress, pair) - self.connect('realize', realize_cb) - -class LinkLabel(gtk.EventBox): - - def __init__(self, label, func, handle, decoration=None): - if decoration == None: - relation_display_theme = Config.get(Config.RELATION_DISPLAY_THEME) - if relation_display_theme == "CLASSIC": - decoration = 'underline="single"' - elif relation_display_theme == "WEBPAGE": - decoration = 'foreground="blue"' - else: - raise AttributeError("invalid relation-display-theme: '%s'" % relation_display_theme) - - gtk.EventBox.__init__(self) - self.orig_text = cgi.escape(label[0]) - self.gender = label[1] - self.tooltips = gtk.Tooltips() - self.decoration = decoration - text = '%s' % (self.decoration, self.orig_text) - - if func: - msg = _('Click to make the active person\n' - 'Right click to display the edit menu') - if not Config.get(Config.RELEDITBTN): - msg += "\n" + _('Edit icons can be enabled in the Preferences dialog') - - self.tooltips.set_tip(self, msg) - - self.label = gtk.Label(text) - self.label.set_use_markup(True) - self.label.set_alignment(0, 0.5) - - hbox = gtk.HBox() - hbox.pack_start(self.label, False, False, 0) - if label[1]: - hbox.pack_start(GenderLabel(label[1]), False, False, 0) - hbox.set_spacing(4) - self.add(hbox) - - if func: - self.connect('button-press-event', func, handle) - self.connect('enter-notify-event', self.enter_text, handle) - self.connect('leave-notify-event', self.leave_text, handle) - self.connect('realize', realize_cb) - - def set_padding(self, x, y): - self.label.set_padding(x, y) - - def enter_text(self, obj, event, handle): - relation_display_theme = Config.get(Config.RELATION_DISPLAY_THEME) - if relation_display_theme == "CLASSIC": - text = '%s' % (self.decoration, self.orig_text) - elif relation_display_theme == "WEBPAGE": - text = '%s' % (self.decoration, self.orig_text) - else: - raise AttributeError("invalid relation-display-theme: '%s'" % relation_display_theme) - self.label.set_text(text) - self.label.set_use_markup(True) - - def leave_text(self, obj, event, handle): - text = '%s' % (self.decoration, self.orig_text) - self.label.set_text(text) - self.label.set_use_markup(True) - -class IconButton(gtk.Button): - - def __init__(self, func, handle, icon=gtk.STOCK_EDIT, - size=gtk.ICON_SIZE_MENU): - gtk.Button.__init__(self) - image = gtk.Image() - image.set_from_stock(icon, size) - image.show() - self.add(image) - self.set_relief(gtk.RELIEF_NONE) - self.show() - - if func: - self.connect('button-press-event', func, handle) - self.connect('key-press-event', func, handle) - -class WarnButton(gtk.Button): - def __init__(self): - gtk.Button.__init__(self) - - image = gtk.Image() - image.set_from_stock(INFO_ICON, gtk.ICON_SIZE_MENU) - image.show() - self.add(image) - - self.set_relief(gtk.RELIEF_NONE) - self.show() - self.func = None - self.hide() - - def on_clicked(self, func): - self.connect('button-press-event', self._button_press) - self.func = func - - def _button_press(self, obj, event): - if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1: - self.func(obj) - -class SimpleButton(gtk.Button): - - def __init__(self, image, func): - gtk.Button.__init__(self) - self.set_relief(gtk.RELIEF_NONE) - self.add(gtk.image_new_from_stock(image, gtk.ICON_SIZE_BUTTON)) - self.connect('clicked', func) - self.show() - -class LinkBox(gtk.HBox): - - def __init__(self, link, button): - gtk.HBox.__init__(self) - self.set_spacing(6) - self.pack_start(link, False) - if button: - self.pack_start(button, False) - self.show() - -class EditLabel(gtk.HBox): - def __init__(self, text): - gtk.HBox.__init__(self) - label = BasicLabel(text) - self.pack_start(label, False) - self.pack_start(gtk.image_new_from_stock(gtk.STOCK_EDIT, - gtk.ICON_SIZE_MENU), False) - self.set_spacing(4) - self.show_all() - -class BasicLabel(gtk.Label): - - def __init__(self, text): - gtk.Label.__init__(self, text) - self.set_alignment(0, 0.5) - self.show() - -class GenderLabel(gtk.Label): - - def __init__(self, text): - gtk.Label.__init__(self, text) - self.set_alignment(0, 0.5) - if os.sys.platform == "win32": - pangoFont = pango.FontDescription('Arial') - self.modify_font(pangoFont) - self.show() - -class MarkupLabel(gtk.Label): - - def __init__(self, text, x_align=0, y_align=0.5): - gtk.Label.__init__(self, text) - self.set_alignment(x_align, y_align) - self.set_use_markup(True) - self.show_all() - -class DualMarkupLabel(gtk.HBox): - - def __init__(self, text, alt, x_align=0, y_align=0.5): - gtk.HBox.__init__(self) - label = gtk.Label(text) - label.set_alignment(x_align, y_align) - label.set_use_markup(True) - - self.pack_start(label, False, False, 0) - b = GenderLabel(alt) - b.set_use_markup(True) - self.pack_start(b, False, False, 4) - self.show() - -class IntEdit(gtk.Entry): - """An gtk.Edit widget that only allows integers.""" - - def __init__(self): - gtk.Entry.__init__(self) - - self._signal = self.connect("insert_text", self.insert_cb) - - def insert_cb(self, widget, text, length, *args): - # if you don't do this, garbage comes in with text - text = text[:length] - pos = widget.get_position() - # stop default emission - widget.emit_stop_by_name("insert_text") - gobject.idle_add(self.insert, widget, text, pos) - - def insert(self, widget, text, pos): - if len(text) > 0 and text.isdigit(): - # the next three lines set up the text. this is done because we - # can't use insert_text(): it always inserts at position zero. - orig_text = widget.get_text() - new_text = orig_text[:pos] + text + orig_text[pos:] - # avoid recursive calls triggered by set_text - widget.handler_block(self._signal) - # replace the text with some new text - widget.set_text(new_text) - widget.handler_unblock(self._signal) - # set the correct position in the widget - widget.set_position(pos + len(text)) - -class TypeCellRenderer(gtk.CellRendererCombo): - - def __init__(self, values): - gtk.CellRendererCombo.__init__(self) - - model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_INT) - for key in values: - model.append(row=[values[key], key]) - self.set_property('editable', True) - self.set_property('model', model) - self.set_property('text-column', 0) - -class PrivacyButton: - - def __init__(self, button, obj, readonly=False): - self.button = button - self.button.connect('toggled', self._on_toggle) - self.tooltips = gtk.Tooltips() - self.obj = obj - self.set_active(obj.get_privacy()) - self.button.set_sensitive(not readonly) - - def set_sensitive(self, val): - self.button.set_sensitive(val) - - def set_active(self, val): - self.button.set_active(val) - self._on_toggle(self.button) - - def get_active(self): - return self.button.get_active() - - def _on_toggle(self, obj): - child = obj.child - if child: - obj.remove(child) - image = gtk.Image() - if obj.get_active(): -# image.set_from_icon_name('stock_lock', gtk.ICON_SIZE_MENU) - image.set_from_stock('gramps-lock', gtk.ICON_SIZE_MENU) - self.tooltips.set_tip(obj, _('Record is private')) - self.obj.set_privacy(True) - else: -# image.set_from_icon_name('stock_lock-open', gtk.ICON_SIZE_MENU) - image.set_from_stock('gramps-unlock', gtk.ICON_SIZE_MENU) - self.tooltips.set_tip(obj, _('Record is public')) - self.obj.set_privacy(False) - image.show() - obj.add(image) - -class MonitoredCheckbox: - - def __init__(self, obj, button, set_val, get_val, on_toggle=None, readonly = False): - self.button = button - self.button.connect('toggled', self._on_toggle) - self.on_toggle = on_toggle - self.obj = obj - self.set_val = set_val - self.get_val = get_val - self.button.set_active(get_val()) - self.button.set_sensitive(not readonly) - - def _on_toggle(self, obj): - self.set_val(obj.get_active()) - if self.on_toggle: - self.on_toggle(self.get_val()) - -class MonitoredEntry: - - def __init__(self, obj, set_val, get_val, read_only=False, - autolist=None, changed=None): - self.obj = obj - self.set_val = set_val - self.get_val = get_val - self.changed = changed - - if get_val(): - self.obj.set_text(get_val()) - self.obj.connect('changed', self._on_change) - self.obj.set_editable(not read_only) - - if autolist: - AutoComp.fill_entry(obj, autolist) - - def reinit(self, set_val, get_val): - self.set_val = set_val - self.get_val = get_val - self.update() - - def set_text(self, text): - self.obj.set_text(text) - - def connect(self, signal, callback, *data): - self.obj.connect(signal, callback, *data) - - def _on_change(self, obj): - self.set_val(unicode(obj.get_text())) - if self.changed: - self.changed(obj) - - def force_value(self, value): - self.obj.set_text(value) - - def get_value(self, value): - return unicode(self.obj.get_text()) - - def enable(self, value): - self.obj.set_sensitive(value) - self.obj.set_editable(value) - - def grab_focus(self): - self.obj.grab_focus() - - def update(self): - if self.get_val() is not None: - self.obj.set_text(self.get_val()) - -class MonitoredSpinButton: - """ - Class for signal handling of spinbuttons. - (Code is a modified copy of MonitoredEntry) - """ - - def __init__(self, obj, set_val, get_val, read_only=False, - autolist=None, changed=None): - """ - @param obj: widget to be monitored - @type obj: gtk.SpinButton - @param set_val: callback to be called when obj is changed - @param get_val: callback to be called to retrieve value for obj - @param read_only: If SpinButton is read only. - """ - - self.obj = obj - self.set_val = set_val - self.get_val = get_val - self.changed = changed - - if get_val(): - self.obj.set_value(get_val()) - self.obj.connect('value-changed', self._on_change) - self.obj.set_editable(not read_only) - - if autolist: - AutoComp.fill_entry(obj,autolist) - - def reinit(self, set_val, get_val): - """ - Reinitialize class with the specified callback functions. - - @param set_val: callback to be called when SpinButton is changed - @param get_val: callback to be called to retrieve value for SpinButton - """ - - self.set_val = set_val - self.get_val = get_val - self.update() - - def set_value(self, value): - """ - Set the value of the monitored widget to the specified value. - - @param value: Value to be set. - """ - - self.obj.set_value(value) - - def connect(self, signal, callback): - """ - Connect the signal of monitored widget to the specified callback. - - @param signal: Signal prototype for which a connection should be set up. - @param callback: Callback function to be called when signal is emitted. - """ - - self.obj.connect(signal, callback) - - def _on_change(self, obj): - """ - Event handler to be called when the monitored widget is changed. - - @param obj: Widget that has been changed. - @type obj: gtk.SpinButton - """ - - self.set_val(obj.get_value()) - if self.changed: - self.changed(obj) - - def force_value(self, value): - """ - Set the value of the monitored widget to the specified value. - - @param value: Value to be set. - """ - - self.obj.set_value(value) - - def get_value(self): - """ - Get the current value of the monitored widget. - - @returns: Current value of monitored widget. - """ - - return self.obj.get_value() - - def enable(self, value): - """ - Change the property editable and sensitive of the monitored widget to value. - - @param value: If widget should be editable or deactivated. - @type value: bool - """ - - self.obj.set_sensitive(value) - self.obj.set_editable(value) - - def grab_focus(self): - """ - Assign the keyboard focus to the monitored widget. - """ - - self.obj.grab_focus() - - def update(self): - """ - Updates value of monitored SpinButton with the value returned by the get_val callback. - """ - - if self.get_val(): - self.obj.set_value(self.get_val()) - -class MonitoredText: - - def __init__(self, obj, set_val, get_val, read_only=False): - self.buf = obj.get_buffer() - self.set_val = set_val - self.get_val = get_val - - if get_val(): - self.buf.set_text(get_val()) - self.buf.connect('changed', self.on_change) - obj.set_editable(not read_only) - - def on_change(self, obj): - s, e = self.buf.get_bounds() - self.set_val(unicode(self.buf.get_text(s, e, False))) - -class MonitoredType: - - def __init__(self, obj, set_val, get_val, mapping, custom, readonly=False, - custom_values=None): - self.set_val = set_val - self.get_val = get_val - - self.obj = obj - - val = get_val() - if val: - default = val[0] - else: - default = None - - self.sel = AutoComp.StandardCustomSelector( - mapping, obj, custom, default, additional=custom_values) - - self.set_val(self.sel.get_values()) - self.obj.set_sensitive(not readonly) - self.obj.connect('changed', self.on_change) - - def reinit(self, set_val, get_val): - self.set_val = set_val - self.get_val = get_val - self.update() - - def update(self): - if self.get_val(): - self.sel.set_values(self.get_val()) - - def on_change(self, obj): - self.set_val(self.sel.get_values()) - -class MonitoredDataType: - - - def __init__(self, obj, set_val, get_val, readonly=False, - custom_values=None, ignore_values=None): - """ - Constructor for the MonitoredDataType class. - - @param obj: Existing ComboBoxEntry widget to use. - @type obj: gtk.ComboBoxEntry - @param set_val: The function that sets value of the type in the object - @type set_val: method - @param get_val: The function that gets value of the type in the object. - This returns a GrampsType, of which get_map returns all possible types - @type get_val: method - @param custom_values: Extra values to show in the combobox. These can be - text of custom type, tuple with type info or GrampsType class - @type : list of str, tuple or GrampsType - @ignore_values: list of values not to show in the combobox. If the result - of get_val is in these, it is not ignored - @type : list of int - """ - self.set_val = set_val - self.get_val = get_val - - self.obj = obj - - val = get_val() - - if val: - default = int(val) - else: - default = None - - map = get_val().get_map().copy() - if ignore_values : - for key in map.keys(): - try : - i = ignore_values.index(key) - except ValueError: - i = None - if (not i==None) and (not ignore_values[i] == default) : - del map[key] - - self.sel = AutoComp.StandardCustomSelector( - map, - obj, - get_val().get_custom(), - default, - additional=custom_values) - - self.sel.set_values((int(get_val()), str(get_val()))) - self.obj.set_sensitive(not readonly) - self.obj.connect('changed', self.on_change) - - def reinit(self, set_val, get_val): - self.set_val = set_val - self.get_val = get_val - self.update() - - def fix_value(self, value): - if value[0] == self.get_val().get_custom(): - return value - else: - return (value[0], '') - - def update(self): - val = self.get_val() - if type(val) == tuple : - self.sel.set_values(val) - else: - self.sel.set_values((int(val), str(val))) - - def on_change(self, obj): - value = self.fix_value(self.sel.get_values()) - self.set_val(value) - -class MonitoredMenu: - - def __init__(self, obj, set_val, get_val, mapping, - readonly=False, changed=None): - self.set_val = set_val - self.get_val = get_val - - self.changed = changed - self.obj = obj - - self.change_menu(mapping) - self.obj.connect('changed', self.on_change) - self.obj.set_sensitive(not readonly) - - def force(self, value): - self.obj.set_active(value) - - def change_menu(self, mapping): - self.data = {} - self.model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_INT) - index = 0 - for t, v in mapping: - self.model.append(row=[t, v]) - self.data[v] = index - index += 1 - self.obj.set_model(self.model) - self.obj.set_active(self.data.get(self.get_val(), 0)) - - def on_change(self, obj): - self.set_val(self.model.get_value(obj.get_active_iter(), 1)) - if self.changed: - self.changed() - -class MonitoredStrMenu: - - def __init__(self, obj, set_val, get_val, mapping, readonly=False): - self.set_val = set_val - self.get_val = get_val - - self.obj = obj - self.model = gtk.ListStore(gobject.TYPE_STRING) - - if len(mapping) > 20: - self.obj.set_wrap_width(3) - - self.model.append(row=['']) - index = 0 - self.data = [''] - - default = get_val() - active = 0 - - for t, v in mapping: - self.model.append(row=[v]) - self.data.append(t) - index += 1 - if t == default: - active = index - - self.obj.set_model(self.model) - self.obj.set_active(active) - self.obj.connect('changed', self.on_change) - self.obj.set_sensitive(not readonly) - - def on_change(self, obj): - self.set_val(self.data[obj.get_active()]) - -class MonitoredDate: - - def __init__(self, field, button, value, uistate, track, readonly=False): - self.date = value - self.date_check = DateEdit.DateEdit( - self.date, field, button, uistate, track) - field.set_editable(not readonly) - button.set_sensitive(not readonly) - -class ObjEntry: - """ - Handles the selection of a existing or new Object. Supports Drag and Drop - to select the object. - This is the base class to create a real entry - """ - def __init__(self, dbstate, uistate, track, label, set_val, - get_val, add_edt, share): - """Pass the dbstate and uistate and present track. - label is a gtk.Label that shows the persent value - set_val is function that is called when handle changes, use it - to update the calling module - get_val is function that is called to obtain handle from calling - module - share is the gtk.Button to call the object selector or del connect - add_edt is the gtk.Button with add or edit value. Pass None if - this button should not be present. - """ - self.label = label - self.add_edt = add_edt - self.share = share - self.dbstate = dbstate - self.db = dbstate.db - self.get_val = get_val - self.set_val = set_val - self.uistate = uistate - self.track = track - self.tooltips = gtk.Tooltips() - - #connect drag and drop - self._init_dnd() - #set the object specific code - self._init_object() - - #check if valid object: - handle = self.get_val() - if handle: - obj = self.get_from_handle(handle) - if not obj: - #invalid val, set it to None - self.set_val(None) - if self.get_val(): - self.set_button(True) - obj = self.get_from_handle(self.get_val()) - name = self.get_label(obj) - else: - name = u"" - self.set_button(False) - - if self.db.readonly: - if self.add_edt is not None: - self.add_edt.set_sensitive(False) - self.share.set_sensitive(False) - else: - if self.add_edt is not None: - self.add_edt.set_sensitive(True) - self.share.set_sensitive(True) - - if self.add_edt is not None: - self.add_edt.connect('clicked', self.add_edt_clicked) - self.share.connect('clicked', self.share_clicked) - - if not self.db.readonly and not name: - if self.add_edt is None: - self.label.set_text(self.EMPTY_TEXT_RED) - else: - self.label.set_text(self.EMPTY_TEXT) - self.label.set_use_markup(True) - else: - self.label.set_text(name) - - def _init_dnd(self): - """inheriting objects must set this - """ - pass - - def _init_object(self): - """inheriting objects can use this to set extra variables - """ - pass - - def get_from_handle(self, handle): - """ return the object given the hande - inheriting objects must set this - """ - pass - - def get_label(self, object): - """ return the label - inheriting objects must set this - """ - pass - - def after_edit(self, obj): - name = self.get_label(obj) - self.label.set_text(name) - - def add_edt_clicked(self, obj): - """ if value, edit, if no value, call editor on new object - """ - if self.get_val(): - obj = self.get_from_handle(self.get_val()) - self.call_editor(obj) - else: - self.call_editor() - - def call_editor(self, obj): - """inheriting objects must set this - """ - pass - - def call_selector(self): - """inheriting objects must set this - """ - pass - - def drag_data_received(self, widget, context, x, y, selection, info, time): - (drag_type, idval, obj, val) = pickle.loads(selection.data) - - data = self.db.get_place_from_handle(obj) - self.obj_added(data) - - def obj_added(self, data): - """ callback from adding an object to the entry""" - self.set_val(data.handle) - self.label.set_text(self.get_label(data)) - self.set_button(True) - - def share_clicked(self, obj): - """ if value, delete connect, in no value, select existing object - """ - if self.get_val(): - self.set_val(None) - self.label.set_text(self.EMPTY_TEXT) - self.label.set_use_markup(True) - self.set_button(False) - else: - select = self.call_selector() - obj = select.run() - if obj: - self.obj_added(obj) - - def set_button(self, use_add): - """ This sets the correct image to the two buttons. - If False: select icon and add icon - If True: remove icon and edit icon - """ - if self.add_edt is not None: - for i in self.add_edt.get_children(): - self.add_edt.remove(i) - for i in self.share.get_children(): - self.share.remove(i) - - if use_add: - image = gtk.Image() - image.set_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_BUTTON) - image.show() - self.share.add(image) - self.tooltips.set_tip(self.share, self.DEL_STR) - if self.add_edt is not None: - image = gtk.Image() - image.set_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_BUTTON) - image.show() - self.add_edt.add(image) - self.tooltips.set_tip(self.add_edt, self.EDIT_STR) - else: - image = gtk.Image() - image.set_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON) - image.show() - self.share.add(image) - self.tooltips.set_tip(self.share, self.SHARE_STR) - if self.add_edt is not None: - image = gtk.Image() - image.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_BUTTON) - image.show() - self.add_edt.add(image) - self.tooltips.set_tip(self.add_edt, self.ADD_STR) - -class PlaceEntry(ObjEntry): - """ - Handles the selection of a existing or new Place. Supports Drag and Drop - to select a place. - """ - EMPTY_TEXT = "%s" % _('To select a place, use drag-and-drop ' - 'or use the buttons') - EMPTY_TEXT_RED = "%s" % _('No place given, click button to select one') - EDIT_STR = _('Edit place') - SHARE_STR = _('Select an existing place') - ADD_STR = _('Add a new place') - DEL_STR = _('Remove place') - - def __init__(self, dbstate, uistate, track, label, set_val, - get_val, add_edt, share): - ObjEntry.__init__(self, dbstate, uistate, track, label, set_val, - get_val, add_edt, share) - - def _init_dnd(self): - """connect drag and drop of places - """ - self.label.drag_dest_set(gtk.DEST_DEFAULT_ALL, [DdTargets.PLACE_LINK.target()], - gtk.gdk.ACTION_COPY) - self.label.connect('drag_data_received', self.drag_data_received) - - def get_from_handle(self, handle): - """ return the object given the hande - """ - return self.db.get_place_from_handle(handle) - - def get_label(self, place): - return "%s [%s]" % (place.get_title(), place.gramps_id) - - def call_editor(self, obj=None): - from Editors import EditPlace - - if obj is None: - from gen.lib import Place - place = Place() - func = self.obj_added - else: - place = obj - func = self.after_edit - try: - EditPlace(self.dbstate, self.uistate, self.track, - place, func) - except WindowActiveError: - pass - - def call_selector(self): - from Selectors import selector_factory - cls = selector_factory('Place') - return cls(self.dbstate, self.uistate, self.track) - -class MediaEntry(ObjEntry): - """ - Handles the selection of a existing or new media. Supports Drag and Drop - to select a media object. - """ - EMPTY_TEXT = "%s" % _('To select a media object, use drag-and-drop ' - 'or use the buttons') - EMPTY_TEXT_RED = "%s" % _('No image given, click button to select one') - EDIT_STR = _('Edit media object') - SHARE_STR = _('Select an existing media object') - ADD_STR = _('Add a new media object') - DEL_STR = _('Remove media object') - - def __init__(self, dbstate, uistate, track, label, set_val, - get_val, add_edt, share): - ObjEntry.__init__(self, dbstate, uistate, track, label, set_val, - get_val, add_edt, share) - - def _init_dnd(self): - """connect drag and drop of places - """ - self.label.drag_dest_set(gtk.DEST_DEFAULT_ALL, [DdTargets.MEDIAOBJ.target()], - gtk.gdk.ACTION_COPY) - self.label.connect('drag_data_received', self.drag_data_received) - - def get_from_handle(self, handle): - """ return the object given the hande - """ - return self.db.get_object_from_handle(handle) - - def get_label(self, object): - return "%s [%s]" % (object.get_description(), object.gramps_id) - - def call_editor(self, obj=None): - from Editors import EditMedia - - if obj is None: - from gen.lib import MediaObject - object = MediaObject() - func = self.obj_added - else: - object = obj - func = self.after_edit - try: - EditMedia(self.dbstate, self.uistate, self.track, - object, func) - except WindowActiveError: - pass - - def call_selector(self): - from Selectors import selector_factory - cls = selector_factory('MediaObject') - return cls(self.dbstate, self.uistate, self.track) - -class NoteEntry(ObjEntry): - """ - Handles the selection of a existing or new Note. Supports Drag and Drop - to select a Note. - """ - EMPTY_TEXT = "%s" % _('To select a note, use drag-and-drop ' - 'or use the buttons') - EMPTY_TEXT_RED = "%s" % _('No note given, click button to select one') - EDIT_STR = _('Edit Note') - SHARE_STR = _('Select an existing note') - ADD_STR = _('Add a new note') - DEL_STR = _('Remove note') - - def __init__(self, dbstate, uistate, track, label, set_val, - get_val, add_edt, share): - ObjEntry.__init__(self, dbstate, uistate, track, label, set_val, - get_val, add_edt, share) - self.notetype = None - - def set_notetype(self, type): - """ set a notetype to use in new notes - """ - self.notetype = type - - def get_notetype(self): - """ return the set notetype - """ - return self.notetype - - def _init_dnd(self): - """connect drag and drop of places - """ - self.label.drag_dest_set(gtk.DEST_DEFAULT_ALL, [DdTargets.NOTE_LINK.target()], - gtk.gdk.ACTION_COPY) - self.label.connect('drag_data_received', self.drag_data_received) - - def get_from_handle(self, handle): - """ return the object given the hande - """ - return self.db.get_note_from_handle(handle) - - def get_label(self, note): - txt = " ".join(note.get().split()) - if len(txt) > 35: - txt = txt[:35]+"..." - else: - txt = txt - return "%s [%s]" % (txt, note.gramps_id) - - def call_editor(self, obj=None): - from Editors import EditNote - - if obj is None: - from gen.lib import Note - note = Note() - note.set_type(self.get_notetype()) - func = self.obj_added - else: - note = obj - func = self.after_edit - try: - EditNote(self.dbstate, self.uistate, self.track, - note, func) - except WindowActiveError: - pass - - def call_selector(self): - from Selectors import selector_factory - cls = selector_factory('Note') - return cls(self.dbstate, self.uistate, self.track) - -class Statusbar(gtk.HBox): - """Custom Statusbar with flexible number of "bars". - - Statusbar can have any number of fields included, each identified - by it's own bar id. It has by default one field with id = 0. This - defult field is used when no bar id is given in the relevant (push, pop, - etc.) methods, thus Statusbar behaves as a single gtk.Statusbar. - - To add a new field use the "insert" method. Using the received bar id - one can push, pop and remove messages to/from this newly inserted field. - - """ - __gtype_name__ = 'Statusbar' - - ##__gsignals__ = { - ##'text-popped': , - ##'text-pushed': , - ##} - - __gproperties__ = { - 'has-resize-grip': (gobject.TYPE_BOOLEAN, - 'Resize grip', - 'Whether resize grip is visible', - True, - gobject.PARAM_READWRITE), - } - - def __init__(self, min_width=30): - gtk.HBox.__init__(self) - - # initialize pixel/character scale - pl = pango.Layout(self.get_pango_context()) - pl.set_text("M") - (self._char_width, h) = pl.get_pixel_size() - - # initialize property values - self.__has_resize_grip = True - - # create the main statusbar with id #0 - main_bar = gtk.Statusbar() - main_bar.set_size_request(min_width*self._char_width, -1) - main_bar.show() - self.pack_start(main_bar) - self._bars = {0: main_bar} - self._set_resize_grip() - - # Virtual methods - - def do_get_property(self, prop): - """Return the gproperty's value. - """ - if prop.name == 'has-resize-grip': - return self.__has_resize_grip - else: - raise AttributeError, 'unknown property %s' % prop.name - - def do_set_property(self, prop, value): - """Set the property of writable properties. - """ - if prop.name == 'has-resize-grip': - self.__has_resize_grip = value - self._set_resize_grip() - else: - raise AttributeError, 'unknown or read only property %s' % prop.name - - # Private - - def _set_resize_grip(self): - """Set the resize grip for the statusbar. - - Resize grip is disabled for all statusbars except the last one, - which is set according to the "has-resize-grip" propery. - - """ - for bar in self.get_children(): - bar.set_has_resize_grip(False) - - bar.set_has_resize_grip(self.get_property('has-resize-grip')) - - def _set_packing(self): - """Set packing style of the statusbars. - - All bars are packed with "expand"=True, "fill"=True parameters, - except the last one, which is packed with "expand"=False, "fill"=False. - - """ - for bar in self.get_children(): - self.set_child_packing(bar, True, True, 0, gtk.PACK_START) - - self.set_child_packing(bar, False, False, 0, gtk.PACK_START) - - def _get_next_id(self): - """Get next unused statusbar id. - """ - id = 1 - while id in self._bars.keys(): - id = id + 1 - - return id - - # Public API - - def insert(self, index=-1, min_width=30, ralign=False): - """Insert a new statusbar. - - Create a new statusbar and insert it at the given index. Index starts - from '0'. If index is negative the new statusbar is appended. - The new bar_id is returned. - - """ - new_bar = gtk.Statusbar() - new_bar.set_size_request(min_width*self._char_width, -1) - new_bar.show() - self.pack_start(new_bar) - self.reorder_child(new_bar, index) - self._set_resize_grip() - self._set_packing() - - if ralign: - label = new_bar.get_children()[0].get_children()[0] - label.set_alignment(xalign=1.0, yalign=0.5) - - new_bar_id = self._get_next_id() - self._bars[new_bar_id] = new_bar - - return new_bar_id - - def get_context_id(self, context_description, bar_id=0): - """Return a new or existing context identifier. - - The target statusbar is identified by bar_id created when statusbar - was added. - Existence of the bar_id is not checked as giving a wrong id is - programming fault. - - """ - return self._bars[bar_id].get_context_id(context_description) - - def push(self, context_id, text, bar_id=0): - """Push message onto a statusbar's stack. - - The target statusbar is identified by bar_id created when statusbar - was added. - Existence of the bar_id is not checked as giving a wrong id is - programming fault. - - """ - return self._bars[bar_id].push(context_id, text) - - def pop(self, context_id, bar_id=0): - """Remove the top message from a statusbar's stack. - - The target statusbar is identified by bar_id created when statusbar - was added. - Existence of the bar_id is not checked as giving a wrong id is - programming fault. - - """ - self._bars[bar_id].pop(context_id) - - def remove(self, context_id, message_id, bar_id=0): - """Remove the message with the specified message_id. - - Remove the message with the specified message_id and context_id - from the statusbar's stack, which is identified by bar_id. - Existence of the bar_id is not checked as giving a wrong id is - programming fault. - - """ - self._bars[bar_id].remove(context_id, message_id) - - def set_has_resize_grip(self, setting): - """Mirror gtk.Statusbar functionaliy. - """ - self.set_property('has-resize-grip', setting) - - def get_has_resize_grip(self): - """Mirror gtk.Statusbar functionaliy. - """ - return self.get_property('has-resize-grip') - #============================================================================ # # MaskedEntry and ValidatableMaskedEntry copied and merged from the Kiwi @@ -1332,7 +100,7 @@ class FadeOut(gobject.GObject): Change the background of widget from src_color to dst_color in the number of steps specified """ - ##log.debug('_merge_colors: %s -> %s' % (src_color, dst_color)) + ##_LOG.debug('_merge_colors: %s -> %s' % (src_color, dst_color)) rs, gs, bs = src_color.red, src_color.green, src_color.blue rd, gd, bd = dst_color.red, dst_color.green, dst_color.blue @@ -1357,10 +125,10 @@ class FadeOut(gobject.GObject): def _start_merging(self): # If we changed during the delay if self._background_timeout_id != -1: - ##log.debug('_start_merging: Already running') + ##_LOG.debug('_start_merging: Already running') return - ##log.debug('_start_merging: Starting') + ##_LOG.debug('_start_merging: Starting') func = self._merge_colors(self._start_color, gtk.gdk.color_parse(self.ERROR_COLOR)).next self._background_timeout_id = ( @@ -1373,17 +141,17 @@ class FadeOut(gobject.GObject): @returns: True if we could start, False if was already in progress """ if self._background_timeout_id != -1: - ##log.debug('start: Background change already running') + ##_LOG.debug('start: Background change already running') return False if self._countdown_timeout_id != -1: - ##log.debug('start: Countdown already running') + ##_LOG.debug('start: Countdown already running') return False if self._done: - ##log.debug('start: Not running, already set') + ##_LOG.debug('start: Not running, already set') return False self._start_color = color - ##log.debug('start: Scheduling') + ##_LOG.debug('start: Scheduling') self._countdown_timeout_id = gobject.timeout_add( FadeOut.COMPLAIN_DELAY, self._start_merging) @@ -1391,7 +159,7 @@ class FadeOut(gobject.GObject): def stop(self): """Stops the fadeout and restores the background color""" - ##log.debug('Stopping') + ##_LOG.debug('Stopping') if self._background_timeout_id != -1: gobject.source_remove(self._background_timeout_id) self._background_timeout_id = -1 @@ -1402,9 +170,6 @@ class FadeOut(gobject.GObject): self._widget.update_background(self._start_color) self._done = False -if gtk.pygtk_version < (2, 8, 0): - gobject.type_register(FadeOut) - class Tooltip(gtk.Window): """Tooltip for the Icon in the MaskedEntry""" @@ -2627,9 +1392,6 @@ class MaskedEntry(gtk.Entry): model.append((item, )) -if gtk.pygtk_version < (2, 8, 0): - gobject.type_register(MaskedEntry) - #number = (int, float, long) VALIDATION_ICON_WIDTH = 16 @@ -2770,7 +1532,7 @@ class ValidatableMaskedEntry(MaskedEntry): try: text = self.get_text() - ##log.debug('Read %r for %s' % (data, self.model_attribute)) + ##_LOG.debug('Read %r for %s' % (data, self.model_attribute)) # check if we should draw the mandatory icon # this need to be done before any data conversion because we @@ -2805,7 +1567,7 @@ class ValidatableMaskedEntry(MaskedEntry): reset the background color """ - ##log.debug('Setting state for %s to VALID' % self.model_attribute) + ##_LOG.debug('Setting state for %s to VALID' % self.model_attribute) self._set_valid_state(True) self._fade.stop() @@ -2816,7 +1578,7 @@ class ValidatableMaskedEntry(MaskedEntry): @param text: text of tooltip of None @param fade: if we should fade the background """ - ##log.debug('Setting state for %s to INVALID' % self.model_attribute) + ##_LOG.debug('Setting state for %s to INVALID' % self.model_attribute) self._set_valid_state(False) @@ -2835,7 +1597,7 @@ class ValidatableMaskedEntry(MaskedEntry): text = text % self.get_text() except TypeError: # if text contains '%s' more than once - log.error('There must be only one instance of "%s"' + _LOG.error('There must be only one instance of "%s"' ' in validation error message') # fall back to a generic one so the error icon still have a tooltip text = generic_text @@ -2873,7 +1635,7 @@ class ValidatableMaskedEntry(MaskedEntry): """Change the validation state to blank state, this only applies for mandatory widgets, draw an icon and set a tooltip""" - ##log.debug('Setting state for %s to BLANK' % self.model_attribute) + ##_LOG.debug('Setting state for %s to BLANK' % self.model_attribute) if self.mandatory: self.set_stock(MANDATORY_ICON) @@ -2919,8 +1681,6 @@ class ValidatableMaskedEntry(MaskedEntry): def _on_fadeout__color_changed(self, fadeout, color): self.update_background(color) -if gtk.pygtk_version < (2, 8, 0): - gobject.type_register(ValidatableMaskedEntry) def main(args): from DateHandler import parser @@ -2961,29 +1721,11 @@ def main(args): widget2.mandatory = True vbox.pack_start(widget2, fill=False) - # == Statusbar testing ==================================================== - statusbar = Statusbar() - vbox.pack_end(statusbar, False) - - statusbar.push(1, "This is my statusbar...") - - my_statusbar = statusbar.insert(min_width=24) - statusbar.push(1, "Testing status bar width", my_statusbar) - - yet_another_statusbar = statusbar.insert(1, 11) - statusbar.push(1, "A short one", yet_another_statusbar) - - last_statusbar = statusbar.insert(min_width=41, ralign=True) - statusbar.push(1, "The last statusbar has always fixed width", - last_statusbar) - - # ========================================================================= - win.show_all() gtk.main() if __name__ == '__main__': import sys # fall back to root logger for testing - log = logging + _LOG = logging sys.exit(main(sys.argv))