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

* 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
This commit is contained in:
Zsolt Foldvari 2007-03-13 20:31:50 +00:00
parent 7593dbfe69
commit 63e344754f
5 changed files with 196 additions and 206 deletions

View File

@ -1,3 +1,11 @@
2007-03-13 Zsolt Foldvari <zfoldvar@users.sourceforge.net>
* 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 <brian@gramps-project.org> 2007-03-11 Brian Matherly <brian@gramps-project.org>
* src/ReportBase/_ReportDialog.py: don't catch all exceptions - we won't * src/ReportBase/_ReportDialog.py: don't catch all exceptions - we won't
get a traceback. get a traceback.

View File

@ -69,10 +69,10 @@ class EditNote(EditPrimary):
return RelLib.Note() return RelLib.Note()
def get_menu_title(self): def get_menu_title(self):
if self.obj.get_handle(): if self.obj.get_handle():
title = _('Note') + ': %s' % self.obj.get_gramps_id() title = _('Note') + ': %s' % self.obj.get_gramps_id()
else: else:
title = _('New Note') title = _('New Note')
return title return title
def _local_init(self): def _local_init(self):
@ -89,26 +89,23 @@ 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)
self.type = self.top.get_widget('type') self.build_interface()
self.format = self.top.get_widget('format') self.window.show_all()
container = self.top.get_widget('container')
container.pack_start(self.build_interface())
container.show_all()
def _setup_fields(self): def _setup_fields(self):
"""Get control widgets and attached them to Note's attributes."""
self.type_selector = MonitoredDataType( self.type_selector = MonitoredDataType(
self.top.get_widget("type"), self.top.get_widget('type'),
self.obj.set_type, self.obj.set_type,
self.obj.get_type, self.obj.get_type,
self.db.readonly) self.db.readonly)
self.check = MonitoredCheckbox( self.check = MonitoredCheckbox(
self.obj, self.obj,
self.format, self.top.get_widget('format'),
self.obj.set_format, self.obj.set_format,
self.obj.get_format, self.obj.get_format,
on_toggle = self.flow_changed,
readonly = self.db.readonly) readonly = self.db.readonly)
self.gid = MonitoredEntry( self.gid = MonitoredEntry(
@ -125,118 +122,102 @@ class EditNote(EditPrimary):
self.db.get_marker_types()) self.db.get_marker_types())
def _connect_signals(self): def _connect_signals(self):
""" """Connects any signals that need to be connected.
Connects any signals that need to be connected. Called by the
init routine of the base class (_EditPrimary). Called by the init routine of the base class (_EditPrimary).
""" """
self.define_ok_button(self.top.get_widget('ok'),self.save) self.define_ok_button(self.top.get_widget('ok'),self.save)
self.define_cancel_button(self.top.get_widget('cancel')) self.define_cancel_button(self.top.get_widget('cancel'))
self.define_help_button(self.top.get_widget('help'), '')
def build_interface(self): def build_interface(self):
BUTTON = [(_('Italic'),gtk.STOCK_ITALIC,'<i>i</i>','<Control>I'), FORMAT_TOOLBAR = '''
(_('Bold'),gtk.STOCK_BOLD,'<b>b</b>','<Control>B'), <ui>
(_('Underline'),gtk.STOCK_UNDERLINE,'<u>u</u>','<Control>U'), <toolbar name="ToolBar">
#('Separator', None, None, None), <toolitem action="italic"/>
] <toolitem action="bold"/>
<toolitem action="underline"/>
</toolbar>
</ui>
'''
format_actions = [
('<i>i</i>','<Control>I',
('italic',_('Italic'),_('Italic'),gtk.STOCK_ITALIC)),
('<b>b</b>','<Control>B',
('bold',_('Bold'),_('Bold'),gtk.STOCK_BOLD)),
('<u>u</u>','<Control>U',
('underline',_('Underline'),_('Underline'),gtk.STOCK_UNDERLINE)),
]
vbox = gtk.VBox() buffer = EditorBuffer()
self.text = gtk.TextView() self.text = self.top.get_widget('text')
self.text.set_accepts_tab(True) 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(): # create a formatting toolbar and pass the actions
self.format.set_active(True) # together with the related markup tag to the buffer
self.text.set_wrap_mode(gtk.WRAP_NONE) if not self.dbstate.db.readonly:
else: uimanager = gtk.UIManager()
self.format.set_active(False) accelgroup = uimanager.get_accel_group()
self.text.set_wrap_mode(gtk.WRAP_WORD) self.window.add_accel_group(accelgroup)
scroll = gtk.ScrolledWindow() action_group = gtk.ActionGroup('Format')
scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) for markup, accel, action_desc in format_actions:
scroll.set_shadow_type(gtk.SHADOW_IN) action = gtk.ToggleAction(*action_desc)
scroll.add(self.text) 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)
self.buf = EditorBuffer() uimanager.insert_action_group(action_group, 0)
self.text.set_buffer(self.buf) 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: if self.obj:
self.empty = False 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: else:
self.empty = True self.empty = True
if not self.dbstate.db.readonly: # connection to buffer signals must be after the initial values are set
self.accelerator = {} buffer.connect('changed', self.update_note)
hbox = gtk.HBox() buffer.connect_after('apply-tag', self.update_note)
hbox.set_spacing(0) buffer.connect_after('remove-tag', self.update_note)
hbox.set_border_width(0)
vbox.pack_start(hbox, False)
tooltips = gtk.Tooltips() def update_note(self, buffer, *args):
for tip, stock, markup, accel in BUTTON: """Update the Note object with current value.
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) This happens after each change in the text or the formatting.
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):
if self.obj: if self.obj:
start = self.buf.get_start_iter() start = buffer.get_start_iter()
stop = self.buf.get_end_iter() stop = buffer.get_end_iter()
text = self.buf.get_text(start, stop) text = buffer.get_text(start, stop)
self.obj.set(text) self.obj.set(text)
else: else:
print "NOTE OBJ DOES NOT EXIST" log.debug("NOTE OBJ DOES NOT EXIST")
return False return False
def flow_changed(self, obj): def flow_changed(self, active):
if obj.get_active(): if active:
self.text.set_wrap_mode(gtk.WRAP_NONE) self.text.set_wrap_mode(gtk.WRAP_NONE)
self.obj.set_format(True)
else: else:
self.text.set_wrap_mode(gtk.WRAP_WORD) self.text.set_wrap_mode(gtk.WRAP_WORD)
self.obj.set_format(False)
def save(self, *obj): def save(self, *obj):
""" """

View File

@ -53,12 +53,29 @@ log = logging.getLogger(".MarkupText")
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
import gtk import gtk
#-------------------------------------------------------------------------
#
# Constants
#
#-------------------------------------------------------------------------
ROOT_ELEMENT = 'gramps'
ROOT_START_TAG = '<' + ROOT_ELEMENT + '>'
ROOT_END_TAG = '</' + ROOT_ELEMENT + '>'
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): class MarkupParser(ContentHandler):
"""A simple ContentHandler class to parse Gramps markup'ed text. """A simple ContentHandler class to parse Gramps markup'ed text.
Use it with xml.sax.parse() or xml.sax.parseString(). A root tag, 'gramps', Use it with xml.sax.parse() or xml.sax.parseString(). A root tag is
is required. Parsing result can be obtained via the public attributes of required. Parsing result can be obtained via the public attributes of
the class: the class:
@attr content: clean text @attr content: clean text
@attr type: str @attr type: str
@ -77,7 +94,7 @@ class MarkupParser(ContentHandler):
def startElement(self, name, attrs): def startElement(self, name, attrs):
if not self._open_document: if not self._open_document:
if name == 'gramps': if name == ROOT_ELEMENT:
self._open_document = True self._open_document = True
else: else:
raise SAXParseException('Root element missing') raise SAXParseException('Root element missing')
@ -89,7 +106,7 @@ class MarkupParser(ContentHandler):
def endElement(self, name): def endElement(self, name):
# skip root element # skip root element
if name == 'gramps': if name == ROOT_ELEMENT:
return return
for e in self._open_elements: for e in self._open_elements:
@ -221,7 +238,7 @@ class MarkupWriter:
def generate(self, text, elements): def generate(self, text, elements):
# reset output and start root element # reset output and start root element
self._output.truncate(0) self._output.truncate(0)
self._writer.startElement('gramps', self._attrs) self._writer.startElement(ROOT_ELEMENT, self._attrs)
# split the elements to events # split the elements to events
events = self._elements_to_events(elements) events = self._elements_to_events(elements)
@ -241,7 +258,7 @@ class MarkupWriter:
self._writer.characters(text[last_pos:]) self._writer.characters(text[last_pos:])
# close root element and end doc # close root element and end doc
self._writer.endElement('gramps') self._writer.endElement(ROOT_ELEMENT)
self._writer.endDocument() self._writer.endDocument()
# copy result # copy result
@ -444,6 +461,7 @@ class EditorBuffer(MarkupBuffer):
if normal_button: if normal_button:
normal_button.connect('clicked',lambda *args: self.remove_all_tags()) normal_button.connect('clicked',lambda *args: self.remove_all_tags())
self.tag_widgets = {} self.tag_widgets = {}
self.tag_actions = {}
self.internal_toggle = False self.internal_toggle = False
self.insert = self.get_insert() self.insert = self.get_insert()
for widg, name in toggle_widget_alist: for widg, name in toggle_widget_alist:
@ -462,7 +480,8 @@ class EditorBuffer(MarkupBuffer):
if old_itr != insert_itr: if old_itr != insert_itr:
# Use the state of our widgets to determine what # Use the state of our widgets to determine what
# properties to apply... # 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(): if w.get_active():
self.apply_tag(tag, old_itr, insert_itr) self.apply_tag(tag, old_itr, insert_itr)
@ -474,7 +493,8 @@ class EditorBuffer(MarkupBuffer):
self._in_mark_set = True self._in_mark_set = True
if mark.get_name() == 'insert': 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 active = True
if not iter.has_tag(tag): if not iter.has_tag(tag):
active = False active = False
@ -503,10 +523,12 @@ class EditorBuffer(MarkupBuffer):
def setup_widget_from_xml(self, widg, xmlstring): def setup_widget_from_xml(self, widg, xmlstring):
"""Setup widget from an xml markup string.""" """Setup widget from an xml markup string."""
try: try:
parseString("<gramps>%s</gramps>" % xmlstring, self.parser) parseString((ROOT_START_TAG + '%s' + ROOT_END_TAG) % xmlstring,
self.parser)
except: except:
log.error('"%s" is not a valid Gramps XML format.' % xmlstring) 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] (start, end), name, attrs = self.parser.elements[0]
return self.setup_widget(widg, name) return self.setup_widget(widg, name)
@ -517,92 +539,24 @@ class EditorBuffer(MarkupBuffer):
self.tag_widgets[tag] = widg self.tag_widgets[tag] = widg
return widg.connect('toggled', self._toggle, tag) 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): if gtk.pygtk_version < (2,8,0):
gobject.type_register(EditorBuffer) 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</i>','<Control>I'),
('Bold',gtk.STOCK_BOLD,'<b>b</b>','<Control>B'),
('Underline',gtk.STOCK_UNDERLINE,'<u>u</u>','<Control>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('<gramps>'
'<b>Bold</b>. <i>Italic</i>. <u>Underline</u>.'
'</gramps>')
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))

View File

@ -40,6 +40,7 @@ import re
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
from _BasicPrimaryObject import BasicPrimaryObject from _BasicPrimaryObject import BasicPrimaryObject
from _NoteType import NoteType from _NoteType import NoteType
from MarkupText import ROOT_START_TAG, LEN_ROOT_START_TAG
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
@ -114,7 +115,7 @@ class Note(BasicPrimaryObject):
""" """
text = self.text text = self.text
if not markup and text[0:8] == '<gramps>': if not markup and text[0:LEN_ROOT_START_TAG] == ROOT_START_TAG:
text = self.delete_tags(text) text = self.delete_tags(text)
return text return text

View File

@ -14983,7 +14983,6 @@ Very High</property>
</widget> </widget>
<widget class="GtkDialog" id="edit_note"> <widget class="GtkDialog" id="edit_note">
<property name="visible">True</property>
<property name="title" translatable="yes"></property> <property name="title" translatable="yes"></property>
<property name="type">GTK_WINDOW_TOPLEVEL</property> <property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property> <property name="window_position">GTK_WIN_POS_NONE</property>
@ -15008,7 +15007,7 @@ Very High</property>
<property name="spacing">0</property> <property name="spacing">0</property>
<child internal-child="action_area"> <child internal-child="action_area">
<widget class="GtkHButtonBox" id="dialog-action_area25"> <widget class="GtkHButtonBox" id="action_area">
<property name="visible">True</property> <property name="visible">True</property>
<property name="layout_style">GTK_BUTTONBOX_END</property> <property name="layout_style">GTK_BUTTONBOX_END</property>
@ -15037,6 +15036,19 @@ Very High</property>
<property name="response_id">-5</property> <property name="response_id">-5</property>
</widget> </widget>
</child> </child>
<child>
<widget class="GtkButton" id="help">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-help</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-11</property>
</widget>
</child>
</widget> </widget>
<packing> <packing>
<property name="padding">0</property> <property name="padding">0</property>
@ -15054,12 +15066,46 @@ Very High</property>
<child> <child>
<widget class="GtkVBox" id="container"> <widget class="GtkVBox" id="container">
<property name="border_width">6</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="homogeneous">False</property> <property name="homogeneous">False</property>
<property name="spacing">0</property> <property name="spacing">6</property>
<child> <child>
<placeholder/> <widget class="GtkScrolledWindow" id="scroll">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child>
<widget class="GtkTextView" id="text">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">True</property>
<property name="overwrite">False</property>
<property name="accepts_tab">True</property>
<property name="justification">GTK_JUSTIFY_LEFT</property>
<property name="wrap_mode">GTK_WRAP_NONE</property>
<property name="cursor_visible">True</property>
<property name="pixels_above_lines">0</property>
<property name="pixels_below_lines">0</property>
<property name="pixels_inside_wrap">0</property>
<property name="left_margin">0</property>
<property name="right_margin">0</property>
<property name="indent">0</property>
<property name="text" translatable="yes"></property>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child> </child>
</widget> </widget>
<packing> <packing>