* missing installation stuff for undo/redo

*  create a first undo gtk entry field: family name in person editor


svn: r15837
This commit is contained in:
Benny Malengier 2010-08-30 23:00:28 +00:00
parent fe311affb2
commit c2dc10471c
10 changed files with 333 additions and 5 deletions

View File

@ -326,6 +326,9 @@ src/gui/widgets/labels.py
src/gui/widgets/menutoolbuttonaction.py
src/gui/widgets/progressdialog.py
src/gui/widgets/styledtexteditor.py
src/gui/widgets/undoablebuffer.py
src/gui/widgets/undoableentry.py
src/gui/widgets/undoablestyledbuffer.py
src/gui/widgets/validatedmaskedentry.py
# Simple API

View File

@ -3,6 +3,9 @@ import gtk
class ValidatableMaskedEntry(gtk.Entry):
__gtype_name__ = 'ValidatableMaskedEntry'
class UndoableEntry(gtk.Entry):
__gtype_name__ = 'UndoableEntry'
class StyledTextEditor(gtk.TextView):
__gtype_name__ = 'StyledTextEditor'

View File

@ -6,6 +6,10 @@
name="ValidatableMaskedEntry"
title="Validatable Masked Entry"
generic-name="valid_mask"/>
<glade-widget-class
name="UndoableEntry"
title="Undoable Entry"
generic-name="undo_entry"/>
<glade-widget-class
name="StyledTextEditor"
title="Styled Text Editor"
@ -13,6 +17,7 @@
</glade-widget-classes>
<glade-widget-group name="GrampsWidgets" title="Gramps Widgets">
<glade-widget-class-ref name="ValidatableMaskedEntry"/>
<glade-widget-class-ref name="UndoableEntry"/>
<glade-widget-class-ref name="StyledTextEditor"/>
</glade-widget-group>
</glade-catalog>

View File

@ -1,6 +1,7 @@
<?xml version="1.0"?>
<interface>
<!-- interface-requires gtk+ 2.12 -->
<!-- interface-requires grampswidgets 0.0 -->
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkDialog" id="editperson">
<property name="has_focus">True</property>
@ -149,7 +150,7 @@
</packing>
</child>
<child>
<object class="GtkEntry" id="surname">
<object class="UndoableEntry" id="surname">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">part of a person's name indicating the family to which the person belongs</property>

View File

@ -24,6 +24,8 @@ pkgdata_PYTHON = \
tageditor.py \
toolcomboentry.py \
undoablebuffer.py \
undoableentry.py \
undoablestyledbuffer.py \
validatedcomboentry.py \
validatedmaskedentry.py \
valueaction.py \

View File

@ -34,6 +34,9 @@ from statusbar import Statusbar
from styledtextbuffer import *
from styledtexteditor import *
from toolcomboentry import *
from undoablebuffer import *
from undoableentry import *
from undoablestyledbuffer import *
from validatedcomboentry import *
from validatedmaskedentry import *
from valueaction import *

View File

@ -20,7 +20,7 @@
# $Id$
"Text buffer subclassed from gtk.TextBuffer handling L{StyledText}."
"""Text buffer subclassed from gtk.TextBuffer handling L{StyledText}."""
__all__ = ["ALLOWED_STYLES", "MATCH_START", "MATCH_END", "MATCH_FLAVOR",
"MATCH_STRING", "StyledTextBuffer"]

View File

@ -26,6 +26,8 @@
gtk textbuffer with undo functionality
"""
__all__ = ["UndoableBuffer"]
# Originally LGLP from:
# http://bitbucket.org/tiax/gtk-textbuffer-with-undo/
# Please send bugfixes and comments upstream to Florian

View File

@ -0,0 +1,311 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2010 Benny Malengier
#
# based on undoablebuffer Copyright (C) 2009 Florian Heinle
#
# 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: validatedmaskedentry.py 14091 2010-01-18 04:42:17Z pez4brian $
__all__ = ["UndoableEntry"]
#-------------------------------------------------------------------------
#
# Standard python modules
#
#-------------------------------------------------------------------------
from gen.ggettext import gettext as _
import logging
_LOG = logging.getLogger(".widgets.undoableentry")
#-------------------------------------------------------------------------
#
# GTK/Gnome modules
#
#-------------------------------------------------------------------------
import gobject
import gtk
#-------------------------------------------------------------------------
#
# Gramps modules
#
#-------------------------------------------------------------------------
from undoablebuffer import Stack
class UndoableInsertEntry(object):
"""something that has been inserted into our gtk.editable"""
def __init__(self, text, length, position, editable):
self.offset = position
self.text = str(text)
self.length = length
if self.length > 1 or self.text in ("\r", "\n", " "):
self.mergeable = False
else:
self.mergeable = True
class UndoableDeleteEntry(object):
"""something that has been deleted from our textbuffer"""
def __init__(self, editable, start, end):
self.text = str(editable.get_chars(start, end))
self.start = start
self.end = end
# need to find out if backspace or delete key has been used
# so we don't mess up during redo
insert = editable.get_position()
if insert <= start:
self.delete_key_used = True
else:
self.delete_key_used = False
if self.end - self.start > 1 or self.text in ("\r", "\n", " "):
self.mergeable = False
else:
self.mergeable = True
class UndoableEntry(gtk.Entry):
"""
The UndoableEntry is an Entry subclass with additional features.
Additional features:
- Undo and Redo on CTRL-Z/CTRL-SHIFT-Z
"""
__gtype_name__ = 'UndoableEntry'
insertclass = UndoableInsertEntry
deleteclass = UndoableDeleteEntry
#how many undo's are remembered
undo_stack_size = 50
def __init__(self):
gtk.Entry.__init__(self)
self.undo_stack = Stack(self.undo_stack_size)
self.redo_stack = []
self.not_undoable_action = False
self.undo_in_progress = False
self.connect('insert-text', self._on_insert_text)
self.connect('delete-text', self._on_delete_text)
self.connect('key-press-event', self._on_key_press_event)
def set_text(self, text):
gtk.Entry.set_text(self, text)
self.reset()
def _on_key_press_event(self, widget, event):
"""Signal handler.
Handle formatting undo/redo key press.
"""
if ((gtk.gdk.keyval_name(event.keyval) == 'Z') and
(event.state & gtk.gdk.CONTROL_MASK) and
(event.state & gtk.gdk.SHIFT_MASK)):
self.redo()
return True
elif ((gtk.gdk.keyval_name(event.keyval) == 'z') and
(event.state & gtk.gdk.CONTROL_MASK)):
self.undo()
return True
return False
def __empty_redo_stack(self):
self.redo_stack = []
def _on_insert_text(self, editable, text, length, positionptr):
def can_be_merged(prev, cur):
"""see if we can merge multiple inserts here
will try to merge words or whitespace
can't merge if prev and cur are not mergeable in the first place
can't merge when user set the input bar somewhere else
can't merge across word boundaries"""
WHITESPACE = (' ', '\t')
if not cur.mergeable or not prev.mergeable:
return False
elif cur.offset != (prev.offset + prev.length):
return False
elif cur.text in WHITESPACE and not prev.text in WHITESPACE:
return False
elif prev.text in WHITESPACE and not cur.text in WHITESPACE:
return False
return True
if not self.undo_in_progress:
self.__empty_redo_stack()
if self.not_undoable_action:
return
undo_action = self.insertclass(text, length, editable.get_position(),
editable)
try:
prev_insert = self.undo_stack.pop()
except IndexError:
self.undo_stack.append(undo_action)
return
if not isinstance(prev_insert, self.insertclass):
self.undo_stack.append(prev_insert)
self.undo_stack.append(undo_action)
return
if can_be_merged(prev_insert, undo_action):
prev_insert.length += undo_action.length
prev_insert.text += undo_action.text
self.undo_stack.append(prev_insert)
else:
self.undo_stack.append(prev_insert)
self.undo_stack.append(undo_action)
def _on_delete_text(self, editable, start, end):
def can_be_merged(prev, cur):
"""see if we can merge multiple deletions here
will try to merge words or whitespace
can't merge if prev and cur are not mergeable in the first place
can't merge if delete and backspace key were both used
can't merge across word boundaries"""
WHITESPACE = (' ', '\t')
if not cur.mergeable or not prev.mergeable:
return False
elif prev.delete_key_used != cur.delete_key_used:
return False
elif prev.start != cur.start and prev.start != cur.end:
return False
elif cur.text not in WHITESPACE and \
prev.text in WHITESPACE:
return False
elif cur.text in WHITESPACE and \
prev.text not in WHITESPACE:
return False
return True
if not self.undo_in_progress:
self.__empty_redo_stack()
if self.not_undoable_action:
return
undo_action = self.deleteclass(editable, start, end)
try:
prev_delete = self.undo_stack.pop()
except IndexError:
self.undo_stack.append(undo_action)
return
if not isinstance(prev_delete, self.deleteclass):
self.undo_stack.append(prev_delete)
self.undo_stack.append(undo_action)
return
if can_be_merged(prev_delete, undo_action):
if prev_delete.start == undo_action.start: # delete key used
prev_delete.text += undo_action.text
prev_delete.end += (undo_action.end - undo_action.start)
else: # Backspace used
prev_delete.text = "%s%s" % (undo_action.text,
prev_delete.text)
prev_delete.start = undo_action.start
self.undo_stack.append(prev_delete)
else:
self.undo_stack.append(prev_delete)
self.undo_stack.append(undo_action)
def begin_not_undoable_action(self):
"""don't record the next actions
toggles self.not_undoable_action"""
self.not_undoable_action = True
def end_not_undoable_action(self):
"""record next actions
toggles self.not_undoable_action"""
self.not_undoable_action = False
def reset(self):
"""
Resets buffer to initial state.
"""
self.undo_stack = Stack(self.undo_stack_size)
self.redo_stack[:] = []
self.not_undoable_action = False
self.undo_in_progress = False
def undo(self):
"""undo inserts or deletions
undone actions are being moved to redo stack"""
if not self.undo_stack:
return
self.begin_not_undoable_action()
self.undo_in_progress = True
undo_action = self.undo_stack.pop()
self.redo_stack.append(undo_action)
if isinstance(undo_action, self.insertclass):
self._undo_insert(undo_action)
elif isinstance(undo_action, self.deleteclass):
self._undo_delete(undo_action)
else:
self._handle_undo(undo_action)
self.end_not_undoable_action()
self.undo_in_progress = False
def _undo_insert(self, undo_action):
start = undo_action.offset
stop = undo_action.offset + undo_action.length
self.delete_text(start, stop)
self.set_position(undo_action.offset)
def _undo_delete(self, undo_action):
self.insert_text(undo_action.text, undo_action.start)
if undo_action.delete_key_used:
self.set_position(undo_action.start)
else:
self.set_position(undo_action.end)
def _handle_undo(self, undo_action):
raise NotImplementedError
def redo(self):
"""redo inserts or deletions
redone actions are moved to undo stack"""
if not self.redo_stack:
return
self.begin_not_undoable_action()
self.undo_in_progress = True
redo_action = self.redo_stack.pop()
self.undo_stack.append(redo_action)
if isinstance(redo_action, self.insertclass):
self._redo_insert(redo_action)
elif isinstance(redo_action, self.deleteclass):
self._redo_delete(redo_action)
else:
self._handle_redo(redo_action)
self.end_not_undoable_action()
self.undo_in_progress = False
def _redo_insert(self, redo_action):
self.insert_text(redo_action.text, redo_action.offset)
new_cursor_pos = redo_action.offset + redo_action.length
self.set_position(new_cursor_pos)
def _redo_delete(self, redo_action):
start = redo_action.start
stop = redo_action.end
self.delete_text(start, stop)
self.set_position(redo_action.start)
def _handle_redo(self, redo_action):
raise NotImplementedError

View File

@ -26,9 +26,7 @@
gtk textbuffer with undo functionality
"""
# Originally LGLP from:
# http://bitbucket.org/tiax/gtk-textbuffer-with-undo/
# Please send bugfixes and comments upstream to Florian
__all__ = ["UndoableStyledBuffer"]
import gtk