redesign undo/redo for styled notes, much better now
svn: r15834
This commit is contained in:
parent
a151bafaff
commit
3fed4b1951
@ -43,7 +43,7 @@ _LOG = logging.getLogger(".widgets.styledtextbuffer")
|
||||
#-------------------------------------------------------------------------
|
||||
import gobject
|
||||
import gtk
|
||||
from gui.widgets.undoablebuffer import UndoableBufferStyled
|
||||
from gui.widgets.undoablebuffer import UndoableBuffer
|
||||
from pango import WEIGHT_BOLD, STYLE_ITALIC, UNDERLINE_SINGLE
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
@ -226,7 +226,7 @@ class GtkSpellState(object):
|
||||
# StyledTextBuffer class
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class StyledTextBuffer(UndoableBufferStyled):
|
||||
class StyledTextBuffer(UndoableBuffer):
|
||||
"""An extended TextBuffer for handling StyledText strings.
|
||||
|
||||
StyledTextBuffer is an interface between GRAMPS' L{StyledText} format
|
||||
@ -549,6 +549,7 @@ class StyledTextBuffer(UndoableBufferStyled):
|
||||
|
||||
"""
|
||||
super(StyledTextBuffer, self).set_text(str(s_text))
|
||||
#self.remove_all_tags(self.get_start_iter(), self.get_end_iter())
|
||||
|
||||
s_tags = s_text.get_tags()
|
||||
for s_tag in s_tags:
|
||||
|
@ -49,10 +49,11 @@ from pango import UNDERLINE_SINGLE
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from gen.lib import StyledTextTagType
|
||||
from gui.widgets.styledtextbuffer import (StyledTextBuffer, ALLOWED_STYLES,
|
||||
from gui.widgets.styledtextbuffer import (ALLOWED_STYLES,
|
||||
MATCH_START, MATCH_END,
|
||||
MATCH_FLAVOR, MATCH_STRING,
|
||||
LinkTag)
|
||||
from gui.widgets.undoablestyledbuffer import UndoableStyledBuffer
|
||||
from gui.widgets.valueaction import ValueAction
|
||||
from gui.widgets.toolcomboentry import ToolComboEntry
|
||||
from gui.widgets.springseparator import SpringSeparatorAction
|
||||
@ -172,7 +173,7 @@ class StyledTextEditor(gtk.TextView):
|
||||
|
||||
def __init__(self):
|
||||
"""Setup initial instance variable values."""
|
||||
self.textbuffer = StyledTextBuffer()
|
||||
self.textbuffer = UndoableStyledBuffer()
|
||||
self.textbuffer.connect('style-changed', self._on_buffer_style_changed)
|
||||
self.textbuffer.connect('changed', self._on_buffer_changed)
|
||||
gtk.TextView.__init__(self, self.textbuffer)
|
||||
|
@ -44,12 +44,7 @@ class UndoableInsert(object):
|
||||
self.mergeable = False
|
||||
else:
|
||||
self.mergeable = True
|
||||
|
||||
class UndoableInsertStyled(UndoableInsert):
|
||||
"""something that has been inserted into our styledtextbuffer"""
|
||||
def __init__(self, text_iter, text, length, text_buffer):
|
||||
UndoableInsert.__init__(self, text_iter, text, length, text_buffer)
|
||||
self.tags = text_buffer.get_text().get_tags()
|
||||
self.tags = None
|
||||
|
||||
class UndoableDelete(object):
|
||||
"""something that has been deleted from our textbuffer"""
|
||||
@ -68,11 +63,7 @@ class UndoableDelete(object):
|
||||
self.mergeable = False
|
||||
else:
|
||||
self.mergeable = True
|
||||
|
||||
class UndoableDeleteStyled(UndoableDelete):
|
||||
def __init__(self, text_buffer, start_iter, end_iter):
|
||||
UndoableDelete.__init__(self, text_buffer, start_iter, end_iter)
|
||||
self.tags = text_buffer.get_text().get_tags()
|
||||
self.tags = None
|
||||
|
||||
class UndoableBuffer(gtk.TextBuffer):
|
||||
"""text buffer with added undo capabilities
|
||||
@ -131,7 +122,7 @@ class UndoableBuffer(gtk.TextBuffer):
|
||||
except IndexError:
|
||||
self.undo_stack.append(undo_action)
|
||||
return
|
||||
if not isinstance(prev_insert, UndoableInsert):
|
||||
if not isinstance(prev_insert, self.insertclass):
|
||||
self.undo_stack.append(prev_insert)
|
||||
self.undo_stack.append(undo_action)
|
||||
return
|
||||
@ -177,7 +168,7 @@ class UndoableBuffer(gtk.TextBuffer):
|
||||
except IndexError:
|
||||
self.undo_stack.append(undo_action)
|
||||
return
|
||||
if not isinstance(prev_delete, UndoableDelete):
|
||||
if not isinstance(prev_delete, self.deleteclass):
|
||||
self.undo_stack.append(prev_delete)
|
||||
self.undo_stack.append(undo_action)
|
||||
return
|
||||
@ -225,23 +216,34 @@ class UndoableBuffer(gtk.TextBuffer):
|
||||
self.undo_in_progress = True
|
||||
undo_action = self.undo_stack.pop()
|
||||
self.redo_stack.append(undo_action)
|
||||
if isinstance(undo_action, UndoableInsert):
|
||||
start = self.get_iter_at_offset(undo_action.offset)
|
||||
stop = self.get_iter_at_offset(
|
||||
undo_action.offset + undo_action.length
|
||||
)
|
||||
self.delete(start, stop)
|
||||
self.place_cursor(self.get_iter_at_offset(undo_action.offset))
|
||||
if isinstance(undo_action, self.insertclass):
|
||||
self._undo_insert(undo_action)
|
||||
elif isinstance(undo_action, self.deleteclass):
|
||||
self._undo_delete(undo_action)
|
||||
else:
|
||||
start = self.get_iter_at_offset(undo_action.start)
|
||||
self.insert(start, undo_action.text)
|
||||
if undo_action.delete_key_used:
|
||||
self.place_cursor(self.get_iter_at_offset(undo_action.start))
|
||||
else:
|
||||
self.place_cursor(self.get_iter_at_offset(undo_action.end))
|
||||
self._handle_undo(undo_action)
|
||||
self.end_not_undoable_action()
|
||||
self.undo_in_progress = False
|
||||
|
||||
def _undo_insert(self, undo_action):
|
||||
start = self.get_iter_at_offset(undo_action.offset)
|
||||
stop = self.get_iter_at_offset(
|
||||
undo_action.offset + undo_action.length
|
||||
)
|
||||
self.delete(start, stop)
|
||||
self.place_cursor(self.get_iter_at_offset(undo_action.offset))
|
||||
|
||||
def _undo_delete(self, undo_action):
|
||||
start = self.get_iter_at_offset(undo_action.start)
|
||||
self.insert(start, undo_action.text)
|
||||
if undo_action.delete_key_used:
|
||||
self.place_cursor(self.get_iter_at_offset(undo_action.start))
|
||||
else:
|
||||
self.place_cursor(self.get_iter_at_offset(undo_action.end))
|
||||
|
||||
def _handle_undo(self, undo_action):
|
||||
raise NotImplementedError
|
||||
|
||||
def redo(self):
|
||||
"""redo inserts or deletions
|
||||
|
||||
@ -252,93 +254,28 @@ class UndoableBuffer(gtk.TextBuffer):
|
||||
self.undo_in_progress = True
|
||||
redo_action = self.redo_stack.pop()
|
||||
self.undo_stack.append(redo_action)
|
||||
if isinstance(redo_action, UndoableInsert):
|
||||
start = self.get_iter_at_offset(redo_action.offset)
|
||||
self.insert(start, redo_action.text)
|
||||
new_cursor_pos = self.get_iter_at_offset(
|
||||
redo_action.offset + redo_action.length
|
||||
)
|
||||
self.place_cursor(new_cursor_pos)
|
||||
if isinstance(redo_action, self.insertclass):
|
||||
self._redo_insert(redo_action)
|
||||
elif isinstance(redo_action, self.deleteclass):
|
||||
self._redo_delete(redo_action)
|
||||
else:
|
||||
start = self.get_iter_at_offset(redo_action.start)
|
||||
stop = self.get_iter_at_offset(redo_action.end)
|
||||
self.delete(start, stop)
|
||||
self.place_cursor(self.get_iter_at_offset(redo_action.start))
|
||||
self._handle_redo(redo_action)
|
||||
self.end_not_undoable_action()
|
||||
self.undo_in_progress = False
|
||||
|
||||
class UndoableBufferStyled(UndoableBuffer):
|
||||
"""text buffer with added undo capabilities for styledtextbuffer
|
||||
def _redo_insert(self, redo_action):
|
||||
start = self.get_iter_at_offset(redo_action.offset)
|
||||
self.insert(start, redo_action.text)
|
||||
new_cursor_pos = self.get_iter_at_offset(
|
||||
redo_action.offset + redo_action.length
|
||||
)
|
||||
self.place_cursor(new_cursor_pos)
|
||||
|
||||
designed as a drop-in replacement for gtksourceview,
|
||||
at least as far as undo is concerned"""
|
||||
insertclass = UndoableInsertStyled
|
||||
deleteclass = UndoableDeleteStyled
|
||||
def _redo_delete(self, redo_action):
|
||||
start = self.get_iter_at_offset(redo_action.start)
|
||||
stop = self.get_iter_at_offset(redo_action.end)
|
||||
self.delete(start, stop)
|
||||
self.place_cursor(self.get_iter_at_offset(redo_action.start))
|
||||
|
||||
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, UndoableInsert):
|
||||
start = self.get_iter_at_offset(undo_action.offset)
|
||||
stop = self.get_iter_at_offset(
|
||||
undo_action.offset + undo_action.length
|
||||
)
|
||||
self.delete(start, stop)
|
||||
#the text is correct again, now we create correct styled text
|
||||
s_text = StyledText(gtk.TextBuffer.get_text(self,
|
||||
self.get_start_iter(), self.get_end_iter()), undo_action.tags)
|
||||
self.set_text(s_text)
|
||||
self.place_cursor(self.get_iter_at_offset(undo_action.offset))
|
||||
else:
|
||||
start = self.get_iter_at_offset(undo_action.start)
|
||||
self.insert(start, undo_action.text)
|
||||
#the text is correct again, now we create correct styled text
|
||||
s_text = StyledText(gtk.TextBuffer.get_text(self,
|
||||
self.get_start_iter(), self.get_end_iter()), undo_action.tags)
|
||||
self.set_text(s_text)
|
||||
if undo_action.delete_key_used:
|
||||
self.place_cursor(self.get_iter_at_offset(undo_action.start))
|
||||
else:
|
||||
self.place_cursor(self.get_iter_at_offset(undo_action.end))
|
||||
self.end_not_undoable_action()
|
||||
self.undo_in_progress = False
|
||||
|
||||
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, UndoableInsert):
|
||||
start = self.get_iter_at_offset(redo_action.offset)
|
||||
self.insert(start, redo_action.text)
|
||||
#the text is correct again, now we create correct styled text
|
||||
s_text = StyledText(gtk.TextBuffer.get_text(self,
|
||||
self.get_start_iter(), self.get_end_iter()), redo_action.tags)
|
||||
self.set_text(s_text)
|
||||
new_cursor_pos = self.get_iter_at_offset(
|
||||
redo_action.offset + redo_action.length
|
||||
)
|
||||
self.place_cursor(new_cursor_pos)
|
||||
else:
|
||||
start = self.get_iter_at_offset(redo_action.start)
|
||||
stop = self.get_iter_at_offset(redo_action.end)
|
||||
self.delete(start, stop)
|
||||
#the text is correct again, now we create correct styled text
|
||||
s_text = StyledText(gtk.TextBuffer.get_text(self,
|
||||
self.get_start_iter(), self.get_end_iter()), redo_action.tags)
|
||||
self.set_text(s_text)
|
||||
self.place_cursor(self.get_iter_at_offset(redo_action.start))
|
||||
self.end_not_undoable_action()
|
||||
self.undo_in_progress = False
|
||||
def _handle_redo(self, redo_action):
|
||||
raise NotImplementedError
|
||||
|
179
src/gui/widgets/undoablestyledbuffer.py
Normal file
179
src/gui/widgets/undoablestyledbuffer.py
Normal file
@ -0,0 +1,179 @@
|
||||
#
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2009 Florian Heinle
|
||||
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
|
||||
# Copyright (C) 2010 Benny Malengier
|
||||
#
|
||||
# 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: $
|
||||
|
||||
"""
|
||||
gtk textbuffer with undo functionality
|
||||
"""
|
||||
|
||||
# Originally LGLP from:
|
||||
# http://bitbucket.org/tiax/gtk-textbuffer-with-undo/
|
||||
# Please send bugfixes and comments upstream to Florian
|
||||
|
||||
import gtk
|
||||
|
||||
from gen.lib.styledtext import StyledText
|
||||
from gui.widgets.undoablebuffer import UndoableInsert, UndoableDelete
|
||||
from gui.widgets.styledtextbuffer import StyledTextBuffer
|
||||
|
||||
class UndoableInsertStyled(UndoableInsert):
|
||||
"""something that has been inserted into our styledtextbuffer"""
|
||||
def __init__(self, text_iter, text, length, text_buffer):
|
||||
#we obtain the buffer before the text has been inserted
|
||||
if text_iter is None:
|
||||
#style change
|
||||
self.mergeable = False
|
||||
self.offset = text_buffer.get_iter_at_mark(text_buffer.get_insert()).get_offset()
|
||||
self.text = None
|
||||
self.length = 0
|
||||
else:
|
||||
self.offset = text_iter.get_offset()
|
||||
self.text = str(text)
|
||||
self.length = length
|
||||
if self.length > 1 or self.text in ("\r", "\n", " "):
|
||||
self.mergeable = False
|
||||
else:
|
||||
self.mergeable = True
|
||||
self.tags = text_buffer.get_text(text_buffer.get_start_iter(),
|
||||
text_buffer.get_end_iter()).get_tags()
|
||||
self.tagsafter = None
|
||||
|
||||
class UndoableDeleteStyled(UndoableDelete):
|
||||
def __init__(self, text_buffer, start_iter, end_iter):
|
||||
#we obtain the buffer before the text has been deleted
|
||||
UndoableDelete.__init__(self, text_buffer, start_iter, end_iter)
|
||||
self.tags = text_buffer.get_text(text_buffer.get_start_iter(),
|
||||
text_buffer.get_end_iter()).get_tags()
|
||||
|
||||
class UndoableApplyStyle():
|
||||
"""a style has been applied to our textbuffer"""
|
||||
def __init__(self, text_buffer, tag, start, end):
|
||||
self.offset = text_buffer.get_iter_at_mark(text_buffer.get_insert()).get_offset()
|
||||
|
||||
self.mergeable = False
|
||||
self.tags = text_buffer.get_text(text_buffer.get_start_iter(),
|
||||
text_buffer.get_end_iter()).get_tags()
|
||||
#
|
||||
self.tags_after = None
|
||||
self.offset_after = None
|
||||
|
||||
def set_after(self, tags, offset):
|
||||
self.tags_after = tags
|
||||
self.offset_after = offset
|
||||
|
||||
class UndoableStyledBuffer(StyledTextBuffer):
|
||||
"""text buffer with added undo capabilities for styledtextbuffer
|
||||
|
||||
designed as a drop-in replacement for gtksourceview,
|
||||
at least as far as undo is concerned"""
|
||||
insertclass = UndoableInsertStyled
|
||||
deleteclass = UndoableDeleteStyled
|
||||
|
||||
def __init__(self):
|
||||
StyledTextBuffer.__init__(self)
|
||||
self.connect('apply-tag', self.on_tag_insert_undoable)
|
||||
self.connect_after('apply-tag', self.on_tag_afterinsert_undoable)
|
||||
|
||||
def on_tag_insert_undoable(self, buffer, tag, start, end):
|
||||
if not self.undo_in_progress:
|
||||
self.redo_stack = []
|
||||
if self.not_undoable_action:
|
||||
return
|
||||
if end.get_offset() - start.get_offset() == 1:
|
||||
#only store this 1 character tag if in a different place
|
||||
if self.undo_stack and isinstance(self.undo_stack[-1],
|
||||
UndoableInsertStyled) and self.undo_stack[-1].offset + \
|
||||
self.undo_stack[-1].length == end.get_offset():
|
||||
return
|
||||
undo_action = UndoableApplyStyle(buffer, tag, start, end)
|
||||
self.undo_stack.append(undo_action)
|
||||
|
||||
def on_tag_afterinsert_undoable(self, buffer, tag, start, end):
|
||||
if self.not_undoable_action:
|
||||
return
|
||||
if not self.undo_stack or not isinstance(self.undo_stack[-1],
|
||||
UndoableApplyStyle):
|
||||
return
|
||||
self.undo_stack[-1].set_after(buffer.get_text(buffer.get_start_iter(),
|
||||
buffer.get_end_iter()).get_tags(),
|
||||
buffer.get_iter_at_mark(buffer.get_insert()).get_offset())
|
||||
|
||||
def _undo_insert(self, undo_action):
|
||||
start = self.get_iter_at_offset(undo_action.offset)
|
||||
stop = self.get_iter_at_offset(
|
||||
undo_action.offset + undo_action.length
|
||||
)
|
||||
self.delete(start, stop)
|
||||
#the text is correct again, now we create correct styled text
|
||||
s_text = StyledText(gtk.TextBuffer.get_text(self,
|
||||
self.get_start_iter(), self.get_end_iter()), undo_action.tags)
|
||||
self.set_text(s_text)
|
||||
self.place_cursor(self.get_iter_at_offset(undo_action.offset))
|
||||
|
||||
def _undo_delete(self, undo_action):
|
||||
start = self.get_iter_at_offset(undo_action.start)
|
||||
self.insert(start, undo_action.text)
|
||||
#the text is correct again, now we create correct styled text
|
||||
s_text = StyledText(gtk.TextBuffer.get_text(self,
|
||||
self.get_start_iter(), self.get_end_iter()), undo_action.tags)
|
||||
self.set_text(s_text)
|
||||
if undo_action.delete_key_used:
|
||||
self.place_cursor(self.get_iter_at_offset(undo_action.start))
|
||||
else:
|
||||
self.place_cursor(self.get_iter_at_offset(undo_action.end))
|
||||
|
||||
def _redo_insert(self, redo_action):
|
||||
s_text = StyledText(gtk.TextBuffer.get_text(self,
|
||||
self.get_start_iter(), self.get_end_iter()), redo_action.tags)
|
||||
self.set_text(s_text)
|
||||
start = self.get_iter_at_offset(redo_action.offset)
|
||||
self.insert(start, redo_action.text)
|
||||
new_cursor_pos = self.get_iter_at_offset(
|
||||
redo_action.offset + redo_action.length
|
||||
)
|
||||
self.place_cursor(new_cursor_pos)
|
||||
|
||||
def _redo_delete(self, redo_action):
|
||||
start = self.get_iter_at_offset(redo_action.start)
|
||||
stop = self.get_iter_at_offset(redo_action.end)
|
||||
self.delete(start, stop)
|
||||
#the text is correct again, now we create correct styled text
|
||||
#s_text = StyledText(gtk.TextBuffer.get_text(self,
|
||||
# self.get_start_iter(), self.get_end_iter()), redo_action.tags)
|
||||
#self.set_text(s_text)
|
||||
self.place_cursor(self.get_iter_at_offset(redo_action.start))
|
||||
|
||||
def _handle_undo(self, undo_action):
|
||||
""" undo of apply of style """
|
||||
s_text = StyledText(gtk.TextBuffer.get_text(self,
|
||||
self.get_start_iter(), self.get_end_iter()), undo_action.tags)
|
||||
self.set_text(s_text)
|
||||
self.place_cursor(self.get_iter_at_offset(undo_action.offset))
|
||||
|
||||
def _handle_redo(self, redo_action):
|
||||
""" redo of apply of style """
|
||||
s_text = StyledText(gtk.TextBuffer.get_text(self,
|
||||
self.get_start_iter(), self.get_end_iter()),
|
||||
redo_action.tags_after)
|
||||
self.set_text(s_text)
|
||||
self.place_cursor(self.get_iter_at_offset(redo_action.offset_after))
|
Loading…
Reference in New Issue
Block a user