Introducing StyledText in Notes.
svn: r10410
This commit is contained in:
parent
52ad89909c
commit
0542a9b78c
@ -1,7 +1,7 @@
|
||||
#
|
||||
# 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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -25,7 +25,7 @@
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
import logging
|
||||
log = logging.getLogger(".")
|
||||
_LOG = logging.getLogger(".DisplayModels.NoteModel")
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
@ -40,20 +40,19 @@ import gtk
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from _BaseModel import BaseModel
|
||||
import gen.lib
|
||||
from gen.lib import (Note, NoteType, MarkerType, StyledText)
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# PlaceModel
|
||||
# NoteModel
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
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):
|
||||
"""Setup initial values for instance variables."""
|
||||
self.gen_cursor = db.get_note_cursor
|
||||
self.map = db.get_raw_note_data
|
||||
self.fmap = [
|
||||
@ -63,7 +62,7 @@ class NoteModel(BaseModel):
|
||||
self.column_marker,
|
||||
self.column_handle,
|
||||
self.column_marker_color
|
||||
]
|
||||
]
|
||||
self.smap = [
|
||||
self.column_preview,
|
||||
self.column_id,
|
||||
@ -71,48 +70,57 @@ class NoteModel(BaseModel):
|
||||
self.column_marker,
|
||||
self.column_handle,
|
||||
self.column_marker_color
|
||||
]
|
||||
]
|
||||
self.marker_color_column = 5
|
||||
BaseModel.__init__(self, db, scol, order,
|
||||
search=search, skip=skip, sort_map=sort_map)
|
||||
BaseModel.__init__(self, db, scol, order, search=search,
|
||||
skip=skip, sort_map=sort_map)
|
||||
|
||||
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):
|
||||
return data[0]
|
||||
def column_handle(self, data):
|
||||
"""Return the handle of the Note."""
|
||||
return data[Note.POS_HANDLE]
|
||||
|
||||
def column_id(self,data):
|
||||
return unicode(data[1])
|
||||
def column_id(self, data):
|
||||
"""Return the id of the Note."""
|
||||
return unicode(data[Note.POS_ID])
|
||||
|
||||
def column_type(self,data):
|
||||
temp = gen.lib.NoteType()
|
||||
temp.set(data[4])
|
||||
def column_type(self, data):
|
||||
"""Return the type of the Note in readable format."""
|
||||
temp = NoteType()
|
||||
temp.set(data[Note.POS_TYPE])
|
||||
return unicode(str(temp))
|
||||
|
||||
def column_marker(self, data):
|
||||
temp = gen.lib.MarkerType()
|
||||
temp.set(data[6])
|
||||
"""Return the marker type of the Note in readable format."""
|
||||
temp = MarkerType()
|
||||
temp.set(data[Note.POS_MARKER])
|
||||
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
|
||||
#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:
|
||||
return note[:80]+"..."
|
||||
return note[:80] + "..."
|
||||
else:
|
||||
return note
|
||||
|
||||
def column_marker_color(self, data):
|
||||
"""Return the color of the Note's marker type if exist."""
|
||||
try:
|
||||
col = data[NoteModel._MARKER_COL][0]
|
||||
if col == gen.lib.MarkerType.COMPLETE:
|
||||
col = data[Note.POS_MARKER][MarkerType.POS_VALUE]
|
||||
if col == MarkerType.COMPLETE:
|
||||
return self.complete_color
|
||||
elif col == gen.lib.MarkerType.TODO_TYPE:
|
||||
elif col == MarkerType.TODO_TYPE:
|
||||
return self.todo_color
|
||||
elif col == gen.lib.MarkerType.CUSTOM:
|
||||
elif col == MarkerType.CUSTOM:
|
||||
return self.custom_color
|
||||
else:
|
||||
return None
|
||||
except IndexError:
|
||||
pass
|
||||
return None
|
||||
return None
|
||||
|
@ -28,7 +28,8 @@
|
||||
from gettext import gettext as _
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(".")
|
||||
_LOG = logging.getLogger(".Editors.EditNote")
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# GTK libraries
|
||||
@ -41,19 +42,20 @@ import pango
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# GRAMPS classes
|
||||
# GRAMPS modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
import const
|
||||
import Spell
|
||||
import Config
|
||||
import GrampsDisplay
|
||||
import MarkupText
|
||||
from const import GLADE_FILE
|
||||
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 DisplayTabs import GrampsTab, NoteBackRefList
|
||||
from GrampsWidgets import (MonitoredDataType, MonitoredCheckbox,
|
||||
MonitoredEntry, PrivacyButton)
|
||||
import gen.lib
|
||||
from gen.lib import Note
|
||||
from QuestionDialog import ErrorDialog
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
@ -61,17 +63,16 @@ from QuestionDialog import ErrorDialog
|
||||
# Constants
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
#USERCHARS = "-A-Za-z0-9"
|
||||
#PASSCHARS = "-A-Za-z0-9,?;.:/!%$^*&~\"#'"
|
||||
#HOSTCHARS = "-A-Za-z0-9"
|
||||
#PATHCHARS = "-A-Za-z0-9_$.+!*(),;:@&=?/~#%"
|
||||
##SCHEME = "(news:|telnet:|nntp:|file:/|https?:|ftps?:|webcal:)"
|
||||
#SCHEME = "(file:/|https?:|ftps?:|webcal:)"
|
||||
#USER = "[" + USERCHARS + "]+(:[" + PASSCHARS + "]+)?"
|
||||
#URLPATH = "/[" + PATHCHARS + "]*[^]'.}>) \t\r\n,\\\"]"
|
||||
#
|
||||
#(GENERAL, HTTP, MAIL) = range(3)
|
||||
USERCHARS = "-A-Za-z0-9"
|
||||
PASSCHARS = "-A-Za-z0-9,?;.:/!%$^*&~\"#'"
|
||||
HOSTCHARS = "-A-Za-z0-9"
|
||||
PATHCHARS = "-A-Za-z0-9_$.+!*(),;:@&=?/~#%"
|
||||
#SCHEME = "(news:|telnet:|nntp:|file:/|https?:|ftps?:|webcal:)"
|
||||
SCHEME = "(file:/|https?:|ftps?:|webcal:)"
|
||||
USER = "[" + USERCHARS + "]+(:[" + PASSCHARS + "]+)?"
|
||||
URLPATH = "/[" + PATHCHARS + "]*[^]'.}>) \t\r\n,\\\"]"
|
||||
|
||||
(GENERAL, HTTP, MAIL) = range(3)
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
@ -91,17 +92,17 @@ class NoteTab(GrampsTab):
|
||||
the database, along with other state information. The GrampsTab
|
||||
uses this to access the database and to pass to and created
|
||||
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
|
||||
subwindows.
|
||||
@type uistate: DisplayState
|
||||
@type uistate: L{DisplayState.DisplayState}
|
||||
@param track: The window tracking mechanism used to manage windows.
|
||||
This is only used to pass to generted child windows.
|
||||
@type track: list
|
||||
@param name: Notebook label name
|
||||
@type name: str/unicode
|
||||
@param widget: widget to be shown in the tab
|
||||
@type widge: gtk widget
|
||||
@type widget: gtk widget
|
||||
"""
|
||||
GrampsTab.__init__(self, dbstate, uistate, track, name)
|
||||
eventbox = gtk.EventBox()
|
||||
@ -131,11 +132,12 @@ class EditNote(EditPrimary):
|
||||
callertitle = None, extratype = None):
|
||||
"""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
|
||||
@param extratype: extra NoteType values to add to the default types
|
||||
They are removed from the ignorelist of NoteType.
|
||||
@param extratype: Extra L{NoteType} values to add to the default types.
|
||||
They are removed from the ignorelist of L{NoteType}.
|
||||
@type extratype: list of int
|
||||
|
||||
"""
|
||||
self.callertitle = callertitle
|
||||
self.extratype = extratype
|
||||
@ -146,9 +148,10 @@ class EditNote(EditPrimary):
|
||||
def empty_object(self):
|
||||
"""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:
|
||||
empty_note.set_type(self.extratype[0])
|
||||
return empty_note
|
||||
@ -157,16 +160,16 @@ class EditNote(EditPrimary):
|
||||
if self.obj.get_handle():
|
||||
if self.callertitle :
|
||||
title = _('Note: %(id)s - %(context)s') % {
|
||||
'id' : self.obj.get_gramps_id(),
|
||||
'context' : self.callertitle
|
||||
}
|
||||
'id' : self.obj.get_gramps_id(),
|
||||
'context' : self.callertitle
|
||||
}
|
||||
else :
|
||||
title = _('Note: %s') % self.obj.get_gramps_id()
|
||||
else:
|
||||
if self.callertitle :
|
||||
title = _('New Note - %(context)s') % {
|
||||
'context' : self.callertitle
|
||||
}
|
||||
'context' : self.callertitle
|
||||
}
|
||||
else :
|
||||
title = _('New Note')
|
||||
|
||||
@ -179,11 +182,11 @@ class EditNote(EditPrimary):
|
||||
"""Local initialization function.
|
||||
|
||||
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.
|
||||
|
||||
"""
|
||||
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")
|
||||
self.set_window(win, None, self.get_menu_title())
|
||||
|
||||
@ -242,7 +245,7 @@ class EditNote(EditPrimary):
|
||||
def _connect_signals(self):
|
||||
"""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)
|
||||
@ -250,122 +253,68 @@ class EditNote(EditPrimary):
|
||||
self.define_help_button(self.top.get_widget('help'), '')
|
||||
|
||||
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")
|
||||
|
||||
self._add_tab(notebook, self.ntab)
|
||||
|
||||
self.backref_tab = self._add_tab(
|
||||
notebook,
|
||||
NoteBackRefList(self.dbstate, self.uistate, self.track,
|
||||
self.dbstate.db.find_backlink_handles(
|
||||
self.obj.handle))
|
||||
)
|
||||
handles = self.dbstate.db.find_backlink_handles(self.obj.handle)
|
||||
rlist = NoteBackRefList(self.dbstate, self.uistate, self.track, handles)
|
||||
self.backref_tab = self._add_tab(notebook, rlist)
|
||||
|
||||
self._setup_notebook_tabs( notebook)
|
||||
self._setup_notebook_tabs(notebook)
|
||||
|
||||
# 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):
|
||||
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.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
|
||||
spellcheck = Spell.Spell(self.text)
|
||||
spellcheck = Spell(self.text)
|
||||
liststore = gtk.ListStore(gobject.TYPE_STRING)
|
||||
cell = gtk.CellRendererText()
|
||||
lang_selector = self.top.get_widget('spell')
|
||||
@ -382,14 +331,62 @@ class EditNote(EditPrimary):
|
||||
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(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:
|
||||
self.empty = False
|
||||
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:
|
||||
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):
|
||||
"""
|
||||
@ -401,119 +398,116 @@ class EditNote(EditPrimary):
|
||||
def _post_init(self):
|
||||
self.text.grab_focus()
|
||||
|
||||
# enable for markup
|
||||
# def on_textview_key_press_event(self, textview, event):
|
||||
# """Handle shortcuts in the TextView."""
|
||||
# return textview.get_buffer().on_key_press_event(textview, event)
|
||||
#
|
||||
# def on_textview_insert_at_cursor(self, textview, string):
|
||||
# log.debug("Textview insert '%s'" % string)
|
||||
#
|
||||
# def on_textview_delete_from_cursor(self, textview, type, count):
|
||||
# log.debug("Textview delete type %d count %d" % (type, count))
|
||||
#
|
||||
# def on_textview_paste_clipboard(self, textview):
|
||||
# log.debug("Textview paste clipboard")
|
||||
#
|
||||
# def on_textview_motion_notify_event(self, textview, event):
|
||||
# window = textview.get_window(gtk.TEXT_WINDOW_TEXT)
|
||||
# x, y = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET,
|
||||
# int(event.x), int(event.y))
|
||||
# iter = textview.get_iter_at_location(x, y)
|
||||
# buffer_ = textview.get_buffer()
|
||||
# self.match = buffer_.match_check(iter.get_offset())
|
||||
#
|
||||
# if self.match != self.last_match:
|
||||
# start, end = buffer_.get_bounds()
|
||||
# buffer_.remove_tag_by_name('hyperlink', start, end)
|
||||
# if self.match:
|
||||
# start_offset = self.match[MarkupText.MATCH_START]
|
||||
# end_offset = self.match[MarkupText.MATCH_END]
|
||||
#
|
||||
# start = buffer_.get_iter_at_offset(start_offset)
|
||||
# end = buffer_.get_iter_at_offset(end_offset)
|
||||
#
|
||||
# buffer_.apply_tag_by_name('hyperlink', start, end)
|
||||
# window.set_cursor(self.hand_cursor)
|
||||
# else:
|
||||
# window.set_cursor(self.regular_cursor)
|
||||
#
|
||||
# self.last_match = self.match
|
||||
#
|
||||
# textview.window.get_pointer()
|
||||
# return False
|
||||
#
|
||||
# def on_textview_button_press_event(self, textview, event):
|
||||
# if ((event.type == gtk.gdk.BUTTON_PRESS) and
|
||||
# (event.button == 1) and
|
||||
# (event.state and gtk.gdk.CONTROL_MASK) and
|
||||
# (self.match)):
|
||||
#
|
||||
# flavor = self.match[MarkupText.MATCH_FLAVOR]
|
||||
# url = self.match[MarkupText.MATCH_STRING]
|
||||
# self.open_url_cb(None, url, flavor)
|
||||
#
|
||||
# return False
|
||||
#
|
||||
# def on_textview_populate_popup(self, textview, menu):
|
||||
# """Insert extra menuitems according to matched pattern."""
|
||||
# if self.match:
|
||||
# flavor = self.match[MarkupText.MATCH_FLAVOR]
|
||||
# url = self.match[MarkupText.MATCH_STRING]
|
||||
#
|
||||
# if flavor == MAIL:
|
||||
# open_menu = gtk.MenuItem(_('_Send Mail To...'))
|
||||
# copy_menu = gtk.MenuItem(_('Copy _E-mail Address'))
|
||||
# else:
|
||||
# open_menu = gtk.MenuItem(_('_Open Link'))
|
||||
# copy_menu = gtk.MenuItem(_('Copy _Link Address'))
|
||||
#
|
||||
# copy_menu.connect('activate', self.copy_url_cb, url, flavor)
|
||||
# copy_menu.show()
|
||||
# menu.prepend(copy_menu)
|
||||
#
|
||||
# open_menu.connect('activate', self.open_url_cb, url, flavor)
|
||||
# open_menu.show()
|
||||
# menu.prepend(open_menu)
|
||||
def on_textview_key_press_event(self, textview, event):
|
||||
"""Handle shortcuts in the TextView."""
|
||||
return textview.get_buffer().on_key_press_event(textview, event)
|
||||
|
||||
def on_textview_insert_at_cursor(self, textview, string):
|
||||
_LOG.debug("Textview insert '%s'" % string)
|
||||
|
||||
def on_textview_delete_from_cursor(self, textview, type, count):
|
||||
_LOG.debug("Textview delete type %d count %d" % (type, count))
|
||||
|
||||
def on_textview_paste_clipboard(self, textview):
|
||||
_LOG.debug("Textview paste clipboard")
|
||||
|
||||
def on_textview_motion_notify_event(self, textview, event):
|
||||
window = textview.get_window(gtk.TEXT_WINDOW_TEXT)
|
||||
x, y = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET,
|
||||
int(event.x), int(event.y))
|
||||
iter = textview.get_iter_at_location(x, y)
|
||||
textbuffer = textview.get_buffer()
|
||||
self.match = textbuffer.match_check(iter.get_offset())
|
||||
|
||||
if self.match != self.last_match:
|
||||
start, end = textbuffer.get_bounds()
|
||||
textbuffer.remove_tag_by_name('hyperlink', start, end)
|
||||
if self.match:
|
||||
start_offset = self.match[MATCH_START]
|
||||
end_offset = self.match[MATCH_END]
|
||||
|
||||
start = textbuffer.get_iter_at_offset(start_offset)
|
||||
end = textbuffer.get_iter_at_offset(end_offset)
|
||||
|
||||
textbuffer.apply_tag_by_name('hyperlink', start, end)
|
||||
window.set_cursor(self.hand_cursor)
|
||||
else:
|
||||
window.set_cursor(self.regular_cursor)
|
||||
|
||||
self.last_match = self.match
|
||||
|
||||
textview.window.get_pointer()
|
||||
return False
|
||||
|
||||
def on_textview_button_press_event(self, textview, event):
|
||||
if ((event.type == gtk.gdk.BUTTON_PRESS) and
|
||||
(event.button == 1) and
|
||||
(event.state and gtk.gdk.CONTROL_MASK) and
|
||||
(self.match)):
|
||||
|
||||
flavor = self.match[MATCH_FLAVOR]
|
||||
url = self.match[MATCH_STRING]
|
||||
self.open_url_cb(None, url, flavor)
|
||||
|
||||
return False
|
||||
|
||||
def on_textview_populate_popup(self, textview, menu):
|
||||
"""Insert extra menuitems according to matched pattern."""
|
||||
if self.match:
|
||||
flavor = self.match[MATCH_FLAVOR]
|
||||
url = self.match[MATCH_STRING]
|
||||
|
||||
if flavor == MAIL:
|
||||
open_menu = gtk.MenuItem(_('_Send Mail To...'))
|
||||
copy_menu = gtk.MenuItem(_('Copy _E-mail Address'))
|
||||
else:
|
||||
open_menu = gtk.MenuItem(_('_Open Link'))
|
||||
copy_menu = gtk.MenuItem(_('Copy _Link Address'))
|
||||
|
||||
copy_menu.connect('activate', self.copy_url_cb, url, flavor)
|
||||
copy_menu.show()
|
||||
menu.prepend(copy_menu)
|
||||
|
||||
open_menu.connect('activate', self.open_url_cb, url, flavor)
|
||||
open_menu.show()
|
||||
menu.prepend(open_menu)
|
||||
|
||||
def on_spell_change(self, combobox, spell):
|
||||
"""Set spell checker language according to user selection."""
|
||||
lang = combobox.get_active_text()
|
||||
spell.set_active_language(lang)
|
||||
|
||||
# enable for markup
|
||||
# def open_url_cb(self, menuitem, url, flavor):
|
||||
# if not url:
|
||||
# return
|
||||
#
|
||||
# if flavor == HTTP:
|
||||
# url = 'http:' + url
|
||||
# elif flavor == MAIL:
|
||||
# if not url.startswith('mailto:'):
|
||||
# url = 'mailto:' + url
|
||||
# elif flavor == GENERAL:
|
||||
# pass
|
||||
# else:
|
||||
# return
|
||||
#
|
||||
# GrampsDisplay.url(url)
|
||||
#
|
||||
# def copy_url_cb(self, menuitem, url, flavor):
|
||||
# """Copy url to both useful selections."""
|
||||
# clipboard = gtk.Clipboard(selection="CLIPBOARD")
|
||||
# clipboard.set_text(url)
|
||||
#
|
||||
# clipboard = gtk.Clipboard(selection="PRIMARY")
|
||||
# clipboard.set_text(url)
|
||||
def open_url_cb(self, menuitem, url, flavor):
|
||||
if not url:
|
||||
return
|
||||
|
||||
if flavor == HTTP:
|
||||
url = 'http:' + url
|
||||
elif flavor == MAIL:
|
||||
if not url.startswith('mailto:'):
|
||||
url = 'mailto:' + url
|
||||
elif flavor == GENERAL:
|
||||
pass
|
||||
else:
|
||||
return
|
||||
|
||||
url(url)
|
||||
|
||||
def copy_url_cb(self, menuitem, url, flavor):
|
||||
"""Copy url to both useful selections."""
|
||||
clipboard = gtk.Clipboard(selection="CLIPBOARD")
|
||||
clipboard.set_text(url)
|
||||
|
||||
clipboard = gtk.Clipboard(selection="PRIMARY")
|
||||
clipboard.set_text(url)
|
||||
|
||||
def update_note(self):
|
||||
"""Update the Note object with current value."""
|
||||
if self.obj:
|
||||
buffer_ = self.text.get_buffer()
|
||||
(start, stop) = buffer_.get_bounds()
|
||||
text = buffer_.get_text(start, stop)
|
||||
self.obj.set(text)
|
||||
log.debug(text)
|
||||
textbuffer = self.text.get_buffer()
|
||||
text = textbuffer.get_text()
|
||||
self.obj.set_styledtext(text)
|
||||
_LOG.debug(str(text))
|
||||
|
||||
def flow_changed(self, active):
|
||||
if active:
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# 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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -20,28 +20,18 @@
|
||||
|
||||
# $Id$
|
||||
|
||||
"Handling formatted ('rich text') strings"
|
||||
"Text buffer subclassed from gtk.TextBuffer handling L{StyledText}."
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Python modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from xml.sax import saxutils, xmlreader, ContentHandler
|
||||
from xml.sax import parseString, SAXParseException
|
||||
from gettext import gettext as _
|
||||
import re
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except:
|
||||
from StringIO import StringIO
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Set up logging
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
import logging
|
||||
log = logging.getLogger(".MarkupText")
|
||||
_LOG = logging.getLogger(".StyledTextBuffer")
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
@ -50,318 +40,49 @@ log = logging.getLogger(".MarkupText")
|
||||
#-------------------------------------------------------------------------
|
||||
import gtk
|
||||
from pango import WEIGHT_BOLD, STYLE_ITALIC, UNDERLINE_SINGLE
|
||||
import gobject
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# GRAMPS modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from gen.lib import (StyledText, StyledTextTag, StyledTextTagType)
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# 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_END,
|
||||
MATCH_FLAVOR,
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# GtkSpellState class
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class GtkSpellState:
|
||||
"""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.
|
||||
|
||||
|
||||
"""
|
||||
(STATE_NONE,
|
||||
STATE_CLICKED,
|
||||
STATE_DELETED,
|
||||
STATE_INSERTING) = range(4)
|
||||
|
||||
def __init__(self, buffer):
|
||||
if not isinstance(buffer, gtk.TextBuffer):
|
||||
def __init__(self, textbuffer):
|
||||
if not isinstance(textbuffer, gtk.TextBuffer):
|
||||
raise TypeError("Init parameter must be instance of gtk.TextBuffer")
|
||||
|
||||
buffer.connect('mark-set', self.on_buffer_mark_set)
|
||||
buffer.connect('delete-range', self.on_buffer_delete_range)
|
||||
buffer.connect('insert-text', self.on_buffer_insert_text)
|
||||
buffer.connect_after('insert-text', self.after_buffer_insert_text)
|
||||
textbuffer.connect('mark-set', self.on_buffer_mark_set)
|
||||
textbuffer.connect('delete-range', self.on_buffer_delete_range)
|
||||
textbuffer.connect('insert-text', self.on_buffer_insert_text)
|
||||
textbuffer.connect_after('insert-text', self.after_buffer_insert_text)
|
||||
|
||||
self.reset_state()
|
||||
|
||||
@ -371,36 +92,37 @@ class GtkSpellState:
|
||||
self.end = 0
|
||||
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()
|
||||
if mark_name == 'gtkspell-click':
|
||||
self.state = self.STATE_CLICKED
|
||||
self.start, self.end = self.get_word_extents_from_mark(buffer, mark)
|
||||
log.debug("SpellState got start %d end %d" % (self.start, self.end))
|
||||
self.start, self.end = self.get_word_extents_from_mark(textbuffer,
|
||||
mark)
|
||||
_LOG.debug("SpellState got start %d end %d" % (self.start, self.end))
|
||||
elif mark_name == 'insert':
|
||||
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
|
||||
(start.get_offset() == self.start) and
|
||||
(end.get_offset() == self.end)):
|
||||
self.state = self.STATE_DELETED
|
||||
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:
|
||||
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:
|
||||
mark = buffer.get_mark('gtkspell-insert-start')
|
||||
insert_start = buffer.get_iter_at_mark(mark)
|
||||
mark = textbuffer.get_mark('gtkspell-insert-start')
|
||||
insert_start = textbuffer.get_iter_at_mark(mark)
|
||||
for tag in self.tags:
|
||||
buffer.apply_tag(tag, insert_start, iter)
|
||||
textbuffer.apply_tag(tag, insert_start, iter)
|
||||
|
||||
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.
|
||||
|
||||
Used to get the beginning of the word, in which user right clicked.
|
||||
@ -408,7 +130,7 @@ class GtkSpellState:
|
||||
misspelled words.
|
||||
|
||||
"""
|
||||
start = buffer.get_iter_at_mark(mark)
|
||||
start = textbuffer.get_iter_at_mark(mark)
|
||||
if not start.starts_word():
|
||||
#start.backward_word_start()
|
||||
self.backward_word_start(start)
|
||||
@ -455,14 +177,27 @@ class GtkSpellState:
|
||||
|
||||
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.
|
||||
Also translates Gramps XML markup language to gtk.TextTag's and vice versa.
|
||||
StyledTextBuffer is an interface between GRAMPS' L{StyledText} format
|
||||
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',
|
||||
'font', 'foreground', 'background',)
|
||||
@ -470,9 +205,6 @@ class MarkupBuffer(gtk.TextBuffer):
|
||||
def __init__(self):
|
||||
gtk.TextBuffer.__init__(self)
|
||||
|
||||
self.parser = MarkupParser()
|
||||
self.writer = MarkupWriter()
|
||||
|
||||
# Create fix tags.
|
||||
# Other tags (e.g. color) have to be created on the fly
|
||||
self.create_tag('bold', weight=WEIGHT_BOLD)
|
||||
@ -520,6 +252,8 @@ class MarkupBuffer(gtk.TextBuffer):
|
||||
self.bold = False
|
||||
self.underline = False
|
||||
self.font = None
|
||||
# TODO could we separate font name and size?
|
||||
##self.size = None
|
||||
self.foreground = None
|
||||
self.background = None
|
||||
|
||||
@ -545,15 +279,15 @@ class MarkupBuffer(gtk.TextBuffer):
|
||||
|
||||
# Virtual methods
|
||||
|
||||
def on_insert_text(self, buffer, iter, text, length):
|
||||
log.debug("Will insert at %d length %d" % (iter.get_offset(), length))
|
||||
def on_insert_text(self, textbuffer, iter, text, length):
|
||||
_LOG.debug("Will insert at %d length %d" % (iter.get_offset(), length))
|
||||
|
||||
# let's remember where we started inserting
|
||||
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."""
|
||||
log.debug("Have inserted at %d length %d (%s)" %
|
||||
_LOG.debug("Have inserted at %d length %d (%s)" %
|
||||
(iter.get_offset(), length, text))
|
||||
|
||||
if not length:
|
||||
@ -572,8 +306,8 @@ class MarkupBuffer(gtk.TextBuffer):
|
||||
self.apply_tag(self._find_tag_by_name(format, value),
|
||||
insert_start, iter)
|
||||
|
||||
def after_delete_range(self, buffer, start, end):
|
||||
log.debug("Deleted from %d till %d" %
|
||||
def after_delete_range(self, textbuffer, start, end):
|
||||
_LOG.debug("Deleted from %d till %d" %
|
||||
(start.get_offset(), end.get_offset()))
|
||||
|
||||
# move 'insert' marker to have the format attributes updated
|
||||
@ -592,16 +326,15 @@ class MarkupBuffer(gtk.TextBuffer):
|
||||
match = iter.next()
|
||||
self.matches.append((match.start(), match.end(),
|
||||
flavor, match.group()))
|
||||
log.debug("Matches: %d, %d: %s [%d]" %
|
||||
_LOG.debug("Matches: %d, %d: %s [%d]" %
|
||||
(match.start(), match.end(),
|
||||
match.group(), flavor))
|
||||
except StopIteration:
|
||||
break
|
||||
|
||||
|
||||
def do_mark_set(self, iter, mark):
|
||||
"""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()))
|
||||
|
||||
if mark.get_name() != 'insert':
|
||||
@ -629,72 +362,32 @@ class MarkupBuffer(gtk.TextBuffer):
|
||||
|
||||
# Private
|
||||
|
||||
def _xmltag_to_texttag(self, name, attrs):
|
||||
"""Convert XML tag to gtk.TextTag.
|
||||
|
||||
Return only the name of the TextTag.
|
||||
def _tagname_to_tagtype(self, name):
|
||||
"""Convert gtk.TextTag names to StyledTextTagType values."""
|
||||
tag2type = {
|
||||
'bold': StyledTextTagType.BOLD,
|
||||
'italic': StyledTextTagType.ITALIC,
|
||||
'underline': StyledTextTagType.UNDERLINE,
|
||||
'foreground': StyledTextTagType.FONTCOLOR,
|
||||
'background': StyledTextTagType.HIGHLIGHT,
|
||||
'font': StyledTextTagType.FONTFACE,
|
||||
}
|
||||
|
||||
@param name: name of the XML tag
|
||||
@param type: string
|
||||
@param attrs: attributes of the XML tag
|
||||
@param type: xmlreader.AttributesImpl
|
||||
@return: property of gtk.TextTag, value of property
|
||||
@rtype: [(string, string), ]
|
||||
|
||||
"""
|
||||
if name == 'b':
|
||||
return [('bold', None)]
|
||||
elif name == 'i':
|
||||
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 StyledTextTagType(tag2type[name])
|
||||
|
||||
def _tagtype_to_tagname(self, tagtype):
|
||||
"""Convert StyledTextTagType values to gtk.TextTag names."""
|
||||
type2tag = {
|
||||
StyledTextTagType.BOLD: 'bold',
|
||||
StyledTextTagType.ITALIC: 'italic',
|
||||
StyledTextTagType.UNDERLINE: 'underline',
|
||||
StyledTextTagType.FONTCOLOR: 'foreground',
|
||||
StyledTextTagType.HIGHLIGHT: 'background',
|
||||
StyledTextTagType.FONTFACE: 'font',
|
||||
}
|
||||
|
||||
return type2tag[tagtype]
|
||||
|
||||
##def get_tag_value_at_insert(self, name):
|
||||
##"""Get the value of the given tag at the insertion point."""
|
||||
##tags = self.get_iter_at_mark(self._insert).get_tags()
|
||||
@ -835,12 +528,7 @@ class MarkupBuffer(gtk.TextBuffer):
|
||||
setattr(self, action.get_name(), action.get_active())
|
||||
|
||||
def on_action_activate(self, action):
|
||||
"""Apply a format.
|
||||
|
||||
Other tags for the same format have to be removed from the range
|
||||
first otherwise XML would get messy.
|
||||
|
||||
"""
|
||||
"""Apply a format."""
|
||||
format = action.get_name()
|
||||
|
||||
if format == 'foreground':
|
||||
@ -870,11 +558,11 @@ class MarkupBuffer(gtk.TextBuffer):
|
||||
value = font_selection.fontsel.get_font_name()
|
||||
font_selection.destroy()
|
||||
else:
|
||||
log.debug("unknown format: '%s'" % format)
|
||||
_LOG.debug("unknown format: '%s'" % format)
|
||||
return
|
||||
|
||||
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)
|
||||
self.remove_format_from_selection(format)
|
||||
@ -905,64 +593,51 @@ class MarkupBuffer(gtk.TextBuffer):
|
||||
|
||||
# Public API
|
||||
|
||||
def set_text(self, xmltext):
|
||||
def set_text(self, r_text):
|
||||
"""Set the content of the buffer with markup tags."""
|
||||
try:
|
||||
parseString(xmltext.encode('utf-8'), self.parser)
|
||||
text = self.parser.content
|
||||
except:
|
||||
# if parse fails remove all tags and use clear text instead
|
||||
text = re.sub(r'(<.*?>)', '', xmltext)
|
||||
text = saxutils.unescape(text)
|
||||
|
||||
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:
|
||||
gtk.TextBuffer.set_text(self, str(r_text))
|
||||
|
||||
r_tags = r_text.get_tags()
|
||||
for r_tag in r_tags:
|
||||
tagname = self._tagtype_to_tagname(int(r_tag.name))
|
||||
g_tag = self._find_tag_by_name(tagname, r_tag.value)
|
||||
if g_tag is not None:
|
||||
for (start, end) in r_tag.ranges:
|
||||
start_iter = self.get_iter_at_offset(start)
|
||||
end_iter = self.get_iter_at_offset(end)
|
||||
tag = self._find_tag_by_name(texttag_name, value)
|
||||
if tag is not None:
|
||||
self.apply_tag(tag, start_iter, end_iter)
|
||||
|
||||
self.apply_tag(g_tag, start_iter, end_iter)
|
||||
|
||||
def get_text(self, start=None, end=None, include_hidden_chars=True):
|
||||
"""Return the buffer text with xml markup tags.
|
||||
|
||||
If no markup was applied returns clean text
|
||||
(i.e. without even root tags).
|
||||
|
||||
"""
|
||||
# get the clear text from the buffer
|
||||
"""Return the buffer text."""
|
||||
if not start:
|
||||
start = self.get_start_iter()
|
||||
if not end:
|
||||
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
|
||||
texttag = self.get_tag_from_range()
|
||||
g_tags = self.get_tag_from_range()
|
||||
r_tags = []
|
||||
|
||||
if len(texttag):
|
||||
# convert the texttags to xml elements
|
||||
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))
|
||||
for g_tagname, g_ranges in g_tags.items():
|
||||
name_value = g_tagname.split(' ', 1)
|
||||
|
||||
# feed the elements into the xml writer
|
||||
self.writer.generate(txt, xml_elements)
|
||||
txt = self.writer.content
|
||||
if len(name_value) == 1:
|
||||
name = name_value[0]
|
||||
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):
|
||||
"""Add a pattern to look for in the text."""
|
||||
@ -972,11 +647,7 @@ class MarkupBuffer(gtk.TextBuffer):
|
||||
def match_check(self, pos):
|
||||
"""Check if pos falls into any of the matched patterns."""
|
||||
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 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
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(".GrampsDb")
|
||||
_LOG = logging.getLogger(".GrampsDb")
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
@ -56,7 +56,7 @@ from gen.db.cursor import GrampsCursor
|
||||
import Errors
|
||||
|
||||
_MINVERSION = 9
|
||||
_DBVERSION = 13
|
||||
_DBVERSION = 14
|
||||
|
||||
IDTRANS = "person_id"
|
||||
FIDTRANS = "family_id"
|
||||
@ -1485,7 +1485,7 @@ class GrampsDBDir(GrampsDbBase, UpdateCallback):
|
||||
# under certain circumstances during a database reload,
|
||||
# data_map can be none. If so, then don't report an error
|
||||
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:
|
||||
newobj = InstanceType(class_type)
|
||||
newobj.unserialize(data)
|
||||
@ -1665,13 +1665,42 @@ class GrampsDBDir(GrampsDbBase, UpdateCallback):
|
||||
def gramps_upgrade(self, callback=None):
|
||||
UpdateCallback.__init__(self, callback)
|
||||
|
||||
# version = self.metadata.get('version', default=_MINVERSION)
|
||||
version = self.metadata.get('version', default=_MINVERSION)
|
||||
|
||||
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"
|
||||
|
||||
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):
|
||||
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.markertype import MarkerType
|
||||
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 _
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# _init_map function
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
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:
|
||||
new_data = dict([ (item[key_col], item[data_col])
|
||||
for item in data
|
||||
if not item[0] in blacklist ])
|
||||
new_data = dict([(item[key_col], item[data_col])
|
||||
for item in data if not item[0] in blacklist])
|
||||
else:
|
||||
new_data = dict([ (item[key_col], item[data_col])
|
||||
for item in data ])
|
||||
new_data = dict([(item[key_col], item[data_col]) for item in data])
|
||||
|
||||
return new_data
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# GrampsTypeMeta class
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class GrampsTypeMeta(type):
|
||||
"""Metaclass for L{GrampsType}.
|
||||
|
||||
@ -55,15 +62,30 @@ class GrampsTypeMeta(type):
|
||||
type.__init__(mcs, name, bases, namespace)
|
||||
mcs.__class_init__(namespace)
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# GrampsType class
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class GrampsType(object):
|
||||
"""Base class for all Gramps object types.
|
||||
|
||||
_DATAMAP is a 3-tuple like (index, localized_string, english_string)
|
||||
_BLACKLIST is a list of indices to ignore (obsolete/retired entries)
|
||||
(gramps policy is never to delete type values,
|
||||
or reuse the name (TOKEN) of any specific type value)
|
||||
@cvar _DATAMAP: 3-tuple like (index, localized_string, english_string).
|
||||
@type _DATAMAP: list
|
||||
@cvar _BLACKLIST: List of indices to ignore (obsolete/retired entries).
|
||||
(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
|
||||
_DEFAULT = 0
|
||||
@ -89,8 +111,8 @@ class GrampsType(object):
|
||||
self.set(value)
|
||||
|
||||
def __set_tuple(self, value):
|
||||
v,s = self._DEFAULT,u''
|
||||
if len(value) > 0:
|
||||
v, s = self._DEFAULT, u''
|
||||
if value:
|
||||
v = value[0]
|
||||
if len(value) > 1:
|
||||
s = value[1]
|
||||
@ -148,15 +170,11 @@ class GrampsType(object):
|
||||
return self._I2EMAP[self.val]
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
def __str__(self):
|
||||
@ -172,16 +190,12 @@ class GrampsType(object):
|
||||
return self._I2SMAP
|
||||
|
||||
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()
|
||||
if (i != self._CUSTOM) and s.strip()]
|
||||
|
||||
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()
|
||||
if (i != self._CUSTOM) and s.strip()]
|
||||
|
||||
|
@ -32,6 +32,7 @@ Note class for GRAMPS.
|
||||
from gen.lib.primaryobj import BasicPrimaryObject
|
||||
from gen.lib.notetype import NoteType
|
||||
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):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
"""Define a text note.
|
||||
|
||||
FLOWED = 0
|
||||
FORMATTED = 1
|
||||
Starting from GRAMPS 3.1 Note object stores the text in L{StyledText}
|
||||
instance, thus it can have text formatting information.
|
||||
|
||||
def __init__(self, text = ""):
|
||||
"""
|
||||
Create a new Note object, initializing from the passed string.
|
||||
"""
|
||||
To get and set only the clear text of the note use the L{get} and L{set}
|
||||
methods.
|
||||
|
||||
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)
|
||||
self.text = text
|
||||
self.text = StyledText(text)
|
||||
self.format = Note.FLOWED
|
||||
self.type = NoteType()
|
||||
|
||||
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, self.format,
|
||||
return (self.handle, self.gramps_id, self.text.serialize(), self.format,
|
||||
self.type.serialize(), self.change, self.marker.serialize(),
|
||||
self.private)
|
||||
|
||||
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, self.text, self.format,
|
||||
(self.handle, self.gramps_id, the_text, self.format,
|
||||
the_type, self.change, the_marker, self.private) = data
|
||||
|
||||
self.text = StyledText()
|
||||
self.text.unserialize(the_text)
|
||||
self.marker = MarkerType()
|
||||
self.marker.unserialize(the_marker)
|
||||
self.type = NoteType()
|
||||
self.type.unserialize(the_type)
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
return [self.text]
|
||||
return [str(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
|
||||
|
||||
"""
|
||||
self.text = text
|
||||
self.text = StyledText(text)
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
text = self.text
|
||||
return text
|
||||
return str(self.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.
|
||||
@type text: str
|
||||
@type text: str or L{StyledText}
|
||||
|
||||
"""
|
||||
self.text = self.text + text
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
self.format = format
|
||||
|
||||
def get_format(self):
|
||||
"""
|
||||
Return the format of the note.
|
||||
"""Return the format of the note.
|
||||
|
||||
The value can either indicate Flowed or Preformatted.
|
||||
|
||||
@returns: 0 indicates Flowed, 1 indicates Preformated
|
||||
@rtype: int
|
||||
|
||||
"""
|
||||
return self.format
|
||||
|
||||
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
|
||||
@type the_type: str
|
||||
|
||||
"""
|
||||
self.type.set(the_type)
|
||||
|
||||
def get_type(self):
|
||||
"""
|
||||
Get descriptive type of the Note.
|
||||
"""Get descriptive type of the Note.
|
||||
|
||||
@returns: the descriptive type of the Note
|
||||
@rtype: str
|
||||
|
||||
"""
|
||||
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)
|
Loading…
x
Reference in New Issue
Block a user