428 lines
15 KiB
Python
428 lines
15 KiB
Python
#
|
|
# Gramps - a GTK+/GNOME based genealogy program
|
|
#
|
|
# Copyright (C) 2000-2007 Donald N. Allingham
|
|
# Copyright (C) 2009 Gary Burton
|
|
# Copyright (C) 2011 Tim G L Lyons
|
|
#
|
|
# 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$
|
|
|
|
"""
|
|
Non GUI/GTK related utility functions
|
|
"""
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Standard python modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
import os
|
|
import sys
|
|
import locale
|
|
import random
|
|
import time
|
|
import uuid
|
|
import logging
|
|
LOG = logging.getLogger(".")
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Gramps modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
from gen.display.name import displayer as name_displayer
|
|
import gen.lib
|
|
from gen.errors import DatabaseError
|
|
from gen.datehandler import displayer as date_displayer, codeset
|
|
from gen.config import config
|
|
from const import GRAMPS_UUID, IMAGE_DIR
|
|
from gen.constfunc import mac, win
|
|
from gen.ggettext import sgettext as _
|
|
from gen.utils.name import family_name
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Integer to String mappings for constants
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
gender = {
|
|
gen.lib.Person.MALE : _("male"),
|
|
gen.lib.Person.FEMALE : _("female"),
|
|
gen.lib.Person.UNKNOWN : _("gender|unknown"),
|
|
}
|
|
|
|
def format_gender( type):
|
|
return gender.get(type[0], _("Invalid"))
|
|
|
|
confidence = {
|
|
gen.lib.Citation.CONF_VERY_HIGH : _("Very High"),
|
|
gen.lib.Citation.CONF_HIGH : _("High"),
|
|
gen.lib.Citation.CONF_NORMAL : _("Normal"),
|
|
gen.lib.Citation.CONF_LOW : _("Low"),
|
|
gen.lib.Citation.CONF_VERY_LOW : _("Very Low"),
|
|
}
|
|
|
|
family_rel_descriptions = {
|
|
gen.lib.FamilyRelType.MARRIED : _("A legal or common-law relationship "
|
|
"between a husband and wife"),
|
|
gen.lib.FamilyRelType.UNMARRIED : _("No legal or common-law relationship "
|
|
"between man and woman"),
|
|
gen.lib.FamilyRelType.CIVIL_UNION : _("An established relationship between "
|
|
"members of the same sex"),
|
|
gen.lib.FamilyRelType.UNKNOWN : _("Unknown relationship between a man "
|
|
"and woman"),
|
|
gen.lib.FamilyRelType.CUSTOM : _("An unspecified relationship between "
|
|
"a man and woman"),
|
|
}
|
|
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# modified flag
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
_history_brokenFlag = 0
|
|
|
|
def history_broken():
|
|
global _history_brokenFlag
|
|
_history_brokenFlag = 1
|
|
|
|
data_recover_msg = _('The data can only be recovered by Undo operation '
|
|
'or by quitting with abandoning changes.')
|
|
|
|
def fix_encoding(value, errors='strict'):
|
|
# The errors argument specifies the response when the input string can't be
|
|
# converted according to the encoding's rules. Legal values for this
|
|
# argument are 'strict' (raise a UnicodeDecodeError exception), 'replace'
|
|
# (add U+FFFD, 'REPLACEMENT CHARACTER'), or 'ignore' (just leave the
|
|
# character out of the Unicode result).
|
|
if not isinstance(value, unicode):
|
|
try:
|
|
return unicode(value)
|
|
except:
|
|
try:
|
|
if mac():
|
|
codeset = locale.getlocale()[1]
|
|
else:
|
|
codeset = locale.getpreferredencoding()
|
|
except:
|
|
codeset = "UTF-8"
|
|
return unicode(value, codeset, errors)
|
|
else:
|
|
return value
|
|
|
|
def xml_lang():
|
|
loc = locale.getlocale()
|
|
if loc[0] is None:
|
|
return ""
|
|
else:
|
|
return loc[0].replace('_', '-')
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# force_unicode
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
|
|
def force_unicode(n):
|
|
if not isinstance(n, unicode):
|
|
return (unicode(n).lower(), unicode(n))
|
|
else:
|
|
return (n.lower(), n)
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Clears the modified flag. Should be called after data is saved.
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
def clearHistory_broken():
|
|
global _history_brokenFlag
|
|
_history_brokenFlag = 0
|
|
|
|
def wasHistory_broken():
|
|
return _history_brokenFlag
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# String Encoding functions
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
|
|
def encodingdefs():
|
|
"""
|
|
4 functions are defined to obtain a byte string that can be used as
|
|
sort key and is locale aware. Do not print the sortkey, it is a sortable
|
|
string, but is not a human readable correct string!
|
|
When gtk is defined, one can avoid some function calls as then the default
|
|
python encoding is not ascii but utf-8, so use the gtk functions in those
|
|
cases.
|
|
|
|
conv_utf8_tosrtkey: convert a utf8 encoded string to sortkey usable string
|
|
|
|
conv_unicode_tosrtkey: convert a unicode object to sortkey usable string
|
|
|
|
conv_utf8_tosrtkey_ongtk: convert a utf8 encoded string to sortkey usable
|
|
string when gtk is loaded or utf-8 is default python encoding
|
|
|
|
conv_unicode_tosrtkey_ongtk: convert a unicode object to sortkey usable
|
|
string when gtk is loaded or utf-8 is default python encoding
|
|
"""
|
|
pass
|
|
|
|
if win():
|
|
# python encoding is ascii, but C functions need to receive the
|
|
# windows codeset, so convert over to it
|
|
conv_utf8_tosrtkey = lambda x: locale.strxfrm(x.decode("utf-8").encode(
|
|
codeset))
|
|
conv_unicode_tosrtkey = lambda x: locale.strxfrm(x.encode(codeset))
|
|
#when gtk is imported the python defaultencoding is utf-8,
|
|
#so no need to specify it
|
|
conv_utf8_tosrtkey_ongtk = lambda x: locale.strxfrm(unicode(x).encode(
|
|
codeset))
|
|
conv_unicode_tosrtkey_ongtk = lambda x: locale.strxfrm(x.encode(codeset,'replace'))
|
|
else:
|
|
# on unix C functions need to receive utf-8. Default conversion would
|
|
# use ascii, so it is needed to be explicit about the resulting encoding
|
|
conv_utf8_tosrtkey = lambda x: locale.strxfrm(x)
|
|
conv_unicode_tosrtkey = lambda x: locale.strxfrm(x.encode("utf-8"))
|
|
# when gtk loaded, default encoding (sys.getdefaultencoding ) is utf-8,
|
|
# so default conversion happens with utf-8
|
|
conv_utf8_tosrtkey_ongtk = lambda x: locale.strxfrm(x)
|
|
conv_unicode_tosrtkey_ongtk = lambda x: locale.strxfrm(x)
|
|
|
|
|
|
def title(n):
|
|
return '<span weight="bold" size="larger">%s</span>' % n
|
|
|
|
def set_title_label(xmlobj, t):
|
|
title_label = xmlobj.get_widget('title')
|
|
title_label.set_text('<span weight="bold" size="larger">%s</span>' % t)
|
|
title_label.set_use_markup(True)
|
|
|
|
from warnings import warn
|
|
def set_titles(window, title, t, msg=None):
|
|
warn('The Utils.set_titles is deprecated. Use ManagedWindow methods')
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# create_id
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
rand = random.Random(time.time())
|
|
|
|
def create_id():
|
|
return "%08x%08x" % ( int(time.time()*10000),
|
|
rand.randint(0, sys.maxint))
|
|
|
|
def create_uid(self, handle=None):
|
|
if handle:
|
|
uid = uuid.uuid5(GRAMPS_UUID, handle)
|
|
else:
|
|
uid = uuid.uuid4()
|
|
return uid.hex.upper()
|
|
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
#
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
def profile(func, *args):
|
|
import hotshot.stats
|
|
|
|
prf = hotshot.Profile('mystats.profile')
|
|
print "Start"
|
|
prf.runcall(func, *args)
|
|
print "Finished"
|
|
prf.close()
|
|
print "Loading profile"
|
|
stats = hotshot.stats.load('mystats.profile')
|
|
print "done"
|
|
stats.strip_dirs()
|
|
stats.sort_stats('time', 'calls')
|
|
stats.print_stats(100)
|
|
stats.print_callers(100)
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Config-based functions
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
def get_researcher():
|
|
"""
|
|
Return a new database owner with the default values from the config file.
|
|
"""
|
|
name = config.get('researcher.researcher-name')
|
|
address = config.get('researcher.researcher-addr')
|
|
locality = config.get('researcher.researcher-locality')
|
|
city = config.get('researcher.researcher-city')
|
|
state = config.get('researcher.researcher-state')
|
|
country = config.get('researcher.researcher-country')
|
|
post_code = config.get('researcher.researcher-postal')
|
|
phone = config.get('researcher.researcher-phone')
|
|
email = config.get('researcher.researcher-email')
|
|
|
|
owner = gen.lib.Researcher()
|
|
owner.set_name(name)
|
|
owner.set_address(address)
|
|
owner.set_locality(locality)
|
|
owner.set_city(city)
|
|
owner.set_state(state)
|
|
owner.set_country(country)
|
|
owner.set_postal_code(post_code)
|
|
owner.set_phone(phone)
|
|
owner.set_email(email)
|
|
|
|
return owner
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Function to return the name of the main participant of an event
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
def get_participant_from_event(db, event_handle, all_=False):
|
|
"""
|
|
Obtain the first primary or family participant to an event we find in the
|
|
database. Note that an event can have more than one primary or
|
|
family participant, only one is returned, adding ellipses if there are
|
|
more. If the all_ parameter is true a comma-space separated string with
|
|
the names of all primary participants is returned and no ellipses is used.
|
|
"""
|
|
participant = ""
|
|
ellipses = False
|
|
result_list = list(db.find_backlink_handles(event_handle,
|
|
include_classes=['Person', 'Family']))
|
|
#obtain handles without duplicates
|
|
people = set([x[1] for x in result_list if x[0] == 'Person'])
|
|
families = set([x[1] for x in result_list if x[0] == 'Family'])
|
|
for personhandle in people:
|
|
person = db.get_person_from_handle(personhandle)
|
|
if not person:
|
|
continue
|
|
for event_ref in person.get_event_ref_list():
|
|
if event_handle == event_ref.ref and \
|
|
event_ref.get_role().is_primary():
|
|
if participant:
|
|
if all_:
|
|
participant += ', %s' % name_displayer.display(person)
|
|
else:
|
|
ellipses = True
|
|
else:
|
|
participant = name_displayer.display(person)
|
|
break
|
|
if ellipses:
|
|
break
|
|
if ellipses:
|
|
return _('%s, ...') % participant
|
|
|
|
for familyhandle in families:
|
|
family = db.get_family_from_handle(familyhandle)
|
|
for event_ref in family.get_event_ref_list():
|
|
if event_handle == event_ref.ref and \
|
|
event_ref.get_role().is_family():
|
|
if participant:
|
|
if all_:
|
|
participant += ', %s' % family_name(family, db)
|
|
else:
|
|
ellipses = True
|
|
else:
|
|
participant = family_name(family, db)
|
|
break
|
|
if ellipses:
|
|
break
|
|
|
|
if ellipses:
|
|
return _('%s, ...') % participant
|
|
else:
|
|
return participant
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Function to return a label to display the active object in the status bar
|
|
# and to describe bookmarked objects.
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
def navigation_label(db, nav_type, handle):
|
|
|
|
label = None
|
|
obj = None
|
|
if nav_type == 'Person':
|
|
obj = db.get_person_from_handle(handle)
|
|
if obj:
|
|
label = name_displayer.display(obj)
|
|
elif nav_type == 'Family':
|
|
obj = db.get_family_from_handle(handle)
|
|
if obj:
|
|
label = family_name(obj, db)
|
|
elif nav_type == 'Event':
|
|
obj = db.get_event_from_handle(handle)
|
|
if obj:
|
|
try:
|
|
who = get_participant_from_event(db, handle)
|
|
except:
|
|
# get_participants_from_event fails when called during a magic
|
|
# batch transaction because find_backlink_handles tries to
|
|
# access the reference_map_referenced_map which doesn't exist
|
|
# under those circumstances. Since setting the navigation_label
|
|
# is inessential, just accept this and go on.
|
|
who = ''
|
|
desc = obj.get_description()
|
|
label = obj.get_type()
|
|
if desc:
|
|
label = '%s - %s' % (label, desc)
|
|
if who:
|
|
label = '%s - %s' % (label, who)
|
|
elif nav_type == 'Place':
|
|
obj = db.get_place_from_handle(handle)
|
|
if obj:
|
|
label = obj.get_title()
|
|
elif nav_type == 'Source':
|
|
obj = db.get_source_from_handle(handle)
|
|
if obj:
|
|
label = obj.get_title()
|
|
elif nav_type == 'Citation':
|
|
obj = db.get_citation_from_handle(handle)
|
|
if obj:
|
|
label = obj.get_page()
|
|
src = db.get_source_from_handle(obj.get_reference_handle())
|
|
if src:
|
|
label = src.get_title() + " " + label
|
|
elif nav_type == 'Repository':
|
|
obj = db.get_repository_from_handle(handle)
|
|
if obj:
|
|
label = obj.get_name()
|
|
elif nav_type == 'Media' or nav_type == 'MediaObject':
|
|
obj = db.get_object_from_handle(handle)
|
|
if obj:
|
|
label = obj.get_description()
|
|
elif nav_type == 'Note':
|
|
obj = db.get_note_from_handle(handle)
|
|
if obj:
|
|
label = obj.get()
|
|
# When strings are cut, make sure they are unicode
|
|
#otherwise you may end of with cutting within an utf-8 sequence
|
|
label = unicode(label)
|
|
label = " ".join(label.split())
|
|
if len(label) > 40:
|
|
label = label[:40] + "..."
|
|
|
|
if label and obj:
|
|
label = '[%s] %s' % (obj.get_gramps_id(), label)
|
|
|
|
return (label, obj)
|