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
This commit is contained in:
Zsolt Foldvari 2008-04-23 09:07:02 +00:00
parent c4024f91db
commit d003d5e61b
3 changed files with 489 additions and 273 deletions

View File

@ -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)
# 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 = {
'<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)
# 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)
# 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
else:
value = None
for tname in tag_names:
if tname.startswith(format):
value = tname.split(' ', 1)[1]
changed_styles = {}
setattr(self, format, value)
for style in ALLOWED_STYLES:
if STYLE_TYPE[style] == bool:
value = str(style) in tag_names
else:
value = STYLE_DEFAULT[style]
for tname in tag_names:
if tname.startswith(str(style)):
value = tname.split(' ', 1)[1]
value = STYLE_TYPE[style](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.
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)
@note: 's_' prefix means StyledText*, while 'g_' prefix means gtk.*.
"""
gtk.TextBuffer.set_text(self, str(s_text))
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,25 +544,59 @@ 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)
s_tags.append(s_tag)
return StyledText(txt, r_tags)
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."""
@ -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()

View File

@ -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 = '''
<ui>
<toolbar name="ToolBar">
<toolitem action="italic"/>
<toolitem action="bold"/>
<toolitem action="underline"/>
<separator/>
<toolitem action="font"/>
<toolitem action="foreground"/>
<toolitem action="background"/>
<toolitem action="%d"/>
<toolitem action="%d"/>
<toolitem action="%d"/>
<toolitem action="%d"/>
<toolitem action="%d"/>
<toolitem action="%d"/>
<toolitem action="%d"/>
<separator/>
<toolitem action="clear"/>
</toolbar>
</ui>
'''
''' % (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,10 +211,17 @@ 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."""
@ -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 = {
'<Control>i': 'italic',
'<Control>b': 'bold',
'<Control>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.
@ -414,3 +586,111 @@ class StyledTextEditor(gtk.TextView):
"""
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

View File

@ -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"),