diff --git a/src/widgets/Makefile.am b/src/widgets/Makefile.am
index 17f567c52..46bfbbc7e 100644
--- a/src/widgets/Makefile.am
+++ b/src/widgets/Makefile.am
@@ -8,6 +8,8 @@ pkgdatadir = $(datadir)/@PACKAGE@/widgets
pkgdata_PYTHON = \
__init__.py \
grampswidgets.py \
+ multitypecomboentry.py \
+ toolbarwidgets.py \
styledtextbuffer.py \
styledtexteditor.py
diff --git a/src/widgets/__init__.py b/src/widgets/__init__.py
index 8986ec7c3..ef694e949 100644
--- a/src/widgets/__init__.py
+++ b/src/widgets/__init__.py
@@ -23,6 +23,8 @@
"""Custom widgets."""
from grampswidgets import *
+from multitypecomboentry import MultiTypeComboEntry
+from toolbarwidgets import *
from styledtextbuffer import *
from styledtexteditor import *
diff --git a/src/widgets/multitypecomboentry.py b/src/widgets/multitypecomboentry.py
new file mode 100644
index 000000000..6b46727ff
--- /dev/null
+++ b/src/widgets/multitypecomboentry.py
@@ -0,0 +1,200 @@
+#
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2008 Zsolt Foldvari
+#
+# 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$
+
+"The MultiTypeComboEntry widget class."
+
+#-------------------------------------------------------------------------
+#
+# Python modules
+#
+#-------------------------------------------------------------------------
+import logging
+_LOG = logging.getLogger(".widgets.multitypecomboentry")
+
+#-------------------------------------------------------------------------
+#
+# GTK modules
+#
+#-------------------------------------------------------------------------
+import gtk
+
+#-------------------------------------------------------------------------
+#
+# MultiTypeComboEntry class
+#
+#-------------------------------------------------------------------------
+class MultiTypeComboEntry(gtk.ComboBox, gtk.CellLayout):
+ """A ComboBoxEntry widget with validation.
+
+ MultiTypeComboEntry may have data type other then string (tbd.).
+
+ Its behaviour is different from gtk.ComboBoxEntry in the way how
+ the entry part of the widget is handled. While gtk.ComboBoxEntry
+ emits the 'changed' signal immediatelly the text in the entry is
+ changed, MultiTypeComboEntry emits the signal only after the text is
+ activated (enter is pressed, the focus is moved out) and validated.
+
+ Validation function is an optional feature and activated only if a
+ validator function is given at instantiation.
+
+ The entry can be set as editable or not editable using the
+ L{set_entry_editable} method.
+
+ """
+ __gtype_name__ = "MultiTypeComboEntry"
+
+ def __init__(self, model=None, column=-1, validator=None):
+ gtk.ComboBox.__init__(self, model)
+
+ self._entry = gtk.Entry()
+ #
+ dummy_event = gtk.gdk.Event(gtk.gdk.NOTHING)
+ self._entry.start_editing(dummy_event)
+ #
+ self.add(self._entry)
+ self._entry.show()
+
+ self._text_renderer = gtk.CellRendererText()
+ self.pack_start(self._text_renderer, False)
+
+ self._text_column = -1
+ self.set_text_column(column)
+ self._active_text = ''
+ self.set_active(-1)
+
+ self._validator = validator
+
+ self._entry.connect('activate', self._on_entry_activate)
+ self._entry.connect('focus-out-event', self._on_entry_focus_out_event)
+ self._entry.connect('key-press-event', self._on_entry_key_press_event)
+ self.changed_cb_id = self.connect('changed', self._on_changed)
+
+ self._has_frame_changed()
+ self.connect('notify', self._on_notify)
+
+ # Virtual overriden methods
+
+ def do_mnemonic_activate(self, group_cycling):
+ self._entry.grab_focus()
+ return True
+
+ def do_grab_focus(self):
+ self._entry.grab_focus()
+
+ # Signal handlers
+
+ def _on_entry_activate(self, entry):
+ """Signal handler.
+
+ Called when the entry is activated.
+
+ """
+ self._entry_changed(entry)
+
+ def _on_entry_focus_out_event(self, widget, event):
+ """Signal handler.
+
+ Called when the focus leaves the entry.
+
+ """
+ self._entry_changed(widget)
+
+ def _on_entry_key_press_event(self, entry, event):
+ """Signal handler.
+
+ Its purpose is to handle escape button.
+
+ """
+ if event.keyval == gtk.keysyms.Escape:
+ entry.set_text(self._active_text)
+
+ return False
+
+ def _on_changed(self, combobox):
+ """Signal handler.
+
+ Called when the active row is changed in the combo box.
+
+ """
+ iter = self.get_active_iter()
+ if iter:
+ model = self.get_model()
+ new_text = model.get_value(iter, self._text_column)
+ self._entry.set_text(new_text)
+
+ def _on_notify(self, object, gparamspec):
+ """Signal handler.
+
+ Called whenever a property of the object is changed.
+
+ """
+ if gparamspec.name == 'has-frame':
+ self._has_frame_changed()
+
+ # Private methods
+
+ def _entry_changed(self, entry):
+ new_text = entry.get_text()
+
+ if (self._validator is not None) and not self._validator(new_text):
+ entry.set_text(self._active_text)
+ return
+
+ self._active_text = new_text
+ self.handler_block(self.changed_cb_id)
+ self.set_active(-1)
+ self.handler_unblock(self.changed_cb_id)
+
+ def _has_frame_changed(self):
+ has_frame = self.get_property('has-frame')
+ self._entry.set_has_frame(has_frame)
+
+ # Public methods
+
+ def set_text_column(self, text_column):
+ if text_column < 0:
+ return
+
+ if text_column > self.get_model().get_n_columns():
+ return
+
+ if self._text_column == -1:
+ self._text_column = text_column
+ self.set_attributes(self._text_renderer, text=text_column)
+
+ def get_text_column(self):
+ return self._text_column
+
+ def set_active_text(self, text):
+ if self._entry:
+ self._entry.set_text(text)
+ #self._entry.activate()
+
+ def get_active_text(self):
+ if self._entry:
+ return self._entry.get_text()
+
+ return None
+
+ def set_entry_editable(self, is_editable):
+ self._entry.set_editable(is_editable)
diff --git a/src/widgets/styledtexteditor.py b/src/widgets/styledtexteditor.py
index e2d40451c..30ba69dc2 100644
--- a/src/widgets/styledtexteditor.py
+++ b/src/widgets/styledtexteditor.py
@@ -30,7 +30,7 @@
from gettext import gettext as _
import logging
-_LOG = logging.getLogger(".Editors.StyledTextEditor")
+_LOG = logging.getLogger(".widgets.styledtexteditor")
#-------------------------------------------------------------------------
#
@@ -49,7 +49,8 @@ from pango import UNDERLINE_SINGLE
from gen.lib import StyledTextTagType
from widgets import (StyledTextBuffer, ALLOWED_STYLES,
MATCH_START, MATCH_END,
- MATCH_FLAVOR, MATCH_STRING)
+ MATCH_FLAVOR, MATCH_STRING,
+ ComboToolAction, SpringSeparatorAction)
from Spell import Spell
from GrampsDisplay import url as display_url
@@ -350,19 +351,36 @@ class StyledTextEditor(gtk.TextView):
]
# ...last the custom actions, which have custom proxies
+ items = [f.get_name() for f in self.get_pango_context().list_families()]
+ default = StyledTextTagType.STYLE_DEFAULT[StyledTextTagType.FONTFACE]
fontface_action = ComboToolAction(str(StyledTextTagType.FONTFACE),
_("Font family"),
- _("Font family"), None)
+ _("Font family"),
+ None,
+ items,
+ str(default),
+ editable=False)
+ fontface_action.connect('activate', self._on_comboaction_activate)
+
+ items = [str(size) for size in FONT_SIZES]
+ default = StyledTextTagType.STYLE_DEFAULT[StyledTextTagType.FONTSIZE]
fontsize_action = ComboToolAction(str(StyledTextTagType.FONTSIZE),
_("Font size"),
- _("Font size"), None)
+ _("Font size"),
+ None,
+ items,
+ str(default),
+ sortable=False,
+ validator=is_valid_fontsize)
+ fontsize_action.connect('activate', self._on_comboaction_activate)
+
spring = SpringSeparatorAction("spring", "", "", None)
# action accelerators
self.action_accels = {
- 'i': 'italic',
- 'b': 'bold',
- 'u': 'underline',
+ 'i': str(StyledTextTagType.ITALIC),
+ 'b': str(StyledTextTagType.BOLD),
+ 'u': str(StyledTextTagType.UNDERLINE),
}
# create the action group and insert all the actions
@@ -379,15 +397,6 @@ class StyledTextEditor(gtk.TextView):
uimanager.add_ui_from_string(FORMAT_TOOLBAR)
uimanager.ensure_update()
- # now that widget is created for the custom actions set them up
- fontface = uimanager.get_widget('/ToolBar/%d' %
- StyledTextTagType.FONTFACE)
- set_fontface_toolitem(fontface, self._on_combotoolitem_changed)
-
- fontsize = uimanager.get_widget('/ToolBar/%d' %
- StyledTextTagType.FONTSIZE)
- set_fontsize_toolitem(fontsize, self._on_combotoolitem_changed)
-
# get the toolbar and set it's style
toolbar = uimanager.get_widget('/ToolBar')
toolbar.set_style(gtk.TOOLBAR_ICONS)
@@ -477,13 +486,21 @@ class StyledTextEditor(gtk.TextView):
(style, str(value)))
self.textbuffer.apply_style(style, value)
- def _on_combotoolitem_changed(self, combobox, style):
+ def _on_comboaction_activate(self, action):
if self._internal_style_change:
return
- value = StyledTextTagType.STYLE_TYPE[style](combobox.get_active_text())
- _LOG.debug("applying style '%d' with value '%s'" % (style, str(value)))
- self.textbuffer.apply_style(style, value)
+ style = int(action.get_name())
+
+ text = action.get_active_value()
+ try:
+ value = StyledTextTagType.STYLE_TYPE[style](text)
+ _LOG.debug("applying style '%d' with value '%s'" %
+ (style, str(value)))
+ self.textbuffer.apply_style(style, value)
+ except ValueError:
+ _LOG.debug("unable to convert '%s' to '%s'" %
+ (text, StyledTextTagType.STYLE_TYPE[style]))
def _format_clear_cb(self, action):
"""Remove all formats from the selection.
@@ -496,7 +513,7 @@ class StyledTextEditor(gtk.TextView):
self.textbuffer.remove_style(style)
def _on_buffer_style_changed(self, buffer, changed_styles):
- # set state of toggle action
+ # update action values
for style in changed_styles.keys():
if str(style) in self.toggle_actions:
action = self.action_group.get_action(str(style))
@@ -506,25 +523,9 @@ class StyledTextEditor(gtk.TextView):
if ((style == StyledTextTagType.FONTFACE) or
(style == StyledTextTagType.FONTSIZE)):
-
action = self.action_group.get_action(str(style))
- combo = action.get_proxies()[0].child
- model = combo.get_model()
- iter = model.get_iter_first()
- while iter:
- if (StyledTextTagType.STYLE_TYPE[style](
- model.get_value(iter, 0)) == changed_styles[style]):
- break
- iter = model.iter_next(iter)
-
self._internal_style_change = True
- if iter is None:
- combo.child.set_text(str(changed_styles[style]))
- if style == StyledTextTagType.FONTFACE:
- _LOG.debug('font family "%s" is not installed' %
- changed_styles[style])
- else:
- combo.set_active_iter(iter)
+ action.set_active_value(str(changed_styles[style]))
self._internal_style_change = False
def _spell_change_cb(self, menuitem, language):
@@ -586,130 +587,11 @@ class StyledTextEditor(gtk.TextView):
"""
return self.toolbar
-#-------------------------------------------------------------------------
-#
-# ComboToolItem class
-#
-#-------------------------------------------------------------------------
-class ComboToolItem(gtk.ToolItem):
-
- __gtype_name__ = "ComboToolItem"
-
- def __init__(self):
- gtk.ToolItem.__init__(self)
-
- self.set_border_width(2)
- self.set_homogeneous(False)
- self.set_expand(False)
-
- self.combobox = gtk.combo_box_entry_new_text()
- self.combobox.show()
- self.add(self.combobox)
-
- def set_entry_editable(self, editable):
- self.combobox.child.set_editable(editable)
-
-#-------------------------------------------------------------------------
-#
-# ComboToolAction class
-#
-#-------------------------------------------------------------------------
-class ComboToolAction(gtk.Action):
-
- __gtype_name__ = "ComboToolAction"
-
- def __init__(self, name, label, tooltip, stock_id):
- gtk.Action.__init__(self, name, label, tooltip, stock_id)
- ##self.set_tool_item_type(ComboToolItem)
-
- ##def create_tool_item(self):
- ##combobox = ComboToolButton()
- ###self.connect_proxy(combobox)
- ##return combobox
-
- ##def connect_proxy(self, proxy):
- ##gtk.Action.connect_proxy(self, proxy)
-
- ##if isinstance(proxy, ComboToolButton):
- ##proxy.combobox.connect('changed', self.changed)
-
- ##def changed(self, combobox):
- ##self.activate()
-ComboToolAction.set_tool_item_type(ComboToolItem)
-
-#-------------------------------------------------------------------------
-#
-# SpringSeparatorToolItem class
-#
-#-------------------------------------------------------------------------
-class SpringSeparatorToolItem(gtk.SeparatorToolItem):
-
- __gtype_name__ = "SpringSeparatorToolItem"
-
- def __init__(self):
- gtk.SeparatorToolItem.__init__(self)
-
- self.set_draw(False)
- self.set_expand(True)
-
-#-------------------------------------------------------------------------
-#
-# SpringSeparatorAction class
-#
-#-------------------------------------------------------------------------
-class SpringSeparatorAction(gtk.Action):
-
- __gtype_name__ = "SpringSeparatorAction"
-
- def __init__(self, name, label, tooltip, stock_id):
- gtk.Action.__init__(self, name, label, tooltip, stock_id)
-
-SpringSeparatorAction.set_tool_item_type(SpringSeparatorToolItem)
-
#-------------------------------------------------------------------------
#
# Module functions
#
#-------------------------------------------------------------------------
-def set_fontface_toolitem(combotoolitem, callback):
- """Setup font family comboboxentry."""
- combotoolitem.set_entry_editable(False)
-
- fontface = combotoolitem.child
-
- families = [family.get_name()
- for family in fontface.get_pango_context().list_families()]
- families.sort()
- for family in families:
- fontface.append_text(family)
-
- try:
- def_fam = StyledTextTagType.STYLE_DEFAULT[StyledTextTagType.FONTFACE]
- default = families.index(def_fam)
- except ValueError:
- default = 0
- fontface.set_active(default)
-
- fontface.connect('changed', callback, StyledTextTagType.FONTFACE)
-
-def set_fontsize_toolitem(combotoolitem, callback):
- """Setup font size comboboxentry."""
- combotoolitem.set_size_request(60, -1)
-
- fontsize = combotoolitem.child
-
- for size in FONT_SIZES:
- fontsize.append_text(str(size))
-
- try:
- def_size = StyledTextTagType.STYLE_DEFAULT[StyledTextTagType.FONTSIZE]
- default = FONT_SIZES.index(def_size)
- except ValueError:
- default = 0
- fontsize.set_active(default)
-
- fontsize.connect('changed', callback, StyledTextTagType.FONTSIZE)
-
def color_to_hex(color):
"""Convert gtk.gdk.Color to hex string."""
hexstring = ""
@@ -724,3 +606,10 @@ def hex_to_color(hex):
"""Convert hex string to gtk.gdk.Color."""
color = gtk.gdk.color_parse(hex)
return color
+
+def is_valid_fontsize(text):
+ try:
+ size = int(text)
+ return (size > 0) and (size < 73)
+ except ValueError:
+ return False
\ No newline at end of file
diff --git a/src/widgets/toolbarwidgets.py b/src/widgets/toolbarwidgets.py
new file mode 100644
index 000000000..883cc1f90
--- /dev/null
+++ b/src/widgets/toolbarwidgets.py
@@ -0,0 +1,236 @@
+#
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2008 Zsolt Foldvari
+#
+# 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$
+
+"Widget classes used for Toolbar."
+
+#-------------------------------------------------------------------------
+#
+# Python modules
+#
+#-------------------------------------------------------------------------
+import logging
+_LOG = logging.getLogger(".widgets.toolbarwidgets")
+
+#-------------------------------------------------------------------------
+#
+# GTK modules
+#
+#-------------------------------------------------------------------------
+import gobject
+import gtk
+
+#-------------------------------------------------------------------------
+#
+# GRAMPS modules
+#
+#-------------------------------------------------------------------------
+from widgets import MultiTypeComboEntry
+
+#-------------------------------------------------------------------------
+#
+# Constants
+#
+#-------------------------------------------------------------------------
+(COLUMN_ITEM,
+ COLUMN_IS_SEP,) = range(2)
+
+#-------------------------------------------------------------------------
+#
+# ComboToolItem class
+#
+#-------------------------------------------------------------------------
+class ComboToolItem(gtk.ToolItem):
+
+ __gtype_name__ = "ComboToolItem"
+
+ __gsignals__ = {
+ 'changed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, #return value
+ ()), # arguments
+ }
+
+ def __init__(self, model, editable, validator=None):
+ gtk.ToolItem.__init__(self)
+
+ self.set_border_width(2)
+ self.set_homogeneous(False)
+ self.set_expand(False)
+
+ combo_entry = MultiTypeComboEntry(model, COLUMN_ITEM, validator)
+ combo_entry.set_focus_on_click(False)
+ combo_entry.set_entry_editable(editable)
+ combo_entry.show()
+ self.add(combo_entry)
+
+ combo_entry.connect('changed', self._on_combo_changed)
+
+ def _on_combo_changed(self, combo_entry):
+ self.emit('changed')
+
+ def set_active_iter(self, iter):
+ self.child.set_active_iter(iter)
+
+ def get_active_iter(self):
+ return self.child.get_active_iter()
+
+ def set_active_text(self, text):
+ self.child.set_active_text(text)
+
+ def get_active_text(self):
+ return self.child.get_active_text()
+
+#-------------------------------------------------------------------------
+#
+# ComboToolAction class
+#
+#-------------------------------------------------------------------------
+class ComboToolAction(gtk.Action):
+
+ __gtype_name__ = "ComboToolAction"
+
+ def __init__(self, name, label, tooltip, stock_id, items,
+ default=None, sortable=True, editable=True,
+ validator=None):
+
+ gtk.Action.__init__(self, name, label, tooltip, stock_id)
+
+ # create the model and insert the items
+ self.model = gtk.ListStore(gobject.TYPE_STRING,
+ gobject.TYPE_BOOLEAN)
+ for item in items:
+ self.model.append((item, False))
+
+ # sort the rows if allowed
+ if sortable:
+ self.model.set_sort_column_id(COLUMN_ITEM, gtk.SORT_ASCENDING)
+ self.model.sort_column_changed()
+
+ # set the first row (after sorting) as default if default was not set
+ if (default is None) or (default not in items):
+ self.default = self.model.get_value(self.model.get_iter_first(),
+ COLUMN_ITEM)
+ else:
+ self.default = default
+
+ self.set_active_value(self.default)
+
+ # set the first row as separator
+ self.model.set_value(self.model.get_iter_first(), COLUMN_IS_SEP, True)
+
+ # remember if the proxy combo is editable
+ self.editable = editable
+ self.validator = validator
+
+ def do_create_tool_item(self):
+ """Create a toolbar item widget that proxies for the given action.
+
+ Override the default method, to be able to pass the required
+ parameters to the proxy's constructor.
+
+ @returns: a toolbar item connected to the action.
+ @returntype: ComboToolItem
+
+ """
+ combo = ComboToolItem(self.model, self.editable, self.validator)
+ self.connect_proxy(combo)
+ return combo
+
+ def connect_proxy(self, proxy):
+ """Connect a widget to an action object as a proxy.
+
+ @param proxy: widget to be connected
+ @type proxy: gtk.Widget
+
+ """
+ # do this before hand, so that we don't call the "activate" handler
+ if isinstance(proxy, ComboToolItem):
+ proxy.set_active_iter(self.active_iter)
+ proxy.connect('changed', self._on_proxy_changed)
+
+ #gtk.Action.connect_proxy(self, proxy)
+
+ def _on_proxy_changed(self, proxy):
+ if isinstance(proxy, ComboToolItem):
+ self.active_iter = proxy.get_active_iter()
+
+ if self.active_iter:
+ value = self.model.get_value(self.active_iter, COLUMN_ITEM)
+ else:
+ value = proxy.get_active_text()
+
+ self.set_active_value(value)
+
+ def set_active_value(self, value):
+ self.active_value = value
+
+ iter = self.model.get_iter_first()
+ while iter:
+ if self.model.get_value(iter, COLUMN_ITEM) == value:
+ self.active_iter = iter
+ break
+ iter = self.model.iter_next(iter)
+
+ for proxy in self.get_proxies():
+ if isinstance(proxy, ComboToolItem):
+ if iter:
+ proxy.set_active_iter(self.active_iter)
+ else:
+ proxy.set_active_text(self.active_value)
+ else:
+ _LOG.warning("Don't know how to activate %s widget" %
+ proxy.__class__)
+ self.activate()
+
+ def get_active_value(self):
+ return self.active_value
+
+ComboToolAction.set_tool_item_type(ComboToolItem)
+
+#-------------------------------------------------------------------------
+#
+# SpringSeparatorToolItem class
+#
+#-------------------------------------------------------------------------
+class SpringSeparatorToolItem(gtk.SeparatorToolItem):
+
+ __gtype_name__ = "SpringSeparatorToolItem"
+
+ def __init__(self):
+ gtk.SeparatorToolItem.__init__(self)
+
+ self.set_draw(False)
+ self.set_expand(True)
+
+#-------------------------------------------------------------------------
+#
+# SpringSeparatorAction class
+#
+#-------------------------------------------------------------------------
+class SpringSeparatorAction(gtk.Action):
+
+ __gtype_name__ = "SpringSeparatorAction"
+
+ def __init__(self, name, label, tooltip, stock_id):
+ gtk.Action.__init__(self, name, label, tooltip, stock_id)
+
+SpringSeparatorAction.set_tool_item_type(SpringSeparatorToolItem)
+