12369d04a8
* introducing __all__ in modules, so __init__ imports only public objects; * MultiTypeComboEntry renamed to ValidatedComboEntry and different data type support implemented; * ShortlistComboEntry introduced; * ValueAction and ValueToolItem introduced; * ComboToolAction and ComboToolItem are removed, ValueAction and ToolComboEntry are implemented instead; svn: r10763
259 lines
8.0 KiB
Python
259 lines
8.0 KiB
Python
#
|
|
# 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 ValidatedComboEntry widget class."
|
|
|
|
__all__ = ["ValidatedComboEntry"]
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Python modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
import logging
|
|
_LOG = logging.getLogger(".widgets.validatedcomboentry")
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# GTK modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
import gtk
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# ValidatedComboEntry class
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
class ValidatedComboEntry(gtk.ComboBox, gtk.CellLayout):
|
|
"""A ComboBoxEntry widget with validation.
|
|
|
|
ValidatedComboEntry may have data type other then string, and is set
|
|
with the C{datatype} contructor parameter.
|
|
|
|
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, ValidatedComboEntry 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__ = "ValidatedComboEntry"
|
|
|
|
def __init__(self, datatype, 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._data_type = datatype
|
|
self._data_column = -1
|
|
self.set_data_column(column)
|
|
|
|
self._active_text = ''
|
|
self._active_data = None
|
|
self.set_active(-1)
|
|
|
|
self._validator = validator
|
|
|
|
self._entry.connect('activate', self._on_entry_activate)
|
|
self._entry.connect('focus-in-event', self._on_entry_focus_in_event)
|
|
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.connect('changed', self._on_changed)
|
|
self._internal_change = False
|
|
|
|
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_in_event(self, widget, event):
|
|
"""Signal handler.
|
|
|
|
Called when the focus enters the entry, and is used for saving
|
|
the entry's text for later comparison.
|
|
|
|
"""
|
|
self._text_on_focus_in = self._entry.get_text()
|
|
|
|
def _on_entry_focus_out_event(self, widget, event):
|
|
"""Signal handler.
|
|
|
|
Called when the focus leaves the entry.
|
|
|
|
"""
|
|
if (self._entry.get_text() != self._text_on_focus_in):
|
|
self._entry_changed(widget)
|
|
|
|
def _on_entry_key_press_event(self, entry, event):
|
|
"""Signal handler.
|
|
|
|
Its purpose is to handle escape button.
|
|
|
|
"""
|
|
# FIXME Escape never reaches here, the dialog eats it, I assume.
|
|
if event.keyval == gtk.keysyms.Escape:
|
|
entry.set_text(self._active_text)
|
|
entry.set_position(-1)
|
|
return True
|
|
|
|
return False
|
|
|
|
def _on_changed(self, combobox):
|
|
"""Signal handler.
|
|
|
|
Called when the active row is changed in the combo box.
|
|
|
|
"""
|
|
if self._internal_change:
|
|
return
|
|
|
|
iter = self.get_active_iter()
|
|
if iter:
|
|
model = self.get_model()
|
|
self._active_data = model.get_value(iter, self._data_column)
|
|
self._active_text = str(self._active_data)
|
|
self._entry.set_text(self._active_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()
|
|
|
|
try:
|
|
new_data = self._data_type(new_text)
|
|
|
|
if (self._validator is not None) and not self._validator(new_data):
|
|
raise ValueError
|
|
except ValueError:
|
|
entry.set_text(self._active_text)
|
|
entry.set_position(-1)
|
|
return
|
|
|
|
self._active_text = new_text
|
|
self._active_data = new_data
|
|
|
|
self._internal_change = True
|
|
new_iter = self._is_in_model(new_data)
|
|
if new_iter is None:
|
|
self.set_active(-1)
|
|
else:
|
|
self.set_active_iter(new_iter)
|
|
self._internal_change = False
|
|
|
|
def _has_frame_changed(self):
|
|
has_frame = self.get_property('has-frame')
|
|
self._entry.set_has_frame(has_frame)
|
|
|
|
def _is_in_model(self, data):
|
|
"""Check if given data is in the model or not.
|
|
|
|
@param data: data value to check
|
|
@type data: depends on the actual data type of the object
|
|
@returns: position of 'data' in the model
|
|
@returntype: gtk.TreeIter or None
|
|
|
|
"""
|
|
model = self.get_model()
|
|
|
|
iter = model.get_iter_first()
|
|
while iter:
|
|
if model.get_value(iter, self._data_column) == data:
|
|
break
|
|
iter = model.iter_next(iter)
|
|
|
|
return iter
|
|
|
|
# Public methods
|
|
|
|
def set_data_column(self, data_column):
|
|
if data_column < 0:
|
|
return
|
|
|
|
model = self.get_model()
|
|
if model is None:
|
|
return
|
|
|
|
if data_column > model.get_n_columns():
|
|
return
|
|
|
|
if self._data_column == -1:
|
|
self._data_column = data_column
|
|
self.set_attributes(self._text_renderer, text=data_column)
|
|
|
|
def get_data_column(self):
|
|
return self._data_column
|
|
|
|
def set_active_data(self, data):
|
|
# set it via entry so that it will be also validated
|
|
if self._entry:
|
|
self._entry.set_text(str(data))
|
|
self._entry_changed(self._entry)
|
|
|
|
def get_active_data(self):
|
|
return self._active_data
|
|
|
|
def set_entry_editable(self, is_editable):
|
|
self._entry.set_editable(is_editable)
|