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:
parent
698934ce67
commit
e7140f2c25
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
else:
|
if format in self.toggle_actions:
|
||||||
self._last_mark = self.create_mark('last', iter,
|
value = format in tag_names
|
||||||
left_gravity=True)
|
# set state of toggle action
|
||||||
self._in_mark_set = False
|
action = self.format_action_group.get_action(format)
|
||||||
|
self._internal_toggle = True
|
||||||
|
action.set_active(value)
|
||||||
|
self._internal_toggle = False
|
||||||
|
else:
|
||||||
|
value = None
|
||||||
|
for tname in tag_names:
|
||||||
|
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.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user