diff --git a/src/gui/widgets/styledtextbuffer.py b/src/gui/widgets/styledtextbuffer.py index 277567fd3..9afb7aa4b 100644 --- a/src/gui/widgets/styledtextbuffer.py +++ b/src/gui/widgets/styledtextbuffer.py @@ -43,7 +43,7 @@ _LOG = logging.getLogger(".widgets.styledtextbuffer") #------------------------------------------------------------------------- import gobject import gtk -from gui.widgets.undoablebuffer import UndoableBuffer +from gui.widgets.undoablebuffer import UndoableBufferStyled from pango import WEIGHT_BOLD, STYLE_ITALIC, UNDERLINE_SINGLE #------------------------------------------------------------------------- @@ -226,7 +226,7 @@ class GtkSpellState(object): # StyledTextBuffer class # #------------------------------------------------------------------------- -class StyledTextBuffer(UndoableBuffer): +class StyledTextBuffer(UndoableBufferStyled): """An extended TextBuffer for handling StyledText strings. StyledTextBuffer is an interface between GRAMPS' L{StyledText} format diff --git a/src/gui/widgets/styledtexteditor.py b/src/gui/widgets/styledtexteditor.py index db1ee3e92..18e8ac79d 100644 --- a/src/gui/widgets/styledtexteditor.py +++ b/src/gui/widgets/styledtexteditor.py @@ -73,6 +73,8 @@ FORMAT_TOOLBAR = ''' + + @@ -171,6 +173,7 @@ class StyledTextEditor(gtk.TextView): """Setup initial instance variable values.""" self.textbuffer = StyledTextBuffer() self.textbuffer.connect('style-changed', self._on_buffer_style_changed) + self.textbuffer.connect('changed', self._on_buffer_changed) gtk.TextView.__init__(self, self.textbuffer) self.match = None @@ -480,6 +483,14 @@ class StyledTextEditor(gtk.TextView): # create the action group and insert all the actions self.action_group = gtk.ActionGroup('Format') self.action_group.add_toggle_actions(format_toggle_actions) + self.undo_action = gtk.Action("Undo", _('Undo'), _('Undo'), + gtk.STOCK_UNDO) + self.undo_action.connect('activate', self.undo) + self.redo_action = gtk.Action("Redo", _('Redo'), _('Redo'), + gtk.STOCK_REDO) + self.redo_action.connect('activate', self.redo) + self.action_group.add_action(self.undo_action) + self.action_group.add_action(self.redo_action) self.action_group.add_actions(format_actions) self.action_group.add_action(fontface_action) self.action_group.add_action(fontsize_action) @@ -494,7 +505,9 @@ class StyledTextEditor(gtk.TextView): # get the toolbar and set it's style toolbar = uimanager.get_widget('/ToolBar') toolbar.set_style(gtk.TOOLBAR_ICONS) - + self.undo_action.set_sensitive(False) + self.redo_action.set_sensitive(False) + return toolbar def _init_url_match(self): @@ -648,6 +661,11 @@ class StyledTextEditor(gtk.TextView): self.textbuffer.get_iter_at_offset(start), self.textbuffer.get_iter_at_offset(end+1)) + def _on_buffer_changed(self, buffer): + """synchronize the undo/redo buttons with what is possible""" + self.undo_action.set_sensitive(self.textbuffer.can_undo) + self.redo_action.set_sensitive(self.textbuffer.can_redo) + def _on_buffer_style_changed(self, buffer, changed_styles): """Synchronize actions as the format changes at the buffer's cursor.""" for style, style_value in changed_styles.iteritems(): @@ -745,10 +763,10 @@ class StyledTextEditor(gtk.TextView): """ return self.toolbar - def undo(self): + def undo(self, obj=None): self.textbuffer.undo() - def redo(self): + def redo(self, obj=None): self.textbuffer.redo() def uri_dialog(self, uri, callback): diff --git a/src/gui/widgets/undoablebuffer.py b/src/gui/widgets/undoablebuffer.py index 647999711..09472f2ee 100644 --- a/src/gui/widgets/undoablebuffer.py +++ b/src/gui/widgets/undoablebuffer.py @@ -3,6 +3,7 @@ # # Copyright (C) 2009 Florian Heinle # Copyright (C) 2010 Doug Blank +# 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 @@ -31,12 +32,12 @@ gtk textbuffer with undo functionality import gtk +from gen.lib.styledtext import StyledText + class UndoableInsert(object): """something that has been inserted into our textbuffer""" - def __init__(self, text_iter, text, length): + def __init__(self, text_iter, text, length, text_buffer): self.offset = text_iter.get_offset() - # FIXME: GRAMPS change: force to use string rather than - # StyledText self.text = str(text) self.length = length if self.length > 1 or self.text in ("\r", "\n", " "): @@ -44,11 +45,15 @@ class UndoableInsert(object): 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() + class UndoableDelete(object): - """something that has ben deleted from our textbuffer""" + """something that has been deleted from our textbuffer""" def __init__(self, text_buffer, start_iter, end_iter): - # FIXME: GRAMPS change: force to use string rather than - # StyledText self.text = str(text_buffer.get_text(start_iter, end_iter)) self.start = start_iter.get_offset() self.end = end_iter.get_offset() @@ -64,11 +69,18 @@ class UndoableDelete(object): 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() + class UndoableBuffer(gtk.TextBuffer): """text buffer with added undo capabilities designed as a drop-in replacement for gtksourceview, at least as far as undo is concerned""" + insertclass = UndoableInsert + deleteclass = UndoableDelete def __init__(self): """ @@ -113,7 +125,7 @@ class UndoableBuffer(gtk.TextBuffer): self.redo_stack = [] if self.not_undoable_action: return - undo_action = UndoableInsert(text_iter, text, length) + undo_action = self.insertclass(text_iter, text, length, textbuffer) try: prev_insert = self.undo_stack.pop() except IndexError: @@ -159,7 +171,7 @@ class UndoableBuffer(gtk.TextBuffer): self.redo_stack = [] if self.not_undoable_action: return - undo_action = UndoableDelete(text_buffer, start_iter, end_iter) + undo_action = self.deleteclass(text_buffer, start_iter, end_iter) try: prev_delete = self.undo_stack.pop() except IndexError: @@ -219,15 +231,14 @@ class UndoableBuffer(gtk.TextBuffer): undo_action.offset + undo_action.length ) self.delete(start, stop) - self.place_cursor(start) + 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) - stop = self.get_iter_at_offset(undo_action.end) if undo_action.delete_key_used: - self.place_cursor(start) + self.place_cursor(self.get_iter_at_offset(undo_action.start)) else: - self.place_cursor(stop) + self.place_cursor(self.get_iter_at_offset(undo_action.end)) self.end_not_undoable_action() self.undo_in_progress = False @@ -252,6 +263,82 @@ class UndoableBuffer(gtk.TextBuffer): 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(start) + self.place_cursor(self.get_iter_at_offset(redo_action.start)) self.end_not_undoable_action() self.undo_in_progress = False + +class UndoableBufferStyled(UndoableBuffer): + """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 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 \ No newline at end of file