#
# Gramps - a GTK+/GNOME based genealogy program
#
# 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
# 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: Utils.py 10600 2008-04-20 03:05:02Z pez4brian $

#-------------------------------------------------------------------------
#
# Standard python modules
#
#-------------------------------------------------------------------------
import os
import sys
import locale
import random
import time

from gettext import gettext as _

#-------------------------------------------------------------------------
#
# GNOME/GTK
#
#-------------------------------------------------------------------------
import gtk

#-------------------------------------------------------------------------
#
# Gramps modules
#
#-------------------------------------------------------------------------
import Mime
from BasicUtils import name_displayer
import gen.lib
import Errors
from QuestionDialog import WarningDialog

from const import TEMP_DIR, USER_HOME
import shutil

#-------------------------------------------------------------------------
#
# Constants from config .ini keys
#
#-------------------------------------------------------------------------
#obtain the values once, they do not change!
try:
    import Config
    _MAX_AGE_PROB_ALIVE   = Config.get(Config.MAX_AGE_PROB_ALIVE)
    _MAX_SIB_AGE_DIFF     = Config.get(Config.MAX_SIB_AGE_DIFF)
    _MIN_GENERATION_YEARS = Config.get(Config.MIN_GENERATION_YEARS)
    _AVG_GENERATION_GAP   = Config.get(Config.AVG_GENERATION_GAP)
except ImportError:
    # Utils used as module not part of GRAMPS
    _MAX_AGE_PROB_ALIVE   = 110
    _MAX_SIB_AGE_DIFF     = 20
    _MIN_GENERATION_YEARS = 13
    _AVG_GENERATION_GAP   = 20

#-------------------------------------------------------------------------
#
# Integer to String  mappings for constants
#
#-------------------------------------------------------------------------
gender = {
    gen.lib.Person.MALE    : _("male"), 
    gen.lib.Person.FEMALE  : _("female"), 
    gen.lib.Person.UNKNOWN : _("unknown"), 
    }

def format_gender( type):
    return gender.get(type[0], _("Invalid"))

confidence = {
    gen.lib.SourceRef.CONF_VERY_HIGH : _("Very High"), 
    gen.lib.SourceRef.CONF_HIGH      : _("High"), 
    gen.lib.SourceRef.CONF_NORMAL    : _("Normal"), 
    gen.lib.SourceRef.CONF_LOW       : _("Low"), 
    gen.lib.SourceRef.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 "
                                         "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):
    if type(value) != unicode:
        try:
            return unicode(value)
        except:
            try:
                codeset = locale.getpreferredencoding()
            except:
                codeset = "UTF-8"
            return unicode(value, codeset)
    else:
        return value

def xml_lang():
    loc = locale.getlocale()
    if loc[0] == None:
        return ""
    else:
        return loc[0].replace('_', '-')

#-------------------------------------------------------------------------
#
# force_unicode
#
#-------------------------------------------------------------------------

def force_unicode(n):
    if type(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

#-------------------------------------------------------------------------
#
# Short hand function to return either the person's name, or an empty
# string if the person is None
#
#-------------------------------------------------------------------------

def family_name(family, db, noname=_("unknown")):
    """Builds a name for the family from the parents names"""

    father_handle = family.get_father_handle()
    mother_handle = family.get_mother_handle()
    father = db.get_person_from_handle(father_handle)
    mother = db.get_person_from_handle(mother_handle)
    if father and mother:
        fname = name_displayer.display(father)
        mname = name_displayer.display(mother)
        name = _("%(father)s and %(mother)s") % {
                    "father" : fname, 
                    "mother" : mname}
    elif father:
        name = name_displayer.display(father)
    elif mother:
        name = name_displayer.display(mother)
    else:
        name = noname
    return name

def family_upper_name(family, db):
    """Builds a name for the family from the parents names"""
    father_handle = family.get_father_handle()
    mother_handle = family.get_mother_handle()
    father = db.get_person_from_handle(father_handle)
    mother = db.get_person_from_handle(mother_handle)
    if father and mother:
        fname = father.get_primary_name().get_upper_name()
        mname = mother.get_primary_name().get_upper_name()
        name = _("%(father)s and %(mother)s") % {
            'father' : fname, 
            'mother' : mname 
            }
    elif father:
        name = father.get_primary_name().get_upper_name()
    else:
        name = mother.get_primary_name().get_upper_name()
    return name
        
#-------------------------------------------------------------------------
#
# 
#
#-------------------------------------------------------------------------
def redraw_list(dlist, clist, func):
    clist.clear()
    
    index = 0
    for obj in dlist:
        col = 0
        node = clist.append()
        for data in func(obj):
            clist.set_value(node, col, data)
            col = col + 1
        index = index + 1
    return index

#-------------------------------------------------------------------------
#
#
#
#-------------------------------------------------------------------------
def delete_selected(obj, dlist):
    sel = obj.get_selection()
    model, node = sel.get_selected()
    if node:
        index = model.get_path(node)[0]
        del dlist[index]
    return 1

#-------------------------------------------------------------------------
#
#
#
#-------------------------------------------------------------------------
def add_menuitem(menu, msg, obj, func):
    item = gtk.MenuItem(msg)
    item.set_data('o', obj)
    item.connect("activate", func)
    item.show()
    menu.append(item)

#-------------------------------------------------------------------------
#
#
#
#-------------------------------------------------------------------------
def view_photo(photo, db):
    """
    photo is a mediaobject, this utility launches a viewing application
    """
    mime_type = photo.get_mime_type()
    try:
        data = Mime.get_application(mime_type)
        prog = data[0]
    except:
        return
    launch(prog, media_path_full(db, photo.get_path()))

def find_file( filename):
    # try the filename we got
    try:
        fname = filename
        if os.path.isfile( filename):
            return( filename)
    except:
        pass
    
    # Build list of alternate encodings
    encodings = set()

    for enc in [sys.getfilesystemencoding, locale.getpreferredencoding]:
        try:
            encodings.add(enc)
        except:
            pass
    encodings.add('UTF-8')
    encodings.add('ISO-8859-1')

    for enc in encodings:
        try:
            fname = filename.encode(enc)
            if os.path.isfile( fname):
                return fname
        except:
            pass

    # not found
    return ''

def find_folder( filename):
    # try the filename we got
    try:
        fname = filename
        if os.path.isdir( filename):
            return( filename)
    except:
        pass
    
    # Build list of elternate encodings
    try:
        encodings = [sys.getfilesystemencoding(), 
                     locale.getpreferredencoding(), 
                     'UTF-8', 'ISO-8859-1']
    except:
        encodings = [sys.getfilesystemencoding(), 'UTF-8', 'ISO-8859-1']
    encodings = list(set(encodings))
    for enc in encodings:
        try:
            fname = filename.encode(enc)
            if os.path.isdir( fname):
                return fname
        except:
            pass

    # not found
    return ''

def get_unicode_path(path):
    """
    Return the Unicode version of a path string.

    @type  path: str
    @param path: The path to be converted to Unicode
    @rtype:      unicode
    @return:     The Unicode version of path.
    """
    if os.sys.platform == "win32":
        return unicode(path)
    else:
        return unicode(path,sys.getfilesystemencoding())

#-------------------------------------------------------------------------
#
#
#
#-------------------------------------------------------------------------
def build_string_optmenu(mapping, start_val):
    index = 0
    start_index = 0
    keys = mapping.keys()
    keys.sort()
    myMenu = gtk.Menu()

    for key in keys:
        if key == "default":
            menuitem = gtk.MenuItem(_("default"))
        else:
            menuitem = gtk.MenuItem(key)
        menuitem.set_data("d", mapping[key])
        menuitem.set_data("l", key)
        menuitem.show()
        myMenu.append(menuitem)
        if key == start_val:
            start_index = index
        index = index + 1
    
    if start_index:
        myMenu.set_active(start_index)
    return myMenu


#-------------------------------------------------------------------------
#
#
#
#-------------------------------------------------------------------------
def build_columns(tree, list):
    cnum = 0
    for name in list:
        renderer = gtk.CellRendererText()
        renderer.set_fixed_height_from_font(1)
        column = gtk.TreeViewColumn(name[0], renderer, text=cnum)
        column.set_min_width(name[1])
        if name[2] >= 0:
            column.set_sort_column_id(name[2])
        if name[0] == '':
            column.set_clickable(True)
            column.set_visible(False)
        cnum = cnum + 1
        tree.append_column(column)

#-------------------------------------------------------------------------
#
#  Iterate over ancestors.
#
#-------------------------------------------------------------------------
def for_each_ancestor(db, start, func, data):
    """
    Recursively iterate (breadth-first) over ancestors of
    people listed in start.
    Call func(data, pid) for the Id of each person encountered.
    Exit and return 1, as soon as func returns true.
    Return 0 otherwise.
    """
    todo = start
    done_ids = set()
    while len(todo):
        p_handle = todo.pop()
        p = db.get_person_from_handle(p_handle)
        # Don't process the same handle twice.  This can happen
        # if there is a cycle in the database, or if the
        # initial list contains X and some of X's ancestors.
        if p_handle in done_ids:
            continue
        done_ids.add(p_handle)
        if func(data, p_handle):
            return 1
        for fam_handle in p.get_parent_family_handle_list():
            fam = db.get_family_from_handle(fam_handle)
            if fam:
                f_handle = fam.get_father_handle()
                m_handle = fam.get_mother_handle()
                if f_handle: todo.append(f_handle)
                if m_handle: todo.append(m_handle)
    return 0

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

def gfloat(val):
    """Convert to floating number, taking care of possible locale differences.
    
    Useful for reading float values from text entry fields 
    while under non-English locale.
    """

    try:
        return float(val)
    except:
        try:
            return float(val.replace('.', ', '))
        except:
            return float(val.replace(', ', '.'))
    return 0.0

def gformat(val):
    """Performs ('%.3f' % val) formatting with the resulting string always 
    using dot ('.') as a decimal point.
    
    Useful for writing float values into XML when under non-English locale.
    """

    decimal_point = locale.localeconv()['decimal_point']
    return_val = "%.3f" % val
    return return_val.replace(decimal_point, '.')

def search_for(name):
    if name.startswith( '"' ):
        name = name.split('"')[1]
    else:
        name = name.split()[0]
    if os.sys.platform == "win32":
        for i in os.environ['PATH'].split(';'):
            fname = os.path.join(i, name)
            if os.access(fname, os.X_OK) and not os.path.isdir(fname):
                return 1
    else:
        for i in os.environ['PATH'].split(':'):
            fname = os.path.join(i, name)
            if os.access(fname, os.X_OK) and not os.path.isdir(fname):
                return 1
    return 0
                  
#-------------------------------------------------------------------------
#
#  Change label appearance
#
#-------------------------------------------------------------------------
def bold_label(label, widget=None):
    if label.__class__ == gtk.Label:
        text = unicode(label.get_text())
        text = text.replace('<i>', '')
        text = text.replace('</i>', '')
        label.set_text("<b>%s</b>" % text )
        label.set_use_markup(True)
    else:
        clist = label.get_children()
        text = unicode(clist[1].get_text())
        text = text.replace('<i>', '')
        text = text.replace('</i>', '')
        clist[0].show()
        clist[1].set_text("<b>%s</b>" % text )
        clist[1].set_use_markup(True)
    if widget:
        widget.window.set_cursor(None)
        
def unbold_label(label, widget=None):
    if label.__class__ == gtk.Label:
        text = unicode(label.get_text())
        text = text.replace('<b>', '')
        text = text.replace('</b>', '')
        text = text.replace('<i>', '')
        text = text.replace('</i>', '')
        label.set_text(text)
        label.set_use_markup(False)
    else:
        clist = label.get_children()
        text = unicode(clist[1].get_text())
        text = text.replace('<b>', '')
        text = text.replace('</b>', '')
        text = text.replace('<i>', '')
        text = text.replace('</i>', '')
        clist[0].hide()
        clist[1].set_text(text)
        clist[1].set_use_markup(False)
    if widget:
        widget.window.set_cursor(None)

def temp_label(label, widget=None):
    if label.__class__ == gtk.Label:
        text = unicode(label.get_text())
        text = text.replace('<b>', '')
        text = text.replace('</b>', '')
        label.set_text("<i>%s</i>" % text )
        label.set_use_markup(True)
    else:
        clist = label.get_children()
        text = unicode(clist[1].get_text())
        text = text.replace('<b>', '')
        text = text.replace('</b>', '')
        clist[0].hide()
        clist[1].set_text("<i>%s</i>" % text )
        clist[1].set_use_markup(True)
    if widget:
        widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))

#-------------------------------------------------------------------------
#
# create_id
#
#-------------------------------------------------------------------------
rand = random.Random(time.time())

def create_id():
    return "%08x%08x" % ( int(time.time()*10000), 
                          rand.randint(0, sys.maxint))

#-------------------------------------------------------------------------
#
# probably_alive
#
#-------------------------------------------------------------------------
def probably_alive(person, db, current_date=None, limit=0):
    """Return true if the person may be alive on current_date.

    This works by a process of emlimination. If we can't find a good
    reason to believe that someone is dead then we assume they must
    be alive.

    current_date - a date object that is not estimated or modified
                   (defaults to today)
    limit        - number of years to check beyond death_date
    """
    if not current_date:
        current_date = gen.lib.Date()
        # yr, mon, day:
        current_date.set_yr_mon_day(*time.localtime(time.time())[0:3])

    death_date = None
    # If the recorded death year is before current year then
    # things are simple.
    death_ref = person.get_death_ref()
    if death_ref and death_ref.get_role() == gen.lib.EventRoleType.PRIMARY:
        death = db.get_event_from_handle(death_ref.ref)
        if death.get_date_object().get_start_date() != gen.lib.Date.EMPTY:
            death_date = death.get_date_object()
            if death_date.copy_offset_ymd(year=limit).match(current_date, "<<"):
                return False

    # Look for Cause Of Death, Burial or Cremation events.
    # These are fairly good indications that someone's not alive.
    for ev_ref in person.get_primary_event_ref_list():
        ev = db.get_event_from_handle(ev_ref.ref)
        if ev and int(ev.get_type()) in [gen.lib.EventType.CAUSE_DEATH, 
                                         gen.lib.EventType.BURIAL, 
                                         gen.lib.EventType.CREMATION]:
            if not death_date:
                death_date = ev.get_date_object()
            if ev.get_date_object().get_start_date() != gen.lib.Date.EMPTY:
                if ev.get_date_object().copy_offset_ymd(year=limit).match(current_date,"<<"):
                    return False
        # For any other event of this person, check whether it happened
        # too long ago. If so then the person is likely dead now.
        elif ev and too_old(ev.get_date_object(), current_date.get_year()):
            return False

    birth_date = None
    # If they were born within 100 years before current year then
    # assume they are alive (we already know they are not dead).
    birth_ref = person.get_birth_ref()
    if birth_ref and birth_ref.get_role() == gen.lib.EventRoleType.PRIMARY:
        birth = db.get_event_from_handle(birth_ref.ref)
        if birth.get_date_object().get_start_date() != gen.lib.Date.EMPTY:
            if not birth_date:
                birth_date = birth.get_date_object()
            # Check whether the birth event is too old because the
            # code above did not look at birth, only at other events
            if too_old(birth.get_date_object(), current_date.get_year()):
                return False
            if not_too_old(birth.get_date_object(), current_date.get_year()):
                return True
    
    if not birth_date and death_date:
        if death_date.match(current_date.copy_offset_ymd(year=_MAX_AGE_PROB_ALIVE), ">>"):
            # person died more than MAX after current year
            return False

    # Neither birth nor death events are available. Try looking
    # at siblings. If a sibling was born more than 120 years past, 
    # or more than 20 future, then probably this person is
    # not alive. If the sibling died more than 120 years
    # past, or more than 120 years future, then probably not alive.

    family_list = person.get_parent_family_handle_list()
    for family_handle in family_list:
        family = db.get_family_from_handle(family_handle)
        for child_ref in family.get_child_ref_list():
            child_handle = child_ref.ref
            child = db.get_person_from_handle(child_handle)
            child_birth_ref = child.get_birth_ref()
            if child_birth_ref:
                child_birth = db.get_event_from_handle(child_birth_ref.ref)
                dobj = child_birth.get_date_object()
                if dobj.get_start_date() != gen.lib.Date.EMPTY:
                    # if sibling birth date too far away, then not alive:
                    year = dobj.get_year()
                    if year != 0:
                        if not (current_date.copy_offset_ymd(-(_MAX_AGE_PROB_ALIVE + _MAX_SIB_AGE_DIFF)).match(dobj,"<<") and
                                dobj.match(current_date.copy_offset_ymd(_MAX_SIB_AGE_DIFF),"<<")):
                            return False
            child_death_ref = child.get_death_ref()
            if child_death_ref:
                child_death = db.get_event_from_handle(child_death_ref.ref)
                dobj = child_death.get_date_object()
                if dobj.get_start_date() != gen.lib.Date.EMPTY:
                    # if sibling death date too far away, then not alive:
                    year = dobj.get_year()
                    if year != 0:
                        if not (current_date.copy_offset_ymd(-(_MAX_AGE_PROB_ALIVE + _MAX_SIB_AGE_DIFF)).match(dobj,"<<") and
                                dobj.match(current_date.copy_offset_ymd(_MAX_AGE_PROB_ALIVE),"<<")):
                            return False

    # Try looking for descendants that were born more than a lifespan
    # ago.

    def descendants_too_old (person, years):
        for family_handle in person.get_family_handle_list():
            family = db.get_family_from_handle(family_handle)
            
            for child_ref in family.get_child_ref_list():
                child_handle = child_ref.ref
                child = db.get_person_from_handle(child_handle)
                child_birth_ref = child.get_birth_ref()
                if child_birth_ref:
                    child_birth = db.get_event_from_handle(child_birth_ref.ref)
                    dobj = child_birth.get_date_object()
                    if dobj.get_start_date() != gen.lib.Date.EMPTY:
                        d = gen.lib.Date(dobj)
                        val = d.get_start_date()
                        val = d.get_year() - years
                        d.set_year(val)
                        if not not_too_old (d, current_date.get_year()):
                            return True

                child_death_ref = child.get_death_ref()
                if child_death_ref:
                    child_death = db.get_event_from_handle(child_death_ref.ref)
                    dobj = child_death.get_date_object()
                    if dobj.get_start_date() != gen.lib.Date.EMPTY:
                        if not not_too_old (dobj, current_date.get_year()):
                            return True

                if descendants_too_old (child, years + _MIN_GENERATION_YEARS):
                    return True
                
        return False

    # If there are descendants that are too old for the person to have
    # been alive in the current year then they must be dead.

    try:
        if descendants_too_old(person, _MIN_GENERATION_YEARS):
            return False
    except RuntimeError:
        raise Errors.DatabaseError(
            _("Database error: %s is defined as his or her own ancestor") %
            name_displayer.display(person))

    def ancestors_too_old(person, year):
        family_handle = person.get_main_parents_family_handle()
        
        if family_handle:                
            family = db.get_family_from_handle(family_handle)
            father_handle = family.get_father_handle()
            if father_handle:
                father = db.get_person_from_handle(father_handle)
                father_birth_ref = father.get_birth_ref()
                if father_birth_ref and father_birth_ref.get_role() == gen.lib.EventRoleType.PRIMARY:
                    father_birth = db.get_event_from_handle(
                        father_birth_ref.ref)
                    dobj = father_birth.get_date_object()
                    if dobj.get_start_date() != gen.lib.Date.EMPTY:
                        if not not_too_old (dobj, year - _AVG_GENERATION_GAP):
                            return True

                father_death_ref = father.get_death_ref()
                if father_death_ref and father_death_ref.get_role() == gen.lib.EventRoleType.PRIMARY:
                    father_death = db.get_event_from_handle(
                        father_death_ref.ref)
                    dobj = father_death.get_date_object()
                    if dobj.get_start_date() != gen.lib.Date.EMPTY:
                        if dobj.get_year() < year - _AVG_GENERATION_GAP:
                            return True

                if ancestors_too_old (father, year - _AVG_GENERATION_GAP):
                    return True

            mother_handle = family.get_mother_handle()
            if mother_handle:
                mother = db.get_person_from_handle(mother_handle)
                mother_birth_ref = mother.get_birth_ref()
                if mother_birth_ref and mother_birth_ref.get_role() == gen.lib.EventRoleType.PRIMARY:
                    mother_birth = db.get_event_from_handle(mother_birth_ref.ref)
                    dobj = mother_birth.get_date_object()
                    if dobj.get_start_date() != gen.lib.Date.EMPTY:
                        if not not_too_old (dobj, year - _AVG_GENERATION_GAP):
                            return True

                mother_death_ref = mother.get_death_ref()
                if mother_death_ref and mother_death_ref.get_role() == gen.lib.EventRoleType.PRIMARY:
                    mother_death = db.get_event_from_handle(
                        mother_death_ref.ref)
                    dobj = mother_death.get_date_object()
                    if dobj.get_start_date() != gen.lib.Date.EMPTY:
                        if dobj.get_year() < year - _AVG_GENERATION_GAP:
                            return True

                if ancestors_too_old (mother, year - _AVG_GENERATION_GAP):
                    return True

        return False

    # If there are ancestors that would be too old in the current year
    # then assume our person must be dead too.
    if ancestors_too_old (person, current_date.get_year()):
        return False

    # If we can't find any reason to believe that they are dead we
    # must assume they are alive.
    return True

def not_too_old(date, current_year=None):
    if not current_year:
        time_struct = time.localtime(time.time())
        current_year = time_struct[0]
    year = date.get_year()
    if year > current_year:
        return False
    return (year != 0 and current_year - year < _MAX_AGE_PROB_ALIVE)

def too_old(date, current_year=None):
    if current_year:
        the_current_year = current_year
    else:
        time_struct = time.localtime(time.time())
        the_current_year = time_struct[0]
    year = date.get_year()
    if year > the_current_year:
        return True
    return (year != 0 and the_current_year - year > _MAX_AGE_PROB_ALIVE)

#-------------------------------------------------------------------------
#
# 
#
#-------------------------------------------------------------------------
def get_referents(handle, db, primary_objects):
    """ Find objects that refer to an object.
    
    This function is the base for other get_<object>_referents finctions.
    
    """
    # Use one pass through the reference map to grab all the references
    object_list = [item for item in db.find_backlink_handles(handle)]
    
    # Then form the object-specific lists
    the_lists = ()

    for primary in primary_objects:
        primary_list = [item[1] for item in object_list if item[0] == primary]
        the_lists = the_lists + (primary_list, )

    return the_lists

def get_source_referents(source_handle, db):
    """ Find objects that refer the source.

    This function finds all primary objects that refer (directly or through
    secondary child-objects) to a given source handle in a given database.
    
    """
    _primaries = ('Person', 'Family', 'Event', 'Place', 
                  'Source', 'MediaObject', 'Repository')
    
    return (get_referents(source_handle, db, _primaries))

def get_media_referents(media_handle, db):
    """ Find objects that refer the media object.

    This function finds all primary objects that refer
    to a given media handle in a given database.
    
    """
    _primaries = ('Person', 'Family', 'Event', 'Place', 'Source')
    
    return (get_referents(media_handle, db, _primaries))

def get_note_referents(note_handle, db):
    """ Find objects that refer a note object.
    
    This function finds all primary objects that refer
    to a given note handle in a given database.
    
    """
    _primaries = ('Person', 'Family', 'Event', 'Place', 
                  'Source', 'MediaObject', 'Repository')
    
    return (get_referents(note_handle, db, _primaries))

#-------------------------------------------------------------------------
#
# 
#
#-------------------------------------------------------------------------
_NEW_NAME_PATTERN = '%s%sUntitled_%d.%s'

def get_new_filename(ext, folder='~/'):
    ix = 1
    while os.path.isfile(os.path.expanduser(_NEW_NAME_PATTERN %
                                            (folder, os.path.sep, ix, ext))):
        ix = ix + 1
    return os.path.expanduser(_NEW_NAME_PATTERN % (folder, os.path.sep, ix, ext))

def get_empty_tempdir(dirname):
    """ Return path to TEMP_DIR/dirname, a guaranteed empty directory

    makes intervening directories if required
    fails if _file_ by that name already exists, 
    or for inadequate permissions to delete dir/files or create dir(s)

    """
    dirpath = os.path.join(TEMP_DIR,dirname)
    if os.path.isdir(dirpath):
        shutil.rmtree(dirpath)
    os.makedirs(dirpath)
    return dirpath

def rm_tempdir(path):
    """Remove a tempdir created with get_empty_tempdir"""
    if path.startswith(TEMP_DIR) and os.path.isdir(path):
        shutil.rmtree(path)

def cast_to_bool(val):
    if val == str(True):
        return True
    return False

def get_type_converter(val):
    """
    Return function that converts strings into the type of val.
    """
    val_type = type(val)
    if val_type in (str, unicode):
        return unicode
    elif val_type == int:
        return int
    elif val_type == float:
        return float
    elif val_type == bool:
        return cast_to_bool
    elif val_type in (list, tuple):
        return list

def type_name(val):
    """
    Return the name the type of val.
    
    Only numbers and strings are supported.
    The rest becomes strings (unicode).
    """
    val_type = type(val)
    if val_type == int:
        return 'int'
    elif val_type == float:
        return 'float'
    elif val_type == bool:
        return 'bool'
    elif val_type in (str, unicode):
        return 'unicode'
    return 'unicode'

def get_type_converter_by_name(val_str):
    """
    Return function that converts strings into the type given by val_str.
    
    Only numbers and strings are supported.
    The rest becomes strings (unicode).
    """
    if val_str == 'int':
        return int
    elif val_str == 'float':
        return float
    elif val_str == 'bool':
        return cast_to_bool
    elif val_str in ('str', 'unicode'):
        return unicode
    return unicode

def relative_path(original, base):
    """
    Calculate the relative path from base to original, with base a directory,
    and original an absolute path
    On problems, original is returned unchanged
    """
    if not os.path.isdir(base):
        return original
    #original and base must be absolute paths
    if not os.path.isabs(base):
        return original
    if not os.path.isabs(original):
        return original
    original = os.path.normpath(original)
    base = os.path.normpath(base)
    
    # If the db_dir and obj_dir are on different drives (win only)
    # then there cannot be a relative path. Return original obj_path
    (base_drive, base) = os.path.splitdrive(base) 
    (orig_drive, orig_name) = os.path.splitdrive(original)
    if base_drive.upper() != orig_drive.upper():
        return original

    # Starting from the filepath root, work out how much of the filepath is
    # shared by base and target.
    base_list = (base).split(os.sep)
    target_list = (orig_name).split(os.sep)
    # make sure '/home/person' and 'c:/home/person' both give 
    #   list ['home', 'person']
    base_list = [word for word in base_list if word]
    target_list = [word for word in target_list if word]
    i = -1
    for i in range(min(len(base_list), len(target_list))):
        if base_list[i] <> target_list[i]: break
    else:
        #if break did not happen we are here at end, and add 1.
        i += 1
    rel_list = [os.pardir] * (len(base_list)-i) + target_list[i:]
    return os.path.join(*rel_list)

def media_path(db):
    """
    Given a database, return the mediapath to use as basedir for media
    """
    mpath = db.get_mediapath()
    if mpath is None:
        #use home dir
        mpath = USER_HOME
    return mpath

def media_path_full(db, filename):
    """
    Given a database and a filename of a media, return the media filename
    is full form, eg 'graves/tomb.png' becomes '/home/me/genea/graves/tomb.png
    """
    if os.path.isabs(filename):
        return filename
    mpath = media_path(db)
    return os.path.join(mpath, filename)
    

class ProgressMeter:
    """
    Progress meter class for GRAMPS.
    """
    def __init__(self, title, header=''):
        """
        Specify the title and the current pass header.
        """
        self.old_val = -1
        self.ptop = gtk.Dialog()
        self.ptop.connect('delete_event', self.warn)
        self.ptop.set_has_separator(False)
        self.ptop.set_title(title)
        self.ptop.set_border_width(12)
        self.ptop.vbox.set_spacing(10)
        lbl = gtk.Label('<span size="larger" weight="bold">%s</span>' % title)
        lbl.set_use_markup(True)
        self.lbl = gtk.Label(header)
        self.lbl.set_use_markup(True)
        self.ptop.vbox.add(lbl)
        self.ptop.vbox.add(self.lbl)
        self.ptop.vbox.set_border_width(24)
        self.pbar = gtk.ProgressBar()

        self.ptop.set_size_request(350, 125)
        self.ptop.vbox.add(self.pbar)
        self.ptop.show_all()
        if header == '':
            self.lbl.hide()

    def set_pass(self, header, total):
        """
        Reset for another pass. Provide a new header and define number
        of steps to be used.
        """
        if header == '':
            self.lbl.hide()
        else:
            self.lbl.show()
        self.pbar_max = total
        self.pbar_index = 0.0
        self.lbl.set_text(header)
        self.pbar.set_fraction(0.0)
        while gtk.events_pending():
            gtk.main_iteration()

    def step(self):
        """Click the progress bar over to the next value.  Be paranoid
        and insure that it doesn't go over 100%."""
        self.pbar_index = self.pbar_index + 1.0
        
        if self.pbar_index > self.pbar_max:
            self.pbar_index = self.pbar_max

        try:
            val = int(100*self.pbar_index/self.pbar_max)
        except ZeroDivisionError:
            val = 0

        if val != self.old_val:
            self.pbar.set_text("%d%%" % val)
            self.pbar.set_fraction(val/100.0)
            self.old_val = val
        while gtk.events_pending():
            gtk.main_iteration()

    def warn(self, *obj):
        WarningDialog(
            _("Attempt to force closing the dialog"), 
            _("Please do not force closing this important dialog."), 
            self.ptop)
        return True

    def close(self):
        """
        Close the progress meter
        """
        self.ptop.destroy()

def launch(prog_str, path):
    
    if sys.platform == "win32":
        
        import subprocess
        if prog_str.find("%1") != -1:
            prog_str = prog_str.replace("%1", path)
        else:
            prog_str = '%s "%s"' % (prog_str, path)
        subprocess.Popen(prog_str)

    else:
        subval = {
            '%F'   : path, 
            '%f'   : path, 
            '%u'   : path, 
            '%U'   : path, 
            '%n'   : path, 
            '%N'   : path, 
            }
        
        prog_data = prog_str.split()
        prog = prog_data[0]
        prog_list = []
        need_path = True
    
        if len(prog_data) > 1:
            for item in prog_data:
                if subval.has_key(item):
                    need_path = False
                    value = subval[item]
                else:
                    value = item
                prog_list.append(value)
        else:
            prog_list = [prog_data[0]]
    
        if need_path:
            prog_list.append(path)
    
        os.spawnvpe(os.P_NOWAIT, prog, prog_list, os.environ)

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)
    
#-------------------------------------------------------------------------
#
# Keyword translation interface 
#
#-------------------------------------------------------------------------

# keyword, code, translated standard, translated upper
KEYWORDS = [("title",     "t", _("Title"),     _("TITLE")),
            ("given",     "f", _("Given"),     _("GIVEN")),
            ("prefix",    "p", _("Prefix"),    _("PREFIX")),
            ("surname",   "l", _("Surname"),   _("SURNAME")),
            ("suffix",    "s", _("Suffix"),    _("SUFFIX")),
            ("patronymic","y", _("Patronymic"),_("PATRONYMIC")),
            ("call",      "c", _("Call"),      _("CALL")),
            ("common",    "x", _("Common"),    _("COMMON")),
            ("initials",  "i", _("Initials"),  _("INITIALS"))
            ]
KEY_TO_TRANS = {}
TRANS_TO_KEY = {}
for (key, code, standard, upper) in KEYWORDS:
    KEY_TO_TRANS[key] = standard
    KEY_TO_TRANS[key.upper()] = upper
    KEY_TO_TRANS["%" + ("%s" % code)] = standard
    KEY_TO_TRANS["%" + ("%s" % code.upper())] = upper
    TRANS_TO_KEY[standard.lower()] = key
    TRANS_TO_KEY[standard] = key
    TRANS_TO_KEY[upper] = key.upper()

def get_translation_from_keyword(keyword):
    """ Return the translation of keyword """
    return KEY_TO_TRANS.get(keyword, keyword)

def get_keyword_from_translation(word):
    """ Return the keyword of translation """
    return TRANS_TO_KEY.get(word, word)

def get_keywords():
    """ Get all keywords, longest to shortest """
    keys = KEY_TO_TRANS.keys()
    keys.sort(lambda a,b: -cmp(len(a), len(b)))
    return keys

def get_translations():
    """ Get all translations, longest to shortest """
    trans = TRANS_TO_KEY.keys()
    trans.sort(lambda a,b: -cmp(len(a), len(b)))
    return trans
    
