From 63e344754f481ef31c35f78c503f8773e61c604f Mon Sep 17 00:00:00 2001 From: Zsolt Foldvari Date: Tue, 13 Mar 2007 20:31:50 +0000 Subject: [PATCH] 2007-03-13 Zsolt Foldvari * src/RelLib/_Note.py: import const from MarkupText module * src/MarkupText.py: use actions instead of widgets on the interface; new helper functions * src/Editors/_EditNote.py: move widgets out to glade; implement new MarkupText buffer interface; use uimanager/toolbar for formatting * src/glade/gramps.glade: edit_note update svn: r8292 --- ChangeLog | 8 ++ src/Editors/_EditNote.py | 191 ++++++++++++++++++--------------------- src/MarkupText.py | 146 ++++++++++-------------------- src/RelLib/_Note.py | 3 +- src/glade/gramps.glade | 54 ++++++++++- 5 files changed, 196 insertions(+), 206 deletions(-) diff --git a/ChangeLog b/ChangeLog index c8293c9e2..4b281cfb8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2007-03-13 Zsolt Foldvari + * src/RelLib/_Note.py: import const from MarkupText module + * src/MarkupText.py: use actions instead of widgets on the interface; + new helper functions + * src/Editors/_EditNote.py: move widgets out to glade; implement new + MarkupText buffer interface; use uimanager/toolbar for formatting + * src/glade/gramps.glade: edit_note update + 2007-03-11 Brian Matherly * src/ReportBase/_ReportDialog.py: don't catch all exceptions - we won't get a traceback. diff --git a/src/Editors/_EditNote.py b/src/Editors/_EditNote.py index bb91b9b44..14b4da161 100644 --- a/src/Editors/_EditNote.py +++ b/src/Editors/_EditNote.py @@ -69,10 +69,10 @@ class EditNote(EditPrimary): return RelLib.Note() def get_menu_title(self): - if self.obj.get_handle(): - title = _('Note') + ': %s' % self.obj.get_gramps_id() - else: - title = _('New Note') + if self.obj.get_handle(): + title = _('Note') + ': %s' % self.obj.get_gramps_id() + else: + title = _('New Note') return title def _local_init(self): @@ -89,28 +89,25 @@ class EditNote(EditPrimary): height = Config.get(Config.NOTE_HEIGHT) self.window.set_default_size(width, height) - self.type = self.top.get_widget('type') - self.format = self.top.get_widget('format') - - container = self.top.get_widget('container') - container.pack_start(self.build_interface()) - container.show_all() - + self.build_interface() + self.window.show_all() + def _setup_fields(self): - + """Get control widgets and attached them to Note's attributes.""" self.type_selector = MonitoredDataType( - self.top.get_widget("type"), + self.top.get_widget('type'), self.obj.set_type, self.obj.get_type, self.db.readonly) self.check = MonitoredCheckbox( self.obj, - self.format, + self.top.get_widget('format'), self.obj.set_format, self.obj.get_format, + on_toggle = self.flow_changed, readonly = self.db.readonly) - + self.gid = MonitoredEntry( self.top.get_widget('id'), self.obj.set_gramps_id, @@ -125,118 +122,102 @@ class EditNote(EditPrimary): self.db.get_marker_types()) def _connect_signals(self): - """ - Connects any signals that need to be connected. Called by the - init routine of the base class (_EditPrimary). + """Connects any signals that need to be connected. + + Called by the init routine of the base class (_EditPrimary). + """ self.define_ok_button(self.top.get_widget('ok'),self.save) self.define_cancel_button(self.top.get_widget('cancel')) - + self.define_help_button(self.top.get_widget('help'), '') + def build_interface(self): - BUTTON = [(_('Italic'),gtk.STOCK_ITALIC,'i','I'), - (_('Bold'),gtk.STOCK_BOLD,'b','B'), - (_('Underline'),gtk.STOCK_UNDERLINE,'u','U'), - #('Separator', None, None, None), - ] + FORMAT_TOOLBAR = ''' + + + + + + + + ''' + format_actions = [ + ('i','I', + ('italic',_('Italic'),_('Italic'),gtk.STOCK_ITALIC)), + ('b','B', + ('bold',_('Bold'),_('Bold'),gtk.STOCK_BOLD)), + ('u','U', + ('underline',_('Underline'),_('Underline'),gtk.STOCK_UNDERLINE)), + ] - vbox = gtk.VBox() + buffer = EditorBuffer() - self.text = gtk.TextView() - self.text.set_accepts_tab(True) + self.text = self.top.get_widget('text') + self.text.set_editable(not self.dbstate.db.readonly) + self.spellcheck = Spell.Spell(self.text) + self.text.set_buffer(buffer) - if self.obj and self.obj.get_format(): - self.format.set_active(True) - self.text.set_wrap_mode(gtk.WRAP_NONE) - else: - self.format.set_active(False) - self.text.set_wrap_mode(gtk.WRAP_WORD) - - scroll = gtk.ScrolledWindow() - scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scroll.set_shadow_type(gtk.SHADOW_IN) - scroll.add(self.text) - - self.buf = EditorBuffer() - self.text.set_buffer(self.buf) + # create a formatting toolbar and pass the actions + # together with the related markup tag to the buffer + if not self.dbstate.db.readonly: + uimanager = gtk.UIManager() + accelgroup = uimanager.get_accel_group() + self.window.add_accel_group(accelgroup) + action_group = gtk.ActionGroup('Format') + for markup, accel, action_desc in format_actions: + action = gtk.ToggleAction(*action_desc) + action_group.add_action_with_accel(action, accel) + # FIXME why are these needed? + # Shouldn't uimanager do it automatically!? + action.set_accel_group(accelgroup) + action.connect_accelerator() + # + buffer.setup_action_from_xml(action, markup) + + uimanager.insert_action_group(action_group, 0) + uimanager.add_ui_from_string(FORMAT_TOOLBAR) + uimanager.ensure_update() + + toolbar = uimanager.get_widget('/ToolBar') + toolbar.set_style(gtk.TOOLBAR_ICONS) + vbox = self.top.get_widget('container') + vbox.pack_start(toolbar, False) + + # setup initial values for textview and buffer if self.obj: self.empty = False - self.buf.set_text(self.obj.get(markup=True)) + self.flow_changed(self.obj.get_format()) + buffer.set_text(self.obj.get(markup=True)) + log.debug("Initial Note: %s" % buffer.get_text()) else: self.empty = True - if not self.dbstate.db.readonly: - self.accelerator = {} - hbox = gtk.HBox() - hbox.set_spacing(0) - hbox.set_border_width(0) - vbox.pack_start(hbox, False) + # connection to buffer signals must be after the initial values are set + buffer.connect('changed', self.update_note) + buffer.connect_after('apply-tag', self.update_note) + buffer.connect_after('remove-tag', self.update_note) - tooltips = gtk.Tooltips() - for tip, stock, markup, accel in BUTTON: - if markup: - button = gtk.ToggleButton() - image = gtk.Image() - image.set_from_stock(stock, gtk.ICON_SIZE_MENU) - button.set_image(image) - button.set_relief(gtk.RELIEF_NONE) - tooltips.set_tip(button, tip) - self.buf.setup_widget_from_xml(button, markup) - key, mod = gtk.accelerator_parse(accel) - self.accelerator[(key, mod)] = button - hbox.pack_start(button, False) - else: - hbox.pack_start(gtk.VSeparator(), False) - - vbox.pack_start(scroll, True) - vbox.set_spacing(6) - vbox.set_border_width(6) - - if self.dbstate.db.readonly: - self.text.set_editable(False) - return vbox - - # Accelerator dictionary used for formatting shortcuts - # key: tuple(key, modifier) - # value: widget, to emit 'activate' signal on - self.text.connect('key-press-event', self._on_key_press_event) - - self.spellcheck = Spell.Spell(self.text) - - self.format.connect('toggled', self.flow_changed) - - self.buf.connect('changed', self.update) - self.buf.connect_after('apply-tag', self.update) - self.buf.connect_after('remove-tag', self.update) - #self.rebuild() - return vbox - - def _on_key_press_event(self, widget, event): - #log.debug("Key %s (%d) was pressed on %s" % - #(gtk.gdk.keyval_name(event.keyval), event.keyval, widget)) - key = event.keyval - mod = event.state - if self.accelerator.has_key((key, mod)): - self.accelerator[(key, mod)].emit('activate') - return True - - def update(self, obj, *args): + def update_note(self, buffer, *args): + """Update the Note object with current value. + + This happens after each change in the text or the formatting. + + """ if self.obj: - start = self.buf.get_start_iter() - stop = self.buf.get_end_iter() - text = self.buf.get_text(start, stop) + start = buffer.get_start_iter() + stop = buffer.get_end_iter() + text = buffer.get_text(start, stop) self.obj.set(text) else: - print "NOTE OBJ DOES NOT EXIST" + log.debug("NOTE OBJ DOES NOT EXIST") return False - def flow_changed(self, obj): - if obj.get_active(): + def flow_changed(self, active): + if active: self.text.set_wrap_mode(gtk.WRAP_NONE) - self.obj.set_format(True) else: self.text.set_wrap_mode(gtk.WRAP_WORD) - self.obj.set_format(False) def save(self, *obj): """ diff --git a/src/MarkupText.py b/src/MarkupText.py index 9441a3454..c6c5ad1d8 100644 --- a/src/MarkupText.py +++ b/src/MarkupText.py @@ -53,12 +53,29 @@ log = logging.getLogger(".MarkupText") #------------------------------------------------------------------------- import gtk +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- +ROOT_ELEMENT = 'gramps' +ROOT_START_TAG = '<' + ROOT_ELEMENT + '>' +ROOT_END_TAG = '' +LEN_ROOT_START_TAG = len(ROOT_START_TAG) +LEN_ROOT_END_TAG = len(ROOT_END_TAG) +def is_gramps_markup(text): + return (text[:LEN_ROOT_START_TAG] == ROOT_START_TAG and + text[-LEN_ROOT_END_TAG:] == ROOT_END_TAG) + +def clear_root_tags(text): + return text[LEN_ROOT_START_TAG:len(text)-LEN_ROOT_END_TAG] + class MarkupParser(ContentHandler): """A simple ContentHandler class to parse Gramps markup'ed text. - Use it with xml.sax.parse() or xml.sax.parseString(). A root tag, 'gramps', - is required. Parsing result can be obtained via the public attributes of + Use it with xml.sax.parse() or xml.sax.parseString(). A root tag is + required. Parsing result can be obtained via the public attributes of the class: @attr content: clean text @attr type: str @@ -77,7 +94,7 @@ class MarkupParser(ContentHandler): def startElement(self, name, attrs): if not self._open_document: - if name == 'gramps': + if name == ROOT_ELEMENT: self._open_document = True else: raise SAXParseException('Root element missing') @@ -89,7 +106,7 @@ class MarkupParser(ContentHandler): def endElement(self, name): # skip root element - if name == 'gramps': + if name == ROOT_ELEMENT: return for e in self._open_elements: @@ -221,7 +238,7 @@ class MarkupWriter: def generate(self, text, elements): # reset output and start root element self._output.truncate(0) - self._writer.startElement('gramps', self._attrs) + self._writer.startElement(ROOT_ELEMENT, self._attrs) # split the elements to events events = self._elements_to_events(elements) @@ -241,7 +258,7 @@ class MarkupWriter: self._writer.characters(text[last_pos:]) # close root element and end doc - self._writer.endElement('gramps') + self._writer.endElement(ROOT_ELEMENT) self._writer.endDocument() # copy result @@ -444,6 +461,7 @@ class EditorBuffer(MarkupBuffer): if normal_button: normal_button.connect('clicked',lambda *args: self.remove_all_tags()) self.tag_widgets = {} + self.tag_actions = {} self.internal_toggle = False self.insert = self.get_insert() for widg, name in toggle_widget_alist: @@ -462,7 +480,8 @@ class EditorBuffer(MarkupBuffer): if old_itr != insert_itr: # Use the state of our widgets to determine what # properties to apply... - for tag, w in self.tag_widgets.items(): + for tag, w in self.tag_actions.items(): + ##for tag, w in self.tag_widgets.items(): if w.get_active(): self.apply_tag(tag, old_itr, insert_itr) @@ -474,7 +493,8 @@ class EditorBuffer(MarkupBuffer): self._in_mark_set = True if mark.get_name() == 'insert': - for tag,widg in self.tag_widgets.items(): + ##for tag,widg in self.tag_widgets.items(): + for tag,widg in self.tag_actions.items(): active = True if not iter.has_tag(tag): active = False @@ -503,10 +523,12 @@ class EditorBuffer(MarkupBuffer): def setup_widget_from_xml(self, widg, xmlstring): """Setup widget from an xml markup string.""" try: - parseString("%s" % xmlstring, self.parser) + parseString((ROOT_START_TAG + '%s' + ROOT_END_TAG) % xmlstring, + self.parser) except: log.error('"%s" is not a valid Gramps XML format.' % xmlstring) + # whatever is included we'll use only the first element (start, end), name, attrs = self.parser.elements[0] return self.setup_widget(widg, name) @@ -517,92 +539,24 @@ class EditorBuffer(MarkupBuffer): self.tag_widgets[tag] = widg return widg.connect('toggled', self._toggle, tag) + def setup_action_from_xml(self, action, xmlstring): + """Setup action from an xml markup string.""" + try: + parseString((ROOT_START_TAG + '%s' + ROOT_END_TAG) % xmlstring, + self.parser) + except: + log.error('"%s" is not a valid Gramps XML format.' % xmlstring) + + # whatever is included we'll use only the first element + (start, end), name, attrs = self.parser.elements[0] + + return self.setup_action(action, name) + + def setup_action(self, action, name): + """Setup action from Gramps tag name.""" + tag = self.get_tag_from_element(name) + self.tag_actions[tag] = action + return action.connect('activate', self._toggle, tag) + if gtk.pygtk_version < (2,8,0): gobject.type_register(EditorBuffer) - - -if __name__ == '__main__': - import sys - - def main(args): - win = gtk.Window() - win.set_title('MarkupBuffer test window') - win.set_position(gtk.WIN_POS_CENTER) - def cb(window, event): - gtk.main_quit() - win.connect('delete-event', cb) - - accel_group = gtk.AccelGroup() - win.add_accel_group(accel_group) - - vbox = gtk.VBox() - win.add(vbox) - - text = gtk.TextView() - text.set_accepts_tab(True) - - flowed = gtk.RadioButton(None, 'Flowed') - format = gtk.RadioButton(flowed, 'Formatted') - - #if self.note_obj and self.note_obj.get_format(): - #self.format.set_active(True) - #self.text.set_wrap_mode(gtk.WRAP_NONE) - #else: - #self.flowed.set_active(True) - #self.text.set_wrap_mode(gtk.WRAP_WORD) - #self.spellcheck = Spell.Spell(self.text) - - #flowed.connect('toggled', flow_changed) - - scroll = gtk.ScrolledWindow() - scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scroll.add(text) - - vbox.pack_start(scroll, True) - vbox.set_spacing(6) - vbox.set_border_width(6) - - hbox = gtk.HBox() - hbox.set_spacing(12) - hbox.set_border_width(6) - hbox.pack_start(flowed, False) - hbox.pack_start(format, False) - - vbox.pack_start(hbox, False) - - #self.pack_start(vbox, True) - buf = EditorBuffer() - text.set_buffer(buf) - tooltips = gtk.Tooltips() - for tip,stock,font,accel in [('Italic',gtk.STOCK_ITALIC,'i','I'), - ('Bold',gtk.STOCK_BOLD,'b','B'), - ('Underline',gtk.STOCK_UNDERLINE,'u','U'), - ]: - button = gtk.ToggleButton() - image = gtk.Image() - image.set_from_stock(stock, gtk.ICON_SIZE_MENU) - button.set_image(image) - tooltips.set_tip(button, tip) - button.set_relief(gtk.RELIEF_NONE) - buf.setup_widget_from_xml(button,font) - key, mod = gtk.accelerator_parse(accel) - button.add_accelerator('activate', accel_group, - key, mod, gtk.ACCEL_VISIBLE) - hbox.pack_start(button, False) - - buf.set_text('' - 'Bold. Italic. Underline.' - '') - - win.show_all() - gtk.main() - - - stderrh = logging.StreamHandler(sys.stderr) - stderrh.setLevel(logging.DEBUG) - - log = logging.getLogger() - log.setLevel(logging.DEBUG) - log.addHandler(stderrh) - - sys.exit(main(sys.argv)) diff --git a/src/RelLib/_Note.py b/src/RelLib/_Note.py index cc80f7848..f7bf9d43a 100644 --- a/src/RelLib/_Note.py +++ b/src/RelLib/_Note.py @@ -40,6 +40,7 @@ import re #------------------------------------------------------------------------- from _BasicPrimaryObject import BasicPrimaryObject from _NoteType import NoteType +from MarkupText import ROOT_START_TAG, LEN_ROOT_START_TAG #------------------------------------------------------------------------- # @@ -114,7 +115,7 @@ class Note(BasicPrimaryObject): """ text = self.text - if not markup and text[0:8] == '': + if not markup and text[0:LEN_ROOT_START_TAG] == ROOT_START_TAG: text = self.delete_tags(text) return text diff --git a/src/glade/gramps.glade b/src/glade/gramps.glade index fe7bcbc31..d69e614c7 100644 --- a/src/glade/gramps.glade +++ b/src/glade/gramps.glade @@ -14983,7 +14983,6 @@ Very High - True GTK_WINDOW_TOPLEVEL GTK_WIN_POS_NONE @@ -15008,7 +15007,7 @@ Very High 0 - + True GTK_BUTTONBOX_END @@ -15037,6 +15036,19 @@ Very High -5 + + + + True + True + True + gtk-help + True + GTK_RELIEF_NORMAL + True + -11 + + 0 @@ -15054,12 +15066,46 @@ Very High + 6 True False - 0 + 6 - + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_NONE + True + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + 0 + True + True + GTK_PACK_END +