Move the combo box toolbar widgets out from StyledTextEditor using new design.

svn: r10698
This commit is contained in:
Zsolt Foldvari 2008-05-09 12:50:04 +00:00
parent 8ef04948d9
commit b8d75d85e1
5 changed files with 486 additions and 157 deletions

View File

@ -8,6 +8,8 @@ pkgdatadir = $(datadir)/@PACKAGE@/widgets
pkgdata_PYTHON = \
__init__.py \
grampswidgets.py \
multitypecomboentry.py \
toolbarwidgets.py \
styledtextbuffer.py \
styledtexteditor.py

View File

@ -23,6 +23,8 @@
"""Custom widgets."""
from grampswidgets import *
from multitypecomboentry import MultiTypeComboEntry
from toolbarwidgets import *
from styledtextbuffer import *
from styledtexteditor import *

View File

@ -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()
# <hack description="set the GTK_ENTRY (self._entry)->is_cell_renderer
# flag to TRUE in order to tell the entry to fill its allocation.">
dummy_event = gtk.gdk.Event(gtk.gdk.NOTHING)
self._entry.start_editing(dummy_event)
# </hack>
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)

View File

@ -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 = {
'<Control>i': 'italic',
'<Control>b': 'bold',
'<Control>u': 'underline',
'<Control>i': str(StyledTextTagType.ITALIC),
'<Control>b': str(StyledTextTagType.BOLD),
'<Control>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

View File

@ -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)