diff --git a/po/POTFILES.in b/po/POTFILES.in
index 6f88819a3..5f9501306 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -213,6 +213,7 @@ src/gui/editors/editeventref.py
src/gui/editors/editfamily.py
src/gui/editors/editldsord.py
src/gui/editors/editlocation.py
+src/gui/editors/editlink.py
src/gui/editors/editmedia.py
src/gui/editors/editmediaref.py
src/gui/editors/editname.py
@@ -839,6 +840,7 @@ src/glade/editrepository.glade
src/glade/editreporef.glade
src/glade/editpersonref.glade
src/glade/editlocation.glade
+src/glade/editlink.glade
src/glade/editfamily.glade
src/glade/editchildref.glade
src/glade/editattribute.glade
diff --git a/src/gen/lib/styledtexttagtype.py b/src/gen/lib/styledtexttagtype.py
index 0c033c37b..846812c39 100644
--- a/src/gen/lib/styledtexttagtype.py
+++ b/src/gen/lib/styledtexttagtype.py
@@ -56,6 +56,7 @@ class StyledTextTagType(GrampsType):
FONTCOLOR = 5
HIGHLIGHT = 6
SUPERSCRIPT = 7
+ LINK = 8
_CUSTOM = NONE_TYPE
_DEFAULT = NONE_TYPE
@@ -69,6 +70,7 @@ class StyledTextTagType(GrampsType):
(FONTCOLOR, _("Fontcolor"), "fontcolor"),
(HIGHLIGHT, _("Highlight"), "highlight"),
(SUPERSCRIPT, _("Superscript"), "superscript"),
+ (LINK, _("Link"), "link"),
]
STYLE_TYPE = {
@@ -80,6 +82,7 @@ class StyledTextTagType(GrampsType):
FONTFACE: str,
FONTSIZE: int,
SUPERSCRIPT: bool,
+ LINK: str,
}
STYLE_DEFAULT = {
@@ -91,6 +94,7 @@ class StyledTextTagType(GrampsType):
FONTFACE: 'Sans',
FONTSIZE: 10,
SUPERSCRIPT: False,
+ LINK: '',
}
def __init__(self, value=None):
diff --git a/src/gen/plug/docbackend/docbackend.py b/src/gen/plug/docbackend/docbackend.py
index bc7fa5e94..af511a409 100644
--- a/src/gen/plug/docbackend/docbackend.py
+++ b/src/gen/plug/docbackend/docbackend.py
@@ -99,6 +99,7 @@ class DocBackend(object):
FONTCOLOR = 5
HIGHLIGHT = 6
SUPERSCRIPT = 7
+ LINK = 8
SUPPORTED_MARKUP = []
@@ -115,6 +116,7 @@ class DocBackend(object):
ITALIC : ("", ""),
UNDERLINE : ("", ""),
SUPERSCRIPT : ("", ""),
+ LINK : ("", ""),
}
def __init__(self, filename=None):
@@ -216,7 +218,7 @@ class DocBackend(object):
if not self.STYLETYPE_MAP or \
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.UNDERLINE] = self.UNDERLINE
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.HIGHLIGHT] = self.HIGHLIGHT
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)
s_tagvalue = s_tag.value
tag_name = None
@@ -351,3 +356,13 @@ class DocBackend(object):
otext += opentag[1]
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]
+
diff --git a/src/glade/Makefile.am b/src/glade/Makefile.am
index 00afce26c..7943b977c 100644
--- a/src/glade/Makefile.am
+++ b/src/glade/Makefile.am
@@ -28,6 +28,7 @@ dist_pkgdata_DATA = \
editreporef.glade \
editpersonref.glade \
editlocation.glade \
+ editlink.glade \
editfamily.glade \
editchildref.glade \
editattribute.glade \
diff --git a/src/glade/editlink.glade b/src/glade/editlink.glade
new file mode 100644
index 000000000..2d6148cc0
--- /dev/null
+++ b/src/glade/editlink.glade
@@ -0,0 +1,212 @@
+
+
+
+
+
+
diff --git a/src/gui/editors/Makefile.am b/src/gui/editors/Makefile.am
index 0849cd24e..9dfb1d2dc 100644
--- a/src/gui/editors/Makefile.am
+++ b/src/gui/editors/Makefile.am
@@ -19,6 +19,7 @@ pkgdata_PYTHON = \
editfamily.py \
editldsord.py \
editlocation.py \
+ editlink.py \
editmedia.py \
editmediaref.py \
editname.py \
diff --git a/src/gui/editors/__init__.py b/src/gui/editors/__init__.py
index e3e5aa90a..9904dea7a 100644
--- a/src/gui/editors/__init__.py
+++ b/src/gui/editors/__init__.py
@@ -38,8 +38,9 @@ from editreporef import EditRepoRef
from editsource import EditSource, DeleteSrcQuery
from editsourceref import EditSourceRef
from editurl import EditUrl
+from editlink import EditLink
-# Map from gen.obj name to Editor:
+# Map from gen.lib name to Editor:
EDITORS = {
'Person': EditPerson,
'Event': EditEvent,
@@ -58,18 +59,23 @@ def EditObject(dbstate, uistate, track, obj_class, prop, value):
prop is 'handle' or '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 prop in ("gramps_id", "handle"):
obj = dbstate.db.get_table_metadata(obj_class)[prop + "_func"](value)
if obj:
- EDITORS[obj_class](dbstate, uistate, track, obj)
+ try:
+ EDITORS[obj_class](dbstate, uistate, track, obj)
+ except Exception as msg:
+ LOG.warn(str(msg))
else:
- raise AttributeError("gramps://%s/%s/%s not found" %
- (obj_class, prop, value))
+ LOG.warn("gramps://%s/%s/%s not found" %
+ (obj_class, prop, value))
else:
- raise AttributeError("unknown property to edit '%s'; "
- "should be 'gramps_id' or 'handle'" % prop)
+ LOG.warn("unknown property to edit '%s'; "
+ "should be 'gramps_id' or 'handle'" % prop)
else:
- raise AttributeError("unknown object to edit '%s'; "
- "should be one of %s" % (obj_class, EDITORS.keys()))
+ LOG.warn("unknown object to edit '%s'; "
+ "should be one of %s" % (obj_class, EDITORS.keys()))
diff --git a/src/gui/editors/editlink.py b/src/gui/editors/editlink.py
new file mode 100644
index 000000000..7e707c7b0
--- /dev/null
+++ b/src/gui/editors/editlink.py
@@ -0,0 +1,219 @@
+#
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2010 Doug Blank
+#
+# 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))
diff --git a/src/gui/selectors/selectperson.py b/src/gui/selectors/selectperson.py
index 29c6164d5..cd210d3a0 100644
--- a/src/gui/selectors/selectperson.py
+++ b/src/gui/selectors/selectperson.py
@@ -44,8 +44,8 @@ from baseselector import BaseSelector
#-------------------------------------------------------------------------
class SelectPerson(BaseSelector):
- def __init__(self, dbstate, uistate, track=[], title = None, filter = None,
- skip=set(), show_search_bar = False):
+ def __init__(self, dbstate, uistate, track=[], title=None, filter=None,
+ skip=set(), show_search_bar=False, default=None):
# SelectPerson may have a title passed to it which should be used
# instead of the default defined for get_window_title()
@@ -53,7 +53,7 @@ class SelectPerson(BaseSelector):
self.title = title
BaseSelector.__init__(self, dbstate, uistate, track, filter,
- skip, show_search_bar)
+ skip, show_search_bar, default)
def _local_init(self):
"""
diff --git a/src/gui/widgets/styledtextbuffer.py b/src/gui/widgets/styledtextbuffer.py
index 860d82862..43704c257 100644
--- a/src/gui/widgets/styledtextbuffer.py
+++ b/src/gui/widgets/styledtextbuffer.py
@@ -82,6 +82,25 @@ STYLE_TO_PROPERTY = {
MATCH_FLAVOR,
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
@@ -432,6 +451,29 @@ class StyledTextBuffer(gtk.TextBuffer):
self.remove_tag_by_name(tag_name,
self.get_iter_at_offset(start),
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):
"""Extract gtk.TextTags from buffer.
@@ -474,7 +516,9 @@ class StyledTextBuffer(gtk.TextBuffer):
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)
elif StyledTextTagType.STYLE_TYPE[style] == str:
tag_name = "%d %s" % (style, value)
@@ -507,7 +551,12 @@ class StyledTextBuffer(gtk.TextBuffer):
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 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)
if g_tag is not None:
for (start, end) in s_tag.ranges:
start_iter = self.get_iter_at_offset(start)
@@ -533,23 +582,30 @@ class StyledTextBuffer(gtk.TextBuffer):
s_tags = []
for g_tagname, g_ranges in g_tags.items():
- style_and_value = g_tagname.split(' ', 1)
+ 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)
- try:
- style = int(style_and_value[0])
- if len(style_and_value) == 1:
- s_value = None
- else:
- s_value = StyledTextTagType.STYLE_TYPE[style]\
- (style_and_value[1])
-
- if style in ALLOWED_STYLES:
- s_ranges = [(start, end+1) for (start, end) in g_ranges]
- s_tag = StyledTextTag(style, s_value, s_ranges)
-
- s_tags.append(s_tag)
- except ValueError:
- _LOG.debug("silently skipping gtk.TextTag '%s'" % g_tagname)
+ try:
+ style = int(style_and_value[0])
+ if len(style_and_value) == 1:
+ s_value = None
+ else:
+ s_value = StyledTextTagType.STYLE_TYPE[style]\
+ (style_and_value[1])
+
+ if style in ALLOWED_STYLES:
+ s_ranges = [(start, end+1) for (start, end) in g_ranges]
+ s_tag = StyledTextTag(style, s_value, s_ranges)
+
+ s_tags.append(s_tag)
+ except ValueError:
+ _LOG.debug("silently skipping gtk.TextTag '%s'" % g_tagname)
return StyledText(txt, s_tags)
diff --git a/src/gui/widgets/styledtexteditor.py b/src/gui/widgets/styledtexteditor.py
index 48d5d8db1..ddc7f87e3 100644
--- a/src/gui/widgets/styledtexteditor.py
+++ b/src/gui/widgets/styledtexteditor.py
@@ -50,8 +50,9 @@ from pango import UNDERLINE_SINGLE
#-------------------------------------------------------------------------
from gen.lib import StyledTextTagType
from gui.widgets.styledtextbuffer import (StyledTextBuffer, ALLOWED_STYLES,
- MATCH_START, MATCH_END,
- MATCH_FLAVOR, MATCH_STRING)
+ MATCH_START, MATCH_END,
+ MATCH_FLAVOR, MATCH_STRING,
+ LinkTag)
from gui.widgets.valueaction import ValueAction
from gui.widgets.toolcomboentry import ToolComboEntry
from gui.widgets.springseparator import SpringSeparatorAction
@@ -76,6 +77,7 @@ FORMAT_TOOLBAR = '''
+
@@ -87,6 +89,7 @@ FORMAT_TOOLBAR = '''
StyledTextTagType.FONTSIZE,
StyledTextTagType.FONTCOLOR,
StyledTextTagType.HIGHLIGHT,
+ StyledTextTagType.LINK,
)
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 + "]+)?"
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)
window.set_cursor(HAND_CURSOR)
self.url_match = match
+ elif match and (match[MATCH_FLAVOR] in (LINK,)):
+ window.set_cursor(HAND_CURSOR)
+ self.url_match = match
else:
window.set_cursor(REGULAR_CURSOR)
self.url_match = None
@@ -256,15 +273,37 @@ class StyledTextEditor(gtk.TextView):
int(event.x), int(event.y))
iter_at_location = self.get_iter_at_location(x, y)
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:
self.emit('match-changed', self.match)
self.last_match = self.match
-
self.window.get_pointer()
+ self.set_tooltip_text(tooltip)
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):
"""
Copy selection to clipboard for left click if selection given
@@ -325,6 +364,14 @@ class StyledTextEditor(gtk.TextView):
open_menu = gtk.MenuItem(_('_Open Link'))
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.show()
menu.prepend(copy_menu)
@@ -373,6 +420,8 @@ class StyledTextEditor(gtk.TextView):
_('Font Color'), self._on_action_activate),
(str(StyledTextTagType.HIGHLIGHT), 'gramps-font-bgcolor', None, None,
_('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 Markup'), self._format_clear_cb),
]
@@ -487,6 +536,34 @@ class StyledTextEditor(gtk.TextView):
_LOG.debug("applying style '%d' with value '%s'" % (style, str(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):
"""Apply a format set from a gtk.Action type of action."""
style = int(action.get_name())
@@ -533,14 +610,27 @@ class StyledTextEditor(gtk.TextView):
(text, StyledTextTagType.STYLE_TYPE[style]))
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),
thus remove_all_tags() can not be used.
"""
- for style in ALLOWED_STYLES:
- self.textbuffer.remove_style(style)
+ clear_anything = self.textbuffer.clear_selection()
+ if not clear_anything:
+ for style in ALLOWED_STYLES:
+ 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):
"""Synchronize actions as the format changes at the buffer's cursor."""
@@ -574,9 +664,23 @@ class StyledTextEditor(gtk.TextView):
url = 'mailto:' + url
elif flavor == GENURL:
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:
return
-
+ # If ok, then let's open
display_url(url)
def _copy_url_cb(self, menuitem, url, flavor):
@@ -586,6 +690,14 @@ class StyledTextEditor(gtk.TextView):
clipboard = gtk.Clipboard(selection="PRIMARY")
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
@@ -617,6 +729,24 @@ class StyledTextEditor(gtk.TextView):
"""
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
diff --git a/src/plugins/lib/libhtmlbackend.py b/src/plugins/lib/libhtmlbackend.py
index 44e122735..f3d926027 100644
--- a/src/plugins/lib/libhtmlbackend.py
+++ b/src/plugins/lib/libhtmlbackend.py
@@ -75,7 +75,9 @@ class HtmlBackend(DocBackend):
DocBackend.FONTSIZE,
DocBackend.FONTCOLOR,
DocBackend.HIGHLIGHT,
- DocBackend.SUPERSCRIPT ]
+ DocBackend.SUPERSCRIPT,
+ DocBackend.LINK,
+ ]
STYLETAG_MARKUP = {
DocBackend.BOLD : ("", ""),
@@ -85,7 +87,7 @@ class HtmlBackend(DocBackend):
DocBackend.SUPERSCRIPT : ("", ""),
}
- ESCAPE_FUNC = lambda x: escape
+ ESCAPE_FUNC = lambda self: escape
def __init__(self, filename=None):
"""
@@ -97,6 +99,7 @@ class HtmlBackend(DocBackend):
self.html_body = None
self._subdir = None
self.title = None
+ self.build_link = None
def _create_xmltag(self, tagtype, value):
"""
@@ -111,7 +114,6 @@ class HtmlBackend(DocBackend):
elif tagtype == DocBackend.FONTFACE:
#fonts can have strange symbols in them, ' needs to be escaped
value = value.replace("'", "\\'")
-
return ('' % (self.STYLETAG_TO_PROPERTY[tagtype] %
(value)),
'')
@@ -172,3 +174,22 @@ class HtmlBackend(DocBackend):
full path of the datadir directory
"""
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 ('' % self.ESCAPE_FUNC()(value),
+ '')
+
diff --git a/src/plugins/lib/libodfbackend.py b/src/plugins/lib/libodfbackend.py
index 8470e4958..6b98911c5 100644
--- a/src/plugins/lib/libodfbackend.py
+++ b/src/plugins/lib/libodfbackend.py
@@ -81,7 +81,9 @@ class OdfBackend(DocBackend):
DocBackend.FONTSIZE,
DocBackend.FONTCOLOR,
DocBackend.HIGHLIGHT,
- DocBackend.SUPERSCRIPT ]
+ DocBackend.SUPERSCRIPT,
+ DocBackend.LINK,
+ ]
STYLETAG_MARKUP = {
DocBackend.BOLD :
@@ -129,3 +131,14 @@ class OdfBackend(DocBackend):
return ('' %
self.ESCAPE_FUNC()(value),
'')
+
+ def format_link(self, value):
+ """
+ Override of base method.
+ """
+ if value.startswith("gramps://"):
+ return self.STYLETAG_MARKUP[DocBackend.UNDERLINE]
+ else:
+ return ('' % self.ESCAPE_FUNC()(value),
+ '')
+
diff --git a/src/plugins/webreport/NarrativeWeb.py b/src/plugins/webreport/NarrativeWeb.py
index 1e3c4eafa..90d6e16bc 100644
--- a/src/plugins/webreport/NarrativeWeb.py
+++ b/src/plugins/webreport/NarrativeWeb.py
@@ -304,6 +304,7 @@ class BasePage(object):
self.up = False
# class to do conversion of styled notes to html markup
self._backend = HtmlBackend()
+ self._backend.build_link = report.build_link
self.report = report
self.title_str = title
@@ -5557,6 +5558,41 @@ class NavWebReport(Report):
def build_url_fname_html(self, fname, subdir = None, up = False):
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):
"""
Create part of the URL given the filename and optionally the subdirectory.