Introducing StyledText in Notes.
svn: r10410
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
#
|
#
|
||||||
# Gramps - a GTK+/GNOME based genealogy program
|
# Gramps - a GTK+/GNOME based genealogy program
|
||||||
#
|
#
|
||||||
# Copyright (C) 2000-2006 Donald N. Allingham
|
# Copyright (C) 2000-2007 Donald N. Allingham
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
import logging
|
import logging
|
||||||
log = logging.getLogger(".")
|
_LOG = logging.getLogger(".DisplayModels.NoteModel")
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@@ -40,20 +40,19 @@ import gtk
|
|||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
from _BaseModel import BaseModel
|
from _BaseModel import BaseModel
|
||||||
import gen.lib
|
from gen.lib import (Note, NoteType, MarkerType, StyledText)
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# PlaceModel
|
# NoteModel
|
||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
class NoteModel(BaseModel):
|
class NoteModel(BaseModel):
|
||||||
|
"""
|
||||||
HANDLE_COL = 4
|
"""
|
||||||
_MARKER_COL = 6
|
def __init__(self, db, scol=0, order=gtk.SORT_ASCENDING, search=None,
|
||||||
|
|
||||||
def __init__(self,db,scol=0, order=gtk.SORT_ASCENDING,search=None,
|
|
||||||
skip=set(), sort_map=None):
|
skip=set(), sort_map=None):
|
||||||
|
"""Setup initial values for instance variables."""
|
||||||
self.gen_cursor = db.get_note_cursor
|
self.gen_cursor = db.get_note_cursor
|
||||||
self.map = db.get_raw_note_data
|
self.map = db.get_raw_note_data
|
||||||
self.fmap = [
|
self.fmap = [
|
||||||
@@ -63,7 +62,7 @@ class NoteModel(BaseModel):
|
|||||||
self.column_marker,
|
self.column_marker,
|
||||||
self.column_handle,
|
self.column_handle,
|
||||||
self.column_marker_color
|
self.column_marker_color
|
||||||
]
|
]
|
||||||
self.smap = [
|
self.smap = [
|
||||||
self.column_preview,
|
self.column_preview,
|
||||||
self.column_id,
|
self.column_id,
|
||||||
@@ -71,48 +70,57 @@ class NoteModel(BaseModel):
|
|||||||
self.column_marker,
|
self.column_marker,
|
||||||
self.column_handle,
|
self.column_handle,
|
||||||
self.column_marker_color
|
self.column_marker_color
|
||||||
]
|
]
|
||||||
self.marker_color_column = 5
|
self.marker_color_column = 5
|
||||||
BaseModel.__init__(self, db, scol, order,
|
BaseModel.__init__(self, db, scol, order, search=search,
|
||||||
search=search, skip=skip, sort_map=sort_map)
|
skip=skip, sort_map=sort_map)
|
||||||
|
|
||||||
def on_get_n_columns(self):
|
def on_get_n_columns(self):
|
||||||
return len(self.fmap)+1
|
"""Return the column number of the Note tab."""
|
||||||
|
return len(self.fmap) + 1
|
||||||
|
|
||||||
def column_handle(self,data):
|
def column_handle(self, data):
|
||||||
return data[0]
|
"""Return the handle of the Note."""
|
||||||
|
return data[Note.POS_HANDLE]
|
||||||
|
|
||||||
def column_id(self,data):
|
def column_id(self, data):
|
||||||
return unicode(data[1])
|
"""Return the id of the Note."""
|
||||||
|
return unicode(data[Note.POS_ID])
|
||||||
|
|
||||||
def column_type(self,data):
|
def column_type(self, data):
|
||||||
temp = gen.lib.NoteType()
|
"""Return the type of the Note in readable format."""
|
||||||
temp.set(data[4])
|
temp = NoteType()
|
||||||
|
temp.set(data[Note.POS_TYPE])
|
||||||
return unicode(str(temp))
|
return unicode(str(temp))
|
||||||
|
|
||||||
def column_marker(self, data):
|
def column_marker(self, data):
|
||||||
temp = gen.lib.MarkerType()
|
"""Return the marker type of the Note in readable format."""
|
||||||
temp.set(data[6])
|
temp = MarkerType()
|
||||||
|
temp.set(data[Note.POS_MARKER])
|
||||||
return unicode(str(temp))
|
return unicode(str(temp))
|
||||||
|
|
||||||
def column_preview(self,data):
|
def column_preview(self, data):
|
||||||
|
"""Return a shortend version of the Note's text."""
|
||||||
#data is the encoding in the database, make it a unicode object
|
#data is the encoding in the database, make it a unicode object
|
||||||
#for universal work
|
#for universal work
|
||||||
note = " ".join(unicode(data[2]).split())
|
note = unicode(data[Note.POS_TEXT][StyledText.POS_TEXT])
|
||||||
|
note = " ".join(note.split())
|
||||||
if len(note) > 80:
|
if len(note) > 80:
|
||||||
return note[:80]+"..."
|
return note[:80] + "..."
|
||||||
else:
|
else:
|
||||||
return note
|
return note
|
||||||
|
|
||||||
def column_marker_color(self, data):
|
def column_marker_color(self, data):
|
||||||
|
"""Return the color of the Note's marker type if exist."""
|
||||||
try:
|
try:
|
||||||
col = data[NoteModel._MARKER_COL][0]
|
col = data[Note.POS_MARKER][MarkerType.POS_VALUE]
|
||||||
if col == gen.lib.MarkerType.COMPLETE:
|
if col == MarkerType.COMPLETE:
|
||||||
return self.complete_color
|
return self.complete_color
|
||||||
elif col == gen.lib.MarkerType.TODO_TYPE:
|
elif col == MarkerType.TODO_TYPE:
|
||||||
return self.todo_color
|
return self.todo_color
|
||||||
elif col == gen.lib.MarkerType.CUSTOM:
|
elif col == MarkerType.CUSTOM:
|
||||||
return self.custom_color
|
return self.custom_color
|
||||||
|
else:
|
||||||
|
return None
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
return None
|
||||||
return None
|
|
||||||
|
@@ -28,7 +28,8 @@
|
|||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
log = logging.getLogger(".")
|
_LOG = logging.getLogger(".Editors.EditNote")
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# GTK libraries
|
# GTK libraries
|
||||||
@@ -41,19 +42,20 @@ import pango
|
|||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# GRAMPS classes
|
# GRAMPS modules
|
||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
import const
|
|
||||||
import Spell
|
|
||||||
import Config
|
import Config
|
||||||
import GrampsDisplay
|
from const import GLADE_FILE
|
||||||
import MarkupText
|
from Spell import Spell
|
||||||
|
from GrampsDisplay import url
|
||||||
|
from Editors._StyledTextBuffer import (StyledTextBuffer, MATCH_START,
|
||||||
|
MATCH_END, MATCH_FLAVOR, MATCH_STRING)
|
||||||
from Editors._EditPrimary import EditPrimary
|
from Editors._EditPrimary import EditPrimary
|
||||||
from DisplayTabs import GrampsTab, NoteBackRefList
|
from DisplayTabs import GrampsTab, NoteBackRefList
|
||||||
from GrampsWidgets import (MonitoredDataType, MonitoredCheckbox,
|
from GrampsWidgets import (MonitoredDataType, MonitoredCheckbox,
|
||||||
MonitoredEntry, PrivacyButton)
|
MonitoredEntry, PrivacyButton)
|
||||||
import gen.lib
|
from gen.lib import Note
|
||||||
from QuestionDialog import ErrorDialog
|
from QuestionDialog import ErrorDialog
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
@@ -61,17 +63,16 @@ from QuestionDialog import ErrorDialog
|
|||||||
# Constants
|
# Constants
|
||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#USERCHARS = "-A-Za-z0-9"
|
USERCHARS = "-A-Za-z0-9"
|
||||||
#PASSCHARS = "-A-Za-z0-9,?;.:/!%$^*&~\"#'"
|
PASSCHARS = "-A-Za-z0-9,?;.:/!%$^*&~\"#'"
|
||||||
#HOSTCHARS = "-A-Za-z0-9"
|
HOSTCHARS = "-A-Za-z0-9"
|
||||||
#PATHCHARS = "-A-Za-z0-9_$.+!*(),;:@&=?/~#%"
|
PATHCHARS = "-A-Za-z0-9_$.+!*(),;:@&=?/~#%"
|
||||||
##SCHEME = "(news:|telnet:|nntp:|file:/|https?:|ftps?:|webcal:)"
|
#SCHEME = "(news:|telnet:|nntp:|file:/|https?:|ftps?:|webcal:)"
|
||||||
#SCHEME = "(file:/|https?:|ftps?:|webcal:)"
|
SCHEME = "(file:/|https?:|ftps?:|webcal:)"
|
||||||
#USER = "[" + USERCHARS + "]+(:[" + PASSCHARS + "]+)?"
|
USER = "[" + USERCHARS + "]+(:[" + PASSCHARS + "]+)?"
|
||||||
#URLPATH = "/[" + PATHCHARS + "]*[^]'.}>) \t\r\n,\\\"]"
|
URLPATH = "/[" + PATHCHARS + "]*[^]'.}>) \t\r\n,\\\"]"
|
||||||
#
|
|
||||||
#(GENERAL, HTTP, MAIL) = range(3)
|
|
||||||
|
|
||||||
|
(GENERAL, HTTP, MAIL) = range(3)
|
||||||
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
@@ -91,17 +92,17 @@ class NoteTab(GrampsTab):
|
|||||||
the database, along with other state information. The GrampsTab
|
the database, along with other state information. The GrampsTab
|
||||||
uses this to access the database and to pass to and created
|
uses this to access the database and to pass to and created
|
||||||
child windows (such as edit dialogs).
|
child windows (such as edit dialogs).
|
||||||
@type dbstate: DbState
|
@type dbstate: L{DbState.DbState}
|
||||||
@param uistate: The UI state. Used primarily to pass to any created
|
@param uistate: The UI state. Used primarily to pass to any created
|
||||||
subwindows.
|
subwindows.
|
||||||
@type uistate: DisplayState
|
@type uistate: L{DisplayState.DisplayState}
|
||||||
@param track: The window tracking mechanism used to manage windows.
|
@param track: The window tracking mechanism used to manage windows.
|
||||||
This is only used to pass to generted child windows.
|
This is only used to pass to generted child windows.
|
||||||
@type track: list
|
@type track: list
|
||||||
@param name: Notebook label name
|
@param name: Notebook label name
|
||||||
@type name: str/unicode
|
@type name: str/unicode
|
||||||
@param widget: widget to be shown in the tab
|
@param widget: widget to be shown in the tab
|
||||||
@type widge: gtk widget
|
@type widget: gtk widget
|
||||||
"""
|
"""
|
||||||
GrampsTab.__init__(self, dbstate, uistate, track, name)
|
GrampsTab.__init__(self, dbstate, uistate, track, name)
|
||||||
eventbox = gtk.EventBox()
|
eventbox = gtk.EventBox()
|
||||||
@@ -131,11 +132,12 @@ class EditNote(EditPrimary):
|
|||||||
callertitle = None, extratype = None):
|
callertitle = None, extratype = None):
|
||||||
"""Create an EditNote window. Associate a note with the window.
|
"""Create an EditNote window. Associate a note with the window.
|
||||||
|
|
||||||
@param callertitle: a text passed by calling object to add to title
|
@param callertitle: Text passed by calling object to add to title
|
||||||
@type callertitle: str
|
@type callertitle: str
|
||||||
@param extratype: extra NoteType values to add to the default types
|
@param extratype: Extra L{NoteType} values to add to the default types.
|
||||||
They are removed from the ignorelist of NoteType.
|
They are removed from the ignorelist of L{NoteType}.
|
||||||
@type extratype: list of int
|
@type extratype: list of int
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.callertitle = callertitle
|
self.callertitle = callertitle
|
||||||
self.extratype = extratype
|
self.extratype = extratype
|
||||||
@@ -146,9 +148,10 @@ class EditNote(EditPrimary):
|
|||||||
def empty_object(self):
|
def empty_object(self):
|
||||||
"""Return an empty Note object for comparison for changes.
|
"""Return an empty Note object for comparison for changes.
|
||||||
|
|
||||||
It is used by the base class (EditPrimary).
|
It is used by the base class L{EditPrimary}.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
empty_note = gen.lib.Note();
|
empty_note = Note();
|
||||||
if self.extratype:
|
if self.extratype:
|
||||||
empty_note.set_type(self.extratype[0])
|
empty_note.set_type(self.extratype[0])
|
||||||
return empty_note
|
return empty_note
|
||||||
@@ -157,16 +160,16 @@ class EditNote(EditPrimary):
|
|||||||
if self.obj.get_handle():
|
if self.obj.get_handle():
|
||||||
if self.callertitle :
|
if self.callertitle :
|
||||||
title = _('Note: %(id)s - %(context)s') % {
|
title = _('Note: %(id)s - %(context)s') % {
|
||||||
'id' : self.obj.get_gramps_id(),
|
'id' : self.obj.get_gramps_id(),
|
||||||
'context' : self.callertitle
|
'context' : self.callertitle
|
||||||
}
|
}
|
||||||
else :
|
else :
|
||||||
title = _('Note: %s') % self.obj.get_gramps_id()
|
title = _('Note: %s') % self.obj.get_gramps_id()
|
||||||
else:
|
else:
|
||||||
if self.callertitle :
|
if self.callertitle :
|
||||||
title = _('New Note - %(context)s') % {
|
title = _('New Note - %(context)s') % {
|
||||||
'context' : self.callertitle
|
'context' : self.callertitle
|
||||||
}
|
}
|
||||||
else :
|
else :
|
||||||
title = _('New Note')
|
title = _('New Note')
|
||||||
|
|
||||||
@@ -179,11 +182,11 @@ class EditNote(EditPrimary):
|
|||||||
"""Local initialization function.
|
"""Local initialization function.
|
||||||
|
|
||||||
Perform basic initialization, including setting up widgets
|
Perform basic initialization, including setting up widgets
|
||||||
and the glade interface. It is called by the base class (EditPrimary),
|
and the glade interface. It is called by the base class L{EditPrimary},
|
||||||
and overridden here.
|
and overridden here.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.top = glade.XML(const.GLADE_FILE, "edit_note", "gramps")
|
self.top = glade.XML(GLADE_FILE, "edit_note", "gramps")
|
||||||
win = self.top.get_widget("edit_note")
|
win = self.top.get_widget("edit_note")
|
||||||
self.set_window(win, None, self.get_menu_title())
|
self.set_window(win, None, self.get_menu_title())
|
||||||
|
|
||||||
@@ -242,7 +245,7 @@ class EditNote(EditPrimary):
|
|||||||
def _connect_signals(self):
|
def _connect_signals(self):
|
||||||
"""Connects any signals that need to be connected.
|
"""Connects any signals that need to be connected.
|
||||||
|
|
||||||
Called by the init routine of the base class (_EditPrimary).
|
Called by the init routine of the base class L{EditPrimary}.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.define_ok_button(self.top.get_widget('ok'), self.save)
|
self.define_ok_button(self.top.get_widget('ok'), self.save)
|
||||||
@@ -250,122 +253,68 @@ class EditNote(EditPrimary):
|
|||||||
self.define_help_button(self.top.get_widget('help'), '')
|
self.define_help_button(self.top.get_widget('help'), '')
|
||||||
|
|
||||||
def _create_tabbed_pages(self):
|
def _create_tabbed_pages(self):
|
||||||
"""
|
"""Create the notebook tabs and inserts them into the main window."""
|
||||||
Create the notebook tabs and inserts them into the main
|
|
||||||
window.
|
|
||||||
"""
|
|
||||||
notebook = self.top.get_widget("note_notebook")
|
notebook = self.top.get_widget("note_notebook")
|
||||||
|
|
||||||
self._add_tab(notebook, self.ntab)
|
self._add_tab(notebook, self.ntab)
|
||||||
|
|
||||||
self.backref_tab = self._add_tab(
|
handles = self.dbstate.db.find_backlink_handles(self.obj.handle)
|
||||||
notebook,
|
rlist = NoteBackRefList(self.dbstate, self.uistate, self.track, handles)
|
||||||
NoteBackRefList(self.dbstate, self.uistate, self.track,
|
self.backref_tab = self._add_tab(notebook, rlist)
|
||||||
self.dbstate.db.find_backlink_handles(
|
|
||||||
self.obj.handle))
|
|
||||||
)
|
|
||||||
|
|
||||||
self._setup_notebook_tabs( notebook)
|
self._setup_notebook_tabs(notebook)
|
||||||
|
|
||||||
# THIS IS THE MARKUP VERSION - enable for markup
|
# THIS IS THE MARKUP VERSION - enable for markup
|
||||||
# def build_interface(self):
|
|
||||||
# FORMAT_TOOLBAR = '''
|
|
||||||
# <ui>
|
|
||||||
# <toolbar name="ToolBar">
|
|
||||||
# <toolitem action="italic"/>
|
|
||||||
# <toolitem action="bold"/>
|
|
||||||
# <toolitem action="underline"/>
|
|
||||||
# <separator/>
|
|
||||||
# <toolitem action="font"/>
|
|
||||||
# <toolitem action="foreground"/>
|
|
||||||
# <toolitem action="background"/>
|
|
||||||
# <separator/>
|
|
||||||
# <toolitem action="clear"/>
|
|
||||||
# </toolbar>
|
|
||||||
# </ui>
|
|
||||||
# '''
|
|
||||||
#
|
|
||||||
# buffer_ = MarkupText.MarkupBuffer()
|
|
||||||
# buffer_.create_tag('hyperlink',
|
|
||||||
# underline=pango.UNDERLINE_SINGLE,
|
|
||||||
# foreground='blue')
|
|
||||||
# buffer_.match_add("(www|ftp)[" + HOSTCHARS + "]*\\.[" + HOSTCHARS +
|
|
||||||
# ".]+" + "(:[0-9]+)?(" + URLPATH + ")?/?", HTTP)
|
|
||||||
# buffer_.match_add("(mailto:)?[a-z0-9][a-z0-9.-]*@[a-z0-9][a-z0-9-]*"
|
|
||||||
# "(\\.[a-z0-9][a-z0-9-]*)+", MAIL)
|
|
||||||
# buffer_.match_add(SCHEME + "//(" + USER + "@)?[" + HOSTCHARS + ".]+" +
|
|
||||||
# "(:[0-9]+)?(" + URLPATH + ")?/?", GENERAL)
|
|
||||||
# self.match = None
|
|
||||||
# self.last_match = None
|
|
||||||
#
|
|
||||||
# self.text = self.top.get_widget('text')
|
|
||||||
# self.text.set_editable(not self.dbstate.db.readonly)
|
|
||||||
# self.text.set_buffer(buffer_)
|
|
||||||
# self.text.connect('key-press-event',
|
|
||||||
# self.on_textview_key_press_event)
|
|
||||||
# self.text.connect('insert-at-cursor',
|
|
||||||
# self.on_textview_insert_at_cursor)
|
|
||||||
# self.text.connect('delete-from-cursor',
|
|
||||||
# self.on_textview_delete_from_cursor)
|
|
||||||
# self.text.connect('paste-clipboard',
|
|
||||||
# self.on_textview_paste_clipboard)
|
|
||||||
# self.text.connect('motion-notify-event',
|
|
||||||
# self.on_textview_motion_notify_event)
|
|
||||||
# self.text.connect('button-press-event',
|
|
||||||
# self.on_textview_button_press_event)
|
|
||||||
# self.text.connect('populate-popup',
|
|
||||||
# self.on_textview_populate_popup)
|
|
||||||
#
|
|
||||||
# # setup spell checking interface
|
|
||||||
# spellcheck = Spell.Spell(self.text)
|
|
||||||
# liststore = gtk.ListStore(gobject.TYPE_STRING)
|
|
||||||
# cell = gtk.CellRendererText()
|
|
||||||
# lang_selector = self.top.get_widget('spell')
|
|
||||||
# lang_selector.set_model(liststore)
|
|
||||||
# lang_selector.pack_start(cell, True)
|
|
||||||
# lang_selector.add_attribute(cell, 'text', 0)
|
|
||||||
# act_lang = spellcheck.get_active_language()
|
|
||||||
# idx = 0
|
|
||||||
# for lang in spellcheck.get_all_languages():
|
|
||||||
# lang_selector.append_text(lang)
|
|
||||||
# if lang == act_lang:
|
|
||||||
# act_idx = idx
|
|
||||||
# idx = idx + 1
|
|
||||||
# lang_selector.set_active(act_idx)
|
|
||||||
# lang_selector.connect('changed', self.on_spell_change, spellcheck)
|
|
||||||
# #lang_selector.set_sensitive(Config.get(Config.SPELLCHECK))
|
|
||||||
#
|
|
||||||
# # create a formatting toolbar
|
|
||||||
# if not self.dbstate.db.readonly:
|
|
||||||
# uimanager = gtk.UIManager()
|
|
||||||
# uimanager.insert_action_group(buffer_.format_action_group, 0)
|
|
||||||
# uimanager.add_ui_from_string(FORMAT_TOOLBAR)
|
|
||||||
# uimanager.ensure_update()
|
|
||||||
#
|
|
||||||
# toolbar = uimanager.get_widget('/ToolBar')
|
|
||||||
# toolbar.set_style(gtk.TOOLBAR_ICONS)
|
|
||||||
# vbox = self.top.get_widget('container')
|
|
||||||
# vbox.pack_start(toolbar)
|
|
||||||
#
|
|
||||||
# # setup initial values for textview and buffer_
|
|
||||||
# if self.obj:
|
|
||||||
# self.empty = False
|
|
||||||
# self.flow_changed(self.obj.get_format())
|
|
||||||
# buffer_.set_text(self.obj.get(markup=True))
|
|
||||||
# log.debug("Initial Note: %s" % buffer_.get_text())
|
|
||||||
# else:
|
|
||||||
# self.empty = True
|
|
||||||
|
|
||||||
# NON-MARKUP VERSION - Disable for markup
|
|
||||||
def build_interface(self):
|
def build_interface(self):
|
||||||
buffer_ = gtk.TextBuffer()
|
FORMAT_TOOLBAR = '''
|
||||||
|
<ui>
|
||||||
|
<toolbar name="ToolBar">
|
||||||
|
<toolitem action="italic"/>
|
||||||
|
<toolitem action="bold"/>
|
||||||
|
<toolitem action="underline"/>
|
||||||
|
<separator/>
|
||||||
|
<toolitem action="font"/>
|
||||||
|
<toolitem action="foreground"/>
|
||||||
|
<toolitem action="background"/>
|
||||||
|
<separator/>
|
||||||
|
<toolitem action="clear"/>
|
||||||
|
</toolbar>
|
||||||
|
</ui>
|
||||||
|
'''
|
||||||
|
|
||||||
|
textbuffer = StyledTextBuffer()
|
||||||
|
textbuffer.create_tag('hyperlink',
|
||||||
|
underline=pango.UNDERLINE_SINGLE,
|
||||||
|
foreground='blue')
|
||||||
|
textbuffer.match_add("(www|ftp)[" + HOSTCHARS + "]*\\.[" + HOSTCHARS +
|
||||||
|
".]+" + "(:[0-9]+)?(" + URLPATH + ")?/?", HTTP)
|
||||||
|
textbuffer.match_add("(mailto:)?[a-z0-9][a-z0-9.-]*@[a-z0-9][a-z0-9-]*"
|
||||||
|
"(\\.[a-z0-9][a-z0-9-]*)+", MAIL)
|
||||||
|
textbuffer.match_add(SCHEME + "//(" + USER + "@)?[" + HOSTCHARS +
|
||||||
|
".]+" + "(:[0-9]+)?(" + URLPATH + ")?/?", GENERAL)
|
||||||
|
self.match = None
|
||||||
|
self.last_match = None
|
||||||
|
|
||||||
self.text = self.top.get_widget('text')
|
self.text = self.top.get_widget('text')
|
||||||
self.text.set_editable(not self.dbstate.db.readonly)
|
self.text.set_editable(not self.dbstate.db.readonly)
|
||||||
self.text.set_buffer(buffer_)
|
self.text.set_buffer(textbuffer)
|
||||||
|
self.text.connect('key-press-event',
|
||||||
|
self.on_textview_key_press_event)
|
||||||
|
self.text.connect('insert-at-cursor',
|
||||||
|
self.on_textview_insert_at_cursor)
|
||||||
|
self.text.connect('delete-from-cursor',
|
||||||
|
self.on_textview_delete_from_cursor)
|
||||||
|
self.text.connect('paste-clipboard',
|
||||||
|
self.on_textview_paste_clipboard)
|
||||||
|
self.text.connect('motion-notify-event',
|
||||||
|
self.on_textview_motion_notify_event)
|
||||||
|
self.text.connect('button-press-event',
|
||||||
|
self.on_textview_button_press_event)
|
||||||
|
self.text.connect('populate-popup',
|
||||||
|
self.on_textview_populate_popup)
|
||||||
|
|
||||||
# setup spell checking interface
|
# setup spell checking interface
|
||||||
spellcheck = Spell.Spell(self.text)
|
spellcheck = Spell(self.text)
|
||||||
liststore = gtk.ListStore(gobject.TYPE_STRING)
|
liststore = gtk.ListStore(gobject.TYPE_STRING)
|
||||||
cell = gtk.CellRendererText()
|
cell = gtk.CellRendererText()
|
||||||
lang_selector = self.top.get_widget('spell')
|
lang_selector = self.top.get_widget('spell')
|
||||||
@@ -382,14 +331,62 @@ class EditNote(EditPrimary):
|
|||||||
lang_selector.set_active(act_idx)
|
lang_selector.set_active(act_idx)
|
||||||
lang_selector.connect('changed', self.on_spell_change, spellcheck)
|
lang_selector.connect('changed', self.on_spell_change, spellcheck)
|
||||||
#lang_selector.set_sensitive(Config.get(Config.SPELLCHECK))
|
#lang_selector.set_sensitive(Config.get(Config.SPELLCHECK))
|
||||||
|
|
||||||
|
# create a formatting toolbar
|
||||||
|
if not self.dbstate.db.readonly:
|
||||||
|
uimanager = gtk.UIManager()
|
||||||
|
uimanager.insert_action_group(textbuffer.format_action_group, 0)
|
||||||
|
uimanager.add_ui_from_string(FORMAT_TOOLBAR)
|
||||||
|
uimanager.ensure_update()
|
||||||
|
|
||||||
|
toolbar = uimanager.get_widget('/ToolBar')
|
||||||
|
toolbar.set_style(gtk.TOOLBAR_ICONS)
|
||||||
|
vbox = self.top.get_widget('container')
|
||||||
|
vbox.pack_start(toolbar)
|
||||||
|
|
||||||
# setup initial values for textview and buffer_
|
# setup initial values for textview and textbuffer
|
||||||
if self.obj:
|
if self.obj:
|
||||||
self.empty = False
|
self.empty = False
|
||||||
self.flow_changed(self.obj.get_format())
|
self.flow_changed(self.obj.get_format())
|
||||||
buffer_.set_text(self.obj.get())
|
textbuffer.set_text(self.obj.get_styledtext())
|
||||||
|
_LOG.debug("Initial Note: %s" % str(textbuffer.get_text()))
|
||||||
else:
|
else:
|
||||||
self.empty = True
|
self.empty = True
|
||||||
|
|
||||||
|
# NON-MARKUP VERSION - Disable for markup
|
||||||
|
#def build_interface(self):
|
||||||
|
#textbuffer = gtk.TextBuffer()
|
||||||
|
|
||||||
|
#self.text = self.top.get_widget('text')
|
||||||
|
#self.text.set_editable(not self.dbstate.db.readonly)
|
||||||
|
#self.text.set_buffer(textbuffer)
|
||||||
|
|
||||||
|
## setup spell checking interface
|
||||||
|
#spellcheck = Spell(self.text)
|
||||||
|
#liststore = gtk.ListStore(gobject.TYPE_STRING)
|
||||||
|
#cell = gtk.CellRendererText()
|
||||||
|
#lang_selector = self.top.get_widget('spell')
|
||||||
|
#lang_selector.set_model(liststore)
|
||||||
|
#lang_selector.pack_start(cell, True)
|
||||||
|
#lang_selector.add_attribute(cell, 'text', 0)
|
||||||
|
#act_lang = spellcheck.get_active_language()
|
||||||
|
#idx = 0
|
||||||
|
#for lang in spellcheck.get_all_languages():
|
||||||
|
#lang_selector.append_text(lang)
|
||||||
|
#if lang == act_lang:
|
||||||
|
#act_idx = idx
|
||||||
|
#idx = idx + 1
|
||||||
|
#lang_selector.set_active(act_idx)
|
||||||
|
#lang_selector.connect('changed', self.on_spell_change, spellcheck)
|
||||||
|
##lang_selector.set_sensitive(Config.get(Config.SPELLCHECK))
|
||||||
|
|
||||||
|
## setup initial values for textview and textbuffer
|
||||||
|
#if self.obj:
|
||||||
|
#self.empty = False
|
||||||
|
#self.flow_changed(self.obj.get_format())
|
||||||
|
#textbuffer.set_text(self.obj.get())
|
||||||
|
#else:
|
||||||
|
#self.empty = True
|
||||||
|
|
||||||
def build_menu_names(self, person):
|
def build_menu_names(self, person):
|
||||||
"""
|
"""
|
||||||
@@ -401,119 +398,116 @@ class EditNote(EditPrimary):
|
|||||||
def _post_init(self):
|
def _post_init(self):
|
||||||
self.text.grab_focus()
|
self.text.grab_focus()
|
||||||
|
|
||||||
# enable for markup
|
def on_textview_key_press_event(self, textview, event):
|
||||||
# def on_textview_key_press_event(self, textview, event):
|
"""Handle shortcuts in the TextView."""
|
||||||
# """Handle shortcuts in the TextView."""
|
return textview.get_buffer().on_key_press_event(textview, event)
|
||||||
# return textview.get_buffer().on_key_press_event(textview, event)
|
|
||||||
#
|
def on_textview_insert_at_cursor(self, textview, string):
|
||||||
# def on_textview_insert_at_cursor(self, textview, string):
|
_LOG.debug("Textview insert '%s'" % string)
|
||||||
# log.debug("Textview insert '%s'" % string)
|
|
||||||
#
|
def on_textview_delete_from_cursor(self, textview, type, count):
|
||||||
# def on_textview_delete_from_cursor(self, textview, type, count):
|
_LOG.debug("Textview delete type %d count %d" % (type, count))
|
||||||
# log.debug("Textview delete type %d count %d" % (type, count))
|
|
||||||
#
|
def on_textview_paste_clipboard(self, textview):
|
||||||
# def on_textview_paste_clipboard(self, textview):
|
_LOG.debug("Textview paste clipboard")
|
||||||
# log.debug("Textview paste clipboard")
|
|
||||||
#
|
def on_textview_motion_notify_event(self, textview, event):
|
||||||
# def on_textview_motion_notify_event(self, textview, event):
|
window = textview.get_window(gtk.TEXT_WINDOW_TEXT)
|
||||||
# window = textview.get_window(gtk.TEXT_WINDOW_TEXT)
|
x, y = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET,
|
||||||
# x, y = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET,
|
int(event.x), int(event.y))
|
||||||
# int(event.x), int(event.y))
|
iter = textview.get_iter_at_location(x, y)
|
||||||
# iter = textview.get_iter_at_location(x, y)
|
textbuffer = textview.get_buffer()
|
||||||
# buffer_ = textview.get_buffer()
|
self.match = textbuffer.match_check(iter.get_offset())
|
||||||
# self.match = buffer_.match_check(iter.get_offset())
|
|
||||||
#
|
if self.match != self.last_match:
|
||||||
# if self.match != self.last_match:
|
start, end = textbuffer.get_bounds()
|
||||||
# start, end = buffer_.get_bounds()
|
textbuffer.remove_tag_by_name('hyperlink', start, end)
|
||||||
# buffer_.remove_tag_by_name('hyperlink', start, end)
|
if self.match:
|
||||||
# if self.match:
|
start_offset = self.match[MATCH_START]
|
||||||
# start_offset = self.match[MarkupText.MATCH_START]
|
end_offset = self.match[MATCH_END]
|
||||||
# end_offset = self.match[MarkupText.MATCH_END]
|
|
||||||
#
|
start = textbuffer.get_iter_at_offset(start_offset)
|
||||||
# start = buffer_.get_iter_at_offset(start_offset)
|
end = textbuffer.get_iter_at_offset(end_offset)
|
||||||
# end = buffer_.get_iter_at_offset(end_offset)
|
|
||||||
#
|
textbuffer.apply_tag_by_name('hyperlink', start, end)
|
||||||
# buffer_.apply_tag_by_name('hyperlink', start, end)
|
window.set_cursor(self.hand_cursor)
|
||||||
# window.set_cursor(self.hand_cursor)
|
else:
|
||||||
# else:
|
window.set_cursor(self.regular_cursor)
|
||||||
# window.set_cursor(self.regular_cursor)
|
|
||||||
#
|
self.last_match = self.match
|
||||||
# self.last_match = self.match
|
|
||||||
#
|
textview.window.get_pointer()
|
||||||
# textview.window.get_pointer()
|
return False
|
||||||
# return False
|
|
||||||
#
|
def on_textview_button_press_event(self, textview, event):
|
||||||
# def on_textview_button_press_event(self, textview, event):
|
if ((event.type == gtk.gdk.BUTTON_PRESS) and
|
||||||
# if ((event.type == gtk.gdk.BUTTON_PRESS) and
|
(event.button == 1) and
|
||||||
# (event.button == 1) and
|
(event.state and gtk.gdk.CONTROL_MASK) and
|
||||||
# (event.state and gtk.gdk.CONTROL_MASK) and
|
(self.match)):
|
||||||
# (self.match)):
|
|
||||||
#
|
flavor = self.match[MATCH_FLAVOR]
|
||||||
# flavor = self.match[MarkupText.MATCH_FLAVOR]
|
url = self.match[MATCH_STRING]
|
||||||
# url = self.match[MarkupText.MATCH_STRING]
|
self.open_url_cb(None, url, flavor)
|
||||||
# self.open_url_cb(None, url, flavor)
|
|
||||||
#
|
return False
|
||||||
# return False
|
|
||||||
#
|
def on_textview_populate_popup(self, textview, menu):
|
||||||
# def on_textview_populate_popup(self, textview, menu):
|
"""Insert extra menuitems according to matched pattern."""
|
||||||
# """Insert extra menuitems according to matched pattern."""
|
if self.match:
|
||||||
# if self.match:
|
flavor = self.match[MATCH_FLAVOR]
|
||||||
# flavor = self.match[MarkupText.MATCH_FLAVOR]
|
url = self.match[MATCH_STRING]
|
||||||
# url = self.match[MarkupText.MATCH_STRING]
|
|
||||||
#
|
if flavor == MAIL:
|
||||||
# if flavor == MAIL:
|
open_menu = gtk.MenuItem(_('_Send Mail To...'))
|
||||||
# open_menu = gtk.MenuItem(_('_Send Mail To...'))
|
copy_menu = gtk.MenuItem(_('Copy _E-mail Address'))
|
||||||
# copy_menu = gtk.MenuItem(_('Copy _E-mail Address'))
|
else:
|
||||||
# else:
|
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'))
|
|
||||||
#
|
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)
|
|
||||||
#
|
open_menu.connect('activate', self.open_url_cb, url, flavor)
|
||||||
# open_menu.connect('activate', self.open_url_cb, url, flavor)
|
open_menu.show()
|
||||||
# open_menu.show()
|
menu.prepend(open_menu)
|
||||||
# menu.prepend(open_menu)
|
|
||||||
|
|
||||||
def on_spell_change(self, combobox, spell):
|
def on_spell_change(self, combobox, spell):
|
||||||
"""Set spell checker language according to user selection."""
|
"""Set spell checker language according to user selection."""
|
||||||
lang = combobox.get_active_text()
|
lang = combobox.get_active_text()
|
||||||
spell.set_active_language(lang)
|
spell.set_active_language(lang)
|
||||||
|
|
||||||
# enable for markup
|
def open_url_cb(self, menuitem, url, flavor):
|
||||||
# def open_url_cb(self, menuitem, url, flavor):
|
if not url:
|
||||||
# if not url:
|
return
|
||||||
# return
|
|
||||||
#
|
if flavor == HTTP:
|
||||||
# if flavor == HTTP:
|
url = 'http:' + url
|
||||||
# url = 'http:' + url
|
elif flavor == MAIL:
|
||||||
# elif flavor == MAIL:
|
if not url.startswith('mailto:'):
|
||||||
# if not url.startswith('mailto:'):
|
url = 'mailto:' + url
|
||||||
# url = 'mailto:' + url
|
elif flavor == GENERAL:
|
||||||
# elif flavor == GENERAL:
|
pass
|
||||||
# pass
|
else:
|
||||||
# else:
|
return
|
||||||
# return
|
|
||||||
#
|
url(url)
|
||||||
# GrampsDisplay.url(url)
|
|
||||||
#
|
def copy_url_cb(self, menuitem, url, flavor):
|
||||||
# def copy_url_cb(self, menuitem, url, flavor):
|
"""Copy url to both useful selections."""
|
||||||
# """Copy url to both useful selections."""
|
clipboard = gtk.Clipboard(selection="CLIPBOARD")
|
||||||
# clipboard = gtk.Clipboard(selection="CLIPBOARD")
|
clipboard.set_text(url)
|
||||||
# clipboard.set_text(url)
|
|
||||||
#
|
clipboard = gtk.Clipboard(selection="PRIMARY")
|
||||||
# clipboard = gtk.Clipboard(selection="PRIMARY")
|
clipboard.set_text(url)
|
||||||
# clipboard.set_text(url)
|
|
||||||
|
|
||||||
def update_note(self):
|
def update_note(self):
|
||||||
"""Update the Note object with current value."""
|
"""Update the Note object with current value."""
|
||||||
if self.obj:
|
if self.obj:
|
||||||
buffer_ = self.text.get_buffer()
|
textbuffer = self.text.get_buffer()
|
||||||
(start, stop) = buffer_.get_bounds()
|
text = textbuffer.get_text()
|
||||||
text = buffer_.get_text(start, stop)
|
self.obj.set_styledtext(text)
|
||||||
self.obj.set(text)
|
_LOG.debug(str(text))
|
||||||
log.debug(text)
|
|
||||||
|
|
||||||
def flow_changed(self, active):
|
def flow_changed(self, active):
|
||||||
if active:
|
if active:
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
#
|
#
|
||||||
# Gramps - a GTK+/GNOME based genealogy program
|
# Gramps - a GTK+/GNOME based genealogy program
|
||||||
#
|
#
|
||||||
# Copyright (C) 2000-2006 Donald N. Allingham
|
# Copyright (C) 2007-2008 Zsolt Foldvari
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,28 +20,18 @@
|
|||||||
|
|
||||||
# $Id$
|
# $Id$
|
||||||
|
|
||||||
"Handling formatted ('rich text') strings"
|
"Text buffer subclassed from gtk.TextBuffer handling L{StyledText}."
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# Python modules
|
# Python modules
|
||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
from xml.sax import saxutils, xmlreader, ContentHandler
|
from gettext import gettext as _
|
||||||
from xml.sax import parseString, SAXParseException
|
|
||||||
import re
|
import re
|
||||||
try:
|
|
||||||
from cStringIO import StringIO
|
|
||||||
except:
|
|
||||||
from StringIO import StringIO
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# Set up logging
|
|
||||||
#
|
|
||||||
#-------------------------------------------------------------------------
|
|
||||||
import logging
|
import logging
|
||||||
log = logging.getLogger(".MarkupText")
|
_LOG = logging.getLogger(".StyledTextBuffer")
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@@ -50,318 +40,49 @@ log = logging.getLogger(".MarkupText")
|
|||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
import gtk
|
import gtk
|
||||||
from pango import WEIGHT_BOLD, STYLE_ITALIC, UNDERLINE_SINGLE
|
from pango import WEIGHT_BOLD, STYLE_ITALIC, UNDERLINE_SINGLE
|
||||||
import gobject
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# GRAMPS modules
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
from gen.lib import (StyledText, StyledTextTag, StyledTextTagType)
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# Constants
|
# Constants
|
||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
ROOT_ELEMENT = 'gramps'
|
|
||||||
ROOT_START_TAG = '<' + ROOT_ELEMENT + '>'
|
|
||||||
ROOT_END_TAG = '</' + ROOT_ELEMENT + '>'
|
|
||||||
LEN_ROOT_START_TAG = len(ROOT_START_TAG)
|
|
||||||
LEN_ROOT_END_TAG = len(ROOT_END_TAG)
|
|
||||||
|
|
||||||
(MATCH_START,
|
(MATCH_START,
|
||||||
MATCH_END,
|
MATCH_END,
|
||||||
MATCH_FLAVOR,
|
MATCH_FLAVOR,
|
||||||
MATCH_STRING,) = range(4)
|
MATCH_STRING,) = range(4)
|
||||||
|
|
||||||
def is_gramps_markup(text):
|
#-------------------------------------------------------------------------
|
||||||
return (text[:LEN_ROOT_START_TAG] == ROOT_START_TAG and
|
#
|
||||||
text[-LEN_ROOT_END_TAG:] == ROOT_END_TAG)
|
# GtkSpellState class
|
||||||
|
#
|
||||||
def clear_root_tags(text):
|
#-------------------------------------------------------------------------
|
||||||
return text[LEN_ROOT_START_TAG:len(text)-LEN_ROOT_END_TAG]
|
|
||||||
|
|
||||||
class MarkupParser(ContentHandler):
|
|
||||||
"""A simple ContentHandler class to parse Gramps markup'ed text.
|
|
||||||
|
|
||||||
Use it with xml.sax.parse() or xml.sax.parseString(). A root tag is
|
|
||||||
required. Parsing result can be obtained via the public attributes of
|
|
||||||
the class:
|
|
||||||
@attr content: clean text
|
|
||||||
@attr type: str
|
|
||||||
@attr elements: list of markup elements
|
|
||||||
@attr type: list[tuple((start, end), name, attrs),]
|
|
||||||
|
|
||||||
"""
|
|
||||||
def startDocument(self):
|
|
||||||
self._open_document = False
|
|
||||||
self._open_elements = []
|
|
||||||
self.elements = []
|
|
||||||
self.content = ""
|
|
||||||
|
|
||||||
def endDocument(self):
|
|
||||||
self._open_document = False
|
|
||||||
if len(self._open_elements):
|
|
||||||
raise SAXParseException('Unclosed tags')
|
|
||||||
|
|
||||||
def startElement(self, name, attrs):
|
|
||||||
if not self._open_document:
|
|
||||||
if name == ROOT_ELEMENT:
|
|
||||||
self._open_document = True
|
|
||||||
else:
|
|
||||||
raise SAXParseException('Root element missing')
|
|
||||||
else:
|
|
||||||
self._open_elements.append({'name': name,
|
|
||||||
'attrs': attrs.copy(),
|
|
||||||
'start': len(self.content),
|
|
||||||
})
|
|
||||||
|
|
||||||
def endElement(self, name):
|
|
||||||
# skip root element
|
|
||||||
if name == ROOT_ELEMENT:
|
|
||||||
return
|
|
||||||
|
|
||||||
for e in self._open_elements:
|
|
||||||
if e['name'] == name:
|
|
||||||
self.elements.append(((e['start'], len(self.content)),
|
|
||||||
e['name'], e['attrs']))
|
|
||||||
|
|
||||||
self._open_elements.remove(e)
|
|
||||||
return
|
|
||||||
|
|
||||||
def characters (self, chunk):
|
|
||||||
self.content += chunk
|
|
||||||
|
|
||||||
class MarkupWriter:
|
|
||||||
"""Generate XML markup text for Notes.
|
|
||||||
|
|
||||||
Provides additional feature of accounting opened tags and closing them
|
|
||||||
properly in case of partially overlapping elements.
|
|
||||||
|
|
||||||
"""
|
|
||||||
(EVENT_START,
|
|
||||||
EVENT_END) = range(2)
|
|
||||||
|
|
||||||
def __init__(self, encoding='utf-8'):
|
|
||||||
self._output = StringIO()
|
|
||||||
self._encoding = encoding
|
|
||||||
self._writer = saxutils.XMLGenerator(self._output, self._encoding)
|
|
||||||
|
|
||||||
self._attrs = xmlreader.AttributesImpl({})
|
|
||||||
|
|
||||||
self._open_elements = []
|
|
||||||
self.content = ''
|
|
||||||
|
|
||||||
# Private
|
|
||||||
|
|
||||||
def _elements_to_events(self, elements):
|
|
||||||
"""Create an event list for XML writer.
|
|
||||||
|
|
||||||
@param elements: list of XML elements with start/end indices and attrs
|
|
||||||
@param type: [((start, end), xml_element_name, attrs),]
|
|
||||||
@return: eventdict
|
|
||||||
@rtype: {index: [(xml_element_name, attrs, event_type, pair_index),]}
|
|
||||||
index: place of the event
|
|
||||||
xml_element_name: element to apply
|
|
||||||
attrs: attributes of the tag (xml.sax.xmlreader.AttrubutesImpl)
|
|
||||||
event_type: START or END event
|
|
||||||
pair_index: index of the pair event, used for sorting
|
|
||||||
|
|
||||||
"""
|
|
||||||
eventdict = {}
|
|
||||||
for (start, end), name, attrs in elements:
|
|
||||||
# append START events
|
|
||||||
if eventdict.has_key(start):
|
|
||||||
eventdict[start].append((name, attrs, self.EVENT_START, end))
|
|
||||||
else:
|
|
||||||
eventdict[start] = [(name, attrs, self.EVENT_START, end)]
|
|
||||||
# END events have to prepended to avoid creating empty elements
|
|
||||||
if eventdict.has_key(end):
|
|
||||||
eventdict[end].insert(0, (name, attrs, self.EVENT_END, start))
|
|
||||||
else:
|
|
||||||
eventdict[end] = [(name, attrs, self.EVENT_END, start)]
|
|
||||||
|
|
||||||
# first round optimization
|
|
||||||
active_tags = {}
|
|
||||||
active_idx = {}
|
|
||||||
|
|
||||||
indices = eventdict.keys()
|
|
||||||
indices.sort()
|
|
||||||
for index in indices:
|
|
||||||
# separate the events by tag names
|
|
||||||
tagdict = {}
|
|
||||||
for event in eventdict[index]:
|
|
||||||
# we care only about tags having attributes
|
|
||||||
if event[1].getLength():
|
|
||||||
if tagdict.has_key(event[0]):
|
|
||||||
tagdict[event[0]].append(event)
|
|
||||||
else:
|
|
||||||
tagdict[event[0]] = [event]
|
|
||||||
|
|
||||||
# let's handle each tag
|
|
||||||
for tag_name in tagdict.keys():
|
|
||||||
|
|
||||||
# first we close the tag if it's already open
|
|
||||||
if active_tags.has_key(tag_name):
|
|
||||||
tmp_attrs = xmlreader.AttributesImpl({})
|
|
||||||
tmp_attrs._attrs.update(active_tags[tag_name])
|
|
||||||
eventdict[index].insert(0, (name, tmp_attrs,
|
|
||||||
self.EVENT_END,
|
|
||||||
active_idx[tag_name]))
|
|
||||||
# go back where the tag was opened and update the pair_idx,
|
|
||||||
# i.e. with the current index.
|
|
||||||
# FIXME this is ugly
|
|
||||||
for event in eventdict[active_idx[tag_name]]:
|
|
||||||
if (event[0] == tag_name and
|
|
||||||
event[2] == self.EVENT_START):
|
|
||||||
new_event = (event[0], event[1], event[2], index)
|
|
||||||
eventdict[active_idx[tag_name]].remove(event)
|
|
||||||
eventdict[active_idx[tag_name]].append(new_event)
|
|
||||||
else:
|
|
||||||
active_tags[tag_name] = xmlreader.AttributesImpl({})
|
|
||||||
|
|
||||||
# update
|
|
||||||
for event in tagdict[tag_name]:
|
|
||||||
# remove this event, we will insert new ones instead
|
|
||||||
eventdict[index].remove(event)
|
|
||||||
|
|
||||||
# update the active attribute object for the tag
|
|
||||||
(name, attrs, type, pair_idx) = event
|
|
||||||
if type == self.EVENT_START:
|
|
||||||
active_tags[name]._attrs.update(attrs)
|
|
||||||
elif type == self.EVENT_END:
|
|
||||||
for attr_name in attrs.getNames():
|
|
||||||
try:
|
|
||||||
del active_tags[name]._attrs[attr_name]
|
|
||||||
except:
|
|
||||||
pass # error
|
|
||||||
else:
|
|
||||||
pass # error
|
|
||||||
|
|
||||||
# if the tag's attr list is empty after the updates
|
|
||||||
# delete the tag completely from the list of active tags
|
|
||||||
if not active_tags[name].getLength():
|
|
||||||
del active_tags[name]
|
|
||||||
##del active_idx[name]
|
|
||||||
|
|
||||||
# re-open all tags with updated attrs
|
|
||||||
if active_tags.has_key(tag_name):
|
|
||||||
tmp_attrs = xmlreader.AttributesImpl({})
|
|
||||||
tmp_attrs._attrs.update(active_tags[tag_name])
|
|
||||||
eventdict[index].append((tag_name, tmp_attrs,
|
|
||||||
self.EVENT_START, 0))
|
|
||||||
# also save the index of tag opening
|
|
||||||
active_idx[tag_name] = index
|
|
||||||
|
|
||||||
# sort events at the same index
|
|
||||||
indices = eventdict.keys()
|
|
||||||
for idx in indices:
|
|
||||||
if len(eventdict[idx]) > 1:
|
|
||||||
eventdict[idx].sort(self._sort_events)
|
|
||||||
|
|
||||||
return eventdict
|
|
||||||
|
|
||||||
def _sort_events(self, event_a, event_b):
|
|
||||||
"""Sort events that are at the same index.
|
|
||||||
|
|
||||||
Sorting with the following rules:
|
|
||||||
1. END event goes always before START event;
|
|
||||||
2. from two START events the one goes first, which has it's own END
|
|
||||||
event later;
|
|
||||||
3. from two END events the one goes first, which has it's own START
|
|
||||||
event later.
|
|
||||||
|
|
||||||
"""
|
|
||||||
tag_a, attr_a, type_a, pair_a = event_a
|
|
||||||
tag_b, attr_b, type_b, pair_b = event_b
|
|
||||||
|
|
||||||
if (type_a + type_b) == (self.EVENT_START + self.EVENT_END):
|
|
||||||
return type_b - type_a
|
|
||||||
else:
|
|
||||||
return pair_b - pair_a
|
|
||||||
|
|
||||||
def _startElement(self, name, attrs=None):
|
|
||||||
"""Insert start tag."""
|
|
||||||
if not attrs:
|
|
||||||
attrs = self._attrs
|
|
||||||
self._writer.startElement(name, attrs)
|
|
||||||
self._open_elements.append((name, attrs))
|
|
||||||
|
|
||||||
def _endElement(self, name):
|
|
||||||
"""Insert end tag."""
|
|
||||||
if not len(self._open_elements):
|
|
||||||
log.debug("Trying to close element '%s' when non is open" % name)
|
|
||||||
return
|
|
||||||
|
|
||||||
tmp_list = []
|
|
||||||
elem = ''
|
|
||||||
|
|
||||||
# close all open elements until we reach to the requested one
|
|
||||||
while elem != name:
|
|
||||||
try:
|
|
||||||
elem, attrs = self._open_elements.pop()
|
|
||||||
self._writer.endElement(elem)
|
|
||||||
if elem != name:
|
|
||||||
tmp_list.append((elem, attrs))
|
|
||||||
except:
|
|
||||||
# we need to do something smart here...
|
|
||||||
log.debug("Trying to close non open element '%s'" % name)
|
|
||||||
break
|
|
||||||
|
|
||||||
# open all other elements again
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
elem, attrs = tmp_list.pop()
|
|
||||||
self._startElement(elem, attrs)
|
|
||||||
except:
|
|
||||||
break
|
|
||||||
|
|
||||||
# Public
|
|
||||||
|
|
||||||
def generate(self, text, elements):
|
|
||||||
# reset output and start root element
|
|
||||||
self._output.truncate(0)
|
|
||||||
self._writer.startElement(ROOT_ELEMENT, self._attrs)
|
|
||||||
|
|
||||||
# split the elements to events
|
|
||||||
events = self._elements_to_events(elements)
|
|
||||||
|
|
||||||
# feed the events into the xml generator
|
|
||||||
last_pos = 0
|
|
||||||
indices = events.keys()
|
|
||||||
indices.sort()
|
|
||||||
for index in indices:
|
|
||||||
self._writer.characters(text[last_pos:index])
|
|
||||||
for name, attrs, event_type, p in events[index]:
|
|
||||||
if event_type == self.EVENT_START:
|
|
||||||
self._startElement(name, attrs)
|
|
||||||
elif event_type == self.EVENT_END:
|
|
||||||
self._endElement(name)
|
|
||||||
last_pos = index
|
|
||||||
self._writer.characters(text[last_pos:])
|
|
||||||
|
|
||||||
# close root element and end doc
|
|
||||||
self._writer.endElement(ROOT_ELEMENT)
|
|
||||||
self._writer.endDocument()
|
|
||||||
|
|
||||||
# copy result
|
|
||||||
self.content = self._output.getvalue()
|
|
||||||
log.debug("Gramps XML: %s" % self.content)
|
|
||||||
|
|
||||||
class GtkSpellState:
|
class GtkSpellState:
|
||||||
"""A simple state machine kinda thingy.
|
"""A simple state machine kinda thingy.
|
||||||
|
|
||||||
Try tracking gtk.Spell activities on a buffer and reapply formatting
|
Trying to track gtk.Spell activities on a buffer and re-apply formatting
|
||||||
after gtk.Spell replaces a misspelled word.
|
after gtk.Spell replaces a misspelled word.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
(STATE_NONE,
|
(STATE_NONE,
|
||||||
STATE_CLICKED,
|
STATE_CLICKED,
|
||||||
STATE_DELETED,
|
STATE_DELETED,
|
||||||
STATE_INSERTING) = range(4)
|
STATE_INSERTING) = range(4)
|
||||||
|
|
||||||
def __init__(self, buffer):
|
def __init__(self, textbuffer):
|
||||||
if not isinstance(buffer, gtk.TextBuffer):
|
if not isinstance(textbuffer, gtk.TextBuffer):
|
||||||
raise TypeError("Init parameter must be instance of gtk.TextBuffer")
|
raise TypeError("Init parameter must be instance of gtk.TextBuffer")
|
||||||
|
|
||||||
buffer.connect('mark-set', self.on_buffer_mark_set)
|
textbuffer.connect('mark-set', self.on_buffer_mark_set)
|
||||||
buffer.connect('delete-range', self.on_buffer_delete_range)
|
textbuffer.connect('delete-range', self.on_buffer_delete_range)
|
||||||
buffer.connect('insert-text', self.on_buffer_insert_text)
|
textbuffer.connect('insert-text', self.on_buffer_insert_text)
|
||||||
buffer.connect_after('insert-text', self.after_buffer_insert_text)
|
textbuffer.connect_after('insert-text', self.after_buffer_insert_text)
|
||||||
|
|
||||||
self.reset_state()
|
self.reset_state()
|
||||||
|
|
||||||
@@ -371,36 +92,37 @@ class GtkSpellState:
|
|||||||
self.end = 0
|
self.end = 0
|
||||||
self.tags = None
|
self.tags = None
|
||||||
|
|
||||||
def on_buffer_mark_set(self, buffer, iter, mark):
|
def on_buffer_mark_set(self, textbuffer, iter, mark):
|
||||||
mark_name = mark.get_name()
|
mark_name = mark.get_name()
|
||||||
if mark_name == 'gtkspell-click':
|
if mark_name == 'gtkspell-click':
|
||||||
self.state = self.STATE_CLICKED
|
self.state = self.STATE_CLICKED
|
||||||
self.start, self.end = self.get_word_extents_from_mark(buffer, mark)
|
self.start, self.end = self.get_word_extents_from_mark(textbuffer,
|
||||||
log.debug("SpellState got start %d end %d" % (self.start, self.end))
|
mark)
|
||||||
|
_LOG.debug("SpellState got start %d end %d" % (self.start, self.end))
|
||||||
elif mark_name == 'insert':
|
elif mark_name == 'insert':
|
||||||
self.reset_state()
|
self.reset_state()
|
||||||
|
|
||||||
def on_buffer_delete_range(self, buffer, start, end):
|
def on_buffer_delete_range(self, textbuffer, start, end):
|
||||||
if ((self.state == self.STATE_CLICKED) and
|
if ((self.state == self.STATE_CLICKED) and
|
||||||
(start.get_offset() == self.start) and
|
(start.get_offset() == self.start) and
|
||||||
(end.get_offset() == self.end)):
|
(end.get_offset() == self.end)):
|
||||||
self.state = self.STATE_DELETED
|
self.state = self.STATE_DELETED
|
||||||
self.tags = start.get_tags()
|
self.tags = start.get_tags()
|
||||||
|
|
||||||
def on_buffer_insert_text(self, buffer, iter, text, length):
|
def on_buffer_insert_text(self, textbuffer, iter, text, length):
|
||||||
if self.state == self.STATE_DELETED and iter.get_offset() == self.start:
|
if self.state == self.STATE_DELETED and iter.get_offset() == self.start:
|
||||||
self.state = self.STATE_INSERTING
|
self.state = self.STATE_INSERTING
|
||||||
|
|
||||||
def after_buffer_insert_text(self, buffer, iter, text, length):
|
def after_buffer_insert_text(self, textbuffer, iter, text, length):
|
||||||
if self.state == self.STATE_INSERTING:
|
if self.state == self.STATE_INSERTING:
|
||||||
mark = buffer.get_mark('gtkspell-insert-start')
|
mark = textbuffer.get_mark('gtkspell-insert-start')
|
||||||
insert_start = buffer.get_iter_at_mark(mark)
|
insert_start = textbuffer.get_iter_at_mark(mark)
|
||||||
for tag in self.tags:
|
for tag in self.tags:
|
||||||
buffer.apply_tag(tag, insert_start, iter)
|
textbuffer.apply_tag(tag, insert_start, iter)
|
||||||
|
|
||||||
self.reset_state()
|
self.reset_state()
|
||||||
|
|
||||||
def get_word_extents_from_mark(self, buffer, mark):
|
def get_word_extents_from_mark(self, textbuffer, mark):
|
||||||
"""Get the word extents as gtk.Spell does.
|
"""Get the word extents as gtk.Spell does.
|
||||||
|
|
||||||
Used to get the beginning of the word, in which user right clicked.
|
Used to get the beginning of the word, in which user right clicked.
|
||||||
@@ -408,7 +130,7 @@ class GtkSpellState:
|
|||||||
misspelled words.
|
misspelled words.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
start = buffer.get_iter_at_mark(mark)
|
start = textbuffer.get_iter_at_mark(mark)
|
||||||
if not start.starts_word():
|
if not start.starts_word():
|
||||||
#start.backward_word_start()
|
#start.backward_word_start()
|
||||||
self.backward_word_start(start)
|
self.backward_word_start(start)
|
||||||
@@ -455,14 +177,27 @@ class GtkSpellState:
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
class MarkupBuffer(gtk.TextBuffer):
|
#-------------------------------------------------------------------------
|
||||||
"""An extended TextBuffer with Gramps XML markup string interface.
|
#
|
||||||
|
# StyledTextBuffer class
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
class StyledTextBuffer(gtk.TextBuffer):
|
||||||
|
"""An extended TextBuffer for handling StyledText strings.
|
||||||
|
|
||||||
It implements MarkupParser and MarkupWriter on the input/output interfaces.
|
StyledTextBuffer is an interface between GRAMPS' L{StyledText} format
|
||||||
Also translates Gramps XML markup language to gtk.TextTag's and vice versa.
|
and gtk.TextBuffer. To get/set the text use the L{get_text} and
|
||||||
|
L{set_text} methods.
|
||||||
|
|
||||||
|
It provides an action group (L{format_action_group}) for GUIs.
|
||||||
|
|
||||||
|
StyledTextBuffer has a regexp pattern matching mechanism too. To add a
|
||||||
|
regexp pattern to match in the text use the L{match_add} method. To check
|
||||||
|
if there's a match at a certain position in the text use the L{match_check}
|
||||||
|
method.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
__gtype_name__ = 'MarkupBuffer'
|
__gtype_name__ = 'StyledTextBuffer'
|
||||||
|
|
||||||
formats = ('italic', 'bold', 'underline',
|
formats = ('italic', 'bold', 'underline',
|
||||||
'font', 'foreground', 'background',)
|
'font', 'foreground', 'background',)
|
||||||
@@ -470,9 +205,6 @@ class MarkupBuffer(gtk.TextBuffer):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
gtk.TextBuffer.__init__(self)
|
gtk.TextBuffer.__init__(self)
|
||||||
|
|
||||||
self.parser = MarkupParser()
|
|
||||||
self.writer = MarkupWriter()
|
|
||||||
|
|
||||||
# Create fix tags.
|
# Create fix tags.
|
||||||
# Other tags (e.g. color) have to be created on the fly
|
# Other tags (e.g. color) have to be created on the fly
|
||||||
self.create_tag('bold', weight=WEIGHT_BOLD)
|
self.create_tag('bold', weight=WEIGHT_BOLD)
|
||||||
@@ -520,6 +252,8 @@ class MarkupBuffer(gtk.TextBuffer):
|
|||||||
self.bold = False
|
self.bold = False
|
||||||
self.underline = False
|
self.underline = False
|
||||||
self.font = None
|
self.font = None
|
||||||
|
# TODO could we separate font name and size?
|
||||||
|
##self.size = None
|
||||||
self.foreground = None
|
self.foreground = None
|
||||||
self.background = None
|
self.background = None
|
||||||
|
|
||||||
@@ -545,15 +279,15 @@ class MarkupBuffer(gtk.TextBuffer):
|
|||||||
|
|
||||||
# Virtual methods
|
# Virtual methods
|
||||||
|
|
||||||
def on_insert_text(self, buffer, iter, text, length):
|
def on_insert_text(self, textbuffer, iter, text, length):
|
||||||
log.debug("Will insert at %d length %d" % (iter.get_offset(), length))
|
_LOG.debug("Will insert at %d length %d" % (iter.get_offset(), length))
|
||||||
|
|
||||||
# let's remember where we started inserting
|
# let's remember where we started inserting
|
||||||
self.move_mark(self.mark_insert, iter)
|
self.move_mark(self.mark_insert, iter)
|
||||||
|
|
||||||
def after_insert_text(self, buffer, iter, text, length):
|
def after_insert_text(self, textbuffer, iter, text, length):
|
||||||
"""Format inserted text."""
|
"""Format inserted text."""
|
||||||
log.debug("Have inserted at %d length %d (%s)" %
|
_LOG.debug("Have inserted at %d length %d (%s)" %
|
||||||
(iter.get_offset(), length, text))
|
(iter.get_offset(), length, text))
|
||||||
|
|
||||||
if not length:
|
if not length:
|
||||||
@@ -572,8 +306,8 @@ class MarkupBuffer(gtk.TextBuffer):
|
|||||||
self.apply_tag(self._find_tag_by_name(format, value),
|
self.apply_tag(self._find_tag_by_name(format, value),
|
||||||
insert_start, iter)
|
insert_start, iter)
|
||||||
|
|
||||||
def after_delete_range(self, buffer, start, end):
|
def after_delete_range(self, textbuffer, start, end):
|
||||||
log.debug("Deleted from %d till %d" %
|
_LOG.debug("Deleted from %d till %d" %
|
||||||
(start.get_offset(), end.get_offset()))
|
(start.get_offset(), end.get_offset()))
|
||||||
|
|
||||||
# move 'insert' marker to have the format attributes updated
|
# move 'insert' marker to have the format attributes updated
|
||||||
@@ -592,16 +326,15 @@ class MarkupBuffer(gtk.TextBuffer):
|
|||||||
match = iter.next()
|
match = iter.next()
|
||||||
self.matches.append((match.start(), match.end(),
|
self.matches.append((match.start(), match.end(),
|
||||||
flavor, match.group()))
|
flavor, match.group()))
|
||||||
log.debug("Matches: %d, %d: %s [%d]" %
|
_LOG.debug("Matches: %d, %d: %s [%d]" %
|
||||||
(match.start(), match.end(),
|
(match.start(), match.end(),
|
||||||
match.group(), flavor))
|
match.group(), flavor))
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def do_mark_set(self, iter, mark):
|
def do_mark_set(self, iter, mark):
|
||||||
"""Update format attributes each time the cursor moves."""
|
"""Update format attributes each time the cursor moves."""
|
||||||
log.debug("Setting mark %s at %d" %
|
_LOG.debug("Setting mark %s at %d" %
|
||||||
(mark.get_name(), iter.get_offset()))
|
(mark.get_name(), iter.get_offset()))
|
||||||
|
|
||||||
if mark.get_name() != 'insert':
|
if mark.get_name() != 'insert':
|
||||||
@@ -629,72 +362,32 @@ class MarkupBuffer(gtk.TextBuffer):
|
|||||||
|
|
||||||
# Private
|
# Private
|
||||||
|
|
||||||
def _xmltag_to_texttag(self, name, attrs):
|
def _tagname_to_tagtype(self, name):
|
||||||
"""Convert XML tag to gtk.TextTag.
|
"""Convert gtk.TextTag names to StyledTextTagType values."""
|
||||||
|
tag2type = {
|
||||||
Return only the name of the TextTag.
|
'bold': StyledTextTagType.BOLD,
|
||||||
|
'italic': StyledTextTagType.ITALIC,
|
||||||
|
'underline': StyledTextTagType.UNDERLINE,
|
||||||
|
'foreground': StyledTextTagType.FONTCOLOR,
|
||||||
|
'background': StyledTextTagType.HIGHLIGHT,
|
||||||
|
'font': StyledTextTagType.FONTFACE,
|
||||||
|
}
|
||||||
|
|
||||||
@param name: name of the XML tag
|
return StyledTextTagType(tag2type[name])
|
||||||
@param type: string
|
|
||||||
@param attrs: attributes of the XML tag
|
def _tagtype_to_tagname(self, tagtype):
|
||||||
@param type: xmlreader.AttributesImpl
|
"""Convert StyledTextTagType values to gtk.TextTag names."""
|
||||||
@return: property of gtk.TextTag, value of property
|
type2tag = {
|
||||||
@rtype: [(string, string), ]
|
StyledTextTagType.BOLD: 'bold',
|
||||||
|
StyledTextTagType.ITALIC: 'italic',
|
||||||
"""
|
StyledTextTagType.UNDERLINE: 'underline',
|
||||||
if name == 'b':
|
StyledTextTagType.FONTCOLOR: 'foreground',
|
||||||
return [('bold', None)]
|
StyledTextTagType.HIGHLIGHT: 'background',
|
||||||
elif name == 'i':
|
StyledTextTagType.FONTFACE: 'font',
|
||||||
return [('italic', None)]
|
}
|
||||||
elif name == 'u':
|
|
||||||
return [('underline', None)]
|
|
||||||
elif name == 'font':
|
|
||||||
ret = []
|
|
||||||
attr_names = attrs.getNames()
|
|
||||||
if 'color' in attr_names:
|
|
||||||
ret.append(('foreground', attrs.getValue('color')))
|
|
||||||
if 'highlight' in attr_names:
|
|
||||||
ret.append(('background', attrs.getValue('highlight')))
|
|
||||||
if ('face' in attr_names) and ('size' in attr_names):
|
|
||||||
ret.append(('font', '%s %s' % (attrs.getValue('face'),
|
|
||||||
attrs.getValue('size'))))
|
|
||||||
if len(ret):
|
|
||||||
return ret
|
|
||||||
else:
|
|
||||||
return [(None, None)]
|
|
||||||
else:
|
|
||||||
return [(None, None)]
|
|
||||||
|
|
||||||
def _texttag_to_xmltag(self, name):
|
|
||||||
"""Convert gtk.TextTag to XML tag.
|
|
||||||
|
|
||||||
@param name: name of the gtk.TextTag
|
|
||||||
@param type: string
|
|
||||||
@return: XML tag name, attribute
|
|
||||||
@rtype: string, xmlreader.AttributesImpl
|
|
||||||
|
|
||||||
"""
|
|
||||||
attrs = xmlreader.AttributesImpl({})
|
|
||||||
if name == 'bold':
|
|
||||||
return 'b', attrs
|
|
||||||
elif name == 'italic':
|
|
||||||
return 'i', attrs
|
|
||||||
elif name == 'underline':
|
|
||||||
return 'u', attrs
|
|
||||||
elif name.startswith('foreground'):
|
|
||||||
attrs._attrs['color'] = name.split()[1]
|
|
||||||
return 'font', attrs
|
|
||||||
elif name.startswith('background'):
|
|
||||||
attrs._attrs['highlight'] = name.split()[1]
|
|
||||||
return 'font', attrs
|
|
||||||
elif name.startswith('font'):
|
|
||||||
name = name.replace('font ', '')
|
|
||||||
attrs._attrs['face'] = name.rsplit(' ', 1)[0]
|
|
||||||
attrs._attrs['size'] = name.rsplit(' ', 1)[1]
|
|
||||||
return 'font', attrs
|
|
||||||
else:
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
|
return type2tag[tagtype]
|
||||||
|
|
||||||
##def get_tag_value_at_insert(self, name):
|
##def get_tag_value_at_insert(self, name):
|
||||||
##"""Get the value of the given tag at the insertion point."""
|
##"""Get the value of the given tag at the insertion point."""
|
||||||
##tags = self.get_iter_at_mark(self._insert).get_tags()
|
##tags = self.get_iter_at_mark(self._insert).get_tags()
|
||||||
@@ -835,12 +528,7 @@ class MarkupBuffer(gtk.TextBuffer):
|
|||||||
setattr(self, action.get_name(), action.get_active())
|
setattr(self, action.get_name(), action.get_active())
|
||||||
|
|
||||||
def on_action_activate(self, action):
|
def on_action_activate(self, action):
|
||||||
"""Apply a format.
|
"""Apply a format."""
|
||||||
|
|
||||||
Other tags for the same format have to be removed from the range
|
|
||||||
first otherwise XML would get messy.
|
|
||||||
|
|
||||||
"""
|
|
||||||
format = action.get_name()
|
format = action.get_name()
|
||||||
|
|
||||||
if format == 'foreground':
|
if format == 'foreground':
|
||||||
@@ -870,11 +558,11 @@ class MarkupBuffer(gtk.TextBuffer):
|
|||||||
value = font_selection.fontsel.get_font_name()
|
value = font_selection.fontsel.get_font_name()
|
||||||
font_selection.destroy()
|
font_selection.destroy()
|
||||||
else:
|
else:
|
||||||
log.debug("unknown format: '%s'" % format)
|
_LOG.debug("unknown format: '%s'" % format)
|
||||||
return
|
return
|
||||||
|
|
||||||
if response == gtk.RESPONSE_OK:
|
if response == gtk.RESPONSE_OK:
|
||||||
log.debug("applying format '%s' with value '%s'" % (format, value))
|
_LOG.debug("applying format '%s' with value '%s'" % (format, value))
|
||||||
|
|
||||||
tag = self._find_tag_by_name(format, value)
|
tag = self._find_tag_by_name(format, value)
|
||||||
self.remove_format_from_selection(format)
|
self.remove_format_from_selection(format)
|
||||||
@@ -905,64 +593,51 @@ class MarkupBuffer(gtk.TextBuffer):
|
|||||||
|
|
||||||
# Public API
|
# Public API
|
||||||
|
|
||||||
def set_text(self, xmltext):
|
def set_text(self, r_text):
|
||||||
"""Set the content of the buffer with markup tags."""
|
"""Set the content of the buffer with markup tags."""
|
||||||
try:
|
gtk.TextBuffer.set_text(self, str(r_text))
|
||||||
parseString(xmltext.encode('utf-8'), self.parser)
|
|
||||||
text = self.parser.content
|
r_tags = r_text.get_tags()
|
||||||
except:
|
for r_tag in r_tags:
|
||||||
# if parse fails remove all tags and use clear text instead
|
tagname = self._tagtype_to_tagname(int(r_tag.name))
|
||||||
text = re.sub(r'(<.*?>)', '', xmltext)
|
g_tag = self._find_tag_by_name(tagname, r_tag.value)
|
||||||
text = saxutils.unescape(text)
|
if g_tag is not None:
|
||||||
|
for (start, end) in r_tag.ranges:
|
||||||
gtk.TextBuffer.set_text(self, text)
|
|
||||||
|
|
||||||
for element in self.parser.elements:
|
|
||||||
(start, end), xmltag_name, attrs = element
|
|
||||||
|
|
||||||
#texttag_name, value = self._xmltag_to_texttag(xmltag_name, attrs)
|
|
||||||
tags = self._xmltag_to_texttag(xmltag_name, attrs)
|
|
||||||
|
|
||||||
for texttag_name, value in tags:
|
|
||||||
if texttag_name is not None:
|
|
||||||
start_iter = self.get_iter_at_offset(start)
|
start_iter = self.get_iter_at_offset(start)
|
||||||
end_iter = self.get_iter_at_offset(end)
|
end_iter = self.get_iter_at_offset(end)
|
||||||
tag = self._find_tag_by_name(texttag_name, value)
|
self.apply_tag(g_tag, start_iter, end_iter)
|
||||||
if tag is not None:
|
|
||||||
self.apply_tag(tag, start_iter, end_iter)
|
|
||||||
|
|
||||||
def get_text(self, start=None, end=None, include_hidden_chars=True):
|
def get_text(self, start=None, end=None, include_hidden_chars=True):
|
||||||
"""Return the buffer text with xml markup tags.
|
"""Return the buffer text."""
|
||||||
|
|
||||||
If no markup was applied returns clean text
|
|
||||||
(i.e. without even root tags).
|
|
||||||
|
|
||||||
"""
|
|
||||||
# get the clear text from the buffer
|
|
||||||
if not start:
|
if not start:
|
||||||
start = self.get_start_iter()
|
start = self.get_start_iter()
|
||||||
if not end:
|
if not end:
|
||||||
end = self.get_end_iter()
|
end = self.get_end_iter()
|
||||||
txt = unicode(gtk.TextBuffer.get_text(self, start, end))
|
|
||||||
|
|
||||||
|
txt = gtk.TextBuffer.get_text(self, start, end, include_hidden_chars)
|
||||||
|
txt = unicode(txt)
|
||||||
|
|
||||||
# extract tags out of the buffer
|
# extract tags out of the buffer
|
||||||
texttag = self.get_tag_from_range()
|
g_tags = self.get_tag_from_range()
|
||||||
|
r_tags = []
|
||||||
|
|
||||||
if len(texttag):
|
for g_tagname, g_ranges in g_tags.items():
|
||||||
# convert the texttags to xml elements
|
name_value = g_tagname.split(' ', 1)
|
||||||
xml_elements = []
|
|
||||||
for texttag_name, indices in texttag.items():
|
|
||||||
xml_tag_name, attrs = self._texttag_to_xmltag(texttag_name)
|
|
||||||
if xml_tag_name is not None:
|
|
||||||
for start_idx, end_idx in indices:
|
|
||||||
xml_elements.append(((start_idx, end_idx+1),
|
|
||||||
xml_tag_name, attrs))
|
|
||||||
|
|
||||||
# feed the elements into the xml writer
|
if len(name_value) == 1:
|
||||||
self.writer.generate(txt, xml_elements)
|
name = name_value[0]
|
||||||
txt = self.writer.content
|
r_value = None
|
||||||
|
else:
|
||||||
|
(name, r_value) = name_value
|
||||||
|
|
||||||
|
if name in self.formats:
|
||||||
|
r_tagtype = self._tagname_to_tagtype(name)
|
||||||
|
r_ranges = [(start, end+1) for (start, end) in g_ranges]
|
||||||
|
r_tag = StyledTextTag(r_tagtype, r_value, r_ranges)
|
||||||
|
|
||||||
|
r_tags.append(r_tag)
|
||||||
|
|
||||||
return txt
|
return StyledText(txt, r_tags)
|
||||||
|
|
||||||
def match_add(self, pattern, flavor):
|
def match_add(self, pattern, flavor):
|
||||||
"""Add a pattern to look for in the text."""
|
"""Add a pattern to look for in the text."""
|
||||||
@@ -972,11 +647,7 @@ class MarkupBuffer(gtk.TextBuffer):
|
|||||||
def match_check(self, pos):
|
def match_check(self, pos):
|
||||||
"""Check if pos falls into any of the matched patterns."""
|
"""Check if pos falls into any of the matched patterns."""
|
||||||
for match in self.matches:
|
for match in self.matches:
|
||||||
if pos >= match[0] and pos <= match[1]:
|
if pos >= match[MATCH_START] and pos <= match[MATCH_END]:
|
||||||
return match
|
return match
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
if gtk.pygtk_version < (2,8,0):
|
|
||||||
gobject.type_register(MarkupBuffer)
|
|
@@ -39,7 +39,7 @@ from gettext import gettext as _
|
|||||||
from bsddb import dbshelve, db
|
from bsddb import dbshelve, db
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger(".GrampsDb")
|
_LOG = logging.getLogger(".GrampsDb")
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@@ -56,7 +56,7 @@ from gen.db.cursor import GrampsCursor
|
|||||||
import Errors
|
import Errors
|
||||||
|
|
||||||
_MINVERSION = 9
|
_MINVERSION = 9
|
||||||
_DBVERSION = 13
|
_DBVERSION = 14
|
||||||
|
|
||||||
IDTRANS = "person_id"
|
IDTRANS = "person_id"
|
||||||
FIDTRANS = "family_id"
|
FIDTRANS = "family_id"
|
||||||
@@ -1485,7 +1485,7 @@ class GrampsDBDir(GrampsDbBase, UpdateCallback):
|
|||||||
# under certain circumstances during a database reload,
|
# under certain circumstances during a database reload,
|
||||||
# data_map can be none. If so, then don't report an error
|
# data_map can be none. If so, then don't report an error
|
||||||
if data_map:
|
if data_map:
|
||||||
log.error("Failed to get from handle", exc_info=True)
|
_LOG.error("Failed to get from handle", exc_info=True)
|
||||||
if data:
|
if data:
|
||||||
newobj = InstanceType(class_type)
|
newobj = InstanceType(class_type)
|
||||||
newobj.unserialize(data)
|
newobj.unserialize(data)
|
||||||
@@ -1665,13 +1665,42 @@ class GrampsDBDir(GrampsDbBase, UpdateCallback):
|
|||||||
def gramps_upgrade(self, callback=None):
|
def gramps_upgrade(self, callback=None):
|
||||||
UpdateCallback.__init__(self, callback)
|
UpdateCallback.__init__(self, callback)
|
||||||
|
|
||||||
# version = self.metadata.get('version', default=_MINVERSION)
|
version = self.metadata.get('version', default=_MINVERSION)
|
||||||
|
|
||||||
t = time.time()
|
t = time.time()
|
||||||
# if version < 13:
|
|
||||||
# self.gramps_upgrade_13()
|
if version < 14:
|
||||||
|
self.gramps_upgrade_14()
|
||||||
|
|
||||||
print "Upgrade time:", int(time.time()-t), "seconds"
|
print "Upgrade time:", int(time.time()-t), "seconds"
|
||||||
|
|
||||||
|
def gramps_upgrade_14(self):
|
||||||
|
"""Upgrade database from version 13 to 14."""
|
||||||
|
# This upgrade modifies notes
|
||||||
|
length = len(self.note_map)
|
||||||
|
self.set_total(length)
|
||||||
|
|
||||||
|
# replace clear text with StyledText in Notes
|
||||||
|
for handle in self.note_map.keys():
|
||||||
|
note = self.note_map[handle]
|
||||||
|
|
||||||
|
(junk_handle, gramps_id, text, format, note_type,
|
||||||
|
change, marker, private) = note
|
||||||
|
|
||||||
|
styled_text = (text, [])
|
||||||
|
|
||||||
|
new_note = (handle, gramps_id, styled_text, format, note_type,
|
||||||
|
change, marker, private)
|
||||||
|
|
||||||
|
the_txn = self.env.txn_begin()
|
||||||
|
self.note_map.put(str(handle), new_note, txn=the_txn)
|
||||||
|
the_txn.commit()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
# Bump up database version. Separate transaction to save metadata.
|
||||||
|
the_txn = self.env.txn_begin()
|
||||||
|
self.metadata.put('version', 14, txn=the_txn)
|
||||||
|
the_txn.commit()
|
||||||
|
|
||||||
class BdbTransaction(Transaction):
|
class BdbTransaction(Transaction):
|
||||||
def __init__(self, msg, db, batch=False, no_magic=False):
|
def __init__(self, msg, db, batch=False, no_magic=False):
|
||||||
|
@@ -69,3 +69,8 @@ from gen.lib.srcmediatype import SourceMediaType
|
|||||||
from gen.lib.eventroletype import EventRoleType
|
from gen.lib.eventroletype import EventRoleType
|
||||||
from gen.lib.markertype import MarkerType
|
from gen.lib.markertype import MarkerType
|
||||||
from gen.lib.notetype import NoteType
|
from gen.lib.notetype import NoteType
|
||||||
|
from gen.lib.styledtexttagtype import StyledTextTagType
|
||||||
|
|
||||||
|
# Text
|
||||||
|
from gen.lib.styledtexttag import StyledTextTag
|
||||||
|
from gen.lib.styledtext import StyledText
|
||||||
|
@@ -31,19 +31,26 @@ Base type for all gramps types.
|
|||||||
#------------------------------------------------------------------------
|
#------------------------------------------------------------------------
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# _init_map function
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
def _init_map(data, key_col, data_col, blacklist=None):
|
def _init_map(data, key_col, data_col, blacklist=None):
|
||||||
"""
|
"""Initialize the map, building a new map from the specified columns."""
|
||||||
Initialize the map, building a new map from the specified columns.
|
|
||||||
"""
|
|
||||||
if blacklist:
|
if blacklist:
|
||||||
new_data = dict([ (item[key_col], item[data_col])
|
new_data = dict([(item[key_col], item[data_col])
|
||||||
for item in data
|
for item in data if not item[0] in blacklist])
|
||||||
if not item[0] in blacklist ])
|
|
||||||
else:
|
else:
|
||||||
new_data = dict([ (item[key_col], item[data_col])
|
new_data = dict([(item[key_col], item[data_col]) for item in data])
|
||||||
for item in data ])
|
|
||||||
return new_data
|
return new_data
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# GrampsTypeMeta class
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
class GrampsTypeMeta(type):
|
class GrampsTypeMeta(type):
|
||||||
"""Metaclass for L{GrampsType}.
|
"""Metaclass for L{GrampsType}.
|
||||||
|
|
||||||
@@ -55,15 +62,30 @@ class GrampsTypeMeta(type):
|
|||||||
type.__init__(mcs, name, bases, namespace)
|
type.__init__(mcs, name, bases, namespace)
|
||||||
mcs.__class_init__(namespace)
|
mcs.__class_init__(namespace)
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# GrampsType class
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
class GrampsType(object):
|
class GrampsType(object):
|
||||||
"""Base class for all Gramps object types.
|
"""Base class for all Gramps object types.
|
||||||
|
|
||||||
_DATAMAP is a 3-tuple like (index, localized_string, english_string)
|
@cvar _DATAMAP: 3-tuple like (index, localized_string, english_string).
|
||||||
_BLACKLIST is a list of indices to ignore (obsolete/retired entries)
|
@type _DATAMAP: list
|
||||||
(gramps policy is never to delete type values,
|
@cvar _BLACKLIST: List of indices to ignore (obsolete/retired entries).
|
||||||
or reuse the name (TOKEN) of any specific type value)
|
(gramps policy is never to delete type values, or reuse the name (TOKEN)
|
||||||
|
of any specific type value)
|
||||||
|
@type _BLACKLIST: list
|
||||||
|
@cvar POS_<x>: Position of <x> attribute in the serialized format of
|
||||||
|
an instance.
|
||||||
|
@type POS_<x>: int
|
||||||
|
|
||||||
|
@attention: The POS_<x> class variables reflect the serialized object, they
|
||||||
|
have to be updated in case the data structure or the L{serialize} method
|
||||||
|
changes!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
(POS_VALUE, POS_STRING) = range(2)
|
||||||
|
|
||||||
_CUSTOM = 0
|
_CUSTOM = 0
|
||||||
_DEFAULT = 0
|
_DEFAULT = 0
|
||||||
@@ -89,8 +111,8 @@ class GrampsType(object):
|
|||||||
self.set(value)
|
self.set(value)
|
||||||
|
|
||||||
def __set_tuple(self, value):
|
def __set_tuple(self, value):
|
||||||
v,s = self._DEFAULT,u''
|
v, s = self._DEFAULT, u''
|
||||||
if len(value) > 0:
|
if value:
|
||||||
v = value[0]
|
v = value[0]
|
||||||
if len(value) > 1:
|
if len(value) > 1:
|
||||||
s = value[1]
|
s = value[1]
|
||||||
@@ -148,15 +170,11 @@ class GrampsType(object):
|
|||||||
return self._I2EMAP[self.val]
|
return self._I2EMAP[self.val]
|
||||||
|
|
||||||
def serialize(self):
|
def serialize(self):
|
||||||
"""
|
"""Convert the object to a serialized tuple of data. """
|
||||||
Convert the object to a serialized tuple of data.
|
|
||||||
"""
|
|
||||||
return (self.val, self.string)
|
return (self.val, self.string)
|
||||||
|
|
||||||
def unserialize(self, data):
|
def unserialize(self, data):
|
||||||
"""
|
"""Convert a serialized tuple of data to an object."""
|
||||||
Convert a serialized tuple of data to an object.
|
|
||||||
"""
|
|
||||||
self.val, self.string = data
|
self.val, self.string = data
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -172,16 +190,12 @@ class GrampsType(object):
|
|||||||
return self._I2SMAP
|
return self._I2SMAP
|
||||||
|
|
||||||
def get_standard_names(self):
|
def get_standard_names(self):
|
||||||
"""
|
"""Return the list of localized names for all standard types."""
|
||||||
Return the list of localized names for all standard types.
|
|
||||||
"""
|
|
||||||
return [s for (i, s) in self._I2SMAP.items()
|
return [s for (i, s) in self._I2SMAP.items()
|
||||||
if (i != self._CUSTOM) and s.strip()]
|
if (i != self._CUSTOM) and s.strip()]
|
||||||
|
|
||||||
def get_standard_xml(self):
|
def get_standard_xml(self):
|
||||||
"""
|
"""Return the list of XML (english) names for all standard types."""
|
||||||
Return the list of XML (english) names for all standard types.
|
|
||||||
"""
|
|
||||||
return [s for (i, s) in self._I2EMAP.items()
|
return [s for (i, s) in self._I2EMAP.items()
|
||||||
if (i != self._CUSTOM) and s.strip()]
|
if (i != self._CUSTOM) and s.strip()]
|
||||||
|
|
||||||
|
@@ -32,6 +32,7 @@ Note class for GRAMPS.
|
|||||||
from gen.lib.primaryobj import BasicPrimaryObject
|
from gen.lib.primaryobj import BasicPrimaryObject
|
||||||
from gen.lib.notetype import NoteType
|
from gen.lib.notetype import NoteType
|
||||||
from gen.lib.markertype import MarkerType
|
from gen.lib.markertype import MarkerType
|
||||||
|
from gen.lib.styledtext import StyledText
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@@ -39,118 +40,163 @@ from gen.lib.markertype import MarkerType
|
|||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
class Note(BasicPrimaryObject):
|
class Note(BasicPrimaryObject):
|
||||||
"""
|
"""Define a text note.
|
||||||
Introduction
|
|
||||||
============
|
|
||||||
The Note class defines a text note. The note may be preformatted
|
|
||||||
or 'flowed', which indicates that it text string is considered
|
|
||||||
to be in paragraphs, separated by newlines.
|
|
||||||
"""
|
|
||||||
|
|
||||||
FLOWED = 0
|
Starting from GRAMPS 3.1 Note object stores the text in L{StyledText}
|
||||||
FORMATTED = 1
|
instance, thus it can have text formatting information.
|
||||||
|
|
||||||
def __init__(self, text = ""):
|
To get and set only the clear text of the note use the L{get} and L{set}
|
||||||
"""
|
methods.
|
||||||
Create a new Note object, initializing from the passed string.
|
|
||||||
"""
|
To get and set the formatted version of the Note's text use the
|
||||||
|
L{get_styledtext} and L{set_styledtext} methods.
|
||||||
|
|
||||||
|
The note may be 'preformatted' or 'flowed', which indicates that the
|
||||||
|
text string is considered to be in paragraphs, separated by newlines.
|
||||||
|
|
||||||
|
@cvar POS_<x>: Position of <x> attribute in the serialized format of
|
||||||
|
an instance.
|
||||||
|
@type POS_<x>: int
|
||||||
|
|
||||||
|
@attention: The POS_<x> class variables reflect the serialized object, they
|
||||||
|
have to be updated in case the data structure or the L{serialize} method
|
||||||
|
changes!
|
||||||
|
|
||||||
|
"""
|
||||||
|
(FLOWED, FORMATTED) = range(2)
|
||||||
|
|
||||||
|
(POS_HANDLE,
|
||||||
|
POS_ID,
|
||||||
|
POS_TEXT,
|
||||||
|
POS_FORMAT,
|
||||||
|
POS_TYPE,
|
||||||
|
POS_CHANGE,
|
||||||
|
POS_MARKER,
|
||||||
|
POS_PRIVATE,) = range(8)
|
||||||
|
|
||||||
|
def __init__(self, text=""):
|
||||||
|
"""Create a new Note object, initializing from the passed string."""
|
||||||
BasicPrimaryObject.__init__(self)
|
BasicPrimaryObject.__init__(self)
|
||||||
self.text = text
|
self.text = StyledText(text)
|
||||||
self.format = Note.FLOWED
|
self.format = Note.FLOWED
|
||||||
self.type = NoteType()
|
self.type = NoteType()
|
||||||
|
|
||||||
def serialize(self):
|
def serialize(self):
|
||||||
|
"""Convert the object to a serialized tuple of data.
|
||||||
|
|
||||||
|
@returns: The serialized format of the instance.
|
||||||
|
@rtype: tuple
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Convert the object to a serialized tuple of data.
|
return (self.handle, self.gramps_id, self.text.serialize(), self.format,
|
||||||
"""
|
|
||||||
return (self.handle, self.gramps_id, self.text, self.format,
|
|
||||||
self.type.serialize(), self.change, self.marker.serialize(),
|
self.type.serialize(), self.change, self.marker.serialize(),
|
||||||
self.private)
|
self.private)
|
||||||
|
|
||||||
def unserialize(self, data):
|
def unserialize(self, data):
|
||||||
|
"""Convert a serialized tuple of data to an object.
|
||||||
|
|
||||||
|
@param data: The serialized format of a Note.
|
||||||
|
@type: data: tuple
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Convert a serialized tuple of data to an object.
|
(self.handle, self.gramps_id, the_text, self.format,
|
||||||
"""
|
|
||||||
(self.handle, self.gramps_id, self.text, self.format,
|
|
||||||
the_type, self.change, the_marker, self.private) = data
|
the_type, self.change, the_marker, self.private) = data
|
||||||
|
|
||||||
|
self.text = StyledText()
|
||||||
|
self.text.unserialize(the_text)
|
||||||
self.marker = MarkerType()
|
self.marker = MarkerType()
|
||||||
self.marker.unserialize(the_marker)
|
self.marker.unserialize(the_marker)
|
||||||
self.type = NoteType()
|
self.type = NoteType()
|
||||||
self.type.unserialize(the_type)
|
self.type.unserialize(the_type)
|
||||||
|
|
||||||
def get_text_data_list(self):
|
def get_text_data_list(self):
|
||||||
"""
|
"""Return the list of all textual attributes of the object.
|
||||||
Return the list of all textual attributes of the object.
|
|
||||||
|
|
||||||
@return: Returns the list of all textual attributes of the object.
|
@returns: The list of all textual attributes of the object.
|
||||||
@rtype: list
|
@rtype: list
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return [self.text]
|
return [str(self.text)]
|
||||||
|
|
||||||
def set(self, text):
|
def set(self, text):
|
||||||
"""
|
"""Set the text associated with the note to the passed string.
|
||||||
Set the text associated with the note to the passed string.
|
|
||||||
|
|
||||||
@param text: Text string defining the note contents.
|
@param text: The I{clear} text defining the note contents.
|
||||||
@type text: str
|
@type text: str
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.text = text
|
self.text = StyledText(text)
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
"""
|
"""Return the text string associated with the note.
|
||||||
Return the text string associated with the note.
|
|
||||||
|
|
||||||
@returns: Returns the text string defining the note contents.
|
@returns: The I{clear} text of the note contents.
|
||||||
@rtype: str
|
@rtype: str
|
||||||
|
|
||||||
"""
|
"""
|
||||||
text = self.text
|
return str(self.text)
|
||||||
return text
|
|
||||||
|
|
||||||
def append(self, text):
|
def set_styledtext(self, text):
|
||||||
|
"""Set the text associated with the note to the passed string.
|
||||||
|
|
||||||
|
@param text: The I{formatted} text defining the note contents.
|
||||||
|
@type text: L{StyledText}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Append the specified text to the text associated with the note.
|
self.text = text
|
||||||
|
|
||||||
|
def get_styledtext(self):
|
||||||
|
"""Return the text string associated with the note.
|
||||||
|
|
||||||
|
@returns: The I{formatted} text of the note contents.
|
||||||
|
@rtype: L{StyledText}
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.text
|
||||||
|
|
||||||
|
def append(self, text):
|
||||||
|
"""Append the specified text to the text associated with the note.
|
||||||
|
|
||||||
@param text: Text string to be appended to the note.
|
@param text: Text string to be appended to the note.
|
||||||
@type text: str
|
@type text: str or L{StyledText}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.text = self.text + text
|
self.text = self.text + text
|
||||||
|
|
||||||
def set_format(self, format):
|
def set_format(self, format):
|
||||||
"""
|
"""Set the format of the note to the passed value.
|
||||||
Set the format of the note to the passed value.
|
|
||||||
|
|
||||||
The value can either indicate Flowed or Preformatted.
|
@param: format: The value can either indicate Flowed or Preformatted.
|
||||||
|
|
||||||
@type format: int
|
@type format: int
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.format = format
|
self.format = format
|
||||||
|
|
||||||
def get_format(self):
|
def get_format(self):
|
||||||
"""
|
"""Return the format of the note.
|
||||||
Return the format of the note.
|
|
||||||
|
|
||||||
The value can either indicate Flowed or Preformatted.
|
The value can either indicate Flowed or Preformatted.
|
||||||
|
|
||||||
@returns: 0 indicates Flowed, 1 indicates Preformated
|
@returns: 0 indicates Flowed, 1 indicates Preformated
|
||||||
@rtype: int
|
@rtype: int
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.format
|
return self.format
|
||||||
|
|
||||||
def set_type(self, the_type):
|
def set_type(self, the_type):
|
||||||
"""
|
"""Set descriptive type of the Note.
|
||||||
Set descriptive type of the Note.
|
|
||||||
|
|
||||||
@param the_type: descriptive type of the Note
|
@param the_type: descriptive type of the Note
|
||||||
@type the_type: str
|
@type the_type: str
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.type.set(the_type)
|
self.type.set(the_type)
|
||||||
|
|
||||||
def get_type(self):
|
def get_type(self):
|
||||||
"""
|
"""Get descriptive type of the Note.
|
||||||
Get descriptive type of the Note.
|
|
||||||
|
|
||||||
@returns: the descriptive type of the Note
|
@returns: the descriptive type of the Note
|
||||||
@rtype: str
|
@rtype: str
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.type
|
return self.type
|
||||||
|
162
src/gen/lib/styledtext.py
Normal file
162
src/gen/lib/styledtext.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
#
|
||||||
|
# Gramps - a GTK+/GNOME based genealogy program
|
||||||
|
#
|
||||||
|
# Copyright (C) 2008 Zsolt Foldvari
|
||||||
|
#
|
||||||
|
# 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$
|
||||||
|
|
||||||
|
"Handling formatted ('rich text') strings"
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# GRAMPS modules
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
from gen.lib.styledtexttag import StyledTextTag
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# StyledText class
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
class StyledText(object):
|
||||||
|
"""Helper class to enable character based text formatting.
|
||||||
|
|
||||||
|
|
||||||
|
@ivar string: The clear text part.
|
||||||
|
@type string: str
|
||||||
|
@ivar tags: Text tags holding formatting information for the string.
|
||||||
|
@type tags: list of L{StyledTextTag}
|
||||||
|
|
||||||
|
@cvar POS_TEXT: Position of I{string} attribute in the serialized format of
|
||||||
|
an instance.
|
||||||
|
@type POS_TEXT: int
|
||||||
|
@cvar POS_TAGS: Position of I{tags} attribute in the serialized format of
|
||||||
|
an instance.
|
||||||
|
@type POS_TAGS: int
|
||||||
|
|
||||||
|
@attention: The POS_<x> class variables reflect the serialized object, they
|
||||||
|
have to be updated in case the data structure or the L{serialize} method
|
||||||
|
changes!
|
||||||
|
|
||||||
|
"""
|
||||||
|
##StyledText provides interface
|
||||||
|
##Provide interface for:
|
||||||
|
##- tag manipulation for editor access:
|
||||||
|
##. get_tags
|
||||||
|
##. set_tags
|
||||||
|
##- explicit formatting for reports; at the moment:
|
||||||
|
##. start_bold() - end_bold()
|
||||||
|
##. start_superscript() - end_superscript()
|
||||||
|
|
||||||
|
(POS_TEXT, POS_TAGS) = range(2)
|
||||||
|
|
||||||
|
def __init__(self, text="", tags=None):
|
||||||
|
"""Setup initial instance variable values."""
|
||||||
|
self._string = text
|
||||||
|
# TODO we might want to make simple sanity check first
|
||||||
|
if tags:
|
||||||
|
self._tags = tags
|
||||||
|
else:
|
||||||
|
self._tags = []
|
||||||
|
|
||||||
|
# special methods
|
||||||
|
|
||||||
|
def __str__(self): return self._string.__str__()
|
||||||
|
def __repr__(self): return self._string.__repr__()
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
if isinstance(other, StyledText):
|
||||||
|
# FIXME merging tags missing
|
||||||
|
return self.__class__("".join([self._string, other.string]))
|
||||||
|
elif isinstance(other, basestring):
|
||||||
|
# in this case tags remain the same, only text becomes longer
|
||||||
|
return self.__class__("".join([self._string, other]))
|
||||||
|
else:
|
||||||
|
return self.__class__("".join([self._string, str(other)]))
|
||||||
|
|
||||||
|
# string methods in alphabetical order:
|
||||||
|
|
||||||
|
def join(self, seq):
|
||||||
|
# FIXME handling tags missing
|
||||||
|
return self.__class__(self._string.join(seq))
|
||||||
|
|
||||||
|
def replace(self, old, new, maxsplit=-1):
|
||||||
|
# FIXME handling tags missing
|
||||||
|
return self.__class__(self._string.replace(old, new, maxsplit))
|
||||||
|
|
||||||
|
def split(self, sep=None, maxsplit=-1):
|
||||||
|
# FIXME handling tags missing
|
||||||
|
string_list = self._string.split(sep, maxsplit)
|
||||||
|
return [self.__class__(string) for string in string_list]
|
||||||
|
|
||||||
|
# other public methods
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
"""Convert the object to a serialized tuple of data.
|
||||||
|
|
||||||
|
@returns: Serialized format of the instance.
|
||||||
|
@returntype: tuple
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self._tags:
|
||||||
|
the_tags = [tag.serialize() for tag in self._tags]
|
||||||
|
else:
|
||||||
|
the_tags = []
|
||||||
|
|
||||||
|
return (self._string, the_tags)
|
||||||
|
|
||||||
|
def unserialize(self, data):
|
||||||
|
"""Convert a serialized tuple of data to an object.
|
||||||
|
|
||||||
|
@param data: Serialized format of instance variables.
|
||||||
|
@type data: tuple
|
||||||
|
|
||||||
|
"""
|
||||||
|
(self._string, the_tags) = data
|
||||||
|
|
||||||
|
# I really wonder why this doesn't work... it does for all other types
|
||||||
|
#self._tags = [StyledTextTag().unserialize(tag) for tag in the_tags]
|
||||||
|
for tag in the_tags:
|
||||||
|
gtt = StyledTextTag()
|
||||||
|
gtt.unserialize(tag)
|
||||||
|
self._tags.append(gtt)
|
||||||
|
|
||||||
|
def get_tags(self):
|
||||||
|
"""Return the list of formatting tags.
|
||||||
|
|
||||||
|
@returns: The formatting tags applied on the text.
|
||||||
|
@returntype: list of 0 or more L{StyledTextTag} instances.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self._tags
|
||||||
|
|
||||||
|
##def set_tags(self, tags):
|
||||||
|
##"""Set all the formatting tags at once.
|
||||||
|
|
||||||
|
##@param tags: The formatting tags to be applied on the text.
|
||||||
|
##@type tags: list of 0 or more StyledTextTag instances.
|
||||||
|
|
||||||
|
##"""
|
||||||
|
### TODO we might want to make simple sanity check first
|
||||||
|
##self._tags = tags
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
GT = StyledText("asbcde")
|
||||||
|
print GT
|
73
src/gen/lib/styledtexttag.py
Normal file
73
src/gen/lib/styledtexttag.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#
|
||||||
|
# Gramps - a GTK+/GNOME based genealogy program
|
||||||
|
#
|
||||||
|
# Copyright (C) 2008 Zsolt Foldvari
|
||||||
|
#
|
||||||
|
# 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$
|
||||||
|
|
||||||
|
"Provide formatting tag definition for StyledText."
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# GRAMPS modules
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
from gen.lib.styledtexttagtype import StyledTextTagType
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# StyledTextTag class
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
class StyledTextTag():
|
||||||
|
"""Hold formatting information for StyledText.
|
||||||
|
|
||||||
|
@ivar name: Type or name of the tag instance. E.g. bold, etc.
|
||||||
|
@type name: L{gen.lib.StyledTextTagType} instace
|
||||||
|
@ivar value: Value of the tag. E.g. color hex string for font color, etc.
|
||||||
|
@type value: str or None
|
||||||
|
@ivar ranges: Pointer pairs into the string, where the tag applies.
|
||||||
|
@type ranges: list of (int(start), int(end)) tuples.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, name=None, value=None, ranges=None):
|
||||||
|
"""Setup initial instance variable values."""
|
||||||
|
self.name = StyledTextTagType(name)
|
||||||
|
self.value = value
|
||||||
|
self.ranges = ranges
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
"""Convert the object to a serialized tuple of data.
|
||||||
|
|
||||||
|
@returns: Serialized format of the instance.
|
||||||
|
@returntype: tuple
|
||||||
|
|
||||||
|
"""
|
||||||
|
return (self.name.serialize(), self.value, self.ranges)
|
||||||
|
|
||||||
|
def unserialize(self, data):
|
||||||
|
"""Convert a serialized tuple of data to an object.
|
||||||
|
|
||||||
|
@param data: Serialized format of instance variables.
|
||||||
|
@type data: tuple
|
||||||
|
|
||||||
|
"""
|
||||||
|
(the_name, self.value, self.ranges) = data
|
||||||
|
|
||||||
|
self.name = StyledTextTagType()
|
||||||
|
self.name.unserialize(the_name)
|
73
src/gen/lib/styledtexttagtype.py
Normal file
73
src/gen/lib/styledtexttagtype.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#
|
||||||
|
# Gramps - a GTK+/GNOME based genealogy program
|
||||||
|
#
|
||||||
|
# Copyright (C) 2008 Zsolt Foldvari
|
||||||
|
#
|
||||||
|
# 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$
|
||||||
|
|
||||||
|
"Define text formatting tag types."
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Python modules
|
||||||
|
#
|
||||||
|
#------------------------------------------------------------------------
|
||||||
|
from gettext import gettext as _
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# GRAMPS modules
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
from gen.lib.grampstype import GrampsType
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# StyledTextTagType class
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
class StyledTextTagType(GrampsType):
|
||||||
|
"""Text formatting tag type definition.
|
||||||
|
|
||||||
|
Here we only define new class variables. For details see L{GrampsType}.
|
||||||
|
|
||||||
|
"""
|
||||||
|
NONE_ = -1
|
||||||
|
BOLD = 0
|
||||||
|
ITALIC = 1
|
||||||
|
UNDERLINE = 2
|
||||||
|
FONTFACE = 3
|
||||||
|
FONTCOLOR = 4
|
||||||
|
HIGHLIGHT = 5
|
||||||
|
SUPERSCRIPT = 6
|
||||||
|
|
||||||
|
_CUSTOM = NONE_
|
||||||
|
_DEFAULT = NONE_
|
||||||
|
|
||||||
|
_DATAMAP = [
|
||||||
|
(BOLD, _("Bold"), "bold"),
|
||||||
|
(ITALIC, _("Italic"), "italic"),
|
||||||
|
(UNDERLINE, _("Underline"), "underline"),
|
||||||
|
(FONTFACE, _("Fontface"), "fontface"),
|
||||||
|
(FONTCOLOR, _("Fontcolor"), "fontcolor"),
|
||||||
|
(HIGHLIGHT, _("Highlight"), "highlight"),
|
||||||
|
(SUPERSCRIPT, _("Superscript"), "superscript"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, value=None):
|
||||||
|
GrampsType.__init__(self, value)
|
Reference in New Issue
Block a user