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/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

View File

@ -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):

View File

@ -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]

View File

@ -28,6 +28,7 @@ dist_pkgdata_DATA = \
editreporef.glade \
editpersonref.glade \
editlocation.glade \
editlink.glade \
editfamily.glade \
editchildref.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 \
editldsord.py \
editlocation.py \
editlink.py \
editmedia.py \
editmediaref.py \
editname.py \

View File

@ -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()))

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

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

View File

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

View File

@ -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 = '''
<toolitem action="%d"/>
<toolitem action="%d"/>
<toolitem action="%d"/>
<toolitem action="%d"/>
<toolitem action="spring"/>
<toolitem action="clear"/>
</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

View File

@ -75,7 +75,9 @@ class HtmlBackend(DocBackend):
DocBackend.FONTSIZE,
DocBackend.FONTCOLOR,
DocBackend.HIGHLIGHT,
DocBackend.SUPERSCRIPT ]
DocBackend.SUPERSCRIPT,
DocBackend.LINK,
]
STYLETAG_MARKUP = {
DocBackend.BOLD : ("<strong>", "</strong>"),
@ -85,7 +87,7 @@ class HtmlBackend(DocBackend):
DocBackend.SUPERSCRIPT : ("<sup>", "</sup>"),
}
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 ('<span style="%s">' % (self.STYLETAG_TO_PROPERTY[tagtype] %
(value)),
'</span>')
@ -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 ('<a href="%s">' % self.ESCAPE_FUNC()(value),
'</a>')

View File

@ -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 ('<text:span text:style-name=\"FontHighlight__%s__\">' %
self.ESCAPE_FUNC()(value),
'</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
# 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.