3914: Add a new markup for creating links to URLs and for gramps objects

svn: r15340
This commit is contained in:
Doug Blank
2010-05-06 15:54:33 +00:00
parent ef002c54ad
commit 40b8c2d9a2
14 changed files with 758 additions and 42 deletions

View File

@ -213,6 +213,7 @@ src/gui/editors/editeventref.py
src/gui/editors/editfamily.py src/gui/editors/editfamily.py
src/gui/editors/editldsord.py src/gui/editors/editldsord.py
src/gui/editors/editlocation.py src/gui/editors/editlocation.py
src/gui/editors/editlink.py
src/gui/editors/editmedia.py src/gui/editors/editmedia.py
src/gui/editors/editmediaref.py src/gui/editors/editmediaref.py
src/gui/editors/editname.py src/gui/editors/editname.py
@ -839,6 +840,7 @@ src/glade/editrepository.glade
src/glade/editreporef.glade src/glade/editreporef.glade
src/glade/editpersonref.glade src/glade/editpersonref.glade
src/glade/editlocation.glade src/glade/editlocation.glade
src/glade/editlink.glade
src/glade/editfamily.glade src/glade/editfamily.glade
src/glade/editchildref.glade src/glade/editchildref.glade
src/glade/editattribute.glade src/glade/editattribute.glade

View File

@ -56,6 +56,7 @@ class StyledTextTagType(GrampsType):
FONTCOLOR = 5 FONTCOLOR = 5
HIGHLIGHT = 6 HIGHLIGHT = 6
SUPERSCRIPT = 7 SUPERSCRIPT = 7
LINK = 8
_CUSTOM = NONE_TYPE _CUSTOM = NONE_TYPE
_DEFAULT = NONE_TYPE _DEFAULT = NONE_TYPE
@ -69,6 +70,7 @@ class StyledTextTagType(GrampsType):
(FONTCOLOR, _("Fontcolor"), "fontcolor"), (FONTCOLOR, _("Fontcolor"), "fontcolor"),
(HIGHLIGHT, _("Highlight"), "highlight"), (HIGHLIGHT, _("Highlight"), "highlight"),
(SUPERSCRIPT, _("Superscript"), "superscript"), (SUPERSCRIPT, _("Superscript"), "superscript"),
(LINK, _("Link"), "link"),
] ]
STYLE_TYPE = { STYLE_TYPE = {
@ -80,6 +82,7 @@ class StyledTextTagType(GrampsType):
FONTFACE: str, FONTFACE: str,
FONTSIZE: int, FONTSIZE: int,
SUPERSCRIPT: bool, SUPERSCRIPT: bool,
LINK: str,
} }
STYLE_DEFAULT = { STYLE_DEFAULT = {
@ -91,6 +94,7 @@ class StyledTextTagType(GrampsType):
FONTFACE: 'Sans', FONTFACE: 'Sans',
FONTSIZE: 10, FONTSIZE: 10,
SUPERSCRIPT: False, SUPERSCRIPT: False,
LINK: '',
} }
def __init__(self, value=None): def __init__(self, value=None):

View File

@ -99,6 +99,7 @@ class DocBackend(object):
FONTCOLOR = 5 FONTCOLOR = 5
HIGHLIGHT = 6 HIGHLIGHT = 6
SUPERSCRIPT = 7 SUPERSCRIPT = 7
LINK = 8
SUPPORTED_MARKUP = [] SUPPORTED_MARKUP = []
@ -115,6 +116,7 @@ class DocBackend(object):
ITALIC : ("", ""), ITALIC : ("", ""),
UNDERLINE : ("", ""), UNDERLINE : ("", ""),
SUPERSCRIPT : ("", ""), SUPERSCRIPT : ("", ""),
LINK : ("", ""),
} }
def __init__(self, filename=None): def __init__(self, filename=None):
@ -216,7 +218,7 @@ class DocBackend(object):
if not self.STYLETYPE_MAP or \ if not self.STYLETYPE_MAP or \
self.CLASSMAP != tagtype.__class__.__name__ : self.CLASSMAP != tagtype.__class__.__name__ :
self.CLASSMAP == tagtype.__class__.__name__ self.CLASSMAP == tagtype.__class__.__name__
self.STYLETYPE_MAP[tagtype.__class__.BOLD] = self.BOLD self.STYLETYPE_MAP[tagtype.BOLD] = self.BOLD
self.STYLETYPE_MAP[tagtype.ITALIC] = self.ITALIC self.STYLETYPE_MAP[tagtype.ITALIC] = self.ITALIC
self.STYLETYPE_MAP[tagtype.UNDERLINE] = self.UNDERLINE self.STYLETYPE_MAP[tagtype.UNDERLINE] = self.UNDERLINE
self.STYLETYPE_MAP[tagtype.FONTFACE] = self.FONTFACE self.STYLETYPE_MAP[tagtype.FONTFACE] = self.FONTFACE
@ -224,7 +226,10 @@ class DocBackend(object):
self.STYLETYPE_MAP[tagtype.FONTCOLOR] = self.FONTCOLOR self.STYLETYPE_MAP[tagtype.FONTCOLOR] = self.FONTCOLOR
self.STYLETYPE_MAP[tagtype.HIGHLIGHT] = self.HIGHLIGHT self.STYLETYPE_MAP[tagtype.HIGHLIGHT] = self.HIGHLIGHT
self.STYLETYPE_MAP[tagtype.SUPERSCRIPT] = self.SUPERSCRIPT self.STYLETYPE_MAP[tagtype.SUPERSCRIPT] = self.SUPERSCRIPT
self.STYLETYPE_MAP[tagtype.LINK] = self.LINK
if s_tag.name == 'link':
return self.format_link(s_tag.value)
typeval = int(s_tag.name) typeval = int(s_tag.name)
s_tagvalue = s_tag.value s_tagvalue = s_tag.value
tag_name = None tag_name = None
@ -351,3 +356,13 @@ class DocBackend(object):
otext += opentag[1] otext += opentag[1]
return otext return otext
def format_link(self, value):
"""
Default format for links. Override for better support.
value is: "TYPE DATA" where TYPE is 'url' or 'ref'.
"""
return self.STYLETAG_MARKUP[DocBackend.UNDERLINE]

View File

@ -28,6 +28,7 @@ dist_pkgdata_DATA = \
editreporef.glade \ editreporef.glade \
editpersonref.glade \ editpersonref.glade \
editlocation.glade \ editlocation.glade \
editlink.glade \
editfamily.glade \ editfamily.glade \
editchildref.glade \ editchildref.glade \
editattribute.glade \ editattribute.glade \

212
src/glade/editlink.glade Normal file
View File

@ -0,0 +1,212 @@
<?xml version="1.0"?>
<interface>
<!-- interface-requires gtk+ 2.12 -->
<!-- interface-naming-policy project-wide -->
<object class="GtkDialog" id="editurl">
<property name="modal">True</property>
<property name="default_width">600</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox29">
<property name="visible">True</property>
<child>
<object class="GtkVBox" id="vbox37">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkTable" id="table27">
<property name="visible">True</property>
<property name="border_width">12</property>
<property name="n_rows">3</property>
<property name="n_columns">3</property>
<property name="column_spacing">12</property>
<property name="row_spacing">6</property>
<child>
<object class="GtkLabel" id="label219">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Gramps item:</property>
<property name="use_underline">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label220">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Web Address:</property>
<property name="use_underline">True</property>
<property name="justify">center</property>
<property name="mnemonic_widget">entry1</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label591">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Type:</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">3</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkButton" id="button1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<child>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="stock">gtk-index</property>
<property name="icon-size">2</property>
</object>
</child>
</object>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="x_options"></property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkFrame" id="frame1">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
</object>
</child>
</object>
</child>
<child type="label_item">
<placeholder/>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">3</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area29">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button125">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button124">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="receives_default">True</property>
<property name="has_tooltip">True</property>
<property name="tooltip_markup">Accept changes and close window</property>
<property name="tooltip_text" translatable="yes">Accept changes and close window</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button130">
<property name="label">gtk-help</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-6">button125</action-widget>
<action-widget response="-5">button124</action-widget>
<action-widget response="-11">button130</action-widget>
</action-widgets>
</object>
</interface>

View File

@ -19,6 +19,7 @@ pkgdata_PYTHON = \
editfamily.py \ editfamily.py \
editldsord.py \ editldsord.py \
editlocation.py \ editlocation.py \
editlink.py \
editmedia.py \ editmedia.py \
editmediaref.py \ editmediaref.py \
editname.py \ editname.py \

View File

@ -38,8 +38,9 @@ from editreporef import EditRepoRef
from editsource import EditSource, DeleteSrcQuery from editsource import EditSource, DeleteSrcQuery
from editsourceref import EditSourceRef from editsourceref import EditSourceRef
from editurl import EditUrl from editurl import EditUrl
from editlink import EditLink
# Map from gen.obj name to Editor: # Map from gen.lib name to Editor:
EDITORS = { EDITORS = {
'Person': EditPerson, 'Person': EditPerson,
'Event': EditEvent, 'Event': EditEvent,
@ -58,18 +59,23 @@ def EditObject(dbstate, uistate, track, obj_class, prop, value):
prop is 'handle' or 'gramps_id' prop is 'handle' or 'gramps_id'
value is string handle or string gramps_id value is string handle or string gramps_id
""" """
import logging
LOG = logging.getLogger(".Edit")
if obj_class in dbstate.db.get_table_names(): if obj_class in dbstate.db.get_table_names():
if prop in ("gramps_id", "handle"): if prop in ("gramps_id", "handle"):
obj = dbstate.db.get_table_metadata(obj_class)[prop + "_func"](value) obj = dbstate.db.get_table_metadata(obj_class)[prop + "_func"](value)
if obj: if obj:
try:
EDITORS[obj_class](dbstate, uistate, track, obj) EDITORS[obj_class](dbstate, uistate, track, obj)
except Exception as msg:
LOG.warn(str(msg))
else: else:
raise AttributeError("gramps://%s/%s/%s not found" % LOG.warn("gramps://%s/%s/%s not found" %
(obj_class, prop, value)) (obj_class, prop, value))
else: else:
raise AttributeError("unknown property to edit '%s'; " LOG.warn("unknown property to edit '%s'; "
"should be 'gramps_id' or 'handle'" % prop) "should be 'gramps_id' or 'handle'" % prop)
else: else:
raise AttributeError("unknown object to edit '%s'; " LOG.warn("unknown object to edit '%s'; "
"should be one of %s" % (obj_class, EDITORS.keys())) "should be one of %s" % (obj_class, EDITORS.keys()))

219
src/gui/editors/editlink.py Normal file
View File

@ -0,0 +1,219 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id: $
#-------------------------------------------------------------------------
#
# python modules
#
#-------------------------------------------------------------------------
import gtk
import re
#-------------------------------------------------------------------------
#
# gramps modules
#
#-------------------------------------------------------------------------
from gen.ggettext import gettext as _
import ManagedWindow
import GrampsDisplay
from glade import Glade
from Simple import SimpleAccess
WEB, EVENT, FAMILY, MEDIA, NOTE, PERSON, PLACE, REPOSITORY, SOURCE = range(9)
OBJECT_MAP = {
EVENT: "Event",
FAMILY: "Family",
MEDIA: "Media",
NOTE: "Note",
PERSON: "Person",
PLACE: "Place",
REPOSITORY: "Repository",
SOURCE: "Source",
}
#-------------------------------------------------------------------------
#
# EditUrl class
#
#-------------------------------------------------------------------------
class EditLink(ManagedWindow.ManagedWindow):
def __init__(self, dbstate, uistate, track, url, callback):
self.url = url
self.dbstate = dbstate
self.simple_access = SimpleAccess(self.dbstate.db)
self.callback = callback
ManagedWindow.ManagedWindow.__init__(self, uistate, track, url)
self._local_init()
self._connect_signals()
self.show()
def _local_init(self):
self.top = Glade()
self.set_window(self.top.toplevel,
self.top.get_object("title"),
_('Link Editor'))
self.table = self.top.get_object('table27')
self.uri_list = gtk.combo_box_new_text()
for text in [_("Web Address"), # 0 this order range above
_("Gramps Event"), # 1
_("Gramps Family"), # 2
_("Gramps Media"), # 3
_("Gramps Note"), # 4
_("Gramps Person"), # 5
_("Gramps Place"), # 6
_("Gramps Repository"), # 7
_("Gramps Source"), # 8
]:
self.uri_list.append_text(text)
self.table.attach(self.uri_list, 1, 2, 0, 1)
self.pick_item = self.top.get_object('button1')
#self.edit_item = self.top.get_object('button2')
self.selected = self.top.get_object('label1')
self.url_link = self.top.get_object('entry1')
self.uri_list.connect("changed", self._on_type_changed)
self.pick_item.connect("clicked", self._on_pick_one)
#self.edit_item.connect("clicked", self._on_edit_one)
if self.url.startswith("gramps://"):
object_class, prop, value = self.url[9:].split("/", 2)
if object_class == "Event":
self.uri_list.set_active(EVENT)
elif object_class == "Family":
self.uri_list.set_active(FAMILY)
elif object_class == "Media":
self.uri_list.set_active(MEDIA)
elif object_class == "Note":
self.uri_list.set_active(NOTE)
elif object_class == "Person":
self.uri_list.set_active(PERSON)
elif object_class == "Place":
self.uri_list.set_active(PLACE)
elif object_class == "Repository":
self.uri_list.set_active(REPOSITORY)
elif object_class == "Source":
self.uri_list.set_active(SOURCE)
# set texts:
self.selected.set_text(self.display_link(
object_class, prop, value))
self.url_link.set_text("gramps://%s/%s/%s" %
(object_class, prop, value))
else:
self.uri_list.set_active(WEB)
self.url_link.set_text(self.url)
self.url_link.connect("changed", self.update_ui)
def update_ui(self, widget):
url = self.url_link.get_text()
# text needs to have 3 or more chars://and at least one char
match = re.match("\w{3,}://\w+", url)
if match:
self.ok_button.set_sensitive(True)
else:
self.ok_button.set_sensitive(False)
def display_link(self, obj_class, prop, value):
return self.simple_access.display(obj_class, prop, value)
def _on_edit_one(self, widget):
from gui.editors import EditObject
uri = self.url_link.get_text()
if uri.startswith("gramps://"):
obj_class, prop, value = uri[9:].split("/", 2)
EditObject(self.dbstate,
self.uistate,
self.track,
obj_class, prop, value)
def _on_pick_one(self, widget):
from gui.selectors import SelectorFactory
object_class = OBJECT_MAP[self.uri_list.get_active()]
Select = SelectorFactory(object_class)
uri = self.url_link.get_text()
default = None
if uri.startswith("gramps://"):
obj_class, prop, value = uri[9:].split("/", 2)
if object_class == obj_class:
if prop == "handle":
default = value
elif (prop == "gramps_id" and
object_class in self.dbstate.db.get_table_names()):
person = self.dbstate.db.get_table_metadata(object_class)["gramps_id_func"](value)
if person:
default = person.handle
d = Select(self.dbstate, self.uistate, self.track,
default=default)
result = d.run()
if result:
prop = "handle"
value = result.handle
self.selected.set_text(self.display_link(
object_class, prop, value))
self.url_link.set_text("gramps://%s/%s/%s" %
(object_class, prop, value))
def _on_type_changed(self, widget):
self.selected.set_text("")
if self.uri_list.get_active() == WEB:
self.url_link.set_sensitive(True)
self.pick_item.set_sensitive(False)
else:
self.url_link.set_sensitive(False)
self.pick_item.set_sensitive(True)
def get_uri(self):
if self.uri_list.get_active() == WEB:
return self.url_link.get_text()
else:
#object_class = OBJECT_MAP[self.uri_list.get_active()]
#prop = "handle"
#value = ""
#return "gramps://%s/%s/%s" % (object_class, prop, value)
return self.url_link.get_text()
def _connect_signals(self):
self.define_cancel_button(self.top.get_object('button125'))
self.ok_button = self.top.get_object('button124')
self.define_ok_button(self.ok_button, self.save)
self.define_help_button(self.top.get_object('button130'))
self.update_ui(self.url_link)
def build_menu_names(self, obj):
etitle =_('Link Editor')
return (etitle, etitle)
def define_ok_button(self,button,function):
button.connect('clicked',function)
def save(self, widget):
self.callback(self.get_uri())
self.close()
def define_cancel_button(self,button):
button.connect('clicked',self.close)
def define_help_button(self, button, webpage='', section=''):
button.connect('clicked', lambda x: GrampsDisplay.help(webpage,
section))

View File

@ -45,7 +45,7 @@ from baseselector import BaseSelector
class SelectPerson(BaseSelector): class SelectPerson(BaseSelector):
def __init__(self, dbstate, uistate, track=[], title=None, filter=None, def __init__(self, dbstate, uistate, track=[], title=None, filter=None,
skip=set(), show_search_bar = False): skip=set(), show_search_bar=False, default=None):
# SelectPerson may have a title passed to it which should be used # SelectPerson may have a title passed to it which should be used
# instead of the default defined for get_window_title() # instead of the default defined for get_window_title()
@ -53,7 +53,7 @@ class SelectPerson(BaseSelector):
self.title = title self.title = title
BaseSelector.__init__(self, dbstate, uistate, track, filter, BaseSelector.__init__(self, dbstate, uistate, track, filter,
skip, show_search_bar) skip, show_search_bar, default)
def _local_init(self): def _local_init(self):
""" """

View File

@ -82,6 +82,25 @@ STYLE_TO_PROPERTY = {
MATCH_FLAVOR, MATCH_FLAVOR,
MATCH_STRING,) = range(4) MATCH_STRING,) = range(4)
#-------------------------------------------------------------------------
#
# LinkTag class
#
#-------------------------------------------------------------------------
class LinkTag(gtk.TextTag):
"""
Class for keeping track of link data.
"""
lid = 0
def __init__(self, buffer, data, **properties):
LinkTag.lid += 1
self.data = data
gtk.TextTag.__init__(self, "link-%d" % LinkTag.lid)
tag_table = buffer.get_tag_table()
for property in properties:
self.set_property(property, properties[property])
tag_table.add(self)
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
# GtkSpellState class # GtkSpellState class
@ -433,6 +452,29 @@ class StyledTextBuffer(gtk.TextBuffer):
self.get_iter_at_offset(start), self.get_iter_at_offset(start),
self.get_iter_at_offset(end+1)) self.get_iter_at_offset(end+1))
def clear_selection(self):
"""
Clear tags from selection.
"""
start, end = self._get_selection()
tags = self._get_tag_from_range(start.get_offset(), end.get_offset())
removed_something = False
for tag_name, tag_data in tags.iteritems():
if tag_name.startswith("link"):
for start_pos, end_pos in tag_data:
self.remove_tag_by_name(tag_name,
self.get_iter_at_offset(start_pos),
self.get_iter_at_offset(end_pos+1))
removed_something = True
for style in ALLOWED_STYLES:
value = self.style_state[style]
if value and (value != StyledTextTagType.STYLE_DEFAULT[style]):
self.remove_tag(self._find_tag_by_name(style, value),
start, end)
removed_something = True
return removed_something
def _get_tag_from_range(self, start=None, end=None): def _get_tag_from_range(self, start=None, end=None):
"""Extract gtk.TextTags from buffer. """Extract gtk.TextTags from buffer.
@ -474,7 +516,9 @@ class StyledTextBuffer(gtk.TextBuffer):
If TextTag does not exist yet, it is created. If TextTag does not exist yet, it is created.
""" """
if StyledTextTagType.STYLE_TYPE[style] == bool: if style not in StyledTextTagType.STYLE_TYPE:
return None
elif StyledTextTagType.STYLE_TYPE[style] == bool:
tag_name = str(style) tag_name = str(style)
elif StyledTextTagType.STYLE_TYPE[style] == str: elif StyledTextTagType.STYLE_TYPE[style] == str:
tag_name = "%d %s" % (style, value) tag_name = "%d %s" % (style, value)
@ -507,6 +551,11 @@ class StyledTextBuffer(gtk.TextBuffer):
s_tags = s_text.get_tags() s_tags = s_text.get_tags()
for s_tag in s_tags: for s_tag in s_tags:
if s_tag.name == 'Link':
g_tag = LinkTag(self, s_tag.value,
foreground="blue",
underline=UNDERLINE_SINGLE)
else:
g_tag = self._find_tag_by_name(int(s_tag.name), s_tag.value) g_tag = self._find_tag_by_name(int(s_tag.name), s_tag.value)
if g_tag is not None: if g_tag is not None:
for (start, end) in s_tag.ranges: for (start, end) in s_tag.ranges:
@ -533,6 +582,13 @@ class StyledTextBuffer(gtk.TextBuffer):
s_tags = [] s_tags = []
for g_tagname, g_ranges in g_tags.items(): for g_tagname, g_ranges in g_tags.items():
if g_tagname.startswith('link'):
tag = self.get_tag_table().lookup(g_tagname)
s_ranges = [(start, end+1) for (start, end) in g_ranges]
s_value = tag.data
s_tag = StyledTextTag('Link', s_value, s_ranges)
s_tags.append(s_tag)
else:
style_and_value = g_tagname.split(' ', 1) style_and_value = g_tagname.split(' ', 1)
try: try:

View File

@ -51,7 +51,8 @@ from pango import UNDERLINE_SINGLE
from gen.lib import StyledTextTagType from gen.lib import StyledTextTagType
from gui.widgets.styledtextbuffer import (StyledTextBuffer, ALLOWED_STYLES, from gui.widgets.styledtextbuffer import (StyledTextBuffer, ALLOWED_STYLES,
MATCH_START, MATCH_END, MATCH_START, MATCH_END,
MATCH_FLAVOR, MATCH_STRING) MATCH_FLAVOR, MATCH_STRING,
LinkTag)
from gui.widgets.valueaction import ValueAction from gui.widgets.valueaction import ValueAction
from gui.widgets.toolcomboentry import ToolComboEntry from gui.widgets.toolcomboentry import ToolComboEntry
from gui.widgets.springseparator import SpringSeparatorAction from gui.widgets.springseparator import SpringSeparatorAction
@ -76,6 +77,7 @@ FORMAT_TOOLBAR = '''
<toolitem action="%d"/> <toolitem action="%d"/>
<toolitem action="%d"/> <toolitem action="%d"/>
<toolitem action="%d"/> <toolitem action="%d"/>
<toolitem action="%d"/>
<toolitem action="spring"/> <toolitem action="spring"/>
<toolitem action="clear"/> <toolitem action="clear"/>
</toolbar> </toolbar>
@ -87,6 +89,7 @@ FORMAT_TOOLBAR = '''
StyledTextTagType.FONTSIZE, StyledTextTagType.FONTSIZE,
StyledTextTagType.FONTCOLOR, StyledTextTagType.FONTCOLOR,
StyledTextTagType.HIGHLIGHT, StyledTextTagType.HIGHLIGHT,
StyledTextTagType.LINK,
) )
FONT_SIZES = [8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, FONT_SIZES = [8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22,
@ -101,7 +104,18 @@ SCHEME = "(file:/|https?:|ftps?:|webcal:)"
USER = "[" + USERCHARS + "]+(:[" + PASSCHARS + "]+)?" USER = "[" + USERCHARS + "]+(:[" + PASSCHARS + "]+)?"
URLPATH = "/[" + PATHCHARS + "]*[^]'.}>) \t\r\n,\\\"]" URLPATH = "/[" + PATHCHARS + "]*[^]'.}>) \t\r\n,\\\"]"
(GENURL, HTTP, MAIL) = range(3) (GENURL, HTTP, MAIL, LINK) = range(4)
def find_parent_with_attr(self, attr="dbstate"):
"""
"""
# Find a parent with attr:
obj = self
while obj:
if hasattr(obj, attr):
break
obj = obj.get_parent()
return obj
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
@ -204,6 +218,9 @@ class StyledTextEditor(gtk.TextView):
self.textbuffer.apply_tag_by_name('hyperlink', start, end) self.textbuffer.apply_tag_by_name('hyperlink', start, end)
window.set_cursor(HAND_CURSOR) window.set_cursor(HAND_CURSOR)
self.url_match = match self.url_match = match
elif match and (match[MATCH_FLAVOR] in (LINK,)):
window.set_cursor(HAND_CURSOR)
self.url_match = match
else: else:
window.set_cursor(REGULAR_CURSOR) window.set_cursor(REGULAR_CURSOR)
self.url_match = None self.url_match = None
@ -256,15 +273,37 @@ class StyledTextEditor(gtk.TextView):
int(event.x), int(event.y)) int(event.x), int(event.y))
iter_at_location = self.get_iter_at_location(x, y) iter_at_location = self.get_iter_at_location(x, y)
self.match = self.textbuffer.match_check(iter_at_location.get_offset()) self.match = self.textbuffer.match_check(iter_at_location.get_offset())
tooltip = None
if not self.match:
for tag in (tag for tag in iter_at_location.get_tags()
if tag.get_property('name').startswith("link")):
self.match = (x, y, LINK, tag.data, tag)
tooltip = self.make_tooltip_from_link(tag)
break
if self.match != self.last_match: if self.match != self.last_match:
self.emit('match-changed', self.match) self.emit('match-changed', self.match)
self.last_match = self.match self.last_match = self.match
self.window.get_pointer() self.window.get_pointer()
self.set_tooltip_text(tooltip)
return False return False
def make_tooltip_from_link(self, link_tag):
"""
Return a string useful for a tooltip given a LinkTag object.
"""
from Simple import SimpleAccess
win_obj = find_parent_with_attr(self, attr="dbstate")
display = link_tag.data
if win_obj:
simple_access = SimpleAccess(win_obj.dbstate.db)
url = link_tag.data
if url.startswith("gramps://"):
obj_class, prop, value = url[9:].split("/")
display = simple_access.display(obj_class, prop, value) or url
return display
def on_button_release_event(self, widget, event): def on_button_release_event(self, widget, event):
""" """
Copy selection to clipboard for left click if selection given Copy selection to clipboard for left click if selection given
@ -325,6 +364,14 @@ class StyledTextEditor(gtk.TextView):
open_menu = gtk.MenuItem(_('_Open Link')) open_menu = gtk.MenuItem(_('_Open Link'))
copy_menu = gtk.MenuItem(_('Copy _Link Address')) copy_menu = gtk.MenuItem(_('Copy _Link Address'))
if flavor == LINK:
edit_menu = gtk.MenuItem(_('_Edit Link'))
edit_menu.connect('activate', self._edit_url_cb,
self.url_match[-1], # tag
)
edit_menu.show()
menu.prepend(edit_menu)
copy_menu.connect('activate', self._copy_url_cb, url, flavor) copy_menu.connect('activate', self._copy_url_cb, url, flavor)
copy_menu.show() copy_menu.show()
menu.prepend(copy_menu) menu.prepend(copy_menu)
@ -373,6 +420,8 @@ class StyledTextEditor(gtk.TextView):
_('Font Color'), self._on_action_activate), _('Font Color'), self._on_action_activate),
(str(StyledTextTagType.HIGHLIGHT), 'gramps-font-bgcolor', None, None, (str(StyledTextTagType.HIGHLIGHT), 'gramps-font-bgcolor', None, None,
_('Background Color'), self._on_action_activate), _('Background Color'), self._on_action_activate),
(str(StyledTextTagType.LINK), gtk.STOCK_JUMP_TO, None, None,
_('Link'), self._on_link_activate),
('clear', gtk.STOCK_CLEAR, None, None, ('clear', gtk.STOCK_CLEAR, None, None,
_('Clear Markup'), self._format_clear_cb), _('Clear Markup'), self._format_clear_cb),
] ]
@ -487,6 +536,34 @@ class StyledTextEditor(gtk.TextView):
_LOG.debug("applying style '%d' with value '%s'" % (style, str(value))) _LOG.debug("applying style '%d' with value '%s'" % (style, str(value)))
self.textbuffer.apply_style(style, value) self.textbuffer.apply_style(style, value)
def _on_link_activate(self, action):
"""
Create a link of a selected region of text.
"""
# Send in a default link. Could be based on active person.
selection_bounds = self.textbuffer.get_selection_bounds()
if selection_bounds:
uri_dialog(self, None, self.setlink_callback)
def setlink_callback(self, uri, tag=None):
"""
Callback for setting or editing a link's object.
"""
if uri:
_LOG.debug("applying style 'link' with value '%s'" % uri)
if not tag:
tag = LinkTag(self.textbuffer,
data=uri,
underline=UNDERLINE_SINGLE,
foreground="blue")
selection_bounds = self.textbuffer.get_selection_bounds()
self.textbuffer.apply_tag(tag,
selection_bounds[0],
selection_bounds[1])
else:
tag.data = uri
def _on_action_activate(self, action): def _on_action_activate(self, action):
"""Apply a format set from a gtk.Action type of action.""" """Apply a format set from a gtk.Action type of action."""
style = int(action.get_name()) style = int(action.get_name())
@ -533,15 +610,28 @@ class StyledTextEditor(gtk.TextView):
(text, StyledTextTagType.STYLE_TYPE[style])) (text, StyledTextTagType.STYLE_TYPE[style]))
def _format_clear_cb(self, action): def _format_clear_cb(self, action):
"""Remove all formats from the selection. """
Remove all formats from the selection or from all.
Remove only our own tags without touching other ones (e.g. gtk.Spell), Remove only our own tags without touching other ones (e.g. gtk.Spell),
thus remove_all_tags() can not be used. thus remove_all_tags() can not be used.
""" """
clear_anything = self.textbuffer.clear_selection()
if not clear_anything:
for style in ALLOWED_STYLES: for style in ALLOWED_STYLES:
self.textbuffer.remove_style(style) self.textbuffer.remove_style(style)
start, end = self.textbuffer.get_bounds()
tags = self.textbuffer._get_tag_from_range(start.get_offset(),
end.get_offset())
for tag_name, tag_data in tags.iteritems():
if tag_name.startswith("link"):
for start, end in tag_data:
self.textbuffer.remove_tag_by_name(tag_name,
self.textbuffer.get_iter_at_offset(start),
self.textbuffer.get_iter_at_offset(end+1))
def _on_buffer_style_changed(self, buffer, changed_styles): def _on_buffer_style_changed(self, buffer, changed_styles):
"""Synchronize actions as the format changes at the buffer's cursor.""" """Synchronize actions as the format changes at the buffer's cursor."""
for style, style_value in changed_styles.iteritems(): for style, style_value in changed_styles.iteritems():
@ -574,9 +664,23 @@ class StyledTextEditor(gtk.TextView):
url = 'mailto:' + url url = 'mailto:' + url
elif flavor == GENURL: elif flavor == GENURL:
pass pass
elif flavor == LINK:
# gramps://person/id/VALUE
# gramps://person/handle/VALUE
if url.startswith("gramps://"):
# if in a window:
win_obj = find_parent_with_attr(self, attr="dbstate")
if win_obj:
obj_class, prop, value = url[9:].split("/")
from gui.editors import EditObject
EditObject(win_obj.dbstate,
win_obj.uistate,
win_obj.track,
obj_class, prop, value)
return
else: else:
return return
# If ok, then let's open
display_url(url) display_url(url)
def _copy_url_cb(self, menuitem, url, flavor): def _copy_url_cb(self, menuitem, url, flavor):
@ -587,6 +691,14 @@ class StyledTextEditor(gtk.TextView):
clipboard = gtk.Clipboard(selection="PRIMARY") clipboard = gtk.Clipboard(selection="PRIMARY")
clipboard.set_text(url) clipboard.set_text(url)
def _edit_url_cb(self, menuitem, link_tag):
"""
Edit the URI of the link.
"""
uri_dialog(self, link_tag.data,
lambda uri: self.setlink_callback(uri, link_tag))
# public methods # public methods
def set_text(self, text): def set_text(self, text):
@ -617,6 +729,24 @@ class StyledTextEditor(gtk.TextView):
""" """
return self.toolbar return self.toolbar
def uri_dialog(self, uri, callback):
"""
Function to spawn the link editor.
"""
from gui.editors.editlink import EditLink
obj = find_parent_with_attr(self, attr="dbstate")
if obj:
if uri is None:
# make a default link
uri = "http://"
# Check in order for an open page:
for object_class in ["Person", "Place", "Event", "Family",
"Repository", "Source", "Media"]:
handle = obj.uistate.get_active(object_class)
if handle:
uri = "gramps://%s/handle/%s" % (object_class, handle)
EditLink(obj.dbstate, obj.uistate, obj.track, uri, callback)
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
# Module functions # Module functions

View File

@ -75,7 +75,9 @@ class HtmlBackend(DocBackend):
DocBackend.FONTSIZE, DocBackend.FONTSIZE,
DocBackend.FONTCOLOR, DocBackend.FONTCOLOR,
DocBackend.HIGHLIGHT, DocBackend.HIGHLIGHT,
DocBackend.SUPERSCRIPT ] DocBackend.SUPERSCRIPT,
DocBackend.LINK,
]
STYLETAG_MARKUP = { STYLETAG_MARKUP = {
DocBackend.BOLD : ("<strong>", "</strong>"), DocBackend.BOLD : ("<strong>", "</strong>"),
@ -85,7 +87,7 @@ class HtmlBackend(DocBackend):
DocBackend.SUPERSCRIPT : ("<sup>", "</sup>"), DocBackend.SUPERSCRIPT : ("<sup>", "</sup>"),
} }
ESCAPE_FUNC = lambda x: escape ESCAPE_FUNC = lambda self: escape
def __init__(self, filename=None): def __init__(self, filename=None):
""" """
@ -97,6 +99,7 @@ class HtmlBackend(DocBackend):
self.html_body = None self.html_body = None
self._subdir = None self._subdir = None
self.title = None self.title = None
self.build_link = None
def _create_xmltag(self, tagtype, value): def _create_xmltag(self, tagtype, value):
""" """
@ -111,7 +114,6 @@ class HtmlBackend(DocBackend):
elif tagtype == DocBackend.FONTFACE: elif tagtype == DocBackend.FONTFACE:
#fonts can have strange symbols in them, ' needs to be escaped #fonts can have strange symbols in them, ' needs to be escaped
value = value.replace("'", "\\'") value = value.replace("'", "\\'")
return ('<span style="%s">' % (self.STYLETAG_TO_PROPERTY[tagtype] % return ('<span style="%s">' % (self.STYLETAG_TO_PROPERTY[tagtype] %
(value)), (value)),
'</span>') '</span>')
@ -172,3 +174,22 @@ class HtmlBackend(DocBackend):
full path of the datadir directory full path of the datadir directory
""" """
return os.path.join(os.path.dirname(self.getf()), self.datadir()) return os.path.join(os.path.dirname(self.getf()), self.datadir())
def format_link(self, value):
"""
Override of base method.
"""
if value.startswith("gramps://"):
if self.build_link:
obj_class, prop, handle = value[9:].split("/", 3)
if prop in ["handle", "gramps_id"]:
value = self.build_link(prop, handle, obj_class, up=True)
if not value:
return self.STYLETAG_MARKUP[DocBackend.UNDERLINE]
else:
return self.STYLETAG_MARKUP[DocBackend.UNDERLINE]
else:
return self.STYLETAG_MARKUP[DocBackend.UNDERLINE]
return ('<a href="%s">' % self.ESCAPE_FUNC()(value),
'</a>')

View File

@ -81,7 +81,9 @@ class OdfBackend(DocBackend):
DocBackend.FONTSIZE, DocBackend.FONTSIZE,
DocBackend.FONTCOLOR, DocBackend.FONTCOLOR,
DocBackend.HIGHLIGHT, DocBackend.HIGHLIGHT,
DocBackend.SUPERSCRIPT ] DocBackend.SUPERSCRIPT,
DocBackend.LINK,
]
STYLETAG_MARKUP = { STYLETAG_MARKUP = {
DocBackend.BOLD : DocBackend.BOLD :
@ -129,3 +131,14 @@ class OdfBackend(DocBackend):
return ('<text:span text:style-name=\"FontHighlight__%s__\">' % return ('<text:span text:style-name=\"FontHighlight__%s__\">' %
self.ESCAPE_FUNC()(value), self.ESCAPE_FUNC()(value),
'</text:span>') '</text:span>')
def format_link(self, value):
"""
Override of base method.
"""
if value.startswith("gramps://"):
return self.STYLETAG_MARKUP[DocBackend.UNDERLINE]
else:
return ('<text:a xlink:href="%s">' % self.ESCAPE_FUNC()(value),
'</text:a>')

View File

@ -304,6 +304,7 @@ class BasePage(object):
self.up = False self.up = False
# class to do conversion of styled notes to html markup # class to do conversion of styled notes to html markup
self._backend = HtmlBackend() self._backend = HtmlBackend()
self._backend.build_link = report.build_link
self.report = report self.report = report
self.title_str = title self.title_str = title
@ -5557,6 +5558,41 @@ class NavWebReport(Report):
def build_url_fname_html(self, fname, subdir = None, up = False): def build_url_fname_html(self, fname, subdir = None, up = False):
return self.build_url_fname(fname, subdir, up) + self.ext return self.build_url_fname(fname, subdir, up) + self.ext
def build_link(self, prop, handle, obj_class, up = False):
"""
Build a link to an item.
"""
if prop == "gramps_id":
if obj_class in self.database.get_table_names():
obj = self.database.get_table_metadata(obj_class)["gramps_id_func"](handle)
if obj:
handle = obj.handle
else:
raise AttributeError("gramps_id '%s' not found in '%s'" %
handle, obj_class)
else:
raise AttributeError("invalid gramps_id lookup "
"in table name '%s'" % obj_class)
# handle, ppl
if obj_class == "Person":
if handle in self.person_handles:
return self.build_url_fname(handle, "ppl", up) + self.ext
else:
return None
elif obj_class == "Source":
subdir = "src"
elif obj_class == "Place":
subdir = "plc"
elif obj_class == "Event":
subdir = "evt"
elif obj_class == "Media":
subdir = "img"
elif obj_class == "Repository":
subdir = "repo"
else:
raise AttributeError("unknown object type '%s'" % obj_class)
return self.build_url_fname(handle, subdir, up) + self.ext
def build_url_fname(self, fname, subdir = None, up = False): def build_url_fname(self, fname, subdir = None, up = False):
""" """
Create part of the URL given the filename and optionally the subdirectory. Create part of the URL given the filename and optionally the subdirectory.