2007-04-27 Zsolt Foldvari <zfoldvar@users.sourceforge.net>
* src/MarkupText.py: * src/Editors/_EditNote.py: Rewrite of the note markup functionality, also fixes: #1024. svn: r8422
This commit is contained in:
parent
3eebd910d5
commit
58cf8fede0
@ -1,3 +1,8 @@
|
||||
2007-04-27 Zsolt Foldvari <zfoldvar@users.sourceforge.net>
|
||||
* src/MarkupText.py:
|
||||
* src/Editors/_EditNote.py:
|
||||
Rewrite of the note markup functionality, also fixes: #1024.
|
||||
|
||||
2007-04-23 Brian Matherly <brian@gramps-project.org>
|
||||
* src/ReportBase/_ReportUtils.py:
|
||||
* src/ReportBase/_StyleEditor.py:
|
||||
|
@ -42,8 +42,8 @@ import gtk
|
||||
import const
|
||||
import Spell
|
||||
import Config
|
||||
import MarkupText
|
||||
from _EditPrimary import EditPrimary
|
||||
from MarkupText import EditorBuffer
|
||||
from GrampsWidgets import *
|
||||
from RelLib import Note
|
||||
|
||||
@ -55,16 +55,15 @@ from RelLib import Note
|
||||
class EditNote(EditPrimary):
|
||||
|
||||
def __init__(self, state, uistate, track, note, callback=None):
|
||||
"""
|
||||
Creates an EditNote window. Associates a note with the window.
|
||||
"""
|
||||
"""Create an EditNote window. Associate a note with the window."""
|
||||
EditPrimary.__init__(self, state, uistate, track, note,
|
||||
state.db.get_note_from_handle, callback)
|
||||
|
||||
def empty_object(self):
|
||||
"""
|
||||
Returns an empty Person object for comparison for changes. This
|
||||
is used by the base class (EditPrimary)
|
||||
"""Return an empty Note object for comparison for changes.
|
||||
|
||||
It is used by the base class (EditPrimary).
|
||||
|
||||
"""
|
||||
return Note()
|
||||
|
||||
@ -76,10 +75,12 @@ class EditNote(EditPrimary):
|
||||
return title
|
||||
|
||||
def _local_init(self):
|
||||
"""
|
||||
Local initialization function. Performs basic initialization,
|
||||
including setting up widgets and the glade interface. This is called
|
||||
by the base class of EditPrimary, and overridden here.
|
||||
"""Local initialization function.
|
||||
|
||||
Perform basic initialization, including setting up widgets
|
||||
and the glade interface. It is called by the base class (EditPrimary),
|
||||
and overridden here.
|
||||
|
||||
"""
|
||||
self.top = gtk.glade.XML(const.gladeFile, "edit_note", "gramps")
|
||||
win = self.top.get_widget("edit_note")
|
||||
@ -137,45 +138,29 @@ class EditNote(EditPrimary):
|
||||
<toolitem action="italic"/>
|
||||
<toolitem action="bold"/>
|
||||
<toolitem action="underline"/>
|
||||
<separator/>
|
||||
<toolitem action="font"/>
|
||||
<toolitem action="foreground"/>
|
||||
<toolitem action="background"/>
|
||||
<separator/>
|
||||
<toolitem action="clear"/>
|
||||
</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)),
|
||||
]
|
||||
|
||||
buffer = EditorBuffer()
|
||||
self.buffer = buffer
|
||||
buffer = MarkupText.MarkupBuffer()
|
||||
|
||||
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)
|
||||
self.text.connect('key-press-event', self.on_textview_key_press_event)
|
||||
self.text.connect('populate-popup', self.on_textview_populate_popup)
|
||||
self.spellcheck = Spell.Spell(self.text)
|
||||
|
||||
# create a formatting toolbar and pass the actions
|
||||
# together with the related markup tag to the buffer
|
||||
# create a formatting toolbar
|
||||
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.insert_action_group(buffer.format_action_group, 0)
|
||||
uimanager.add_ui_from_string(FORMAT_TOOLBAR)
|
||||
uimanager.ensure_update()
|
||||
|
||||
@ -193,27 +178,22 @@ class EditNote(EditPrimary):
|
||||
else:
|
||||
self.empty = True
|
||||
|
||||
# connection to buffer signals must be after the initial values are set
|
||||
#buffer.connect('changed', self.update_note)
|
||||
self.sig_list = []
|
||||
self.sig_list.append(buffer.connect_after('apply-tag', self.update_note))
|
||||
self.sig_list.append(buffer.connect_after('remove-tag', self.update_note))
|
||||
def on_textview_key_press_event(self, textview, event):
|
||||
"""Handle shortcuts in the TextView."""
|
||||
return textview.get_buffer().on_key_press_event(textview, event)
|
||||
|
||||
def update_note(self, buffer, *args):
|
||||
"""Update the Note object with current value.
|
||||
def on_textview_populate_popup(self, view, menu):
|
||||
"""Hijack popup menu population to be able to edit it."""
|
||||
pass
|
||||
|
||||
This happens after each change in the text or the formatting.
|
||||
|
||||
"""
|
||||
def update_note(self):
|
||||
"""Update the Note object with current value."""
|
||||
if self.obj:
|
||||
start = buffer.get_start_iter()
|
||||
stop = buffer.get_end_iter()
|
||||
buffer = self.text.get_buffer()
|
||||
(start, stop) = buffer.get_bounds()
|
||||
text = buffer.get_text(start, stop)
|
||||
log.debug(text)
|
||||
self.obj.set(text)
|
||||
else:
|
||||
log.debug("NOTE OBJ DOES NOT EXIST")
|
||||
return False
|
||||
log.debug(text)
|
||||
|
||||
def flow_changed(self, active):
|
||||
if active:
|
||||
@ -222,15 +202,10 @@ class EditNote(EditPrimary):
|
||||
self.text.set_wrap_mode(gtk.WRAP_WORD)
|
||||
|
||||
def save(self, *obj):
|
||||
"""
|
||||
Save the data.
|
||||
"""
|
||||
for i in self.sig_list:
|
||||
self.buffer.disconnect(i)
|
||||
|
||||
"""Save the data."""
|
||||
trans = self.db.transaction_begin()
|
||||
|
||||
self.update_note(self.text.get_buffer())
|
||||
self.update_note()
|
||||
|
||||
if self.obj.get_handle():
|
||||
self.db.commit_note(self.obj,trans)
|
||||
@ -302,4 +277,4 @@ class DeleteNoteQuery:
|
||||
self.db.enable_signals()
|
||||
self.db.remove_note(note_handle, trans)
|
||||
self.db.transaction_commit(
|
||||
trans,_("Delete Source (%s)") % self.note.get_gramps_id())
|
||||
trans,_("Delete Note (%s)") % self.note.get_gramps_id())
|
||||
|
@ -52,6 +52,7 @@ log = logging.getLogger(".MarkupText")
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
import gtk
|
||||
from pango import WEIGHT_BOLD, STYLE_ITALIC, UNDERLINE_SINGLE
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
@ -125,7 +126,6 @@ class MarkupWriter:
|
||||
|
||||
Provides additional feature of accounting opened tags and closing them
|
||||
properly in case of partially overlapping markups.
|
||||
It is assumed that 'start name' and 'end name' are equal (e.g. <b>, </b>).
|
||||
|
||||
"""
|
||||
(EVENT_START,
|
||||
@ -149,9 +149,10 @@ class MarkupWriter:
|
||||
@param elements: list of XML elements with start/end indices and attrs
|
||||
@param type: [((start, end), xml_element_name, attrs),]
|
||||
@return: eventdict
|
||||
@rtype: {index: [(xml_element_name, event_type, pair_index),]}
|
||||
@rtype: {index: [(xml_element_name, attrs, event_type, pair_index),]}
|
||||
index: place of the event
|
||||
xml_element_name: element to apply
|
||||
attrs: attributes of the tag (xml.sax.xmlreader.AttrubutesImpl)
|
||||
event_type: START or END event
|
||||
pair_index: index of the pair event, used for sorting
|
||||
|
||||
@ -160,14 +161,14 @@ class MarkupWriter:
|
||||
for (start, end), name, attrs in elements:
|
||||
# append START events
|
||||
if eventdict.has_key(start):
|
||||
eventdict[start].append((name, MarkupWriter.EVENT_START, end))
|
||||
eventdict[start].append((name, attrs, self.EVENT_START, end))
|
||||
else:
|
||||
eventdict[start] = [(name, MarkupWriter.EVENT_START, end)]
|
||||
eventdict[start] = [(name, attrs, self.EVENT_START, end)]
|
||||
# END events have to prepended to avoid creating empty elements
|
||||
if eventdict.has_key(end):
|
||||
eventdict[end].insert(0, (name, MarkupWriter.EVENT_END, start))
|
||||
eventdict[end].insert(0, (name, attrs, self.EVENT_END, start))
|
||||
else:
|
||||
eventdict[end] = [(name, MarkupWriter.EVENT_END, start)]
|
||||
eventdict[end] = [(name, attrs, self.EVENT_END, start)]
|
||||
|
||||
# sort events at the same index
|
||||
indices = eventdict.keys()
|
||||
@ -188,11 +189,10 @@ class MarkupWriter:
|
||||
event later.
|
||||
|
||||
"""
|
||||
tag_a, type_a, pair_a = event_a
|
||||
tag_b, type_b, pair_b = event_b
|
||||
tag_a, attr_a, type_a, pair_a = event_a
|
||||
tag_b, attr_b, type_b, pair_b = event_b
|
||||
|
||||
if (type_a + type_b) == (MarkupWriter.EVENT_START +
|
||||
MarkupWriter.EVENT_END):
|
||||
if (type_a + type_b) == (self.EVENT_START + self.EVENT_END):
|
||||
return type_b - type_a
|
||||
else:
|
||||
return pair_b - pair_a
|
||||
@ -249,10 +249,10 @@ class MarkupWriter:
|
||||
indices.sort()
|
||||
for index in indices:
|
||||
self._writer.characters(text[last_pos:index])
|
||||
for name, event_type, p in events[index]:
|
||||
if event_type == MarkupWriter.EVENT_START:
|
||||
self._startElement(name)
|
||||
elif event_type == MarkupWriter.EVENT_END:
|
||||
for name, attrs, event_type, p in events[index]:
|
||||
if event_type == self.EVENT_START:
|
||||
self._startElement(name, attrs)
|
||||
elif event_type == self.EVENT_END:
|
||||
self._endElement(name)
|
||||
last_pos = index
|
||||
self._writer.characters(text[last_pos:])
|
||||
@ -268,31 +268,364 @@ class MarkupWriter:
|
||||
class MarkupBuffer(gtk.TextBuffer):
|
||||
"""An extended TextBuffer with Gramps XML markup string interface.
|
||||
|
||||
It implements MarkupParser and MarkupWriter on the input/output interface.
|
||||
It implements MarkupParser and MarkupWriter on the input/output interfaces.
|
||||
Also translates Gramps XML markup language to gtk.TextTag's and vice versa.
|
||||
|
||||
Based on 'gourmet-0.13.3' L{http://grecipe-manager.sourceforge.net}
|
||||
Pango markup format is replaced by custom Gramps XML format.
|
||||
|
||||
"""
|
||||
texttag_to_xml = {
|
||||
'weight700': 'b',
|
||||
'style2': 'i',
|
||||
'underline1': 'u',
|
||||
}
|
||||
__gtype_name__ = 'MarkupBuffer'
|
||||
|
||||
xml_to_texttag = {
|
||||
'b': ('weight', 700),
|
||||
'i': ('style', 2),
|
||||
'u': ('underline', 1),
|
||||
}
|
||||
formats = ('italic', 'bold', 'underline',
|
||||
'font', 'foreground', 'background',)
|
||||
|
||||
def __init__(self):
|
||||
gtk.TextBuffer.__init__(self)
|
||||
|
||||
self.parser = MarkupParser()
|
||||
self.writer = MarkupWriter()
|
||||
self.tags = {}
|
||||
self.tag_markup = {}
|
||||
gtk.TextBuffer.__init__(self)
|
||||
|
||||
# Create fix tags.
|
||||
# Other tags (e.g. color) have to be created on the fly
|
||||
self.create_tag('bold', weight=WEIGHT_BOLD)
|
||||
self.create_tag('italic', style=STYLE_ITALIC)
|
||||
self.create_tag('underline', underline=UNDERLINE_SINGLE)
|
||||
|
||||
# Setup action group used from user interface
|
||||
format_toggle_actions = [
|
||||
('italic', gtk.STOCK_ITALIC, None, None,
|
||||
_('Italic'), self.on_toggle_action_activate),
|
||||
('bold', gtk.STOCK_BOLD, None, None,
|
||||
_('Bold'), self.on_toggle_action_activate),
|
||||
('underline', gtk.STOCK_UNDERLINE, None, None,
|
||||
_('Underline'), self.on_toggle_action_activate),
|
||||
]
|
||||
|
||||
self.toggle_actions = [action[0] for action in format_toggle_actions]
|
||||
|
||||
format_actions = [
|
||||
('font', gtk.STOCK_SELECT_FONT, None, None,
|
||||
_('Font'), self.on_action_activate),
|
||||
('foreground', gtk.STOCK_SELECT_COLOR, None, None,
|
||||
_('Font Color'), self.on_action_activate),
|
||||
('background', gtk.STOCK_SELECT_COLOR, None, None,
|
||||
_('Background Color'), self.on_action_activate),
|
||||
('clear', gtk.STOCK_CLEAR, None, None,
|
||||
_('Clear'), self._format_clear_cb),
|
||||
]
|
||||
|
||||
self.action_accels = {
|
||||
'<Control>i': 'italic',
|
||||
'<Control>b': 'bold',
|
||||
'<Control>u': 'underline',
|
||||
}
|
||||
|
||||
self.format_action_group = gtk.ActionGroup('Format')
|
||||
self.format_action_group.add_toggle_actions(format_toggle_actions)
|
||||
self.format_action_group.add_actions(format_actions)
|
||||
|
||||
# internally used attribute
|
||||
self._internal_toggle = False
|
||||
self._insert = self.get_insert()
|
||||
|
||||
# Virtual methods
|
||||
|
||||
def do_changed(self):
|
||||
"""Apply tags at insertion point as user types."""
|
||||
if not hasattr(self, '_last_mark'):
|
||||
return
|
||||
|
||||
old_itr = self.get_iter_at_mark(self._last_mark)
|
||||
insert_itr = self.get_iter_at_mark(self._insert)
|
||||
|
||||
log.debug("buffer changed. last mark:%s insert mark:%s" %
|
||||
(old_itr.get_offset(), insert_itr.get_offset()))
|
||||
|
||||
if old_itr != insert_itr:
|
||||
for tag in old_itr.get_tags():
|
||||
self.apply_tag(tag, old_itr, insert_itr)
|
||||
|
||||
def do_mark_set(self, iter, mark):
|
||||
"""Update toggle widgets each time the cursor moves."""
|
||||
log.debug("setting mark %s at iter %d" %
|
||||
(mark.get_name(), iter.get_offset()))
|
||||
|
||||
if hasattr(self, '_in_mark_set') and self._in_mark_set:
|
||||
return
|
||||
|
||||
if mark.get_name() != 'insert':
|
||||
return
|
||||
|
||||
self._in_mark_set = True
|
||||
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'):
|
||||
self.move_mark(self._last_mark, iter)
|
||||
else:
|
||||
self._last_mark = self.create_mark('last', iter,
|
||||
left_gravity=True)
|
||||
self._in_mark_set = False
|
||||
|
||||
# Private
|
||||
|
||||
def _xmltag_to_texttag(self, name, attrs):
|
||||
"""Convert XML tag to gtk.TextTag.
|
||||
|
||||
Return only the name of the TextTag.
|
||||
|
||||
@param name: name of the XML tag
|
||||
@param type: string
|
||||
@param attrs: attributes of the XML tag
|
||||
@param type: xmlreader.AttributesImpl
|
||||
@return: property of gtk.TextTag, value of property
|
||||
@rtype: string, string
|
||||
|
||||
"""
|
||||
if name == 'b':
|
||||
return 'bold', None
|
||||
elif name == 'i':
|
||||
return 'italic', None
|
||||
elif name == 'u':
|
||||
return 'underline', None
|
||||
##elif name == 'font':
|
||||
##attr_names = attrs.getNames()
|
||||
##if 'color' in attr_names:
|
||||
##return 'foreground', attrs.getValue('color')
|
||||
##elif 'highlight' in attr_names:
|
||||
##return 'background', attrs.getValue('highlight')
|
||||
##elif 'face' in attr_names and 'size' in attr_names:
|
||||
##return 'font', '%s %s' % (attrs.getValue('face'),
|
||||
##attrs.getValue('size'))
|
||||
##else:
|
||||
##return None, None
|
||||
else:
|
||||
return None, None
|
||||
|
||||
def _texttag_to_xmltag(self, name):
|
||||
"""Convert gtk.TextTag to XML tag.
|
||||
|
||||
@param name: name of the gtk.TextTag
|
||||
@param type: string
|
||||
@return: XML tag name, attribute
|
||||
@rtype: string, xmlreader.AttributesImpl
|
||||
|
||||
"""
|
||||
attrs = xmlreader.AttributesImpl({})
|
||||
if name == 'bold':
|
||||
return 'b', attrs
|
||||
elif name == 'italic':
|
||||
return 'i', attrs
|
||||
elif name == 'underline':
|
||||
return 'u', attrs
|
||||
##elif name.startswith('foreground'):
|
||||
##attrs._attrs['color'] = name.split()[1]
|
||||
##return 'font', attrs
|
||||
##elif name.startswith('background'):
|
||||
##attrs._attrs['highlight'] = name.split()[1]
|
||||
##return 'font', attrs
|
||||
##elif name.startswith('font'):
|
||||
##name = name.replace('font ', '')
|
||||
##attrs._attrs['face'] = name.rsplit(' ', 1)[0]
|
||||
##attrs._attrs['size'] = name.rsplit(' ', 1)[1]
|
||||
##return 'font', attrs
|
||||
else:
|
||||
return None, None
|
||||
|
||||
##def get_tag_value_at_insert(self, name):
|
||||
##"""Get the value of the given tag at the insertion point."""
|
||||
##tags = self.get_iter_at_mark(self._insert).get_tags()
|
||||
|
||||
##if name in self.toggle_actions:
|
||||
##for tag in tags:
|
||||
##if tag.get_name() == name:
|
||||
##return True
|
||||
##return False
|
||||
##else:
|
||||
##for tag in tags:
|
||||
##if tag.get_name().starts_with(name):
|
||||
##return tag.get_name().split()[1]
|
||||
##return None
|
||||
|
||||
def _color_to_hex(self, color):
|
||||
"""Convert gtk.gdk.Color to hex string."""
|
||||
hexstring = ""
|
||||
for col in 'red', 'green', 'blue':
|
||||
hexfrag = hex(getattr(color, col) / (16 * 16)).split("x")[1]
|
||||
if len(hexfrag) < 2:
|
||||
hexfrag = "0" + hexfrag
|
||||
hexstring += hexfrag
|
||||
return '#' + hexstring
|
||||
|
||||
def _hex_to_color(self, hex):
|
||||
"""Convert hex string to gtk.gdk.Color."""
|
||||
return gtk.gdk.Color(int(hex[1:3], 16),
|
||||
int(hex[3:5], 16),
|
||||
int(hex[5:7], 16))
|
||||
|
||||
def get_selection(self):
|
||||
bounds = self.get_selection_bounds()
|
||||
if not bounds:
|
||||
iter = self.get_iter_at_mark(self._insert)
|
||||
if iter.inside_word():
|
||||
start_pos = iter.get_offset()
|
||||
iter.forward_word_end()
|
||||
word_end = iter.get_offset()
|
||||
iter.backward_word_start()
|
||||
word_start = iter.get_offset()
|
||||
iter.set_offset(start_pos)
|
||||
bounds = (self.get_iter_at_offset(word_start),
|
||||
self.get_iter_at_offset(word_end))
|
||||
else:
|
||||
bounds = (iter, self.get_iter_at_offset(iter.get_offset() + 1))
|
||||
return bounds
|
||||
|
||||
def apply_tag_to_selection(self, tag):
|
||||
selection = self.get_selection()
|
||||
if selection:
|
||||
self.apply_tag(tag, *selection)
|
||||
|
||||
def remove_tag_from_selection(self, tag):
|
||||
selection = self.get_selection()
|
||||
if selection:
|
||||
self.remove_tag(tag, *selection)
|
||||
|
||||
def remove_format_from_selection(self, format):
|
||||
start, end = self.get_selection()
|
||||
tags = self.get_tag_from_range(start.get_offset(), end.get_offset())
|
||||
for tag_name in tags.keys():
|
||||
if tag_name.startswith(format):
|
||||
for start, end in tags[tag_name]:
|
||||
self.remove_tag_by_name(tag_name,
|
||||
self.get_iter_at_offset(start),
|
||||
self.get_iter_at_offset(end+1))
|
||||
|
||||
def get_tag_from_range(self, start=None, end=None):
|
||||
"""Extract TextTags from buffer.
|
||||
|
||||
@return: tagdict
|
||||
@rtype: {TextTag_Name: [(start, end),]}
|
||||
|
||||
"""
|
||||
if start is None:
|
||||
start = 0
|
||||
if end is None:
|
||||
end = self.get_char_count()
|
||||
|
||||
tagdict = {}
|
||||
for pos in range(start, end):
|
||||
iter = self.get_iter_at_offset(pos)
|
||||
for tag in iter.get_tags():
|
||||
name = tag.get_property('name')
|
||||
if tagdict.has_key(name):
|
||||
if tagdict[name][-1][1] == pos - 1:
|
||||
tagdict[name][-1] = (tagdict[name][-1][0], pos)
|
||||
else:
|
||||
tagdict[name].append((pos, pos))
|
||||
else:
|
||||
tagdict[name]=[(pos, pos)]
|
||||
return tagdict
|
||||
|
||||
def _find_tag_by_name(self, name, value):
|
||||
"""Fetch TextTag from buffer's tag table by it's name.
|
||||
|
||||
If TextTag does not exist yet, it is created.
|
||||
|
||||
"""
|
||||
if value is None:
|
||||
tag_name = name
|
||||
else:
|
||||
tag_name = "%s %s" % (name, value)
|
||||
tag = self.get_tag_table().lookup(tag_name)
|
||||
if not tag:
|
||||
if value is not None:
|
||||
tag = self.create_tag(tag_name)
|
||||
tag.set_property(name, value)
|
||||
else:
|
||||
return None
|
||||
return tag
|
||||
|
||||
# Callbacks
|
||||
|
||||
def on_toggle_action_activate(self, action):
|
||||
"""Toggle a format.
|
||||
|
||||
Toggle formats are e.g. 'bold', 'italic', 'underline'.
|
||||
|
||||
"""
|
||||
if self._internal_toggle:
|
||||
return
|
||||
|
||||
start, end = self.get_selection()
|
||||
|
||||
if action.get_active():
|
||||
self.apply_tag_by_name(action.get_name(), start, end)
|
||||
else:
|
||||
self.remove_tag_by_name(action.get_name(), start, end)
|
||||
|
||||
def on_action_activate(self, action):
|
||||
"""Apply a format.
|
||||
|
||||
Other tags for the same format have to be removed from the range
|
||||
first otherwise XML would get messy.
|
||||
|
||||
"""
|
||||
format = action.get_name()
|
||||
|
||||
if format == 'foreground':
|
||||
color_selection = gtk.ColorSelectionDialog(_("Select font color"))
|
||||
response = color_selection.run()
|
||||
color = color_selection.colorsel.get_current_color()
|
||||
value = self._color_to_hex(color)
|
||||
color_selection.destroy()
|
||||
elif format == 'background':
|
||||
color_selection = gtk.ColorSelectionDialog(_("Select "
|
||||
"background color"))
|
||||
response = color_selection.run()
|
||||
color = color_selection.colorsel.get_current_color()
|
||||
value = self._color_to_hex(color)
|
||||
color_selection.destroy()
|
||||
elif format == 'font':
|
||||
font_selection = gtk.FontSelectionDialog(_("Select font"))
|
||||
response = font_selection.run()
|
||||
value = font_selection.fontsel.get_font_name()
|
||||
font_selection.destroy()
|
||||
else:
|
||||
log.debug("unknown format: '%s'" % format)
|
||||
return
|
||||
|
||||
if response == gtk.RESPONSE_OK:
|
||||
log.debug("applying format '%s' with value '%s'" % (format, value))
|
||||
|
||||
tag = self._find_tag_by_name(format, value)
|
||||
self.remove_format_from_selection(format)
|
||||
self.apply_tag_to_selection(tag)
|
||||
|
||||
def _format_clear_cb(self, action):
|
||||
"""Remove all formats from the selection.
|
||||
|
||||
Remove only our own tags without touching other ones (e.g. gtk.Spell),
|
||||
thus remove_all_tags() can not be used.
|
||||
|
||||
"""
|
||||
for format in self.formats:
|
||||
self.remove_format_from_selection(format)
|
||||
|
||||
def on_key_press_event(self, widget, event):
|
||||
"""Handle formatting shortcuts."""
|
||||
for accel in self.action_accels.keys():
|
||||
key, mod = gtk.accelerator_parse(accel)
|
||||
if (event.keyval, event.state) == (key, mod):
|
||||
action_name = self.action_accels[accel]
|
||||
action = self.format_action_group.get_action(action_name)
|
||||
action.activate()
|
||||
return True
|
||||
return False
|
||||
|
||||
# Public API
|
||||
|
||||
def set_text(self, xmltext):
|
||||
"""Set the content of the buffer with markup tags."""
|
||||
@ -307,35 +640,17 @@ class MarkupBuffer(gtk.TextBuffer):
|
||||
gtk.TextBuffer.set_text(self, text)
|
||||
|
||||
for element in self.parser.elements:
|
||||
self.add_element_to_buffer(element)
|
||||
(start, end), xmltag_name, attrs = element
|
||||
|
||||
def add_element_to_buffer(self, elem):
|
||||
"""Apply the xml element to the buffer"""
|
||||
(start, end), name, attrs = elem
|
||||
texttag_name, value = self._xmltag_to_texttag(xmltag_name, attrs)
|
||||
|
||||
tag = self.get_tag_from_element(name)
|
||||
|
||||
if tag:
|
||||
if texttag_name is not None:
|
||||
start_iter = self.get_iter_at_offset(start)
|
||||
end_iter = self.get_iter_at_offset(end)
|
||||
|
||||
tag = self._find_tag_by_name(texttag_name, value)
|
||||
if tag is not None:
|
||||
self.apply_tag(tag, start_iter, end_iter)
|
||||
|
||||
def get_tag_from_element(self, name):
|
||||
"""Convert xml element to gtk.TextTag."""
|
||||
if not self.xml_to_texttag.has_key(name):
|
||||
return None
|
||||
|
||||
prop, val = self.xml_to_texttag[name]
|
||||
|
||||
key = "%s%s" % (prop, val)
|
||||
if not self.tags.has_key(key):
|
||||
self.tags[key] = self.create_tag()
|
||||
self.tags[key].set_property(prop, val)
|
||||
self.tag_markup[self.tags[key]] = self.texttag_to_xml[key]
|
||||
|
||||
return self.tags[key]
|
||||
|
||||
def get_text(self, start=None, end=None, include_hidden_chars=True):
|
||||
"""Returns the buffer text with xml markup tags.
|
||||
|
||||
@ -350,213 +665,70 @@ class MarkupBuffer(gtk.TextBuffer):
|
||||
txt = unicode(gtk.TextBuffer.get_text(self, start, end))
|
||||
|
||||
# extract tags out of the buffer
|
||||
tags = self.get_tags()
|
||||
texttag = self.get_tag_from_range()
|
||||
|
||||
if len(texttag):
|
||||
# convert the texttags to xml elements
|
||||
xml_elements = []
|
||||
for texttag_name, indices in texttag.items():
|
||||
xml_tag_name, attrs = self._texttag_to_xmltag(texttag_name)
|
||||
if xml_tag_name is not None:
|
||||
for start_idx, end_idx in indices:
|
||||
xml_elements.append(((start_idx, end_idx+1),
|
||||
xml_tag_name, attrs))
|
||||
|
||||
if len(tags):
|
||||
# convert the tags to xml elements
|
||||
elements = self.get_elements(tags)
|
||||
# feed the elements into the xml writer
|
||||
self.writer.generate(txt, elements)
|
||||
self.writer.generate(txt, xml_elements)
|
||||
txt = self.writer.content
|
||||
|
||||
return txt
|
||||
|
||||
def get_tags(self):
|
||||
"""Extract TextTags from buffer.
|
||||
##def apply_format(self, format, value=None):
|
||||
##"""."""
|
||||
##if format not in self.formats:
|
||||
##raise TypeError("%s is not a valid format name" % format)
|
||||
|
||||
@return: tagdict
|
||||
@rtype: {TextTag: [(start, end),]}
|
||||
##start, end = self.get_selection()
|
||||
|
||||
"""
|
||||
tagdict = {}
|
||||
for pos in range(self.get_char_count()):
|
||||
iter = self.get_iter_at_offset(pos)
|
||||
for tag in iter.get_tags():
|
||||
if tagdict.has_key(tag):
|
||||
if tagdict[tag][-1][1] == pos - 1:
|
||||
tagdict[tag][-1] = (tagdict[tag][-1][0], pos)
|
||||
else:
|
||||
tagdict[tag].append((pos, pos))
|
||||
else:
|
||||
tagdict[tag]=[(pos, pos)]
|
||||
return tagdict
|
||||
##log.debug("Applying format '%s' with value '%s' for range %d - %d" %
|
||||
##(format, value, start.get_offset(), end.get_offset()))
|
||||
|
||||
def get_elements(self, tagdict):
|
||||
"""Convert TextTags to xml elements.
|
||||
##if format == 'bold':
|
||||
##self.apply_tag_by_name('bold', start, end)
|
||||
##elif format == 'italic':
|
||||
##self.apply_tag_by_name('italic', start, end)
|
||||
##elif format == 'underline':
|
||||
##self.apply_tag_by_name('underline', start, end)
|
||||
##else:
|
||||
##log.error("Format '%s' is not yet implemented" % format)
|
||||
|
||||
Create the format what MarkupWriter likes
|
||||
@param tagdict: TextTag dictionary
|
||||
@param type: {TextTag: [(start, end),]}
|
||||
@return: elements; xml element list
|
||||
@rtype: [((start, end), name, attrs)]
|
||||
##def remove_format(self, format, value=None):
|
||||
##"""."""
|
||||
##if format not in self.formats:
|
||||
##raise TypeError("%s is not a valid format name" % format)
|
||||
|
||||
"""
|
||||
elements = []
|
||||
for text_tag, indices in tagdict.items():
|
||||
for start_idx, end_idx in indices:
|
||||
elements.append(((start_idx, end_idx+1),
|
||||
self.tag_markup[text_tag],
|
||||
None))
|
||||
return elements
|
||||
##start, end = self.get_selection()
|
||||
|
||||
##def pango_color_to_gdk(self, pc):
|
||||
##return gtk.gdk.Color(pc.red, pc.green, pc.blue)
|
||||
##log.debug("Removing format '%s' with value '%s' for range %d - %d" %
|
||||
##(format, value, start.get_offset(), end.get_offset()))
|
||||
|
||||
##def color_to_hex(self, color):
|
||||
##hexstring = ""
|
||||
##for col in 'red', 'green', 'blue':
|
||||
##hexfrag = hex(getattr(color, col) / (16 * 16)).split("x")[1]
|
||||
##if len(hexfrag) < 2:
|
||||
##hexfrag = "0" + hexfrag
|
||||
##hexstring += hexfrag
|
||||
##return hexstring
|
||||
##if format == 'bold':
|
||||
##self.remove_tag_by_name('bold', start, end)
|
||||
##elif format == 'italic':
|
||||
##self.remove_tag_by_name('italic', start, end)
|
||||
##elif format == 'underline':
|
||||
##self.remove_tag_by_name('underline', start, end)
|
||||
##else:
|
||||
##log.error("Format '%s' is not yet implemented" % format)
|
||||
|
||||
def get_selection(self):
|
||||
bounds = self.get_selection_bounds()
|
||||
if not bounds:
|
||||
iter = self.get_iter_at_mark(self.insert)
|
||||
if iter.inside_word():
|
||||
start_pos = iter.get_offset()
|
||||
iter.forward_word_end()
|
||||
word_end = iter.get_offset()
|
||||
iter.backward_word_start()
|
||||
word_start = iter.get_offset()
|
||||
iter.set_offset(start_pos)
|
||||
bounds = (self.get_iter_at_offset(word_start),
|
||||
self.get_iter_at_offset(word_end + 1))
|
||||
else:
|
||||
bounds = (iter, self.get_iter_at_offset(iter.get_offset() + 1))
|
||||
return bounds
|
||||
##def remove_all_formats(self):
|
||||
##"""."""
|
||||
##start, end = self.get_selection()
|
||||
|
||||
def apply_tag_to_selection(self, tag):
|
||||
selection = self.get_selection()
|
||||
if selection:
|
||||
self.apply_tag(tag, *selection)
|
||||
##log.debug("Removing all format for range %d - %d" %
|
||||
##(start.get_offset(), end.get_offset()))
|
||||
|
||||
def remove_tag_from_selection(self, tag):
|
||||
selection = self.get_selection()
|
||||
if selection:
|
||||
self.remove_tag(tag, *selection)
|
||||
|
||||
def remove_all_tags(self):
|
||||
selection = self.get_selection()
|
||||
if selection:
|
||||
for t in self.tags.values():
|
||||
self.remove_tag(t, *selection)
|
||||
|
||||
class EditorBuffer(MarkupBuffer):
|
||||
"""An interactive interface to allow markup a gtk.TextBuffer.
|
||||
|
||||
normal_button is a widget whose clicked signal will make us normal
|
||||
toggle_widget_alist is a list that looks like this: [(widget, tag_name),]
|
||||
|
||||
Based on 'gourmet-0.13.3' L{http://grecipe-manager.sourceforge.net}
|
||||
Pango markup format is replaces by custom Gramps XML format.
|
||||
|
||||
"""
|
||||
__gtype_name__ = 'EditorBuffer'
|
||||
|
||||
def __init__(self, normal_button=None, toggle_widget_alist=[]):
|
||||
MarkupBuffer.__init__(self)
|
||||
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:
|
||||
self.setup_widget(widg, name)
|
||||
|
||||
# Virtual methods
|
||||
|
||||
def do_changed(self):
|
||||
if not hasattr(self,'last_mark'):
|
||||
return
|
||||
|
||||
# If our insertion point has a mark, we want to apply the tag
|
||||
# each time the user types...
|
||||
old_itr = self.get_iter_at_mark(self.last_mark)
|
||||
insert_itr = self.get_iter_at_mark(self.insert)
|
||||
if old_itr != insert_itr:
|
||||
# Use the state of our widgets to determine what
|
||||
# properties to apply...
|
||||
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)
|
||||
|
||||
def do_mark_set(self, iter, mark):
|
||||
# Every time the cursor moves, update our widgets that reflect
|
||||
# the state of the text.
|
||||
if hasattr(self, '_in_mark_set') and self._in_mark_set:
|
||||
return
|
||||
|
||||
self._in_mark_set = True
|
||||
if mark.get_name() == 'insert':
|
||||
##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
|
||||
self.internal_toggle = True
|
||||
widg.set_active(active)
|
||||
self.internal_toggle = False
|
||||
if hasattr(self, 'last_mark'):
|
||||
self.move_mark(self.last_mark, iter)
|
||||
else:
|
||||
self.last_mark = self.create_mark('last', iter, left_gravity=True)
|
||||
self._in_mark_set = False
|
||||
|
||||
# Private
|
||||
|
||||
def _toggle(self, widget, tag):
|
||||
if self.internal_toggle:
|
||||
return
|
||||
|
||||
if widget.get_active():
|
||||
self.apply_tag_to_selection(tag)
|
||||
else:
|
||||
self.remove_tag_from_selection(tag)
|
||||
|
||||
# Public API
|
||||
|
||||
def setup_widget_from_xml(self, widg, xmlstring):
|
||||
"""Setup widget 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_widget(widg, name)
|
||||
|
||||
def setup_widget(self, widg, name):
|
||||
"""Setup widget from Gramps tag name."""
|
||||
tag = self.get_tag_from_element(name)
|
||||
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)
|
||||
##self.remove_all_tags(start, end)
|
||||
|
||||
if gtk.pygtk_version < (2,8,0):
|
||||
gobject.type_register(EditorBuffer)
|
||||
gobject.type_register(MarkupBuffer)
|
||||
|
Loading…
x
Reference in New Issue
Block a user