From d003d5e61bfb28a8c5705d0aafed9d6978c7aed9 Mon Sep 17 00:00:00 2001 From: Zsolt Foldvari Date: Wed, 23 Apr 2008 09:07:02 +0000 Subject: [PATCH] Refactoring: - better functionality devision between buffer and editor; - better integration with StyledTextTagType; - font family and font size tags are separated; - new font family and font size toolbar widgets; svn: r10626 --- src/Editors/_StyledTextBuffer.py | 434 +++++++++++++------------------ src/Editors/_StyledTextEditor.py | 314 ++++++++++++++++++++-- src/gen/lib/styledtexttagtype.py | 14 +- 3 files changed, 489 insertions(+), 273 deletions(-) diff --git a/src/Editors/_StyledTextBuffer.py b/src/Editors/_StyledTextBuffer.py index b315a6f3e..4ddd9165b 100644 --- a/src/Editors/_StyledTextBuffer.py +++ b/src/Editors/_StyledTextBuffer.py @@ -38,6 +38,7 @@ _LOG = logging.getLogger(".Editors.StyledTextBuffer") # GTK modules # #------------------------------------------------------------------------- +import gobject import gtk from pango import WEIGHT_BOLD, STYLE_ITALIC, UNDERLINE_SINGLE @@ -53,6 +54,43 @@ from gen.lib import (StyledText, StyledTextTag, StyledTextTagType) # Constants # #------------------------------------------------------------------------- +# FIXME +ALLOWED_STYLES = [i for (i, s, e) in StyledTextTagType._DATAMAP] + +STYLE_TYPE = { + StyledTextTagType.BOLD: bool, + StyledTextTagType.ITALIC: bool, + StyledTextTagType.UNDERLINE: bool, + StyledTextTagType.FONTCOLOR: str, + StyledTextTagType.HIGHLIGHT: str, + StyledTextTagType.FONTFACE: str, + StyledTextTagType.FONTSIZE: int, + StyledTextTagType.SUPERSCRIPT: bool, +} + +STYLE_DEFAULT = { + StyledTextTagType.BOLD: False, + StyledTextTagType.ITALIC: False, + StyledTextTagType.UNDERLINE: False, + StyledTextTagType.FONTCOLOR: '#000000', + StyledTextTagType.HIGHLIGHT: '#FFFFFF', + StyledTextTagType.FONTFACE: 'Sans', + StyledTextTagType.FONTSIZE: 10, + StyledTextTagType.SUPERSCRIPT: False, +} + +STYLE_TO_PROPERTY = { + StyledTextTagType.BOLD: 'weight', # permanent tag is used instead + StyledTextTagType.ITALIC: 'style', # permanent tag is used instead + StyledTextTagType.UNDERLINE: 'underline', # permanent tag is used instead + StyledTextTagType.FONTCOLOR: 'foreground', + StyledTextTagType.HIGHLIGHT: 'background', + StyledTextTagType.FONTFACE: 'family', + StyledTextTagType.FONTSIZE: 'size-points', + StyledTextTagType.SUPERSCRIPT: 'rise', +} + + (MATCH_START, MATCH_END, MATCH_FLAVOR, @@ -189,8 +227,14 @@ class StyledTextBuffer(gtk.TextBuffer): and gtk.TextBuffer. To set and get the text use the L{set_text} and L{get_text} methods. - StyledTextBuffer provides an action group (L{format_action_group}) - for GUIs. + To set a style to (a portion of) the text (e.g. from GUI) use the + L{apply_style} and L{remove_style} methods. + + To receive information about the style of the text at the cursor position + StyledTextBuffer provides two mechanism: message driven and polling. + To receive notification of style change as cursor moves connect to the + C{style-changed} signal. To get the value of a certain style at the cursor + use the L{get_style_at_cursor) method. StyledTextBuffer has a regexp pattern matching mechanism too. To add a regexp pattern to match in the text use the L{match_add} method. To check @@ -200,66 +244,30 @@ class StyledTextBuffer(gtk.TextBuffer): """ __gtype_name__ = 'StyledTextBuffer' - formats = ('italic', 'bold', 'underline', - 'font', 'foreground', 'background',) + __gsignals__ = { + 'style-changed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, #return value + (gobject.TYPE_PYOBJECT,)), # arguments + } def __init__(self): 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) + # see self._find_tag_by_name + self.create_tag(str(StyledTextTagType.BOLD), weight=WEIGHT_BOLD) + self.create_tag(str(StyledTextTagType.ITALIC), style=STYLE_ITALIC) + self.create_tag(str(StyledTextTagType.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', 'gramps-font', None, None, - _('Font'), self._on_action_activate), - ('foreground', 'gramps-font-color', None, None, - _('Font Color'), self._on_action_activate), - ('background', 'gramps-font-bgcolor', None, None, - _('Background Color'), self._on_action_activate), - ('clear', gtk.STOCK_CLEAR, None, None, - _('Clear Markup'), self._format_clear_cb), - ] - - self.action_accels = { - 'i': 'italic', - 'b': 'bold', - '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) - # 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 - # TODO could we separate font name and size? - ##self.size = None - self.foreground = None - self.background = None + ## 3. are set when a style is set (self._apply_style_to_selection) + self.style_state = STYLE_DEFAULT.copy() # internally used attribute - self._internal_toggle = False self._insert = self.get_insert() # create a mark used for text formatting @@ -298,13 +306,10 @@ class StyledTextBuffer(gtk.TextBuffer): insert_start = self.get_iter_at_mark(self.mark_insert) # apply active formats for the inserted text - for format in self.__class__.formats: - value = getattr(self, format) - if value: - if format in self.toggle_actions: - value = None - - self.apply_tag(self._find_tag_by_name(format, value), + for style in ALLOWED_STYLES: + value = self.style_state[style] + if value and (value != STYLE_DEFAULT[style]): + self.apply_tag(self._find_tag_by_name(style, value), insert_start, iter) def after_delete_range(self, textbuffer, start, end): @@ -334,7 +339,7 @@ class StyledTextBuffer(gtk.TextBuffer): break def do_mark_set(self, iter, mark): - """Update format attributes each time the cursor moves.""" + """Update style state each time the cursor moves.""" _LOG.debug("Setting mark %s at %d" % (mark.get_name(), iter.get_offset())) @@ -345,50 +350,28 @@ class StyledTextBuffer(gtk.TextBuffer): iter.backward_char() tag_names = [tag.get_property('name') for tag in iter.get_tags()] - 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 + changed_styles = {} + + for style in ALLOWED_STYLES: + if STYLE_TYPE[style] == bool: + value = str(style) in tag_names else: - value = None + value = STYLE_DEFAULT[style] for tname in tag_names: - if tname.startswith(format): + if tname.startswith(str(style)): value = tname.split(' ', 1)[1] + value = STYLE_TYPE[style](value) - setattr(self, format, value) + if self.style_state[style] != value: + changed_styles[style] = value + + self.style_state[style] = value + + if changed_styles: + self.emit('style-changed', changed_styles) # Private - def _tagname_to_tagtype(self, name): - """Convert gtk.TextTag names to StyledTextTagType values.""" - tag2type = { - 'bold': StyledTextTagType.BOLD, - 'italic': StyledTextTagType.ITALIC, - 'underline': StyledTextTagType.UNDERLINE, - 'foreground': StyledTextTagType.FONTCOLOR, - 'background': StyledTextTagType.HIGHLIGHT, - 'font': StyledTextTagType.FONTFACE, - } - - return StyledTextTagType(tag2type[name]) - - def _tagtype_to_tagname(self, tagtype): - """Convert StyledTextTagType values to gtk.TextTag names.""" - type2tag = { - StyledTextTagType.BOLD: 'bold', - StyledTextTagType.ITALIC: 'italic', - StyledTextTagType.UNDERLINE: 'underline', - StyledTextTagType.FONTCOLOR: 'foreground', - StyledTextTagType.HIGHLIGHT: 'background', - StyledTextTagType.FONTFACE: 'font', - } - - return type2tag[tagtype] - ##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() @@ -404,21 +387,6 @@ class StyledTextBuffer(gtk.TextBuffer): ##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.""" - color = gtk.gdk.color_parse(hex) - return color - def _get_selection(self): bounds = self.get_selection_bounds() if not bounds: @@ -446,11 +414,34 @@ class StyledTextBuffer(gtk.TextBuffer): if selection: self.remove_tag(tag, *selection) - def _remove_format_from_selection(self, format): + def _apply_style_to_selection(self, style, value): + # FIXME can this be unified? + if STYLE_TYPE[style] == bool: + start, end = self._get_selection() + + if value: + self.apply_tag_by_name(str(style), start, end) + else: + self.remove_tag_by_name(str(style), start, end) + elif STYLE_TYPE[style] == str: + tag = self._find_tag_by_name(style, value) + self._remove_style_from_selection(style) + self._apply_tag_to_selection(tag) + elif STYLE_TYPE[style] == int: + tag = self._find_tag_by_name(style, value) + self._remove_style_from_selection(style) + self._apply_tag_to_selection(tag) + else: + # we should never get until here + return + + self.style_state[style] = value + + def _remove_style_from_selection(self, style): 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): + if tag_name.startswith(str(style)): for start, end in tags[tag_name]: self.remove_tag_by_name(tag_name, self.get_iter_at_offset(start), @@ -462,6 +453,8 @@ class StyledTextBuffer(gtk.TextBuffer): Return only the name of the TextTag from the specified range. If range is not given, tags extracted from the whole buffer. + @note: TextTag names are always composed like: (%s %s) % (style, value) + @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 @@ -489,130 +482,61 @@ class StyledTextBuffer(gtk.TextBuffer): tagdict[name]=[(pos, pos)] return tagdict - def _find_tag_by_name(self, name, value): + def _find_tag_by_name(self, style, 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 + if STYLE_TYPE[style] == bool: + tag_name = str(style) + elif STYLE_TYPE[style] == str: + tag_name = "%d %s" % (style, value) + elif STYLE_TYPE[style] == int: + tag_name = "%d %d" % (style, value) else: - tag_name = "%s %s" % (name, value) + raise ValueError("Unknown style (%s) value type: %s" % + (style, value.__class__)) + tag = self.get_tag_table().lookup(tag_name) + if not tag: - if value is not None: + if STYLE_TYPE[style] != bool: + # bool style tags are not created here, but in constuctor tag = self.create_tag(tag_name) - tag.set_property(name, value) + tag.set_property(STYLE_TO_PROPERTY[style], 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) - - setattr(self, action.get_name(), action.get_active()) - - def _on_action_activate(self, action): - """Apply a format.""" - format = action.get_name() - - if format == 'foreground': - color_selection = gtk.ColorSelectionDialog(_("Select font color")) - if self.foreground: - color_selection.colorsel.set_current_color( - self._hex_to_color(self.foreground)) - 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")) - if self.background: - color_selection.colorsel.set_current_color( - self._hex_to_color(self.background)) - 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 = CustomFontSelectionDialog(_("Select font")) - if self.font: - font_selection.fontsel.set_font_name(self.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) - - setattr(self, format, value) - - 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, r_text): - """Set the content of the buffer with markup tags.""" - gtk.TextBuffer.set_text(self, str(r_text)) + def set_text(self, s_text): + """Set the content of the buffer with markup tags. + + @note: 's_' prefix means StyledText*, while 'g_' prefix means gtk.*. + + """ + gtk.TextBuffer.set_text(self, str(s_text)) - r_tags = r_text.get_tags() - for r_tag in r_tags: - tagname = self._tagtype_to_tagname(int(r_tag.name)) - g_tag = self._find_tag_by_name(tagname, r_tag.value) + s_tags = s_text.get_tags() + for s_tag in s_tags: + g_tag = self._find_tag_by_name(int(s_tag.name), s_tag.value) if g_tag is not None: - for (start, end) in r_tag.ranges: + for (start, end) in s_tag.ranges: start_iter = self.get_iter_at_offset(start) end_iter = self.get_iter_at_offset(end) self.apply_tag(g_tag, start_iter, end_iter) def get_text(self, start=None, end=None, include_hidden_chars=True): - """Return the buffer text.""" - if not start: + """Return the buffer text. + + @note: 's_' prefix means StyledText*, while 'g_' prefix means gtk.*. + + """ + if start is None: start = self.get_start_iter() - if not end: + if end is None: end = self.get_end_iter() txt = gtk.TextBuffer.get_text(self, start, end, include_hidden_chars) @@ -620,26 +544,60 @@ class StyledTextBuffer(gtk.TextBuffer): # extract tags out of the buffer g_tags = self._get_tag_from_range() - r_tags = [] + s_tags = [] for g_tagname, g_ranges in g_tags.items(): - name_value = g_tagname.split(' ', 1) + style_and_value = g_tagname.split(' ', 1) - if len(name_value) == 1: - name = name_value[0] - r_value = None + style = int(style_and_value[0]) + if len(style_and_value) == 1: + s_value = None else: - (name, r_value) = name_value + s_value = STYLE_TYPE[style](style_and_value[1]) - if name in self.formats: - r_tagtype = self._tagname_to_tagtype(name) - r_ranges = [(start, end+1) for (start, end) in g_ranges] - r_tag = StyledTextTag(r_tagtype, r_value, r_ranges) + if style in ALLOWED_STYLES: + s_ranges = [(start, end+1) for (start, end) in g_ranges] + s_tag = StyledTextTag(style, s_value, s_ranges) - r_tags.append(r_tag) - - return StyledText(txt, r_tags) + s_tags.append(s_tag) + return StyledText(txt, s_tags) + + def apply_style(self, style, value): + """Apply a style with the given value to the selection. + + @param style: style type to apply + @type style: L{StyledTextTagStyle} int value + @param value: value of the style type + @type value: depends on the I{style} type + + """ + if not isinstance(value, STYLE_TYPE[style]): + raise TypeError("Style (%d) value must be %s and not %s" % + (style, STYLE_TYPE[style], value.__class__)) + + self._apply_style_to_selection(style, value) + + def remove_style(self, style): + """Delete all occurences with any value of the given style. + + @param style: style type to apply + @type style: L{StyledTextTagStyle} int value + + """ + self._remove_style_from_selection(style) + + def get_style_at_cursor(self, style): + """Get the actual value of the given style at the cursor position. + + @param style: style type to apply + @type style: L{StyledTextTagStyle} int value + @returns: value of the style type + @returntype: depends on the C{style} type + + """ + return self.style_state[style] + def match_add(self, pattern, flavor): """Add a pattern to look for in the text.""" regex = re.compile(pattern) @@ -652,27 +610,3 @@ class StyledTextBuffer(gtk.TextBuffer): return match return None - -#------------------------------------------------------------------------- -# -# CustomFontSelectionDialog class -# -#------------------------------------------------------------------------- -class CustomFontSelectionDialog(gtk.FontSelectionDialog): - """A FontSelectionDialog without the Style treeview. - - This should be only a workaround until a real custom font selector - is created, because this solution is gtk implementation dependent. - - """ - def __init__(self, title): - gtk.FontSelectionDialog.__init__(self, title) - - # hide the Style label and treeview - for widget in self.fontsel.get_children(): - if isinstance(widget, gtk.Table): - table = widget - - for child in table.get_children(): - if table.child_get_property(child, 'left-attach') == 1: - child.hide() \ No newline at end of file diff --git a/src/Editors/_StyledTextEditor.py b/src/Editors/_StyledTextEditor.py index 3517775f4..e4459720c 100644 --- a/src/Editors/_StyledTextEditor.py +++ b/src/Editors/_StyledTextEditor.py @@ -46,8 +46,11 @@ from pango import UNDERLINE_SINGLE # GRAMPS modules # #------------------------------------------------------------------------- -from Editors._StyledTextBuffer import (StyledTextBuffer, MATCH_START, - MATCH_END, MATCH_FLAVOR, MATCH_STRING) +from gen.lib import StyledTextTagType +from Editors._StyledTextBuffer import (StyledTextBuffer, ALLOWED_STYLES, + STYLE_TYPE, STYLE_DEFAULT, + MATCH_START, MATCH_END, + MATCH_FLAVOR, MATCH_STRING) from Spell import Spell from GrampsDisplay import url as display_url @@ -62,18 +65,28 @@ REGULAR_CURSOR = gtk.gdk.Cursor(gtk.gdk.XTERM) FORMAT_TOOLBAR = ''' - - - - - - - + + + + + + + -''' +''' % (StyledTextTagType.ITALIC, + StyledTextTagType.BOLD, + StyledTextTagType.UNDERLINE, + StyledTextTagType.FONTFACE, + StyledTextTagType.FONTSIZE, + StyledTextTagType.FONTCOLOR, + StyledTextTagType.HIGHLIGHT, + ) + +FONT_SIZES = [8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, + 24, 26, 28, 32, 36, 40, 48, 56, 64, 72] USERCHARS = "-A-Za-z0-9" PASSCHARS = "-A-Za-z0-9,?;.:/!%$^*&~\"#'" @@ -137,6 +150,7 @@ class StyledTextEditor(gtk.TextView): def __init__(self): """Setup initial instance variable values.""" self.textbuffer = StyledTextBuffer() + self.textbuffer.connect('style-changed', self._on_buffer_style_changed) gtk.TextView.__init__(self, self.textbuffer) self.match = None @@ -146,6 +160,7 @@ class StyledTextEditor(gtk.TextView): self.toolbar = self._create_toolbar() self.spellcheck = Spell(self) + self._internal_style_change = False self._connect_signals() @@ -196,11 +211,18 @@ class StyledTextEditor(gtk.TextView): def on_key_press_event(self, widget, event): """Signal handler. - Handle shortcuts in the TextView. + Handle formatting shortcuts. """ - return self.get_buffer().on_key_press_event(self, event) - + 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.action_group.get_action(action_name) + action.activate() + return True + return False + def on_insert_at_cursor(self, widget, string): """Signal handler. for debugging only.""" _LOG.debug("Textview insert '%s'" % string) @@ -301,16 +323,73 @@ class StyledTextEditor(gtk.TextView): def _create_toolbar(self): """Create a formatting toolbar. - @returns: toolbar according to L{StyedTextBuffer} formatting - capabilities. + @returns: toolbar containing text formatting toolitems. @returntype: gtk.Toolbar """ + # define the actions... + # ...first the toggle actions, which have a ToggleToolButton as proxy + format_toggle_actions = [ + (str(StyledTextTagType.ITALIC), gtk.STOCK_ITALIC, None, None, + _('Italic'), self._on_toggle_action_activate), + (str(StyledTextTagType.BOLD), gtk.STOCK_BOLD, None, None, + _('Bold'), self._on_toggle_action_activate), + (str(StyledTextTagType.UNDERLINE), gtk.STOCK_UNDERLINE, None, None, + _('Underline'), self._on_toggle_action_activate), + ] + + self.toggle_actions = [action[0] for action in format_toggle_actions] + + # ...then the normal actions, which have a ToolButton as proxy + format_actions = [ + (str(StyledTextTagType.FONTCOLOR), 'gramps-font-color', None, None, + _('Font Color'), self._on_action_activate), + (str(StyledTextTagType.HIGHLIGHT), 'gramps-font-bgcolor', None, None, + _('Background Color'), self._on_action_activate), + ('clear', gtk.STOCK_CLEAR, None, None, + _('Clear Markup'), self._format_clear_cb), + ] + + # ...last the custom actions, which have custom proxies + fontface_action = ComboToolAction(str(StyledTextTagType.FONTFACE), + _("Font family"), + _("Font family"), None) + fontsize_action = ComboToolAction(str(StyledTextTagType.FONTSIZE), + _("Font size"), + _("Font size"), None) + self.action_accels = { + 'i': 'italic', + 'b': 'bold', + 'u': 'underline', + } + + # create the action group and insert all the actions + self.action_group = gtk.ActionGroup('Format') + self.action_group.add_toggle_actions(format_toggle_actions) + self.action_group.add_actions(format_actions) + self.action_group.add_action(fontface_action) + self.action_group.add_action(fontsize_action) + + # define the toolbar and create the proxies via ensure_update() uimanager = gtk.UIManager() - uimanager.insert_action_group(self.textbuffer.format_action_group, 0) + uimanager.insert_action_group(self.action_group, 0) uimanager.add_ui_from_string(FORMAT_TOOLBAR) uimanager.ensure_update() + # now that widget is created for the custom actions set them up + fontface = uimanager.get_widget('/ToolBar/%d' % + StyledTextTagType.FONTFACE) + set_fontface_toolitem(fontface, self._on_combotoolitem_changed) + + fontsize = uimanager.get_widget('/ToolBar/%d' % + StyledTextTagType.FONTSIZE) + set_fontsize_toolitem(fontsize, self._on_combotoolitem_changed) + + ##separator = uimanager.get_widget('/ToolBar/abcdef') + ##separator = gtk.SeparatorToolItem() + ##separator.set_expand(True) + ##separator.set_draw(False) + toolbar = uimanager.get_widget('/ToolBar') toolbar.set_style(gtk.TOOLBAR_ICONS) @@ -357,6 +436,98 @@ class StyledTextEditor(gtk.TextView): # Callback functions + def _on_toggle_action_activate(self, action): + """Toggle a style. + + Toggle styles are e.g. 'bold', 'italic', 'underline'. + + """ + if self._internal_style_change: + return + + style = int(action.get_name()) + value = action.get_active() + _LOG.debug("applying style '%d' with value '%s'" % (style, str(value))) + self.textbuffer.apply_style(style, value) + + def _on_action_activate(self, action): + """Apply a format.""" + style = int(action.get_name()) + current_value = self.textbuffer.get_style_at_cursor(style) + + if style == StyledTextTagType.FONTCOLOR: + color_selection = gtk.ColorSelectionDialog(_("Select font color")) + elif style == StyledTextTagType.HIGHLIGHT: + color_selection = gtk.ColorSelectionDialog(_("Select " + "background color")) + else: + _LOG.debug("unknown style: '%d'" % style) + return + + if current_value: + color_selection.colorsel.set_current_color( + hex_to_color(current_value)) + + response = color_selection.run() + color = color_selection.colorsel.get_current_color() + value = color_to_hex(color) + color_selection.destroy() + + if response == gtk.RESPONSE_OK: + _LOG.debug("applying style '%d' with value '%s'" % + (style, str(value))) + self.textbuffer.apply_style(style, value) + + def _on_combotoolitem_changed(self, combobox, style): + if self._internal_style_change: + return + + value = STYLE_TYPE[style](combobox.get_active_text()) + _LOG.debug("applying style '%d' with value '%s'" % (style, str(value))) + self.textbuffer.apply_style(style, value) + + 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 style in ALLOWED_STYLES: + self.textbuffer.remove_style(style) + + def _on_buffer_style_changed(self, buffer, changed_styles): + # set state of toggle action + for style in changed_styles.keys(): + if str(style) in self.toggle_actions: + action = self.action_group.get_action(str(style)) + self._internal_style_change = True + action.set_active(changed_styles[style]) + self._internal_style_change = False + + if ((style == StyledTextTagType.FONTFACE) or + (style == StyledTextTagType.FONTSIZE)): + + action = self.action_group.get_action(str(style)) + combo = action.get_proxies()[0].child + model = combo.get_model() + iter = model.get_iter_first() + while iter: + if (STYLE_TYPE[style](model.get_value(iter, 0)) == + changed_styles[style]): + break + iter = model.iter_next(iter) + + self._internal_style_change = True + if iter is None: + combo.child.set_text(str(changed_styles[style])) + if style == StyledTextTagType.FONTFACE: + _LOG.debug('font family "%s" is not installed' % + changed_styles[style]) + else: + combo.set_active_iter(iter) + self._internal_style_change = False + def _spell_change_cb(self, menuitem, language): """Set spell checker language according to user selection.""" self.spellcheck.set_active_language(language) @@ -396,6 +567,7 @@ class StyledTextEditor(gtk.TextView): """ self.textbuffer.set_text(text) + self.textbuffer.place_cursor(self.textbuffer.get_start_iter()) def get_text(self): """Get the text of the text buffer of the editor. @@ -413,4 +585,112 @@ class StyledTextEditor(gtk.TextView): @returntype: gtk.Toolbar """ - return self.toolbar \ No newline at end of file + return self.toolbar + +#------------------------------------------------------------------------- +# +# ComboToolItem class +# +#------------------------------------------------------------------------- +class ComboToolItem(gtk.ToolItem): + + __gtype_name__ = "ComboToolItem" + + def __init__(self): + gtk.ToolItem.__init__(self) + + self.set_border_width(2) + self.set_homogeneous(False) + self.set_expand(False) + + self.combobox = gtk.combo_box_entry_new_text() + self.combobox.show() + self.add(self.combobox) + + def set_entry_editable(self, editable): + self.combobox.child.set_editable(editable) + +#------------------------------------------------------------------------- +# +# ComboToolAction class +# +#------------------------------------------------------------------------- +class ComboToolAction(gtk.Action): + + __gtype_name__ = "ComboToolAction" + + def __init__(self, name, label, tooltip, stock_id): + gtk.Action.__init__(self, name, label, tooltip, stock_id) + ##self.set_tool_item_type(ComboToolItem) + + ##def create_tool_item(self): + ##combobox = ComboToolButton() + ###self.connect_proxy(combobox) + ##return combobox + + ##def connect_proxy(self, proxy): + ##gtk.Action.connect_proxy(self, proxy) + + ##if isinstance(proxy, ComboToolButton): + ##proxy.combobox.connect('changed', self.changed) + + ##def changed(self, combobox): + ##self.activate() +ComboToolAction.set_tool_item_type(ComboToolItem) + +#------------------------------------------------------------------------- +# +# Module functions +# +#------------------------------------------------------------------------- +def set_fontface_toolitem(combotoolitem, callback): + """Setup font family comboboxentry.""" + combotoolitem.set_entry_editable(False) + + fontface = combotoolitem.child + + families = [family.get_name() + for family in fontface.get_pango_context().list_families()] + families.sort() + for family in families: + fontface.append_text(family) + + try: + default = families.index(STYLE_DEFAULT[StyledTextTagType.FONTFACE]) + except ValueError: + default = 0 + fontface.set_active(default) + + fontface.connect('changed', callback, StyledTextTagType.FONTFACE) + +def set_fontsize_toolitem(combotoolitem, callback): + """Setup font size comboboxentry.""" + combotoolitem.set_size_request(60, -1) + + fontsize = combotoolitem.child + + for size in FONT_SIZES: + fontsize.append_text(str(size)) + + try: + default = FONT_SIZES.index(STYLE_DEFAULT[StyledTextTagType.FONTSIZE]) + except ValueError: + default = 0 + fontsize.set_active(default) + + fontsize.connect('changed', callback, StyledTextTagType.FONTSIZE) + +def color_to_hex(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(hex): + """Convert hex string to gtk.gdk.Color.""" + color = gtk.gdk.color_parse(hex) + return color diff --git a/src/gen/lib/styledtexttagtype.py b/src/gen/lib/styledtexttagtype.py index 4d29d56c2..a02fe5024 100644 --- a/src/gen/lib/styledtexttagtype.py +++ b/src/gen/lib/styledtexttagtype.py @@ -47,23 +47,25 @@ class StyledTextTagType(GrampsType): Here we only define new class variables. For details see L{GrampsType}. """ - NONE_ = -1 + NONE_TYPE = -1 BOLD = 0 ITALIC = 1 UNDERLINE = 2 FONTFACE = 3 - FONTCOLOR = 4 - HIGHLIGHT = 5 - SUPERSCRIPT = 6 + FONTSIZE = 4 + FONTCOLOR = 5 + HIGHLIGHT = 6 + SUPERSCRIPT = 7 - _CUSTOM = NONE_ - _DEFAULT = NONE_ + _CUSTOM = NONE_TYPE + _DEFAULT = NONE_TYPE _DATAMAP = [ (BOLD, _("Bold"), "bold"), (ITALIC, _("Italic"), "italic"), (UNDERLINE, _("Underline"), "underline"), (FONTFACE, _("Fontface"), "fontface"), + (FONTSIZE, _("Fontsize"), "fontsize"), (FONTCOLOR, _("Fontcolor"), "fontcolor"), (HIGHLIGHT, _("Highlight"), "highlight"), (SUPERSCRIPT, _("Superscript"), "superscript"),