2007-07-03 Zsolt Foldvari <zfoldvar@users.sourceforge.net>

* src/Editors/_EditNote.py: "Unicode control character" menu removed.
	* src/MarkupText.py (class GtkSpellState): added;
	(class MarkupBuffer): improved editor behavior.



svn: r8692
This commit is contained in:
Zsolt Foldvari 2007-07-03 09:36:48 +00:00
parent 698934ce67
commit e7140f2c25
3 changed files with 229 additions and 39 deletions

View File

@ -1,3 +1,8 @@
2007-07-03 Zsolt Foldvari <zfoldvar@users.sourceforge.net>
* src/Editors/_EditNote.py: "Unicode control character" menu removed.
* src/MarkupText.py (class GtkSpellState): added;
(class MarkupBuffer): improved editor behavior.
2007-07-02 Brian Matherly <brian@gramps-project.org> 2007-07-02 Brian Matherly <brian@gramps-project.org>
Johan Gonqvist <johan.gronqvist@gmail.com> Johan Gonqvist <johan.gronqvist@gmail.com>
* src/ReportBase/_ReportDialog.py (size_changed): * src/ReportBase/_ReportDialog.py (size_changed):

View File

@ -115,6 +115,10 @@ class EditNote(EditPrimary):
height = Config.get(Config.NOTE_HEIGHT) height = Config.get(Config.NOTE_HEIGHT)
self.window.set_default_size(width, height) self.window.set_default_size(width, height)
settings = gtk.settings_get_default()
self.show_unicode = settings.get_property('gtk-show-unicode-menu')
settings.set_property('gtk-show-unicode-menu', False)
self.build_interface() self.build_interface()
def _setup_fields(self): def _setup_fields(self):
@ -181,7 +185,10 @@ class EditNote(EditPrimary):
self.text.set_editable(not self.dbstate.db.readonly) self.text.set_editable(not self.dbstate.db.readonly)
self.text.set_buffer(buffer) self.text.set_buffer(buffer)
self.text.connect('key-press-event', self.on_textview_key_press_event) self.text.connect('key-press-event', self.on_textview_key_press_event)
self.text.connect('populate-popup', self.on_textview_populate_popup) self.text.connect('insert-at-cursor', self.on_textview_insert_at_cursor)
self.text.connect('delete-from-cursor', self.on_textview_delete_from_cursor)
self.text.connect('paste-clipboard', self.on_textview_paste_clipboard)
self.spellcheck = Spell.Spell(self.text) self.spellcheck = Spell.Spell(self.text)
# create a formatting toolbar # create a formatting toolbar
@ -209,9 +216,14 @@ class EditNote(EditPrimary):
"""Handle shortcuts in the TextView.""" """Handle shortcuts in the TextView."""
return textview.get_buffer().on_key_press_event(textview, event) return textview.get_buffer().on_key_press_event(textview, event)
def on_textview_populate_popup(self, view, menu): def on_textview_insert_at_cursor(self, textview, string):
"""Hijack popup menu population to be able to edit it.""" log.debug("Textview insert '%s'" % string)
pass
def on_textview_delete_from_cursor(self, textview, type, count):
log.debug("Textview delete type %d count %d" % (type, count))
def on_textview_paste_clipboard(self, textview):
log.debug("Textview paste clipboard")
def update_note(self): def update_note(self):
"""Update the Note object with current value.""" """Update the Note object with current value."""
@ -250,6 +262,10 @@ class EditNote(EditPrimary):
Config.set(Config.NOTE_HEIGHT, height) Config.set(Config.NOTE_HEIGHT, height)
Config.sync() Config.sync()
settings = gtk.settings_get_default()
settings.set_property('gtk-show-unicode-menu', self.show_unicode)
class DeleteNoteQuery: class DeleteNoteQuery:
def __init__(self, dbstate, uistate, note, the_lists): def __init__(self, dbstate, uistate, note, the_lists):
self.note = note self.note = note

View File

@ -92,6 +92,8 @@ class MarkupParser(ContentHandler):
def endDocument(self): def endDocument(self):
self._open_document = False self._open_document = False
if len(self._open_elements):
raise SAXParseException('Unclosed tags')
def startElement(self, name, attrs): def startElement(self, name, attrs):
if not self._open_document: if not self._open_document:
@ -125,7 +127,7 @@ class MarkupWriter:
"""Generate XML markup text for Notes. """Generate XML markup text for Notes.
Provides additional feature of accounting opened tags and closing them Provides additional feature of accounting opened tags and closing them
properly in case of partially overlapping markups. properly in case of partially overlapping elements.
""" """
(EVENT_START, (EVENT_START,
@ -265,6 +267,119 @@ class MarkupWriter:
self.content = self._output.getvalue() self.content = self._output.getvalue()
log.debug("Gramps XML: %s" % self.content) log.debug("Gramps XML: %s" % self.content)
class GtkSpellState:
"""A simple state machine kinda thingy.
Try tracking gtk.Spell activities on a buffer and reapply formatting
after gtk.Spell replaces a misspelled word.
"""
(STATE_NONE,
STATE_CLICKED,
STATE_DELETED,
STATE_INSERTING) = range(4)
def __init__(self, buffer):
if not isinstance(buffer, gtk.TextBuffer):
raise TypeError("Init parameter must be instance of gtk.TextBuffer")
buffer.connect('mark-set', self.on_buffer_mark_set)
buffer.connect('delete-range', self.on_buffer_delete_range)
buffer.connect('insert-text', self.on_buffer_insert_text)
buffer.connect_after('insert-text', self.after_buffer_insert_text)
self.reset_state()
def reset_state(self):
self.state = self.STATE_NONE
self.start = 0
self.end = 0
self.tags = None
def on_buffer_mark_set(self, buffer, iter, mark):
mark_name = mark.get_name()
if mark_name == 'gtkspell-click':
self.state = self.STATE_CLICKED
self.start, self.end = self.get_word_extents_from_mark(buffer, mark)
log.debug("SpellState got start %d end %d" % (self.start, self.end))
elif mark_name == 'insert':
self.reset_state()
def on_buffer_delete_range(self, buffer, start, end):
if ((self.state == self.STATE_CLICKED) and
(start.get_offset() == self.start) and
(end.get_offset() == self.end)):
self.state = self.STATE_DELETED
self.tags = start.get_tags()
def on_buffer_insert_text(self, buffer, iter, text, length):
if self.state == self.STATE_DELETED and iter.get_offset() == self.start:
self.state = self.STATE_INSERTING
def after_buffer_insert_text(self, buffer, iter, text, length):
if self.state == self.STATE_INSERTING:
mark = buffer.get_mark('gtkspell-insert-start')
insert_start = buffer.get_iter_at_mark(mark)
for tag in self.tags:
buffer.apply_tag(tag, insert_start, iter)
self.reset_state()
def get_word_extents_from_mark(self, buffer, mark):
"""Get the word extents as gtk.Spell does.
Used to get the beginning of the word, in which user right clicked.
Formatting found at that position used after gtk.Spell replaces
misspelled words.
"""
start = buffer.get_iter_at_mark(mark)
if not start.starts_word():
#start.backward_word_start()
self.backward_word_start(start)
end = start.copy()
if end.inside_word():
#end.forward_word_end()
self.forward_word_end(end)
return start.get_offset(), end.get_offset()
def forward_word_end(self, iter):
"""gtk.Spell style gtk.TextIter.forward_word_end.
The parameter 'iter' is changing as side effect.
"""
if not iter.forward_word_end():
return False
if iter.get_char() != "'":
return True
i = iter.copy()
if i.forward_char():
if i.get_char().isalpha():
return iter.forward_word_end()
return True
def backward_word_start(self, iter):
"""gtk.Spell style gtk.TextIter.backward_word_start.
The parameter 'iter' is changing as side effect.
"""
if not iter.backward_word_start():
return False
i = iter.copy()
if i.backward_char():
if i.get_char() == "'":
if i.backward_char():
if i.get_char().isalpha():
return iter.backward_word_start()
return True
class MarkupBuffer(gtk.TextBuffer): class MarkupBuffer(gtk.TextBuffer):
"""An extended TextBuffer with Gramps XML markup string interface. """An extended TextBuffer with Gramps XML markup string interface.
@ -322,53 +437,96 @@ class MarkupBuffer(gtk.TextBuffer):
self.format_action_group.add_toggle_actions(format_toggle_actions) self.format_action_group.add_toggle_actions(format_toggle_actions)
self.format_action_group.add_actions(format_actions) self.format_action_group.add_actions(format_actions)
# internal format state attributes
## 1. are used to format inserted characters (self.after_insert_text)
## 2. are set each time the Insert marker is set (self.do_mark_set)
## 3. are set when format actions are activated (self.*_action_activate)
self.italic = False
self.bold = False
self.underline = False
self.font = None
self.foreground = None
self.background = None
# internally used attribute # internally used attribute
self._internal_toggle = False self._internal_toggle = False
self._insert = self.get_insert() self._insert = self.get_insert()
# create a mark
start, end = self.get_bounds()
self.mark_insert = self.create_mark('insert-start', start, True)
# hook up on some signals whose default handler cannot be overriden
self.connect('insert-text', self.on_insert_text)
self.connect_after('insert-text', self.after_insert_text)
self.connect_after('delete-range', self.after_delete_range)
# init gtkspell "state machine"
self.gtkspell_state = GtkSpellState(self)
# Virtual methods # Virtual methods
def do_changed(self): def on_insert_text(self, buffer, iter, text, length):
"""Apply tags at insertion point as user types.""" log.debug("Will insert at %d length %d" % (iter.get_offset(), length))
if not hasattr(self, '_last_mark'):
# let's remember where we started inserting
self.move_mark(self.mark_insert, iter)
def after_insert_text(self, buffer, iter, text, length):
"""Format inserted text."""
log.debug("Have inserted at %d length %d (%s)" %
(iter.get_offset(), length, text))
if not length:
return return
old_itr = self.get_iter_at_mark(self._last_mark) # where did we start inserting
insert_itr = self.get_iter_at_mark(self._insert) insert_start = self.get_iter_at_mark(self.mark_insert)
log.debug("buffer changed. last mark:%s insert mark:%s" % # apply active formats for the inserted text
(old_itr.get_offset(), insert_itr.get_offset())) for format in self.__class__.formats:
value = getattr(self, format)
if value:
if format in self.toggle_actions:
value = None
if old_itr != insert_itr: self.apply_tag(self._find_tag_by_name(format, value),
for tag in old_itr.get_tags(): insert_start, iter)
self.apply_tag(tag, old_itr, insert_itr)
def after_delete_range(self, buffer, start, end):
log.debug("Deleted from %d till %d" %
(start.get_offset(), end.get_offset()))
# move 'insert' marker to have the format attributes updated
self.move_mark(self._insert, start)
def do_mark_set(self, iter, mark): def do_mark_set(self, iter, mark):
"""Update toggle widgets each time the cursor moves.""" """Update toggle widgets each time the cursor moves."""
log.debug("setting mark %s at iter %d" % log.debug("Setting mark %s at %d" %
(mark.get_name(), iter.get_offset())) (mark.get_name(), iter.get_offset()))
if hasattr(self, '_in_mark_set') and self._in_mark_set:
return
if mark.get_name() != 'insert': if mark.get_name() != 'insert':
return return
self._in_mark_set = True if not iter.starts_line():
iter.backward_char() iter.backward_char()
for action_name in self.toggle_actions:
tag = self.get_tag_table().lookup(action_name)
action = self.format_action_group.get_action(action_name)
self._internal_toggle = True
action.set_active(iter.has_tag(tag))
self._internal_toggle = False
if hasattr(self, '_last_mark'): tag_names = [tag.get_property('name') for tag in iter.get_tags()]
self.move_mark(self._last_mark, iter) for format in self.__class__.formats:
if format in self.toggle_actions:
value = format in tag_names
# set state of toggle action
action = self.format_action_group.get_action(format)
self._internal_toggle = True
action.set_active(value)
self._internal_toggle = False
else: else:
self._last_mark = self.create_mark('last', iter, value = None
left_gravity=True) for tname in tag_names:
self._in_mark_set = False if tname.startswith(format):
value = tname.split(' ', 1)[1]
setattr(self, format, value)
# Private # Private
@ -446,7 +604,7 @@ class MarkupBuffer(gtk.TextBuffer):
##return False ##return False
##else: ##else:
##for tag in tags: ##for tag in tags:
##if tag.get_name().starts_with(name): ##if tag.get_name().startswith(name):
##return tag.get_name().split()[1] ##return tag.get_name().split()[1]
##return None ##return None
@ -504,8 +662,15 @@ class MarkupBuffer(gtk.TextBuffer):
self.get_iter_at_offset(end+1)) self.get_iter_at_offset(end+1))
def get_tag_from_range(self, start=None, end=None): def get_tag_from_range(self, start=None, end=None):
"""Extract TextTags from buffer. """Extract gtk.TextTags from buffer.
Return only the name of the TextTag from the specified range.
If range is not given, tags extracted from the whole buffer.
@param start: an offset pointing to the start of the range of text
@param type: int
@param end: an offset pointing to the end of the range of text
@param type: int
@return: tagdict @return: tagdict
@rtype: {TextTag_Name: [(start, end),]} @rtype: {TextTag_Name: [(start, end),]}
@ -566,6 +731,8 @@ class MarkupBuffer(gtk.TextBuffer):
else: else:
self.remove_tag_by_name(action.get_name(), start, end) self.remove_tag_by_name(action.get_name(), start, end)
setattr(self, action.get_name(), action.get_active())
def on_action_activate(self, action): def on_action_activate(self, action):
"""Apply a format. """Apply a format.
@ -604,6 +771,8 @@ class MarkupBuffer(gtk.TextBuffer):
self.remove_format_from_selection(format) self.remove_format_from_selection(format)
self.apply_tag_to_selection(tag) self.apply_tag_to_selection(tag)
setattr(self, format, value)
def _format_clear_cb(self, action): def _format_clear_cb(self, action):
"""Remove all formats from the selection. """Remove all formats from the selection.