Merge branch 'maintenance/gramps41' of github.com:gramps-project/gramps into gramps41

This commit is contained in:
erikdrgm 2015-06-02 20:48:50 +02:00
commit d8ba31cc29
90 changed files with 11507 additions and 11367 deletions

43
NEWS
View File

@ -1,3 +1,46 @@
2015-05-01
Version 4.1.3, "Thou shalt not count to five", a maintenance release.
* Fix db upgrade failure
* GtkDialog mapped without a transient parent
* [Gedcom} SUBN and SUBM record handling
* [Gedcom] Import/export round trip causes lost information
* [Gedcom] Entering a witness to an event such as marriage might be ignored
* [Gedcom] Gramps can't import estim. date period exported by itself
* [Gedcom] 1/4 and 1/2 ANSEL characters not supported on importing ANSEL
* [Gedcom] Importing file containing multibyte UTF-8 characters fails
* [Gedcom] Import fails for ANSI file under python 3
* [Gedcom] Failure importing ANSEL encoded gedcom file.
* [Gedcom] Characters ignored on a Gedcom encoded ANSI (cp1252 West Europe, USA)
* [Gedcom] NameError in importer
* [Gedcom] Event address is lost on import, i.e. disconnected from event
* Crash on geneweb export with python3
* GuiColorOption missing avail-changed event handler
* Bad generation of [timeline report] ODT files since 4.0.0
* Fix bad handle in explanation note for unknown event
* Fix spurious generation of empty 'Alternative Name' in place.merge()
* Support creating directories in various scenarios
* Attempting to add a bookmark causes an error
* Long series of "unhandled exception" popup boxes while doing a check & repair
* Crash when trying to link existing place as an enclosing place using P0001 number
* HTML view fails to load
* Relationship Graph crashes
* Python3 needs new_subpixbuf not subpixbuf
* Regression: running gramps from crontab fails
* tag_map is not initialized
* Some labels now fit better on citations sidebar filter
* Event columns in web narrative are too narrow
* Problem by start program (launcher)
* Translation string missing in Not Related tool for help and close button
* Date format month/year is not well reported at editing time [in Italian]
* Fix unknown gender relationships handler for the french locale
* Fix a handle type bug on sidebar filter
* Tidy up About dialog
* Cleanup on some man files
* Convert some remaining unicode literals
* Fix mac menubar setting
* Enable python3 to run po/update_po.py
* Updated translations: cs, de, fr, is, nl
2015-02-28
Version 4.1.2, "That's no ordinary rabbit", a maintenance release.
* Error converting python2 utf-8 strings to python3 str when loading data from database

View File

@ -248,6 +248,14 @@ table.primobjlist tr.BeginLetter td, table.primobjlist tr.BeginSurname td {
td.ColumnLetter, td.ColumnRowLabel {
font-weight: bold;
}
/* bug #8213 testing by Stephane, 2014-12-6 */
td.ColumnEvent, td.ColumnDate {
white-space: nowrap;
}
td.ColumnPlace, td.ColumnDescription {
width: 20%
}
/* end of customizations by Stephane */
td.ColumnBirth, td.ColumnDeath, td.ColumnPartner, td.ColumnParents {
font-size: 90%;
}

View File

@ -82,8 +82,8 @@ gramps(1) @VERSION@ gramps(1)
**-i** , **--import=** *FICHIER*
Importer des données depuis un *FICHIER* . Si vous n'avez pas
spécifié de base de données alors une base de données temporaire
est utilisée; elle sera effacée quand vous quitterez gramps.
spécifié de base de données, alors une base de données vide
est utilisée.
Quand plus d'un fichier doit être importé, chacun doit être
précédé par la commande **-i** . Ces fichiers sont importés dans le

View File

@ -1,4 +1,4 @@
.TH "GRAMPS" "1" "28 December 2012" "4.0" "Gramps"
.TH "GRAMPS" "1" "09 mars 2015" "4.1" "Gramps"
.SH NAME
gramps \- Gramps Documentation
.
@ -106,8 +106,8 @@ sources, vous devez utiliser l\(aqoption d\(aqimport.
.TP
.B \fB\-i\fP , \fB\-\-import=\fP \fIFICHIER\fP
Importer des données depuis un \fIFICHIER\fP . Si vous n\(aqavez pas
spécifié de base de données alors une base de données temporaire
est utilisée; elle sera effacée quand vous quitterez gramps.
spécifié de base de données, alors une base de données vide
est utilisée.
.sp
Quand plus d\(aqun fichier doit être importé, chacun doit être
précédé par la commande \fB\-i\fP . Ces fichiers sont importés dans le
@ -337,6 +337,6 @@ gramps(1) @VERSION@ gramps(1)
.SH AUTHOR
Jerome Rapinat
.SH COPYRIGHT
2012, Gramps project
2015, Gramps project
.\" Generated by docutils manpage writer.
.

View File

@ -3,11 +3,11 @@
"http://gramps-project.org/xml/1.6.0/grampsxml.dtd">
<database xmlns="http://gramps-project.org/xml/1.6.0/">
<header>
<created date="2014-11-13" version="4.1.0"/>
<created date="2015-05-10" version="4.1.4"/>
<researcher>
<resname>Alex Roitman,,,</resname>
</researcher>
<mediapath>/home/cristina/gramps/master/example/gramps</mediapath>
<mediapath>/home/pierre/Gramps/master/example/gramps</mediapath>
</header>
<name-formats>
<format number="-1" name="SURNAME, Given (Common)" fmt_str="SURNAME, given (common)" active="1"/>
@ -91,6 +91,11 @@
<dateval val="1592" type="about"/>
<description>Birth of Abbott, Frances</description>
</event>
<event handle="_a5af0eb6abd74c3d7fc" change="1284030605" id="E3415">
<type>Death</type>
<dateval val="1642-01" type="about"/>
<description>Death of Abbott, Frances</description>
</event>
<event handle="_a5af0eb6add73de72aa" change="1284030598" id="E0014">
<type>Birth</type>
<dateval val="1520" type="about"/>
@ -17897,11 +17902,6 @@
<type>Death</type>
<dateval val="1850" type="about" quality="estimated"/>
</event>
<event handle="_a5af0eb6abd74c3d7fc" change="1284030605" id="E3415">
<type>Death</type>
<dateval val="1642-01" type="about"/>
<description>Death of Abbott, Frances</description>
</event>
</events>
<people home="_GNUJQCL9MD64AM56OH">
<person handle="_004KQCGYT27EEPQHK" change="1185438865" id="I0552">
@ -20260,7 +20260,7 @@
<parentin hlink="_HQ8KQCT2UX4S9I0E26"/>
<citationref hlink="_c140d24b31f74169170"/>
</person>
<person handle="_3RFKQCNKMX9HVLNSLW" change="1185438865" id="I1116">
<person handle="_3RFKQCNKMX9HVLNSLW" change="1431174900" id="I1116">
<gender>F</gender>
<name type="Birth Name">
<surname>Garner</surname>
@ -22104,7 +22104,7 @@
<parentin hlink="_JT4KQC83ZKPOLC0UEJ"/>
<citationref hlink="_c140d24fa2503a14583"/>
</person>
<person handle="_6TFKQCUTO94WB2NHN" change="1185438865" id="I1119">
<person handle="_6TFKQCUTO94WB2NHN" change="1431174900" id="I1119">
<gender>F</gender>
<name type="Birth Name">
<first>Zelpha Josephine</first>
@ -23913,7 +23913,7 @@
<parentin hlink="_1RUJQCCL9MVRYLMTBO"/>
<citationref hlink="_c140d254dcc234394a3"/>
</person>
<person handle="_9QFKQC54ET79K2SD57" change="1185438865" id="I1115">
<person handle="_9QFKQC54ET79K2SD57" change="1431174900" id="I1115">
<gender>F</gender>
<name type="Birth Name">
<first>Mary M.</first>
@ -24600,7 +24600,7 @@
<parentin hlink="_4W1KQCYZD6N5M576RA"/>
<citationref hlink="_c140d2566d57b164cf5"/>
</person>
<person handle="_AWFKQCJELLUWDY2PD3" change="1284030919" id="I1123">
<person handle="_AWFKQCJELLUWDY2PD3" change="1431174900" id="I1123">
<gender>M</gender>
<name type="Birth Name">
<first>Robert F.</first>
@ -27182,7 +27182,7 @@
<parentin hlink="_0Q3KQCBZ4421A3L5B4"/>
<citationref hlink="_c140d25c5be3120050a"/>
</person>
<person handle="_EPFKQCETTDTEL3PYIR" change="1185438865" id="I1114">
<person handle="_EPFKQCETTDTEL3PYIR" change="1431174900" id="I1114">
<gender>F</gender>
<name type="Birth Name">
<first>Mary J.</first>
@ -28226,7 +28226,7 @@
<parentin hlink="_BWAKQCZLIWDX9ZEFED"/>
<citationref hlink="_c140d25eec45aabbd80"/>
</person>
<person handle="_GNUJQCL9MD64AM56OH" change="1328027440" id="I0044">
<person handle="_GNUJQCL9MD64AM56OH" change="1431174904" id="I0044">
<gender>M</gender>
<name type="Birth Name">
<first>Lewis Anderson</first>
@ -28454,7 +28454,7 @@
<childof hlink="_05XJQC935HU62H3KL4"/>
<citationref hlink="_c140d25f5c448b251ca"/>
</person>
<person handle="_GYFKQCPH8Q0JDN94GR" change="1185438865" id="I1126">
<person handle="_GYFKQCPH8Q0JDN94GR" change="1431174900" id="I1126">
<gender>F</gender>
<name type="Birth Name">
<first>Anetta</first>
@ -32105,7 +32105,7 @@
<parentin hlink="_ZA6KQC27P0I8E2JZUC"/>
<citationref hlink="_c140d2677c105a1b132"/>
</person>
<person handle="_MUFKQCMXUJ07MCDUNI" change="1185438865" id="I1121">
<person handle="_MUFKQCMXUJ07MCDUNI" change="1431174900" id="I1121">
<gender>F</gender>
<name type="Birth Name">
<first>Iola Elizabeth Betty</first>
@ -33279,7 +33279,7 @@
<parentin hlink="_9SEKQCAAWRUCIO7A0M"/>
<citationref hlink="_c140d269f4c7c13bf87"/>
</person>
<person handle="_ORFKQC4KLWEGTGR19L" change="1185438865" id="I1117">
<person handle="_ORFKQC4KLWEGTGR19L" change="1431174900" id="I1117">
<gender>F</gender>
<name type="Birth Name">
<first>Rebecca Catharine</first>
@ -33963,7 +33963,7 @@
<parentin hlink="_IXDKQCOYLEMDKWJZPC"/>
<citationref hlink="_c140d26b98d33ec7f15"/>
</person>
<person handle="_PXFKQCXEHJX3W1Q1IV" change="1185438865" id="I1125">
<person handle="_PXFKQCXEHJX3W1Q1IV" change="1431174900" id="I1125">
<gender>F</gender>
<name type="Birth Name">
<first>Emma A.</first>
@ -35695,7 +35695,7 @@
<parentin hlink="_FP4KQCQQX8O84KK3IF"/>
<citationref hlink="_c140d27142a05b2d019"/>
</person>
<person handle="_SOFKQCBYAO18OWC0CS" change="1185438865" id="I1113">
<person handle="_SOFKQCBYAO18OWC0CS" change="1431174900" id="I1113">
<gender>F</gender>
<name type="Birth Name">
<first>Phebe</first>
@ -37068,7 +37068,7 @@
<parentin hlink="_7ZWJQC8ZR4WJZE09RW"/>
<citationref hlink="_c140d276c1802ec5ac3"/>
</person>
<person handle="_UZFKQCIHVT44DC9KGH" change="1185438865" id="I1128">
<person handle="_UZFKQCIHVT44DC9KGH" change="1431174900" id="I1128">
<gender>F</gender>
<name type="Birth Name">
<first>Antoinette</first>
@ -42060,12 +42060,12 @@
<childref hlink="_GH0KQCGPLF5J17PELU"/>
<citationref hlink="_c140d286d0e2f46fb29"/>
</family>
<family handle="_8OUJQCUVZ0XML7BQLF" change="1185438865" id="F0018">
<family handle="_8OUJQCUVZ0XML7BQLF" change="1431174900" id="F0018">
<rel type="Married"/>
<father hlink="_35WJQC1B7T7NPV8OLV"/>
<mother hlink="_46WJQCIOLQ0KOX2XCC"/>
<eventref hlink="_a5af0ed602318310d6d" role="Family"/>
<childref hlink="_GNUJQCL9MD64AM56OH"/>
<childref hlink="_GNUJQCL9MD64AM56OH" mrel="Custom relationship to mother" frel="Custom relationship to father"/>
<childref hlink="_SOFKQCBYAO18OWC0CS"/>
<childref hlink="_EPFKQCETTDTEL3PYIR"/>
<childref hlink="_9QFKQC54ET79K2SD57"/>
@ -63928,6 +63928,94 @@ page 26 Repository:Address</text>
<range start="0" end="705"/>
</style>
</note>
<note handle="_d0436bba4ec328d3b631259a4ee" change="1431184305" id="_header1" type="General">
<text>Title for the example pages</text>
<style name="fontcolor" value="#ef2929">
<range start="0" end="27"/>
</style>
<style name="underline">
<range start="0" end="27"/>
</style>
<style name="fontface" value="Serif">
<range start="0" end="27"/>
</style>
<style name="bold">
<range start="0" end="27"/>
</style>
<style name="fontsize" value="8">
<range start="0" end="27"/>
</style>
</note>
<note handle="_d0436bcc69d6bba278bff5bc7db" change="1431184300" id="_footer1" type="General">
<text>Footer: exported by __GRAMPS_HOMEPAGE__ on __EXPORT_DATE__</text>
</note>
<note handle="_d0436be64ac277b615b79b34e72" change="1431211661" id="_custom1" type="General">
<text>Export date: __EXPORT_DATE__
GRAMPS homepage: __GRAMPS_HOMEPAGE__
GRAMPS version: __GRAMPS_VERSION__
Number of families: __NB_FAMILIES__
Number of persons: __NB_INDIVIDUALS__
Number of media objects: __NB_MEDIA__
Number of sources: __NB_SOURCES__
Number of repositories: __NB_REPOSITORIES__
Number of places: __NB_PLACES__
Search form:
__SEARCH_FORM__
Test link person: Garner von Zieliński, Lewis Anderson Sr
Test link family: Family of Warner, Allen Carl and Garner, Rita Marie
Test link source: World of the Wierd
Test link media: 1897_expeditionsmannschaft_rio_a
Test link place: Warren-Farmington Hills-Troy, MI
Test internet link: blog.codinghorror.com
Test relative path link: relative file path to &quot;archive.zip&quot;
Test relative path link: relative file path to &quot;archive.tgz&quot;
Thumbnail for &quot;1897_expeditionsmannschaft_rio_a&quot;:
__THUMB_O0010__
Image &quot;AntoineClaudet&quot;:
__MEDIA_O0011__
Thumbnail for &quot;1897_expeditionsmannschaft_rio_a&quot; with link:
__THUMB_O0010__
Image &quot;AntoineClaudet&quot; with link:
__MEDIA_O0011__
Wrong media ID:
__MEDIA_wrong id__</text>
<style name="link" value="relative://relative.archive.zip">
<range start="663" end="686"/>
</style>
<style name="link" value="gramps://Media/handle/238CGQ939HG18SS5MG">
<range start="952" end="967"/>
</style>
<style name="link" value="gramps://Media/handle/238CGQ939HG18SS5MG">
<range start="520" end="535"/>
</style>
<style name="link" value="gramps://Family/handle/48TJQCGNNIR5SJRCAK">
<range start="413" end="429"/>
</style>
<style name="link" value="gramps://Person/handle/GNUJQCL9MD64AM56OH">
<range start="355" end="371"/>
</style>
<style name="link" value="http://blog.codinghorror.com/">
<range start="621" end="639"/>
</style>
<style name="link" value="gramps://Source/handle/VUBKMQTA2XZG1V6QP8">
<range start="483" end="499"/>
</style>
<style name="link" value="gramps://Place/handle/3WTJQCB9F2MX9W98VP">
<range start="570" end="585"/>
</style>
<style name="link" value="gramps://Media/handle/Y3ARGQWE088EQRTTDH">
<range start="1002" end="1017"/>
</style>
<style name="link" value="relative://relative.archive.tgz">
<range start="724" end="747"/>
</style>
</note>
</notes>
<bookmarks>
<bookmark target="person" hlink="_AWFKQCJELLUWDY2PD3"/>

View File

@ -497,6 +497,8 @@ def time_val(dirpath):
if tval_mod > tval:
tval = tval_mod
last = time.strftime('%x %X', time.localtime(tval))
if sys.version_info[0] < 3:
last = last.decode(glocale.encoding)
else:
tval = 0
last = _("Never")
@ -517,6 +519,6 @@ def find_locker_name(dirpath):
# feature request 2356: avoid genitive form
last = _("Locked by %s") % username
ifile.close()
except (OSError, IOError):
except (OSError, IOError, UnicodeDecodeError):
last = _("Unknown")
return last

View File

@ -117,7 +117,7 @@ class User(user.User):
"""
self._fileout.write("\r100%\n")
def prompt(self, title, message, accept_label, reject_label):
def prompt(self, title, message, accept_label, reject_label, parent=None):
"""
Prompt the user with a message to select an alternative.

View File

@ -49,7 +49,7 @@ from .constfunc import get_env_var, conv_to_unicode
#
#-------------------------------------------------------------------------
PROGRAM_NAME = "Gramps"
from ..version import VERSION, VERSION_TUPLE, major_version
from gramps.version import VERSION, VERSION_TUPLE, major_version
#-------------------------------------------------------------------------
#
# Standard GRAMPS Websites

View File

@ -631,4 +631,4 @@ class DateDisplayEn(DateDisplay):
display = DateDisplay.display_formatted
_locale = _grampslocale.glocale # normally set in register_datehandler
_locale = DateDisplay._locale # normally set in register_datehandler

View File

@ -629,8 +629,12 @@ class DateParser(object):
else:
y = self._get_int(groups[4])
if self.dmy:
m = self._get_int(groups[3])
d = self._get_int(groups[1])
if groups[3] is None:
m = self._get_int(groups[1])
d = 0
else:
m = self._get_int(groups[3])
d = self._get_int(groups[1])
else:
m = self._get_int(groups[1])
d = self._get_int(groups[3])

View File

@ -27,6 +27,7 @@ Class handling language-specific selection for date parser and displayer.
# Python modules
#
#-------------------------------------------------------------------------
import sys
import time
#-------------------------------------------------------------------------
@ -35,6 +36,7 @@ import time
#
#-------------------------------------------------------------------------
from ..lib.date import Date
from ..const import GRAMPS_LOCALE as glocale
from . import LANG_TO_DISPLAY, LANG, parser, displayer
#--------------------------------------------------------------
@ -94,4 +96,7 @@ def format_time(secs):
"""
t = time.localtime(secs)
d = Date(t.tm_year, t.tm_mon, t.tm_mday)
return displayer.display(d) + time.strftime(' %X', t)
if sys.version_info[0] < 3:
return displayer.display(d) + time.strftime(' %X', t).decode(glocale.encoding)
else:
return displayer.display(d) + time.strftime(' %X', t)

View File

@ -55,6 +55,8 @@ class PlaceDisplay(object):
if place_handle:
place = db.get_place_from_handle(place_handle)
return self.display(db, place, event.get_date_object())
else:
return ""
def display(self, db, place, date=None):
if not place:

View File

@ -63,34 +63,42 @@ class IsRelatedWith(Rule):
return person.handle in self.relatives
def add_relative(self, person):
"""Recursive function that scans relatives and add them to self.relatives"""
if not(person) or person.handle in self.relatives:
def add_relative(self, start):
"""Non-recursive function that scans relatives and add them to self.relatives"""
if not(start):
return
# Add the relative to the list
self.relatives.append(person.handle)
expand = [start]
relatives = {}
while expand:
person = expand.pop()
# Add the relative to the list
if person is None or (person.handle in relatives):
continue
relatives[person.handle] = True
for family_handle in person.get_parent_family_handle_list():
family = self.db.get_family_from_handle(family_handle)
if family:
# Check Parents
for parent_handle in (family.get_father_handle(), family.get_mother_handle()):
if parent_handle:
self.add_relative(self.db.get_person_from_handle(parent_handle))
# Check Sibilings
for child_ref in family.get_child_ref_list():
self.add_relative(self.db.get_person_from_handle(child_ref.ref))
for family_handle in person.get_family_handle_list():
family = self.db.get_family_from_handle(family_handle)
if family:
# Check Spouse
for parent_handle in (family.get_father_handle(), family.get_mother_handle()):
if parent_handle:
self.add_relative(self.db.get_person_from_handle(parent_handle))
# Check Children
for child_ref in family.get_child_ref_list():
self.add_relative(self.db.get_person_from_handle(child_ref.ref))
return
for family_handle in person.get_parent_family_handle_list():
family = self.db.get_family_from_handle(family_handle)
if family:
# Check Parents
for parent_handle in (family.get_father_handle(), family.get_mother_handle()):
if parent_handle:
expand.append(self.db.get_person_from_handle(parent_handle))
# Check Sibilings
for child_ref in family.get_child_ref_list():
expand.append(self.db.get_person_from_handle(child_ref.ref))
for family_handle in person.get_family_handle_list():
family = self.db.get_family_from_handle(family_handle)
if family:
# Check Spouse
for parent_handle in (family.get_father_handle(), family.get_mother_handle()):
if parent_handle:
expand.append(self.db.get_person_from_handle(parent_handle))
# Check Children
for child_ref in family.get_child_ref_list():
expand.append(self.db.get_person_from_handle(child_ref.ref))
self.relatives = list(relatives.keys())
return

View File

@ -571,7 +571,7 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject):
:param acquisition: instance to merge
:type acquisition: :class:'~.place.Place
"""
if acquisition.name not in self.alt_names:
if acquisition.name and (acquisition.name not in self.alt_names):
self.alt_names.append(acquisition.name)
for addendum in acquisition.alt_names:

View File

@ -33,7 +33,7 @@ import libxslt
from gramps.plugins.lib.libgrampsxml import GRAMPS_XML_VERSION
from ...const import ROOT_DIR, USER_PLUGINS
from ....version import VERSION
from gramps.version import VERSION
from ...lib import Name, Surname
from ...const import GRAMPS_LOCALE as glocale
_ = glocale.translation.sgettext

View File

@ -42,7 +42,7 @@ import io
# GRAMPS modules
#
#-------------------------------------------------------------------------
from ...version import VERSION as GRAMPSVERSION, VERSION_TUPLE
from gramps.version import VERSION as GRAMPSVERSION, VERSION_TUPLE
from ..const import IMAGE_DIR
from ..const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext
@ -1100,10 +1100,16 @@ class PluginRegister(object):
continue
lenpd = len(self.__plugindata)
full_filename = os.path.join(dir, filename)
if sys.version_info[0] < 3:
fd = open(full_filename, "r")
else:
fd = io.open(full_filename, "r", encoding='utf-8')
try:
if sys.version_info[0] < 3:
fd = open(full_filename, "r")
else:
fd = io.open(full_filename, "r", encoding='utf-8')
except Exception as msg:
print(_('ERROR: Failed reading plugin registration %(filename)s') % \
{'filename' : filename})
print(msg)
continue
stream = fd.read()
fd.close()
if os.path.exists(os.path.join(os.path.dirname(full_filename),

View File

@ -50,7 +50,7 @@ LOG = logging.getLogger(".gen.plug")
#-------------------------------------------------------------------------
from ._pluginreg import make_environment
from ..const import USER_PLUGINS
from ...version import VERSION_TUPLE
from gramps.version import VERSION_TUPLE
from . import BasePluginManager
from ..utils.configmanager import safe_eval
from ..config import config

View File

@ -261,8 +261,15 @@ def get_participant_from_event(db, event_handle, all_=False):
"""
participant = ""
ellipses = False
result_list = list(db.find_backlink_handles(event_handle,
include_classes=['Person', 'Family']))
try:
result_list = list(db.find_backlink_handles(event_handle,
include_classes=['Person', 'Family']))
except:
# during a magic batch transaction find_backlink_handles tries to
# access the reference_map_referenced_map which is closed
# under those circumstances.
return ''
#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'])
@ -328,15 +335,7 @@ def navigation_label(db, nav_type, handle):
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 = ''
who = get_participant_from_event(db, handle)
desc = obj.get_description()
label = obj.get_type()
if desc:

View File

@ -208,7 +208,7 @@ class GrampsLocale(object):
self.lang = loc[0]
self.encoding = loc[1]
else:
(lang, loc) = _check_mswin_locale(lang)
(lang, loc) = _check_mswin_locale(locale.getdefaultlocale()[0])
if lang:
self.lang = lang
self.encoding = loc[1]

View File

@ -90,10 +90,7 @@ def resize_to_jpeg(source, destination, width, height, crop=None):
(start_x, start_y, end_x, end_y
) = crop_percentage_to_pixel(
img.get_width(), img.get_height(), crop)
if sys.version_info[0] < 3:
img = img.new_subpixbuf(start_x, start_y, end_x-start_x, end_y-start_y)
else:
img = img.subpixbuf(start_x, start_y, end_x-start_x, end_y-start_y)
img = img.new_subpixbuf(start_x, start_y, end_x-start_x, end_y-start_y)
# Need to keep the ratio intact, otherwise scaled images look stretched
# if the dimensions aren't close in size
@ -231,10 +228,7 @@ def resize_to_buffer(source, size, crop=None):
(start_x, start_y, end_x, end_y
) = crop_percentage_to_pixel(
img.get_width(), img.get_height(), crop)
if sys.version_info[0] < 3:
img = img.new_subpixbuf(start_x, start_y, end_x-start_x, end_y-start_y)
else:
img = img.subpixbuf(start_x, start_y, end_x-start_x, end_y-start_y)
img = img.new_subpixbuf(start_x, start_y, end_x-start_x, end_y-start_y)
# Need to keep the ratio intact, otherwise scaled images look stretched
# if the dimensions aren't close in size
@ -272,10 +266,7 @@ def resize_to_jpeg_buffer(source, size, crop=None):
) = crop_percentage_to_pixel(
img.get_width(), img.get_height(), crop)
if sys.version_info[0] < 3:
img = img.new_subpixbuf(start_x, start_y, end_x-start_x, end_y-start_y)
else:
img = img.subpixbuf(start_x, start_y, end_x-start_x, end_y-start_y)
img = img.new_subpixbuf(start_x, start_y, end_x-start_x, end_y-start_y)
# Need to keep the ratio intact, otherwise scaled images look stretched
# if the dimensions aren't close in size

View File

@ -29,6 +29,7 @@ Make an 'Unknown' primary object
# Python modules
#
#-------------------------------------------------------------------------
import sys
import time
import os
@ -146,8 +147,11 @@ def make_unknown(class_arg, explanation, class_func, commit_func, transaction,
elif isinstance(obj, Tag):
if not hasattr(make_unknown, 'count'):
make_unknown.count = 1 #primitive static variable
tval = time.strftime('%x %X', time.localtime())
if sys.version_info[0] < 3:
tval = tval.decode(glocale.encoding)
obj.set_name(_("Unknown, was missing %(time)s (%(count)d)") % {
'time': time.strftime('%x %X', time.localtime()),
'time': tval,
'count': make_unknown.count})
make_unknown.count += 1
else:
@ -165,9 +169,11 @@ def create_explanation_note(dbase):
those objects of type "Unknown" need a explanatory note. This funcion
provides such a note for import methods.
"""
tval = time.strftime('%x %X', time.localtime())
if sys.version_info[0] < 3:
tval = tval.decode(glocale.encoding)
note = Note( _('Objects referenced by this note '
'were missing in a file imported on %s.') %
time.strftime('%x %X', time.localtime()))
'were missing in a file imported on %s.') % tval)
note.set_handle(create_id())
note.set_gramps_id(dbase.find_next_note_gramps_id())
# Use defaults for privacy, format and type.

View File

@ -97,7 +97,7 @@ class DisplayNameEditor(ManagedWindow):
def __init__(self, uistate, dbstate, track, dialog):
# Assumes that there are two methods: dialog.name_changed_check(),
# and dialog._build_custom_name_ui()
ManagedWindow.__init__(self, uistate, [], DisplayNameEditor)
ManagedWindow.__init__(self, uistate, track, DisplayNameEditor)
self.dialog = dialog
self.dbstate = dbstate
self.set_window(
@ -136,7 +136,8 @@ UPPERCASE keyword forces uppercase. Extra parentheses, commas are removed. Other
ManagedWindow.close(self, *obj)
def build_menu_names(self, obj):
return (_(" Name Editor"), _("Preferences"))
# NameEditor is leaf of parent branch
return (_(" Name Editor"), None)
#-------------------------------------------------------------------------
@ -188,7 +189,6 @@ class ConfigureDialog(ManagedWindow):
self.__setup_pages(configure_page_funcs)
self.window.show_all()
self.show()
def __setup_pages(self, configure_page_funcs):
@ -231,12 +231,12 @@ class ConfigureDialog(ManagedWindow):
except TypeError:
print("WARNING: ignoring invalid value for '%s'" % constant)
ErrorDialog(_("Invalid or incomplete format definition."),
obj.get_text())
obj.get_text(), parent=self.window)
obj.set_text('<b>%s</b>')
except ValueError:
print("WARNING: ignoring invalid value for '%s'" % constant)
ErrorDialog(_("Invalid or incomplete format definition."),
obj.get_text())
obj.get_text(), parent=self.window)
obj.set_text('<b>%s</b>')
self.__config.set(constant, unicode(obj.get_text()))
@ -770,7 +770,7 @@ class GrampsPreferences(ConfigureDialog):
# check to see if this pattern already exists
if self.__check_for_name(translation, node):
ErrorDialog(_("This format exists already."),
translation)
translation, parent=self.window)
self.edit_button.emit('clicked')
return
# else, change the name
@ -1170,13 +1170,15 @@ class GrampsPreferences(ConfigureDialog):
config.set('preferences.date-format', obj.get_active())
OkDialog(_('Change is not immediate'),
_('Changing the date format will not take '
'effect until the next time Gramps is started.'))
'effect until the next time Gramps is started.'),
parent=self.window)
def place_format_changed(self, obj):
config.set('preferences.place-format', obj.get_active())
OkDialog(_('Change is not immediate'),
_('Changing the place format will not take '
'effect until the next time Gramps is started.'))
'effect until the next time Gramps is started.'),
parent=self.window)
def date_calendar_changed(self, obj):
config.set('preferences.calendar-format-report', obj.get_active())
@ -1388,12 +1390,13 @@ class GrampsPreferences(ConfigureDialog):
def select_mediapath(self, *obj):
f = Gtk.FileChooserDialog(
_("Select media directory"),
action=Gtk.FileChooserAction.SELECT_FOLDER,
buttons=(Gtk.STOCK_CANCEL,
Gtk.ResponseType.CANCEL,
Gtk.STOCK_APPLY,
Gtk.ResponseType.OK))
title=_("Select media directory"),
parent=self.window,
action=Gtk.FileChooserAction.SELECT_FOLDER,
buttons=(Gtk.STOCK_CANCEL,
Gtk.ResponseType.CANCEL,
Gtk.STOCK_APPLY,
Gtk.ResponseType.OK))
mpath = self.dbstate.db.get_mediapath()
if not mpath:
mpath = HOME_DIR
@ -1412,12 +1415,13 @@ class GrampsPreferences(ConfigureDialog):
def select_dbpath(self, *obj):
f = Gtk.FileChooserDialog(
_("Select database directory"),
action=Gtk.FileChooserAction.SELECT_FOLDER,
buttons=(Gtk.STOCK_CANCEL,
Gtk.ResponseType.CANCEL,
Gtk.STOCK_APPLY,
Gtk.ResponseType.OK))
title=_("Select database directory"),
parent=self.window,
action=Gtk.FileChooserAction.SELECT_FOLDER,
buttons=(Gtk.STOCK_CANCEL,
Gtk.ResponseType.CANCEL,
Gtk.STOCK_APPLY,
Gtk.ResponseType.OK))
dbpath = config.get('behavior.database-path')
if not dbpath:
dbpath = os.path.join(HOME_DIR,'grampsdb')
@ -1470,7 +1474,9 @@ class GrampsPreferences(ConfigureDialog):
obj.set_text(str(intval))
def build_menu_names(self, obj):
return (_('Preferences'), None)
# Preferences editor my open other dialog so let main dialog
# be leaf in branch
return (_('Preferences'), _('Preferences'))
# FIXME: is this needed?
def _set_button(self, stock):

View File

@ -83,24 +83,24 @@ class DbLoader(CLIDbLoader):
self.import_info = None
def _warn(self, title, warnmessage):
WarningDialog(title, warnmessage)
WarningDialog(title, warnmessage, parent=self.uistate.window)
def _errordialog(self, title, errormessage):
"""
Show the error.
In the GUI, the error is shown, and a return happens
"""
ErrorDialog(title, errormessage)
ErrorDialog(title, errormessage, parent=self.uistate.window)
return 1
def _dberrordialog(self, msg):
import traceback
exc = traceback.format_exc()
try:
DBErrorDialog(str(msg.value))
DBErrorDialog(str(msg.value), parent=self.uistate.window)
_LOG.error(str(msg.value))
except:
DBErrorDialog(str(msg))
DBErrorDialog(str(msg), parent=self.uistate.window)
_LOG.error(str(msg) +"\n" + exc)
def _begin_progress(self):
@ -198,7 +198,8 @@ class DbLoader(CLIDbLoader):
_("Could not open file: %s") % filename,
_('File type "%s" is unknown to Gramps.\n\n'
'Valid types are: Gramps database, Gramps XML, '
'Gramps package, GEDCOM, and others.') % extension)
'Gramps package, GEDCOM, and others.') % extension,
parent=self.uistate.window)
import_dialog.destroy()
return False
@ -220,13 +221,15 @@ class DbLoader(CLIDbLoader):
elif os.path.isdir(filename):
ErrorDialog(
_('Cannot open file'),
_('The selected file is a directory, not a file.\n'))
_('The selected file is a directory, not a file.\n'),
parent=self.uistate.window)
return True
elif os.path.exists(filename):
if not os.access(filename, os.R_OK):
ErrorDialog(
_('Cannot open file'),
_('You do not have read access to the selected file.'))
_('You do not have read access to the selected file.'),
parent=self.uistate.window)
return True
else:
try:
@ -236,7 +239,8 @@ class DbLoader(CLIDbLoader):
except IOError:
ErrorDialog(
_('Cannot create file'),
_('You do not have write access to the selected file.'))
_('You do not have write access to the selected file.'),
parent=self.uistate.window)
return True
return False
@ -259,7 +263,8 @@ class DbLoader(CLIDbLoader):
_("Could not import file: %s") % filename,
_("This file incorrectly identifies its character "
"set, so it cannot be accurately imported. Please fix the "
"encoding, and import again") + "\n\n %s" % msg)
"encoding, and import again") + "\n\n %s" % msg,
parent=self.uistate.window)
except Exception:
_LOG.error("Failed to import database.", exc_info=True)
self._end_progress()
@ -327,7 +332,8 @@ class DbLoader(CLIDbLoader):
str(msg),
_("I have made a backup,\n"
"please upgrade my Family Tree"),
_("Cancel"), self.uistate.window).run():
_("Cancel"),
parent=self.uistate.window).run():
force_schema_upgrade = True
force_bsddb_upgrade = False
force_bsddb_downgrade = False
@ -341,7 +347,8 @@ class DbLoader(CLIDbLoader):
str(msg),
_("I have made a backup,\n"
"please upgrade my tree"),
_("Cancel"), self.uistate.window).run():
_("Cancel"),
parent=self.uistate.window).run():
force_schema_upgrade = False
force_bsddb_upgrade = True
force_bsddb_downgrade = False
@ -355,7 +362,8 @@ class DbLoader(CLIDbLoader):
str(msg),
_("I have made a backup,\n"
"please downgrade my Family Tree"),
_("Cancel"), self.uistate.window).run():
_("Cancel"),
parent=self.uistate.window).run():
force_schema_upgrade = False
force_bsddb_upgrade = False
force_bsddb_downgrade = True
@ -369,7 +377,8 @@ class DbLoader(CLIDbLoader):
str(msg),
_("I have made a backup,\n"
"please upgrade my Family Tree"),
_("Cancel"), self.uistate.window).run():
_("Cancel"),
parent=self.uistate.window).run():
force_schema_upgrade = False
force_bsddb_upgrade = False
force_bsddb_downgrade = False

View File

@ -248,7 +248,7 @@ class DbManager(CLIDbManager):
if store.get_value(node, STOCK_COL) == Gtk.STOCK_DIALOG_ERROR:
path = conv_to_unicode(store.get_value(node, PATH_COL), 'utf8')
backup = os.path.join(path, u"person.gbkp")
backup = os.path.join(path, "person.gbkp")
self.repair.set_sensitive(os.path.isfile(backup))
else:
self.repair.set_sensitive(False)

View File

@ -514,17 +514,10 @@ class GalleryTab(ButtonTab, DbGUIElement):
elif self._DND_EXTRA and mytype == self._DND_EXTRA.drag_type:
self.handle_extra_type(mytype, obj)
except pickle.UnpicklingError:
#modern file managers provide URI_LIST. For Windows split sel_data.data
if win():
files = sel_data.get_data().split('\n')
else:
files = sel_data.get_uris()
files = sel_data.get_uris()
for file in files:
if win():
d = conv_to_unicode((file.replace('\0',' ').strip()), None)
else:
d = file
protocol, site, mfile, j, k, l = urlparse(d)
protocol, site, mfile, j, k, l = urlparse(file)
if protocol == "file":
name = url2pathname(mfile)
mime = get_type(name)

View File

@ -271,6 +271,7 @@ class EditReference(ManagedWindow, DbGUIElement):
if new_id:
old_primary = self.db.get_from_name_and_gramps_id(type, new_id)
if old_primary:
description = None
if type == 'Event':
msg1 = _("Cannot save event. ID already exists.")
description = old_primary.get_description()
@ -280,6 +281,8 @@ class EditReference(ManagedWindow, DbGUIElement):
elif type == 'Repository':
msg1 = _("Cannot save repository. ID already exists.")
description = old_primary.get_name()
else:
msg1 = _("Cannot save item. ID already exists.")
if description:
msg2 = _("You have attempted to use the existing Gramps "
"ID with value %(id)s. This value is already "

View File

@ -62,11 +62,13 @@ class EventSidebarFilter(SidebarFilter):
self.filter_event = Event()
self.filter_event.set_type((EventType.CUSTOM, ''))
self.etype = Gtk.ComboBox(has_entry=True)
self.custom_types = dbstate.db.get_event_types()
self.event_menu = widgets.MonitoredDataType(
self.etype,
self.filter_event.set_type,
self.filter_event.get_type)
self.filter_event.get_type,
custom_values=self.custom_types)
self.filter_mainparts = widgets.BasicEntry()
self.filter_date = widgets.DateEntry(uistate, [])

View File

@ -29,6 +29,7 @@ from gi.repository import Pango
from ... import widgets
from ...dbguielement import DbGUIElement
from gramps.gen.config import config
from gramps.gen.constfunc import UNITYPE
_RETURN = Gdk.keyval_from_name("Return")
_KP_ENTER = Gdk.keyval_from_name("KP_Enter")
@ -212,6 +213,9 @@ class SidebarFilter(DbGUIElement):
self.__tag_list = []
for handle in self.dbstate.db.get_tag_handles(sort_handles=True):
tag = self.dbstate.db.get_tag_from_handle(handle)
# for python3 this returns a byte object, so conversion needed
if not isinstance(handle, UNITYPE):
handle = handle.decode('utf-8')
self.__tag_list.append((tag.get_name(), handle))
self.on_tags_changed([item[0] for item in self.__tag_list])

View File

@ -350,11 +350,13 @@
<child>
<object class="GtkComboBox" id="place_type">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">What type of place this is. Eg 'Country', 'City', ... .</property>
<property name="has_entry">True</property>
<child internal-child="entry">
<object class="GtkEntry" id="combobox-entry">
<property name="can_focus">False</property>
<property name="can_focus">True</property>
<property name="overwrite_mode">True</property>
</object>
</child>
</object>

View File

@ -378,7 +378,7 @@ def __startgramps(errors, argparser):
% e.code, exc_info=True)
except OSError as e:
quit_now = True
exit_code = e[0] or 1
exit_code = e.errno or 1
try:
fn = e.filename
except AttributeError:

View File

@ -89,6 +89,15 @@ class ErrorReportAssistant(Gtk.Assistant):
self.build_page4()
self.build_page5()
self.create_page_summary()
try:
self.set_transient_for(self.list_toplevels()[-2])
except IndexError:
self.set_position(Gtk.WindowPosition.CENTER)
self.set_urgency_hint(True)
self.set_keep_above(True)
self.set_default_size(800,-1)
self.show_all()
self.ownthread = ownthread

View File

@ -82,6 +82,13 @@ class ErrorView(object):
def draw_window(self):
title = "%s - Gramps" % _("Error Report")
self.top = Gtk.Dialog(title)
try:
self.top.set_transient_for(self.top.list_toplevels()[-2])
except IndexError:
self.top.set_position(Gtk.WindowPosition.CENTER)
self.top.set_urgency_hint(True)
self.top.set_keep_above(True)
self.top.set_default_size(800,-1)
vbox = self.top.get_content_area()
vbox.set_spacing(5)
self.top.set_border_width(12)
@ -128,7 +135,6 @@ class ErrorView(object):
vbox.pack_start(tb_expander, True, True, 5)
self.top.add_button(Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL)
self.top.add_button(_("Report"),Gtk.ResponseType.YES)
self.top.add_button(Gtk.STOCK_HELP,Gtk.ResponseType.HELP)

View File

@ -41,6 +41,7 @@ from gramps.gen.const import URL_MANUAL_PAGE
from ..display import display_help
from ..managedwindow import ManagedWindow
from gramps.gen.merge import MergePlaceQuery
from gramps.gen.display.place import displayer as place_displayer
#-------------------------------------------------------------------------
#
@ -140,6 +141,8 @@ class MergePlace(ManagedWindow):
self.get_widget(widget_name).set_sensitive(False)
# Main window widgets that determine which handle survives
title1 = place_displayer.display(database, self.pl1)
title2 = place_displayer.display(database, self.pl2)
rbutton1 = self.get_widget("handle_btn1")
rbutton_label1 = self.get_widget("label_handle_btn1")
rbutton_label2 = self.get_widget("label_handle_btn2")

View File

@ -292,10 +292,18 @@ class Navigator(object):
# Functions
#
#-------------------------------------------------------------------------
def cb_menu_position(menu, button):
def cb_menu_position(*args):
"""
Determine the position of the popup menu.
"""
# takes two argument: menu, button
if len(args) == 2:
menu = args[0]
button = args[1]
# broken introspection can't handle MenuPositionFunc annotations corectly
else:
menu = args[0]
button = args[3]
ret_val, x_pos, y_pos = button.get_window().get_origin()
x_pos += button.get_allocation().x
y_pos += button.get_allocation().y + button.get_allocation().height

View File

@ -241,6 +241,9 @@ class GuiColorOption(Gtk.ColorButton):
self.changekey = self.connect('color-set', self.__color_changed)
self.valuekey = self.__option.connect('value-changed', self.__value_changed)
self.conkey = self.__option.connect('avail-changed', self.__update_avail)
self.__update_avail()
self.set_tooltip_text(self.__option.get_help())
def __color_changed(self, obj): # IGNORE:W0613 - obj is unused
@ -257,6 +260,13 @@ class GuiColorOption(Gtk.ColorButton):
self.__option.set_value(value)
self.__option.enable_signals()
def __update_avail(self):
"""
Update the availability (sensitivity) of this widget.
"""
avail = self.__option.get_available()
self.set_sensitive(avail)
def __value_changed(self):
"""
Handle the change made programmatically
@ -270,6 +280,7 @@ class GuiColorOption(Gtk.ColorButton):
remove stuff that blocks garbage collection
"""
self.__option.disconnect(self.valuekey)
self.__option.disconnect(self.conkey)
self.__option = None
#-------------------------------------------------------------------------

View File

@ -50,9 +50,10 @@ class Progress(object):
Mirros the same interface that the ExportAssistant uses in the
selection, but this is for the preview selection.
"""
def __init__(self):
def __init__(self, uistate):
from gi.repository import Gtk
self.pm = ProgressMeter(_("Selecting Preview Data"), _('Selecting...'))
self.pm = ProgressMeter(_("Selecting Preview Data"), _('Selecting...'),
parent=uistate.window)
self.progress_cnt = 0
self.title = _("Selecting...")
while Gtk.events_pending():
@ -239,7 +240,7 @@ class WriterOptionBox(object):
Calculate previews to see the selected data.
"""
self.parse_options()
pm = Progress()
pm = Progress(self.uistate)
self.preview_dbase = self.get_filtered_database(self.dbstate.db, pm, preview=True)
pm.close()
self.preview_button.set_sensitive(0)

View File

@ -527,13 +527,20 @@ class ReportDialog(ManagedWindow):
# we will need to create the file/dir
# need to make sure we can create in the parent dir
parent_dir = os.path.dirname(os.path.normpath(self.target_path))
if not os.access(parent_dir, os.W_OK):
ErrorDialog(_('Permission problem'),
_("You do not have permission to create "
"%s\n\n"
"Please select another path or correct "
"the permissions.") % self.target_path
)
if os.path.isdir(parent_dir):
if not os.access(parent_dir, os.W_OK):
ErrorDialog(_('Permission problem'),
_("You do not have permission to create "
"%s\n\n"
"Please select another path or correct "
"the permissions.") % self.target_path
)
return None
else:
ErrorDialog(_('No directory'),
_('There is no directory %s.\n\n'
'Please select another directory '
'or create it.') % parent_dir )
return None
self.set_default_directory(os.path.dirname(self.target_path) + os.sep)

View File

@ -103,7 +103,9 @@ class BatchTool(Tool):
Should be used for tools using batch transactions.
"""
def __init__(self, dbstate, user, options_class, name):
def __init__(self, dbstate, user, options_class, name, parent=None):
if user.uistate:
parent = user.uistate.window
if not user.prompt(
_('Undo history warning'),
_('Proceeding with this tool will erase the undo history '
@ -112,7 +114,7 @@ class BatchTool(Tool):
'made prior to it.\n\n'
'If you think you may want to revert running this tool, '
'please stop here and backup your database.'),
_('_Proceed with the tool'), _('_Stop')):
_('_Proceed with the tool'), _('_Stop'), parent):
self.fail = True
return

View File

@ -88,7 +88,7 @@ class User(user.User):
self._progress.close()
self._progress = None
def prompt(self, title, message, accept_label, reject_label):
def prompt(self, title, message, accept_label, reject_label, parent=None):
"""
Prompt the user with a message to select an alternative.
@ -106,7 +106,7 @@ class User(user.User):
:returns: the user's answer to the question
:rtype: bool
"""
dialog = QuestionDialog2(title, message, accept_label, reject_label)
dialog = QuestionDialog2(title, message, accept_label, reject_label, parent)
return dialog.run()
def warn(self, title, warning=""):

View File

@ -386,7 +386,7 @@ class ViewManager(CLIManager):
hpane.add2(self.notebook)
self.menubar = self.uimanager.get_widget('/MenuBar')
self.toolbar = self.uimanager.get_widget('/ToolBar')
vbox.pack_start(self.menubar, False, True, 0)
self.__attach_menubar(vbox)
vbox.pack_start(self.toolbar, False, True, 0)
vbox.add(hpane)
self.statusbar = Statusbar()
@ -834,13 +834,15 @@ class ViewManager(CLIManager):
self.uimanager.add_ui_from_string(UIDEFAULT)
self.uimanager.ensure_update()
def __attach_menubar(self, vbox):
vbox.pack_start(self.menubar, False, True, 0)
if _GTKOSXAPPLICATION:
menubar = self.uimanager.get_widget("/MenuBar")
menubar.hide()
self.menubar.hide()
quit_item = self.uimanager.get_widget("/MenuBar/FileMenu/Quit")
about_item = self.uimanager.get_widget("/MenuBar/HelpMenu/About")
prefs_item = self.uimanager.get_widget("/MenuBar/EditMenu/Preferences")
self.macapp.set_menu_bar(menubar)
self.macapp.set_menu_bar(self.menubar)
self.macapp.insert_app_menu_item(about_item, 0)
self.macapp.insert_app_menu_item(prefs_item, 1)
@ -1306,7 +1308,8 @@ class ViewManager(CLIManager):
_("Backup file already exists! Overwrite?"),
_("The file '%s' exists.") % filename,
_("Proceed and overwrite"),
_("Cancel the backup"))
_("Cancel the backup"),
parent=self.window)
yes_no = question.run()
if not yes_no:
return
@ -1337,12 +1340,13 @@ class ViewManager(CLIManager):
right pane, otherwise FileChooserDialog will hang.
"""
f = Gtk.FileChooserDialog(
_("Select backup directory"),
action=Gtk.FileChooserAction.SELECT_FOLDER,
buttons=(Gtk.STOCK_CANCEL,
Gtk.ResponseType.CANCEL,
Gtk.STOCK_APPLY,
Gtk.ResponseType.OK))
title=_("Select backup directory"),
parent=self.window,
action=Gtk.FileChooserAction.SELECT_FOLDER,
buttons=(Gtk.STOCK_CANCEL,
Gtk.ResponseType.CANCEL,
Gtk.STOCK_APPLY,
Gtk.ResponseType.OK))
mpath = path_entry.get_text()
if not mpath:
mpath = HOME_DIR

View File

@ -277,10 +277,18 @@ class Tags(DbGUIElement):
view.add_tag(trans, object_handle, tag_handle)
status.end()
def cb_menu_position(menu, button):
def cb_menu_position(*args):
"""
Determine the position of the popup menu.
"""
# takes two argument: menu, button
if len(args) == 2:
menu = args[0]
button = args[1]
# broken introspection can't handle MenuPositionFunc annotations corectly
else:
menu = args[0]
button = args[3]
ret_val, x_pos, y_pos = button.get_window().get_origin()
x_pos += button.get_allocation().x
y_pos += button.get_allocation().y + button.get_allocation().height

View File

@ -44,9 +44,10 @@ from gi.repository import Gtk
# GRAMPS modules
#
#-------------------------------------------------------------------------
from gramps.gen.lib.placetype import PlaceType
from gramps.gen.lib import Place, PlaceType
from gramps.gen.datehandler import format_time
from gramps.gen.utils.place import conv_lat_lon
from gramps.gen.display.place import displayer as place_displayer
from gramps.gen.constfunc import cuni
from .flatbasemodel import FlatBaseModel
from .treebasemodel import TreeBaseModel
@ -116,7 +117,9 @@ class PlaceBaseModel(object):
return len(self.fmap)+1
def column_title(self, data):
return cuni(data[2])
place = Place()
place.unserialize(data)
return place_displayer.display(self.db, place)
def column_name(self, data):
return cuni(data[6])

View File

@ -734,10 +734,18 @@ class TabLabel(Gtk.HBox):
else:
self.closebtn.hide()
def cb_menu_position(menu, button):
def cb_menu_position(*args):
"""
Determine the position of the popup menu.
"""
# takes two argument: menu, button
if len(args) == 2:
menu = args[0]
button = args[1]
# broken introspection can't handle MenuPositionFunc annotations corectly
else:
menu = args[0]
button = args[3]
ret_val, x_pos, y_pos = button.get_window().get_origin()
x_pos += button.get_allocation().x
y_pos += button.get_allocation().y + button.get_allocation().height

View File

@ -284,7 +284,11 @@ class UndoableEntry(Gtk.Entry):
self.set_position(undo_action.offset)
def _undo_delete(self, undo_action):
self.insert_text(undo_action.text, undo_action.start)
if not isinstance(undo_action.text, UNITYPE):
undo_action.text = conv_to_unicode(undo_action.text, 'utf-8')
with warnings.catch_warnings():
warnings.simplefilter('ignore')
self.insert_text(undo_action.text, undo_action.start)
if undo_action.delete_key_used:
self.set_position(undo_action.start)
else:

View File

@ -731,7 +731,7 @@ class FanChartOptions(MenuReportOptions):
p = ParagraphStyle()
p.set_font(f)
p.set_alignment(PARA_ALIGN_CENTER)
p.set_description(_('The style used for the text display of generation ' + "%d" % i))
p.set_description(_('The style used for the text display of generation "%d"') % i)
default_style.add_paragraph_style("FC-Text" + "%02d" % i, p)
# GraphicsStyles

View File

@ -281,7 +281,7 @@ class TimeLine(Report):
# subtitle if the report's output is in the main/UI language
mark = None
if toc:
mark = IndexMark(title, INDEX_TYPE_TOC, 1)
mark = IndexMark(title_one, INDEX_TYPE_TOC, 1)
self.doc.center_text('TLG-title', title, width / 2.0, 0, mark)
def draw_year_headings(self, year_low, year_high, start_pos, stop_pos):

View File

@ -532,16 +532,27 @@ class GedcomWriter(UpdateCallback):
extract the real event to discover the event type.
"""
global adop_written
# adop_written is only shared between this function and
# _process_person_event. This is rather ugly code, but it is difficult
# to support an Adoption event without an Adopted relationship from the
# parent(s), an Adopted relationship from the parent(s) without an
# event, and both an event and a relationship. All these need to be
# supported without duplicating the output of the ADOP GEDCOM tag. See
# bug report 2370.
adop_written = False
for event_ref in person.get_event_ref_list():
event = self.dbase.get_event_from_handle(event_ref.ref)
if not event: continue
self._process_person_event(event, event_ref)
self._adoption_records(person)
self._process_person_event(person, event, event_ref)
if not adop_written:
self._adoption_records(person, adop_written)
def _process_person_event(self, event, event_ref):
def _process_person_event(self, person, event, event_ref):
"""
Process a person event, which is not a BIRTH or DEATH event.
"""
global adop_written
etype = int(event.get_type())
# if the event is a birth or death, skip it.
if etype in (EventType.BIRTH, EventType.DEATH):
@ -578,8 +589,11 @@ class GedcomWriter(UpdateCallback):
if descr:
self._writeln(2, 'NOTE', "Description: " + descr)
self._dump_event_stats(event, event_ref)
if etype == EventType.ADOPT and not adop_written:
adop_written = True
self._adoption_records(person, adop_written)
def _adoption_records(self, person):
def _adoption_records(self, person, adop_written):
"""
Write Adoption events for each child that has been adopted.
@ -603,7 +617,8 @@ class GedcomWriter(UpdateCallback):
adoptions.append((family, child_ref.frel, child_ref.mrel))
for (fam, frel, mrel) in adoptions:
self._writeln(1, 'ADOP', 'Y')
if not adop_written:
self._writeln(1, 'ADOP', 'Y')
self._writeln(2, 'FAMC', '@%s@' % fam.get_gramps_id())
if mrel == frel:
self._writeln(3, 'ADOP', 'BOTH')
@ -1170,16 +1185,22 @@ class GedcomWriter(UpdateCallback):
cal = date.get_calendar()
mod = date.get_modifier()
quality = date.get_quality()
if quality in libgedcom.DATE_QUALITY:
qual_text = libgedcom.DATE_QUALITY[quality] + " "
else:
qual_text = ""
if mod == Date.MOD_SPAN:
val = "FROM %s TO %s" % (
libgedcom.make_gedcom_date(start, cal, mod, quality),
val = "%sFROM %s TO %s" % (
qual_text,
libgedcom.make_gedcom_date(start, cal, mod, None),
libgedcom.make_gedcom_date(date.get_stop_date(),
cal, mod, quality))
cal, mod, None))
elif mod == Date.MOD_RANGE:
val = "BET %s AND %s" % (
libgedcom.make_gedcom_date(start, cal, mod, quality),
val = "%sBET %s AND %s" % (
qual_text,
libgedcom.make_gedcom_date(start, cal, mod, None),
libgedcom.make_gedcom_date(date.get_stop_date(),
cal, mod, quality))
cal, mod, None))
else:
val = libgedcom.make_gedcom_date(start, cal, mod, quality)
self._writeln(level, 'DATE', val)

View File

@ -90,7 +90,7 @@ class GeneWebWriter(object):
self.dirname = os.path.dirname (self.filename)
try:
self.g = open(self.filename, "w")
self.g = open(self.filename, "wb")
except IOError as msg:
msg2 = _("Could not create %s") % self.filename
self.user.notify_error(msg2, str(msg))

View File

@ -140,6 +140,12 @@ class GrampsXmlWriter(UpdateCallback):
"Please make sure you have write access to the "
"directory and try again."))
return 0
else:
raise DbWriteFailure(_('No directory'),
_('There is no directory %s.\n\n'
'Please select another directory '
'or create it.') % base )
return 0
if os.path.exists(filename):
if not os.access(filename, os.W_OK):

View File

@ -249,9 +249,9 @@ class AgeStatsGramplet(Gramplet):
"""
# first, binify:
#print "create_bargraph", hash
bin = [0] * (max_val/bin_size)
bin = [0] * int(max_val/bin_size)
for value, hash_value in hash.items():
bin[value/bin_size] += hash_value
bin[int(value/bin_size)] += hash_value
text = ""
max_bin = float(max(bin))
if max_bin != 0:

View File

@ -18,6 +18,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
#
from __future__ import unicode_literals
from collections import defaultdict
from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext
@ -66,9 +68,9 @@ class GivenNameCloudGramplet(Gramplet):
allnames = [person.get_primary_name()] + person.get_alternate_names()
allnames = set(name.get_first_name().strip() for name in allnames)
for givenname in allnames:
anyNBSP = givenname.split(u'\u00A0')
anyNBSP = givenname.split('\u00A0')
if len(anyNBSP) > 1: # there was an NBSP, a non-breaking space
first_two = anyNBSP[0] + u'\u00A0' + anyNBSP[1].split()[0]
first_two = anyNBSP[0] + '\u00A0' + anyNBSP[1].split()[0]
givensubnames[first_two] += 1
representative_handle[first_two] = person.handle
givenname = ' '.join(anyNBSP[1].split()[1:])

View File

@ -76,15 +76,31 @@ def importData(database, filename, user):
database.__class__.__bases__ = (DbMixin,) + \
database.__class__.__bases__
try:
ifile = open(filename, "r")
except IOError:
return
if sys.version_info[0] < 3:
try:
ifile = open(filename, "rU")
except IOError:
return
else:
try:
ifile = open(filename, "rb")
except IOError:
return
# print("file opened")
ansel = False
gramps = False
for index in range(50):
line = ifile.readline().split()
# Treat the file as though it is UTF-8 since this is the more modern
# option; and anyway it doesn't really matter as we are only trying to
# detect a CHAR or SOUR line which is only 7-bit ASCII anyway, and we
# ignore anything that can't be translated.
line = ifile.readline()
if sys.version_info[0] < 3:
line = unicode(line, encoding='utf-8', errors='replace')
else:
line = line.decode(encoding='utf-8', errors='replace')
line = line.split()
if len(line) == 0:
break
if len(line) > 2 and line[1][0:4] == 'CHAR' and line[2] == "ANSEL":

View File

@ -644,9 +644,8 @@ class GeneWebParser(object):
birth_source = self.get_or_create_source(self.decode(fields[idx]))
idx += 1
elif field[0] == '!':
LOG.debug("Baptize at: %s" % fields[idx])
bapt_date = self.parse_date(self.decode(fields[idx][1:]))
idx += 1
LOG.debug("Baptize at: %s" % field[1:])
bapt_date = self.parse_date(self.decode(field[1:]))
elif field == '#bp' and idx < len(fields):
LOG.debug("Birth Place: %s" % fields[idx])
birth_place = self.get_or_create_place(self.decode(fields[idx]))
@ -668,9 +667,10 @@ class GeneWebParser(object):
death_source = self.get_or_create_source(self.decode(fields[idx]))
idx += 1
elif field == '#buri' and idx < len(fields):
LOG.debug("Burial Date: %s" % fields[idx])
bur_date = self.parse_date(self.decode(fields[idx]))
idx += 1
if fields[idx][0]!='#': # bug in GeneWeb: empty #buri fields
LOG.debug("Burial Date: %s" % fields[idx])
bur_date = self.parse_date(self.decode(fields[idx]))
idx += 1
elif field == '#crem' and idx < len(fields):
LOG.debug("Cremention Date: %s" % fields[idx])
crem_date = self.parse_date(self.decode(fields[idx]))

View File

@ -578,6 +578,10 @@ LDS_STATUS = {
# table for skipping illegal control chars in GEDCOM import
# Only 09, 0A, 0D are allowed.
STRIP_DICT = dict.fromkeys(list(range(9))+list(range(11, 13))+list(range(14, 32)))
# The C1 Control characters are not treated in Latin-1 (ISO-8859-1) as
# undefined, but if they have been used, the file is probably supposed to be
# cp1252
DEL_AND_C1 = dict.fromkeys(list(range(0x7F, 0x9F)))
#-------------------------------------------------------------------------
#
@ -689,7 +693,7 @@ class GedcomDateParser(DateParser):
#-------------------------------------------------------------------------
class Lexer(object):
def __init__(self, ifile):
def __init__(self, ifile, __add_msg):
self.ifile = ifile
self.current_list = []
self.eof = False
@ -700,6 +704,7 @@ class Lexer(object):
TOKEN_CONT : self.__fix_token_cont,
TOKEN_CONC : self.__fix_token_conc,
}
self.__add_msg = __add_msg
def readline(self):
if len(self.current_list) <= 1 and not self.eof:
@ -738,6 +743,7 @@ class Lexer(object):
self.eof = True
return
original_line = line
try:
# According to the GEDCOM 5.5 standard,
# Chapter 1 subsection Grammar
@ -771,6 +777,13 @@ class Lexer(object):
tag = line[0]
line_value = line[2]
except:
problem = _("Line ignored ")
text = original_line.rstrip('\n\r')
prob_width = 66
problem = problem.ljust(prob_width)[0:(prob_width-1)]
text = text.replace("\n", "\n".ljust(prob_width + 22))
message = "%s %s" % (problem, text)
self.__add_msg(message)
continue
token = TOKENS.get(tag, TOKEN_UNKNOWN)
@ -780,6 +793,10 @@ class Lexer(object):
if func:
func(data)
else:
# There will normally only be one space between tag and
# line_value, but in case there is more then one, remove extra
# spaces after CONC/CONT processing
data = data[:2] + (data[2].strip(),) + data[3:]
self.current_list.insert(0, data)
def clean_up(self):
@ -1234,27 +1251,29 @@ class GedInfoParser(object):
#
#-------------------------------------------------------------------------
class BaseReader(object):
def __init__(self, ifile, encoding):
def __init__(self, ifile, encoding, __add_msg):
self.ifile = ifile
self.enc = encoding
self.__add_msg = __add_msg
def reset(self):
self.ifile.seek(0)
def readline(self):
if sys.version_info[0] < 3:
line = unicode(self.ifile.readline(),
encoding=self.enc,
errors='replace')
else:
line = self.ifile.readline()
line = line.decode(self.enc, errors='replace')
return line.translate(STRIP_DICT)
raise NotImplemented
def report_error(self, problem, line):
line = line.rstrip('\n\r')
prob_width = 66
problem = problem.ljust(prob_width)[0:(prob_width-1)]
text = line.replace("\n", "\n".ljust(prob_width + 22))
message = "%s %s" % (problem, text)
self.__add_msg(message)
class UTF8Reader(BaseReader):
def __init__(self, ifile):
BaseReader.__init__(self, ifile, 'utf8')
def __init__(self, ifile, __add_msg):
BaseReader.__init__(self, ifile, 'utf8', __add_msg)
self.reset()
def reset(self):
@ -1275,23 +1294,61 @@ class UTF8Reader(BaseReader):
class UTF16Reader(BaseReader):
def __init__(self, ifile):
def __init__(self, ifile, __add_msg):
new_file = codecs.EncodedFile(ifile, 'utf8', 'utf16')
BaseReader.__init__(self, new_file, 'utf16')
BaseReader.__init__(self, new_file, '', __add_msg)
self.reset()
def readline(self):
l = self.ifile.readline()
if l.strip():
return l
line = self.ifile.readline()
if sys.version_info[0] < 3:
line = unicode(line,
encoding='utf8',
errors='replace')
if line.strip():
return line.translate(STRIP_DICT)
else:
line = self.ifile.readline()
line = unicode(line,
encoding='utf8',
errors='replace')
return line.translate(STRIP_DICT)
else:
return self.ifile.readline()
line = line.decode('utf8', errors='replace')
return line.translate(STRIP_DICT)
class AnsiReader(BaseReader):
def __init__(self, ifile):
BaseReader.__init__(self, ifile, 'latin1')
def __init__(self, ifile, __add_msg):
BaseReader.__init__(self, ifile, 'latin1', __add_msg)
def readline(self):
line = self.ifile.readline()
if sys.version_info[0] < 3:
line = unicode(line,
encoding=self.enc,
errors='replace')
else:
line = line.decode(self.enc, errors='replace')
if line.translate(DEL_AND_C1) != line:
self.report_error("DEL or C1 control chars in line did you mean CHAR cp1252??", line)
return line.translate(STRIP_DICT)
class CP1252Reader(BaseReader):
def __init__(self, ifile, __add_msg):
BaseReader.__init__(self, ifile, 'cp1252', __add_msg)
def readline(self):
line = self.ifile.readline()
if sys.version_info[0] < 3:
line = unicode(line,
encoding=self.enc,
errors='replace')
else:
line = line.decode(self.enc, errors='replace')
return line.translate(STRIP_DICT)
class AnselReader(BaseReader):
"""
ANSEL to Unicode Conversion
@ -1311,7 +1368,8 @@ class AnselReader(BaseReader):
TODO: should we allow TAB, as a Gramps extension?
"""
__printable_ascii = list(map(chr, list(range(32, 127)))) # note: up thru 126
__use_ASCII = list(map(chr, [10, 27, 29 , 30, 31])) + __printable_ascii
# LF CR Esc GS RS US
__use_ASCII = list(map(chr, [10, 13, 27, 29 , 30, 31])) + __printable_ascii
# mappings of single byte ANSEL codes to unicode
__onebyte = {
@ -1324,9 +1382,11 @@ class AnselReader(BaseReader):
b'\xB4' : '\u00fe', b'\xB5' : '\u00e6', b'\xB6' : '\u0153',
b'\xB7' : '\u02ba', b'\xB8' : '\u0131', b'\xB9' : '\u00a3',
b'\xBA' : '\u00f0', b'\xBC' : '\u01a1', b'\xBD' : '\u01b0',
b'\xBE' : '\u25a1', b'\xBF' : '\u25a0',
b'\xC0' : '\u00b0', b'\xC1' : '\u2113', b'\xC2' : '\u2117',
b'\xC3' : '\u00a9', b'\xC4' : '\u266f', b'\xC5' : '\u00bf',
b'\xC6' : '\u00a1', b'\xC7' : '\u00df', b'\xC8' : '\u20ac',
b'\xCD' : '\u0065', b'\xCE' : '\u006f', b'\xCF' : '\u00df',
}
# combining forms (in ANSEL, they precede the modified ASCII character
@ -1347,6 +1407,7 @@ class AnselReader(BaseReader):
b'\xF3' : '\u0324', b'\xF4' : '\u0325', b'\xF5' : '\u0333',
b'\xF6' : '\u0332', b'\xF7' : '\u0326', b'\xF8' : '\u031c',
b'\xF9' : '\u032e', b'\xFA' : '\ufe22', b'\xFB' : '\ufe23',
b'\xFC' : '\u0338',
b'\xFE' : '\u0313',
}
@ -1504,52 +1565,94 @@ class AnselReader(BaseReader):
b'\xF9\x48' : '\u1e2a', b'\xF9\x68' : '\u1e2b',
}
@staticmethod
def __ansel_to_unicode(s):
def __ansel_to_unicode(self, s):
""" Convert an ANSEL encoded string to unicode """
buff = StringIO()
while s:
if ord(s[0]) < 128:
if s[0] in AnselReader.__use_ASCII:
head = s[0]
else:
# substitute space for disallowed (control) chars
head = ' '
s = s[1:]
else:
if s[0:2] in AnselReader.__twobyte:
head = AnselReader.__twobyte[s[0:2]]
s = s[2:]
elif s[0] in AnselReader.__onebyte:
head = AnselReader.__onebyte[s[0]]
s = s[1:]
elif s[0] in AnselReader.__acombiners:
c = AnselReader.__acombiners[s[0]]
# always consume the combiner
s = s[1:]
next = s[0]
if next in AnselReader.__printable_ascii:
# consume next as well
s = s[1:]
# unicode: combiner follows base-char
head = next + c
else:
# just drop the unexpected combiner
continue
else:
head = '\ufffd' # "Replacement Char"
s = s[1:]
buff.write(head.encode("utf-8"))
error = ""
if sys.version_info[0] < 3:
while s:
if ord(s[0]) < 128:
if s[0] in AnselReader.__use_ASCII:
head = s[0]
else:
# substitute space for disallowed (control) chars
error += " (%#X)" % ord(s[0])
head = ' '
s = s[1:]
else:
if s[0:2] in AnselReader.__twobyte:
head = AnselReader.__twobyte[s[0:2]]
s = s[2:]
elif s[0] in AnselReader.__onebyte:
head = AnselReader.__onebyte[s[0]]
s = s[1:]
elif s[0] in AnselReader.__acombiners:
c = AnselReader.__acombiners[s[0]]
# always consume the combiner
s = s[1:]
next = s[0]
if next in AnselReader.__printable_ascii:
# consume next as well
s = s[1:]
# unicode: combiner follows base-char
head = next + c
else:
# just drop the unexpected combiner
error += " (%#X)" % ord(s[0])
continue
else:
error += " (%#X)" % ord(s[0])
head = '\ufffd' # "Replacement Char"
s = s[1:]
buff.write(head.encode("utf-8"))
ans = unicode(buff.getvalue(), "utf-8")
else:
ans = buff.getvalue().decode("utf-8")
while s:
if s[0] < 128:
if chr(s[0]) in AnselReader.__use_ASCII:
head = chr(s[0])
else:
# substitute space for disallowed (control) chars
error += " (%#X)" % s[0]
head = ' '
s = s[1:]
else:
if s[0:2] in AnselReader.__twobyte:
head = AnselReader.__twobyte[s[0:2]]
s = s[2:]
elif bytes([s[0]]) in AnselReader.__onebyte:
head = AnselReader.__onebyte[bytes([s[0]])]
s = s[1:]
elif bytes([s[0]]) in AnselReader.__acombiners:
c = AnselReader.__acombiners[bytes([s[0]])]
# always consume the combiner
s = s[1:]
next_byte = s[0]
if next_byte < 128 and chr(next_byte) in AnselReader.__printable_ascii:
# consume next as well
s = s[1:]
# unicode: combiner follows base-char
head = chr(next_byte) + c
else:
# just drop the unexpected combiner
error += " (%#X)" % s[0]
continue
else:
error += " (%#X)" % s[0]
head = '\ufffd' # "Replacement Char"
s = s[1:]
buff.write(head)
ans = buff.getvalue()
if error:
# e.g. Illegal character (oxAB) (0xCB)... 1 NOTE xyz?pqr?lmn
self.report_error(_("Illegal character%s") % error, ans)
buff.close()
return ans
def __init__(self, ifile):
BaseReader.__init__(self, ifile, "")
def __init__(self, ifile, __add_msg):
BaseReader.__init__(self, ifile, "", __add_msg)
def readline(self):
return self.__ansel_to_unicode(self.ifile.readline())
@ -1807,6 +1910,7 @@ class GedcomParser(UpdateCallback):
__TRUNC_MSG = _("Your GEDCOM file is corrupted. "
"It appears to have been truncated.")
_EMPTY_LOC = Location().serialize()
SyntaxError = "Syntax Error"
BadFile = "Not a GEDCOM file"
@ -2298,12 +2402,12 @@ class GedcomParser(UpdateCallback):
TOKEN_CTRY : self.__location_ctry,
# Not legal GEDCOM - not clear why these are included at this level
TOKEN_ADDR : self.__ignore,
TOKEN_DATE : self.__location_date,
TOKEN_DATE : self.__ignore, # there is nowhere to put a date
TOKEN_NOTE : self.__location_note,
TOKEN_RNOTE : self.__location_note,
TOKEN__LOC : self.__ignore,
TOKEN__NAME : self.__ignore,
TOKEN_PHON : self.__ignore,
TOKEN_PHON : self.__location_phone,
TOKEN_IGNORE : self.__ignore,
}
self.func_list.append(self.parse_loc_tbl)
@ -2638,27 +2742,29 @@ class GedcomParser(UpdateCallback):
self.func_list.append(self.note_parse_tbl)
# look for existing place titles, build a map
self.place_names = {}
self.place_names = defaultdict(list)
cursor = dbase.get_place_cursor()
data = next(cursor)
while data:
(handle, val) = data
self.place_names[val[2]] = handle
self.place_names[val[2]].append(handle)
data = next(cursor)
cursor.close()
enc = stage_one.get_encoding()
if enc == "ANSEL":
rdr = AnselReader(ifile)
rdr = AnselReader(ifile, self.__add_msg)
elif enc in ("UTF-8", "UTF8"):
rdr = UTF8Reader(ifile)
elif enc in ("UTF-16", "UTF16", "UNICODE"):
rdr = UTF16Reader(ifile)
rdr = UTF8Reader(ifile, self.__add_msg)
elif enc in ("UTF-16LE", "UTF-16BE", "UTF16", "UNICODE"):
rdr = UTF16Reader(ifile, self.__add_msg)
elif enc in ("CP1252", "WINDOWS-1252"):
rdr = CP1252Reader(ifile, self.__add_msg)
else:
rdr = AnsiReader(ifile)
rdr = AnsiReader(ifile, self.__add_msg)
self.lexer = Lexer(rdr)
self.lexer = Lexer(rdr, self.__add_msg)
self.filename = filename
self.backoff = False
@ -2716,7 +2822,12 @@ class GedcomParser(UpdateCallback):
else:
message = _("GEDCOM import report: %s errors detected") % \
self.number_of_errors
self.user.info(message, "".join(self.errors), monospaced=True)
if hasattr(self.user.uistate, 'window'):
parent_window = self.user.uistate.window
else:
parent_window = None
self.user.info(message, "".join(self.errors),
parent = parent_window, monospaced=True)
def __clean_up(self):
"""
@ -2869,40 +2980,60 @@ class GedcomParser(UpdateCallback):
self.dbase.add_note(note, self.trans)
return note
def __find_or_create_place(self, title):
def __loc_is_empty(self, location):
"""
Finds or creates a place based on the GRAMPS ID. If the ID is
already used (is in the db), we return the item in the db. Otherwise,
we create a new place, assign the handle and GRAMPS ID.
Determines whether a location is empty.
@param location: The current location
@type location: gen.lib.Location
@return True of False
"""
if location is None:
return True
elif location.serialize() == self._EMPTY_LOC:
return True
elif location.is_empty():
return True
return False
def __find_place(self, title, location):
"""
Finds an existing place based on the title and primary location.
@param title: The place title
@type title: string
@param location: The current location
@type location: gen.lib.Location
@return gen.lib.Place
"""
for place_handle in self.place_names[title]:
place = self.dbase.get_place_from_handle(place_handle)
if place.get_title() == title:
if self.__loc_is_empty(location) and \
self.__loc_is_empty(self.__get_first_loc(place)):
return place
elif (not self.__loc_is_empty(location) and \
not self.__loc_is_empty(self.__get_first_loc(place)) and
self.__get_first_loc(place).is_equivalent(location) == IDENTICAL):
return place
return None
def __create_place(self, title, location):
"""
Create a new place based on the title and primary location.
@param title: The place title
@type title: string
@param location: The current location
@type location: gen.lib.Location
@return gen.lib.Place
"""
place = Place()
# check to see if we've encountered this name before
# if we haven't we need to get a new GRAMPS ID
intid = self.place_names.get(title)
if intid is None:
intid = self.lid2id.get(title)
if intid is None:
new_id = self.dbase.find_next_place_gramps_id()
else:
new_id = None
else:
new_id = None
# check to see if the name already existed in the database
# if it does, create a new name by appending the GRAMPS ID.
# generate a GRAMPS ID if needed
if self.dbase.has_place_handle(intid):
place.unserialize(self.dbase.get_raw_place_data(intid))
else:
intid = create_id()
place.set_handle(intid)
place.set_title(title)
place.set_gramps_id(new_id)
self.dbase.add_place(place, self.trans)
self.lid2id[title] = intid
place.set_title(title)
if location:
place.add_alternate_locations(location)
self.dbase.add_place(place, self.trans)
self.place_names[title].append(place.get_handle())
return place
def __find_file(self, fullname, altpath):
@ -3321,7 +3452,10 @@ class GedcomParser(UpdateCallback):
if line.token_text == self.subm and self.import_researcher:
self.dbase.set_researcher(state.res)
submitter_name = _("SUBM (Submitter): @%s@") % line.token_text
if state.res.get_name() == "":
submitter_name = _("SUBM (Submitter): @%s@") % line.token_text
else:
submitter_name = _("SUBM (Submitter): (@%s@) %s") % (line.token_text, state.res.get_name())
if self.use_def_src:
repo.set_name(submitter_name)
repo.set_handle(create_id())
@ -4466,8 +4600,12 @@ class GedcomParser(UpdateCallback):
@type state: CurrentState
"""
try:
state.place = self.__find_or_create_place(line.data)
state.place.set_title(line.data)
title = line.data
place = self.__find_place(title, None)
if place:
state.place = place
else:
state.place = self.__create_place(title, None)
state.lds_ord.set_place_handle(state.place.handle)
except NameError:
return
@ -5157,7 +5295,7 @@ class GedcomParser(UpdateCallback):
# +1 SOUR @<XREF:SOUR>@ {0:M}
if not line.data:
self.__add_msg(_("Empty note ignored"), line, state)
self.__skip_subordinate_levels(level+1, state)
self.__skip_subordinate_levels(state.level+1, state)
else:
new_note = Note(line.data)
new_note.set_gramps_id(self.nid_map[""])
@ -5305,22 +5443,56 @@ class GedcomParser(UpdateCallback):
if self.is_ftw and state.event.type in FTW_BAD_PLACE:
state.event.set_description(line.data)
else:
# It is possible that we have already got an address structure
# associated with this event. In that case, we will remember the
# location to re-insert later, and set the place as the place name
# and primary location
title = line.data
place_handle = state.event.get_place_handle()
if place_handle:
place = self.dbase.get_place_from_handle(place_handle)
# We encounter a PLAC, having previously encountered an ADDR
old_place = self.dbase.get_place_from_handle(place_handle)
old_title = old_place.get_title()
location = self.__get_first_loc(old_place)
if old_title != "":
# We have previously found a PLAC
self.__add_msg(_("A second PLAC ignored"), line, state)
# ignore this second PLAC, and use the old one
title = old_title
place = old_place
else:
# This is the first PLAC
refs = list(self.dbase.find_backlink_handles(place_handle))
# We haven't commited the event yet, so the place will not
# be linked to it. If there are any refs they will be from
# other events (etc)
if len(refs) == 0:
place = self.__find_place(title, location)
if place is None:
place = old_place
place.set_title(title)
self.place_names[old_title].remove(place_handle)
self.place_names[title].append(place_handle)
else:
place.merge(old_place)
self.place_import.remove_location(old_place.handle)
self.dbase.remove_place(place_handle, self.trans)
self.place_names[old_title].remove(place_handle)
else:
place = self.__find_place(title, location)
if place is None:
place = self.__create_place(title, location)
else:
pass
else:
place = self.__find_or_create_place(line.data)
place.set_title(line.data)
state.event.set_place_handle(place.handle)
# The first thing we encounter is PLAC
location = None
place = self.__find_place(title, location)
if place is None:
place = self.__create_place(title, location)
state.event.set_place_handle(place.handle)
sub_state = CurrentState()
sub_state.place = place
sub_state.level = state.level+1
sub_state.pf = PlaceParser()
sub_state.pf = self.place_parser
self.__parse_level(sub_state, self.event_place_map,
self.__undefined)
@ -5430,8 +5602,8 @@ class GedcomParser(UpdateCallback):
sub_state = CurrentState(level=state.level+1)
sub_state.location = Location()
sub_state.note = []
sub_state.event = state.event
sub_state.place = Place() # temp stash for notes, citations etc
self.__parse_level(sub_state, self.parse_loc_tbl, self.__undefined)
state.msg += sub_state.msg
@ -5439,21 +5611,53 @@ class GedcomParser(UpdateCallback):
self.__merge_address(free_form, sub_state.location, line, state)
location = sub_state.location
note_list = sub_state.note
place_handle = state.event.get_place_handle()
if place_handle:
place = self.dbase.get_place_from_handle(place_handle)
# We encounter an ADDR having previously encountered a PLAC
old_place = self.dbase.get_place_from_handle(place_handle)
title = old_place.get_title()
if len(old_place.get_alternate_locations()) != 0 and \
not self.__get_first_loc(old_place).is_empty():
# We have perviously found an ADDR, or have populated location
# from PLAC title
self.__add_msg(_("Location already populated; ADDR ignored"),
line, state)
# ignore this second ADDR, and use the old one
location = self.__get_first_loc(old_place)
place = old_place
else:
# This is the first ADDR
refs = list(self.dbase.find_backlink_handles(place_handle))
# We haven't commited the event yet, so the place will not be
# linked to it. If there are any refs they will be from other
# events (etc)
if len(refs) == 0:
place = self.__find_place(title, location)
if place is None:
place = old_place
self.__add_location(place, location)
else:
place.merge(old_place)
self.place_import.remove_location(old_place.handle)
self.dbase.remove_place(place_handle, self.trans)
self.place_names[title].remove(place_handle)
else:
place = self.__find_place(title, location)
if place is None:
place = self.__create_place(title, location)
else:
pass
else:
place = self.__find_or_create_place(line.data)
place.set_title(line.data)
place_handle = place.handle
# The first thing we encounter is ADDR
title = ""
place = self.__find_place(title, location)
if place is None:
place = self.__create_place(title, location)
self.__add_location(place, location)
# merge notes etc into place
place.merge(sub_state.place)
list(map(place.add_note, note_list))
state.event.set_place_handle(place_handle)
state.event.set_place_handle(place.get_handle())
self.dbase.commit_place(place, self.trans)
def __add_location(self, place, location):
@ -5468,6 +5672,18 @@ class GedcomParser(UpdateCallback):
return
place.add_alternate_locations(location)
def __get_first_loc(self, place):
"""
@param place: A place object
@type place: Place
@return location: the first alternate location if any else None
@type location: gen.lib.location
"""
if len(place.get_alternate_locations()) == 0:
return None
else:
return place.get_alternate_locations()[0]
def __event_phon(self, line, state):
"""
@param line: The current line in GedLine format
@ -5626,7 +5842,7 @@ class GedcomParser(UpdateCallback):
"""
while True:
line = self.__get_next_line()
if self.__level_is_finished(line, state.level):
if self.__level_is_finished(line, state.level+1):
break
elif line.token == TOKEN_AGE:
attr = Attribute()
@ -5647,7 +5863,7 @@ class GedcomParser(UpdateCallback):
"""
while True:
line = self.__get_next_line()
if self.__level_is_finished(line, state.level):
if self.__level_is_finished(line, state.level+1):
break
elif line.token == TOKEN_AGE:
attr = Attribute()
@ -6635,17 +6851,6 @@ class GedcomParser(UpdateCallback):
url.set_type(UrlType(UrlType.EMAIL))
state.repo.add_url(url)
def __location_date(self, line, state):
"""
@param line: The current line in GedLine format
@type line: GedLine
@param state: The current state
@type state: CurrentState
"""
if not state.location:
state.location = Location()
state.location.set_date_object(line.data)
def __location_adr1(self, line, state):
"""
@param line: The current line in GedLine format
@ -6714,7 +6919,7 @@ class GedcomParser(UpdateCallback):
state.location = Location()
state.location.set_country(line.data)
def __location_note(self, line, state):
def __location_phone(self, line, state):
"""
@param line: The current line in GedLine format
@type line: GedLine
@ -6723,9 +6928,19 @@ class GedcomParser(UpdateCallback):
"""
if not state.location:
state.location = Location()
state.location.set_phone(line.data)
def __location_note(self, line, state):
"""
@param line: The current line in GedLine format
@type line: GedLine
@param state: The current state
@type state: CurrentState
"""
if state.event:
self.__parse_note(line, state.event, state.level+1, state)
self.__parse_note(line, state.place, state.level, state)
else:
# This causes notes below SUBMitter to be ignored
self.__not_recognized(line, state.level, state)
def __optional_note(self, line, state):
@ -7063,8 +7278,13 @@ class GedcomParser(UpdateCallback):
sattr.set_value(line.data)
self.def_src.add_attribute(sattr)
elif line.token == TOKEN_FORM:
if line.data != "LINEAGE-LINKED":
self.__add_msg(_("GEDCOM form not supported"), line, state)
if line.data == "LINEAGE-LINKED":
pass
elif line.data.upper() == "LINEAGE-LINKED":
# Allow Lineage-Linked etc. though it should be in uppercase
self.__add_msg(_("GEDCOM FORM should be in uppercase"), line, state)
else:
self.__add_msg(_("GEDCOM FORM not supported"), line, state)
if self.use_def_src:
sattr = SrcAttribute()
sattr.set_type(_('GEDCOM form'))
@ -7284,6 +7504,7 @@ class GedcomParser(UpdateCallback):
sattr.set_type(msg)
sattr.set_value(line.data)
self.def_src.add_attribute(sattr)
self.dbase.commit_source(self.def_src, self.trans)
def handle_source(self, line, level, state):
"""
@ -7609,7 +7830,7 @@ class GedcomStageOne(object):
input_file.read(1)
self.enc = "UTF8"
return input_file
elif line == b"\xff\xfe":
elif line == b"\xff\xfe" or line == b"\xfe\xff":
self.enc = "UTF16"
input_file.seek(0)
return codecs.EncodedFile(input_file, 'utf8', 'utf16')
@ -7630,25 +7851,33 @@ class GedcomStageOne(object):
reader = self.__detect_file_decoder(self.ifile)
for line in reader:
# Treat the file as though it is UTF-8 since this will be right if a
# BOM was detected; it is the more modern option; and anyway it
# doesn't really matter as we are only trying to detect a CHAR line
# which is only 7-bit ASCII anyway, and we ignore anything that
# can't be translated.
if sys.version_info[0] < 3:
line = unicode(line, encoding='utf-8', errors='replace')
else:
line = line.decode(encoding='utf-8', errors='replace')
line = line.strip()
if not line:
continue
self.lcnt += 1
data = line.split(None, 2) + ['']
try:
data = line.split(None, 2) + ['']
(level, key, value) = data[:3]
level = int(level)
key = conv_to_unicode(key.strip())
value = conv_to_unicode(value.strip())
key = key.strip()
value = value.strip()
except:
LOG.warn(_("Invalid line %d in GEDCOM file.") % self.lcnt)
continue
if level == 0 and key[0] == '@':
if value == ("FAM", "FAMILY") :
if value in ("FAM", "FAMILY") :
current_family_id = key.strip()[1:-1]
elif value == ("INDI", "INDIVIDUAL"):
elif value in ("INDI", "INDIVIDUAL"):
self.pcnt += 1
elif key in ("HUSB", "HUSBAND", "WIFE") and \
self.__is_xref_value(value):
@ -7658,6 +7887,9 @@ class GedcomStageOne(object):
elif key == 'CHAR' and not self.enc:
assert(isinstance(value, STRTYPE))
self.enc = value
LOG.debug("parse pcnt %d" % self.pcnt)
LOG.debug("parse famc %s" % dict(self.famc))
LOG.debug("parse fams %s" % dict(self.fams))
def get_famc_map(self):
"""

View File

@ -51,6 +51,15 @@ class PlaceImport(object):
self.loc2handle[location] = handle
self.handle2loc[handle] = location
def remove_location(self, handle):
"""
Remove the location of a place already in the database.
"""
if handle in self.handle2loc:
loc = self.handle2loc[handle]
del(self.loc2handle[loc])
del(self.handle2loc[handle])
def generate_hierarchy(self, trans):
"""
Generate missing places in the place hierarchy.

View File

@ -181,7 +181,19 @@ class GeoGraphyView(OsmGps, NavigationView):
self.geo_othermap[ident] = cairo.ImageSurface.create_from_png(fh)
#self.geo_othermap[ident] = cairo.ImageSurface.create_from_png(path)
def add_bookmark(self, menu, handle):
def add_bookmark(self, menu):
mlist = self.selected_handles()
if mlist:
self.bookmarks.add(mlist[0])
else:
from gramps.gui.dialog import WarningDialog
WarningDialog(
_("Could Not Set a Bookmark"),
_("A bookmark could not be set because "
"no one was selected."))
def add_bookmark_from_popup(self, menu, handle):
if handle:
self.uistate.set_active(handle, self.navigation_type())
self.bookmarks.add(handle)

View File

@ -25,6 +25,8 @@
Display a people who have a person's same surname or given name.
"""
from __future__ import unicode_literals
from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext
ngettext = glocale.translation.ngettext # else "nearby" comments are ignored
@ -68,9 +70,9 @@ class SameGiven(Rule):
src = self.list[0].upper()
for name in [person.get_primary_name()] + person.get_alternate_names():
if name.first_name:
anyNBSP = name.first_name.split(u'\u00A0')
anyNBSP = name.first_name.split('\u00A0')
if len(anyNBSP) > 1: # there was an NBSP, a non-breaking space
first_two = anyNBSP[0] + u'\u00A0' + anyNBSP[1].split()[0]
first_two = anyNBSP[0] + '\u00A0' + anyNBSP[1].split()[0]
if first_two.upper() == src:
return True
else:

View File

@ -828,9 +828,9 @@ class RelationshipCalculator(gramps.gen.relationship.RelationshipCalculator):
"""
rel_str = "parents llunyans"
#atgen = u" de la %sena generació"
#bygen = u" per la %sena generació"
#cmt = u" (germans o germanes d'un avantpassat" + atgen % Ga + ")"
#atgen = " de la %sena generació"
#bygen = " per la %sena generació"
#cmt = " (germans o germanes d'un avantpassat" + atgen % Ga + ")"
if in_law_a or in_law_b:
inlaw = self.INLAW
@ -920,7 +920,7 @@ class RelationshipCalculator(gramps.gen.relationship.RelationshipCalculator):
inlaw = ""
rel_str = "un parent llunyà%s" % inlaw
#bygen = u" per la %sena generació"
#bygen = " per la %sena generació"
if Ga == 0:
# b is descendant of a

View File

@ -333,7 +333,7 @@ def get_child_unknown(level, inlaw=""):
else:
return "un descendant lointain%s" % inlaw
def get_sibling_unknown(inlaw=""):
def get_sibling_unknown(Ga, inlaw=""):
"""
sibling of an ancestor, gender = unknown
"""
@ -653,7 +653,7 @@ class RelationshipCalculator(gramps.gen.relationship.RelationshipCalculator):
elif gender_b == Person.FEMALE:
rel_str = "la tante lointaine" + bygen % (Ga + 1)
elif gender_b == Person.UNKNOWN:
rel_str = get_sibling_unknown(inlaw)
rel_str = get_sibling_unknown(Ga, inlaw)
else:
return rel_str
elif Ga == 1:
@ -672,7 +672,7 @@ class RelationshipCalculator(gramps.gen.relationship.RelationshipCalculator):
rel_str = "la nièce lointaine%s (%dème génération)" % \
(inlaw, Gb)
elif gender_b == Person.UNKNOWN:
rel_str = get_sibling_unknown(inlaw)
rel_str = get_sibling_unknown(Ga, inlaw)
else:
return rel_str
elif Ga == Gb:
@ -684,7 +684,7 @@ class RelationshipCalculator(gramps.gen.relationship.RelationshipCalculator):
elif gender_b == Person.FEMALE:
rel_str = get_cousine(Ga - 1, 0, inlaw=inlaw)
elif gender_b == Person.UNKNOWN:
rel_str = get_sibling_unknown(inlaw)
rel_str = get_sibling_unknown(Ga, inlaw)
else:
return rel_str
elif Ga > 1 and Ga > Gb:

View File

@ -205,10 +205,18 @@ class DropdownSidebar(BaseSidebar):
self.viewmanager.notebook.set_current_page(page_no)
self.__handlers_unblock()
def cb_menu_position(menu, button):
def cb_menu_position(*args):
"""
Determine the position of the popup menu.
"""
# takes two argument: menu, button
if len(args) == 2:
menu = args[0]
button = args[1]
# broken introspection can't handle MenuPositionFunc annotations corectly
else:
menu = args[0]
button = args[3]
ret_val, x_pos, y_pos = button.get_window().get_origin()
x_pos += button.get_allocation().x
y_pos += button.get_allocation().y + button.get_allocation().height

View File

@ -218,10 +218,18 @@ class ExpanderSidebar(BaseSidebar):
self.viewmanager.notebook.set_current_page(page_no)
self.__handlers_unblock()
def cb_menu_position(menu, button):
def cb_menu_position(*args):
"""
Determine the position of the popup menu.
"""
# takes two argument: menu, button
if len(args) == 2:
menu = args[0]
button = args[1]
# broken introspection can't handle MenuPositionFunc annotations corectly
else:
menu = args[0]
button = args[3]
ret_val, x_pos, y_pos = button.get_window().get_origin()
x_pos += button.get_allocation().x
y_pos += button.get_allocation().y + button.get_allocation().height

View File

@ -3,7 +3,9 @@
<interface>
<requires lib="gtk+" version="3.0"/>
<object class="GtkDialog" id="check">
<!-- This dialog is ManagedWindow class
<property name="visible">True</property>
-->
<property name="can_focus">False</property>
<property name="default_width">450</property>
<property name="default_height">400</property>

View File

@ -83,7 +83,7 @@ from gramps.gen.constfunc import UNITYPE, cuni, handle2internal, conv_to_unicode
strip_dict = dict.fromkeys(list(range(9))+list(range(11,13))+list(range(14, 32)), " ")
class ProgressMeter(object):
def __init__(self, *args): pass
def __init__(self, *args, **kwargs): pass
def set_pass(self, *args): pass
def step(self): pass
def close(self): pass
@ -93,7 +93,7 @@ class ProgressMeter(object):
# Low Level repair
#
#-------------------------------------------------------------------------
def cross_table_duplicates(db):
def cross_table_duplicates(db, uistate):
"""
Function to find the presence of identical handles that occur in different
database tables.
@ -105,7 +105,11 @@ def cross_table_duplicates(db):
:returns: the presence of cross table duplicate handles
:rtype: bool
"""
progress = ProgressMeter(_('Checking Database'),'')
if uistate:
parent = uistate.window
else:
parent = None
progress = ProgressMeter(_('Checking Database'),'', parent)
progress.set_pass(_('Looking for cross table duplicates'), 9)
logging.info('Looking for cross table duplicates')
total_nr_handles = 0
@ -154,7 +158,7 @@ class Check(tool.BatchTool):
# As such, we run it before starting the transaction.
# We only do this for the dbdir backend.
if self.db.__class__.__name__ == 'DbBsddb':
if cross_table_duplicates(self.db):
if cross_table_duplicates(self.db, uistate):
Report(uistate, _(
"Your Family Tree contains cross table duplicate handles.\n "
"This is bad and can be fixed by making a backup of your\n"
@ -216,6 +220,11 @@ class Check(tool.BatchTool):
class CheckIntegrity(object):
def __init__(self, dbstate, uistate, trans):
self.uistate = uistate
if self.uistate:
self.parent_window = self.uistate.window
else:
self.parent_window = None
self.db = dbstate.db
self.trans = trans
self.bad_photo = []
@ -243,7 +252,8 @@ class CheckIntegrity(object):
self.empty_objects = defaultdict(list)
self.replaced_sourceref = []
self.last_img_dir = config.get('behavior.addmedia-image-dir')
self.progress = ProgressMeter(_('Checking Database'),'')
self.progress = ProgressMeter(_('Checking Database'),'',
parent=self.parent_window)
self.explanation = Note(_('Objects referenced by this note '
'were referenced but missing so that is why they have been created '
'when you ran Check and Repair on %s.') %
@ -723,7 +733,7 @@ class CheckIntegrity(object):
"You may choose to either remove the reference from the database, "
"keep the reference to the missing file, or select a new file."
) % {'file_name' : '<b>%s</b>' % photo_name},
remove_clicked, leave_clicked, select_clicked)
remove_clicked, leave_clicked, select_clicked, parent=self.uistate.window)
missmedia_action = mmd.default_action
elif missmedia_action == 1:
logging.warning(' FAIL: media object "%(desc)s" '

View File

@ -63,13 +63,22 @@ class DateParserDisplayTest(tool.Tool):
tool.Tool.__init__(self, dbstate, options_class, name)
if uistate:
# Running with gui -> Show message
QuestionDialog(_("Start date test?"),_("This test will create many persons and events in the current database. Do you really want to run this test?"),_("Run test"),self.run_tool)
self.parent_window = uistate.window
QuestionDialog(_("Start date test?"),
_("This test will create many persons and events " \
"in the current database. Do you really want to " \
"run this test?"),
_("Run test"),
self.run_tool,
parent=self.parent_window)
else:
self.parent_window = None
self.run_tool()
def run_tool(self):
self.progress = ProgressMeter(_('Running Date Test'),'')
self.progress = ProgressMeter(_('Running Date Test'),'',
parent=self.parent_window)
self.progress.set_pass(_('Generating dates'),
4)
dates = []

View File

@ -170,12 +170,13 @@
</child>
<child>
<object class="GtkButton" id="button26">
<property name="label">_Apply</property>
<property name="label" translatable="yes">_Apply</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_apply_clicked" object="filters" swapped="yes"/>
</object>
<packing>

View File

@ -134,7 +134,7 @@ class EventComparison(tool.Tool,ManagedWindow):
})
window = self.filterDialog.toplevel
window.show()
#window.show()
self.filters = self.filterDialog.get_object("filter_list")
self.label = _('Event comparison filter selection')
self.set_window(window,self.filterDialog.get_object('title'),
@ -177,7 +177,8 @@ class EventComparison(tool.Tool,ManagedWindow):
def on_apply_clicked(self, obj):
cfilter = self.filter_model[self.filters.get_active()][1]
progress_bar = ProgressMeter(_('Comparing events'),'')
progress_bar = ProgressMeter(_('Comparing events'),'',
parent=self.window)
progress_bar.set_pass(_('Selecting people'),1)
plist = cfilter.apply(self.db,
@ -190,7 +191,7 @@ class EventComparison(tool.Tool,ManagedWindow):
self.options.handler.save_options()
if len(plist) == 0:
WarningDialog(_("No matches were found"))
WarningDialog(_("No matches were found"), parent=self.window)
else:
DisplayChart(self.dbstate,self.uistate,plist,self.track)
@ -238,7 +239,6 @@ class DisplayChart(ManagedWindow):
})
window = self.topDialog.toplevel
window.show()
self.set_window(window, self.topDialog.get_object('title'),
_('Event Comparison Results'))
@ -306,7 +306,8 @@ class DisplayChart(ManagedWindow):
self.progress_bar.close()
def build_row_data(self):
self.progress_bar = ProgressMeter(_('Comparing Events'),'')
self.progress_bar = ProgressMeter(_('Comparing Events'),'',
parent=self.window)
self.progress_bar.set_pass(_('Building data'),len(self.my_list))
for individual_id in self.my_list:
individual = self.db.get_person_from_handle(individual_id)

View File

@ -122,7 +122,6 @@ class Merge(tool.Tool,ManagedWindow):
self.menu.set_active(0)
window = top.toplevel
window.show()
self.set_window(window, top.get_object('title'),
_('Find Possible Duplicate People'))
@ -164,7 +163,7 @@ class Merge(tool.Tool,ManagedWindow):
try:
self.find_potentials(threshold)
except AttributeError as msg:
RunDatabaseRepair(str(msg))
RunDatabaseRepair(str(msg), parent=self.window)
return
self.options.handler.options_dict['threshold'] = threshold
@ -186,8 +185,8 @@ class Merge(tool.Tool,ManagedWindow):
def find_potentials(self, thresh):
self.progress = ProgressMeter(_('Find Duplicates'),
_('Looking for duplicate people')
)
_('Looking for duplicate people'),
parent=self.window)
index = 0
males = {}
@ -550,7 +549,6 @@ class ShowMatches(ManagedWindow):
top = Glade(toplevel="mergelist")
window = top.toplevel
window.show()
self.set_window(window, top.get_object('title'),
_('Potential Merges'))

View File

@ -20,7 +20,7 @@
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="close">
<property name="label">_Close</property>
<property name="label" translatable="yes">_Close</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
@ -37,7 +37,7 @@
</child>
<child>
<object class="GtkButton" id="help">
<property name="label">_Help</property>
<property name="label" translatable="yes">_Help</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
@ -146,7 +146,7 @@
</child>
<child>
<object class="GtkButton" id="tagapply">
<property name="label">_Apply</property>
<property name="label" translatable="yes">_Apply</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>

View File

@ -129,11 +129,16 @@ class TestcaseGenerator(tool.BatchTool):
def __init__(self, dbstate, user, options_class, name, callback=None):
uistate = user.uistate
if uistate:
parent_window = uistate.window
else:
parent_window = None
self.person = None
if dbstate.db.readonly:
return
tool.BatchTool.__init__(self, dbstate, user, options_class, name)
tool.BatchTool.__init__(self, dbstate, user, options_class, name,
parent=parent_window)
if self.fail:
return
@ -192,7 +197,7 @@ class TestcaseGenerator(tool.BatchTool):
def init_gui(self,uistate):
title = "%s - Gramps" % _("Generate testcases")
self.top = Gtk.Dialog(title)
self.top = Gtk.Dialog(title, parent=uistate.window)
self.top.set_default_size(400,150)
self.top.vbox.set_spacing(5)
label = Gtk.Label(label='<span size="larger" weight="bold">%s</span>' % _("Generate testcases"))
@ -279,7 +284,7 @@ class TestcaseGenerator(tool.BatchTool):
while Gtk.events_pending():
Gtk.main_iteration()
self.progress = ProgressMeter(_('Generating testcases'),'')
self.progress = ProgressMeter(_('Generating testcases'),'', parent=self.window)
self.transaction_count = 0;
if self.options.handler.options_dict['lowlevel']:

View File

@ -232,7 +232,7 @@ class GeoClose(GeoGraphyView):
self._createmap(self.refperson, color, self.place_list_ref, True)
if self.refperson_bookmark is None:
self.refperson_bookmark = self.refperson.get_handle()
self.add_bookmark(None, self.refperson_bookmark)
self.add_bookmark_from_popup(None, self.refperson_bookmark)
else:
self.message_layer.add_message(_("You must choose one reference person."))
self.message_layer.add_message(_("Go to the person view and select "

View File

@ -335,7 +335,7 @@ class GeoEvents(GeoGraphyView):
hdle = evt.get_handle()
bookm = Gtk.MenuItem(label=_("Bookmark this event"))
bookm.show()
bookm.connect("activate", self.add_bookmark, hdle)
bookm.connect("activate", self.add_bookmark_from_popup, hdle)
itemoption.append(bookm)
if mark[0] != oldplace:
message = "%s :" % mark[0]
@ -367,7 +367,7 @@ class GeoEvents(GeoGraphyView):
hdle = evt.get_handle()
bookm = Gtk.MenuItem(label=_("Bookmark this event"))
bookm.show()
bookm.connect("activate", self.add_bookmark, hdle)
bookm.connect("activate", self.add_bookmark_from_popup, hdle)
itemoption.append(bookm)
menu.popup(None, None,
lambda menu, data: (event.get_root_coords()[0],

View File

@ -255,7 +255,7 @@ class GeoFamClose(GeoGraphyView):
self.message_layer.add_message(_("The other family : %s" % _("Unknown")))
if self.reffamily_bookmark is None:
self.reffamily_bookmark = self.reffamily.get_handle()
self.add_bookmark(None, self.reffamily_bookmark)
self.add_bookmark_from_popup(None, self.reffamily_bookmark)
else:
self.message_layer.add_message(_("You must choose one reference family."))
self.message_layer.add_message(_("Go to the family view and select "

View File

@ -607,7 +607,7 @@ class GeoMoves(GeoGraphyView):
hdle = person.get_handle()
bookm = Gtk.MenuItem(label=_("Bookmark this person"))
bookm.show()
bookm.connect("activate", self.add_bookmark, hdle)
bookm.connect("activate", self.add_bookmark_from_popup, hdle)
itemoption.append(bookm)
menu.show()
menu.popup(None, None,

View File

@ -333,7 +333,7 @@ class GeoPlaces(GeoGraphyView):
hdle = place.get_handle()
bookm = Gtk.MenuItem(label=_("Bookmark this place"))
bookm.show()
bookm.connect("activate", self.add_bookmark, hdle)
bookm.connect("activate", self.add_bookmark_from_popup, hdle)
itemoption.append(bookm)
message = "%s" % mark[0]
prevmark = mark
@ -357,7 +357,7 @@ class GeoPlaces(GeoGraphyView):
hdle = place.get_handle()
bookm = Gtk.MenuItem(label=_("Bookmark this place"))
bookm.show()
bookm.connect("activate", self.add_bookmark, hdle)
bookm.connect("activate", self.add_bookmark_from_popup, hdle)
itemoption.append(bookm)
menu.popup(None, None,
lambda menu, data: (event.get_root_coords()[0],

View File

@ -30,7 +30,7 @@ Can use the Webkit or Gecko ( Mozilla ) library
# Python modules
#
#-------------------------------------------------------------------------
import os
import os, io
import sys
if sys.version_info[0] < 3:
from urlparse import urlunsplit
@ -557,7 +557,7 @@ class HtmlView(NavigationView):
# Now we have two views : Web and Geography, we need to create the
# startpage only once.
if not os.path.exists(filename):
ufd = file(filename, "w+")
ufd = io.open(filename, "w+", encoding="utf8")
ufd.write(data)
ufd.close()
return urlunsplit(('file', '',

View File

@ -181,16 +181,9 @@ class MediaView(ListView):
"""
if not sel_data:
return
#modern file managers provide URI_LIST. For Windows split sel_data.data
files = sel_data.get_uris()
for file in files:
if win():
clean_string = conv_to_unicode(
file.replace('\0',' ').replace("\r", " ").strip(),
None)
else:
clean_string = file
protocol, site, mfile, j, k, l = urlparse(clean_string)
protocol, site, mfile, j, k, l = urlparse(file)
if protocol == "file":
name = url2pathname(mfile)
mime = get_type(name)

View File

@ -142,7 +142,7 @@ from gramps.plugins.lib.libhtml import Html, xml_lang
# import styled notes from src/plugins/lib/libhtmlbackend.py
from gramps.plugins.lib.libhtmlbackend import HtmlBackend, process_spaces
from gramps.plugins.lib.libgedcom import make_gedcom_date
from gramps.plugins.lib.libgedcom import make_gedcom_date, DATE_QUALITY
from gramps.gen.utils.place import conv_lat_lon
from gramps.gui.pluginmanager import GuiPluginManager
@ -517,14 +517,20 @@ def format_date(date):
cal = date.get_calendar()
mod = date.get_modifier()
quality = date.get_quality()
if quality in DATE_QUALITY:
qual_text = DATE_QUALITY[quality] + " "
else:
qual_text = ""
if mod == Date.MOD_SPAN:
val = "FROM %s TO %s" % (
make_gedcom_date(start, cal, mod, quality),
make_gedcom_date(date.get_stop_date(), cal, mod, quality))
val = "%sFROM %s TO %s" % (
qual_text,
make_gedcom_date(start, cal, mod, None),
make_gedcom_date(date.get_stop_date(), cal, mod, None))
elif mod == Date.MOD_RANGE:
val = "BET %s AND %s" % (
make_gedcom_date(start, cal, mod, quality),
make_gedcom_date(date.get_stop_date(), cal, mod, quality))
val = "%sBET %s AND %s" % (
qual_text,
make_gedcom_date(start, cal, mod, None),
make_gedcom_date(date.get_stop_date(), cal, mod, None))
else:
val = make_gedcom_date(start, cal, mod, quality)
return val
@ -5046,14 +5052,14 @@ class DownloadPage(BasePage):
else:
tcell += "&nbsp;"
# clear line for proper styling
# create footer section
footer = self.write_footer()
body += (fullclear, footer)
# clear line for proper styling
# create footer section
footer = self.write_footer()
body += (fullclear, footer)
# send page out for processing
# and close the file
self.XHTMLWriter(downloadpage, of, sio)
# send page out for processing
# and close the file
self.XHTMLWriter(downloadpage, of, sio)
class ContactPage(BasePage):
def __init__(self, report, title):

View File

@ -18,6 +18,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
VERSION_TUPLE = (4, 1, 3)
VERSION_TUPLE = (4, 1, 4)
VERSION = '.'.join(map(str,VERSION_TUPLE))
major_version = "%s.%s" % (VERSION_TUPLE[0], VERSION_TUPLE[1])

View File

@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>Gramps</string>
<key>CFBundleGetInfoString</key>
<string>4.1.2, (C) 1997-2015 The Gramps Team http://www.gramps-project.org</string>
<string>4.1.3, (C) 1997-2015 The Gramps Team http://www.gramps-project.org</string>
<key>CFBundleIconFile</key>
<string>gramps.icns</string>
<key>CFBundleIdentifier</key>
@ -17,11 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>4.1.2-1</string>
<string>4.1.3-1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>4.1.2-1</string>
<string>4.1.3-1</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright 1997 - 2015 The Gramps Team, GNU General Public License.</string>
<key>LSMinimumSystemVersion</key>

View File

@ -39,7 +39,8 @@ export PYTHONHOME="$bundle_res"
export GRAMPSDIR="$bundle_lib/python$PYVER/site-packages/gramps"
export GRAMPSI18N="$bundle_data"/locale
export GRAMPS_RESOURCES="$bundle_data"
export GRAMPSHOME="$HOME/Library/Application Support"
export USERPROFILE="$HOME"
export APPDATA="$HOME/Library/Application Support"
# Strip out the argument added by the OS.
if /bin/expr "x$1" : '^x-psn_' > /dev/null; then

View File

@ -58,7 +58,7 @@ gtk-mac-bundler gtk-osx-build/projects/gramps/gramps.bundle
<!--include href="/Users/john/Development/GTK-OSX/gtk-osx-build/modulesets-stable/gtk-osx.modules"/-->
<distutils id="gramps" autogen-sh="configure">
<branch module="gramps/gramps-4.1.2.tar.gz" version="4.1.2"
<branch module="gramps/gramps-4.1.3.tar.gz" version="4.1.3"
repo="sourceforge">
</branch>
<dependencies>

View File

@ -116,6 +116,38 @@ class Check_named_fmt( Check ):
msgstr = msg.msgstr[1]
self.__process( msg, msgid, msgstr )
class Check_mapping_fmt( Check ):
# A pattern to find all {}
find_map_pat = re.compile('\{ \w+ \}', re.VERBOSE)
def __init__( self ):
Check.__init__( self )
self.diag_header = "-------- {} name mismatches --------------"
self.summary_text = "{} name mismatches:"
def __process( self, msg, msgid, msgstr ):
# Same number of named formats?
fmts1 = self.find_map_pat.findall( msgid )
fmts2 = self.find_map_pat.findall( msgstr )
if len( fmts1 ) != len( fmts2 ):
self.msgs.append( msg )
else:
# Do we have the same named formats?
fmts1.sort()
fmts2.sort()
if fmts1 != fmts2:
self.msgs.append( msg )
def process( self, msg ):
msgid = msg.msgid
msgstr = msg.msgstr[0]
self.__process( msg, msgid, msgstr )
if msg.msgidp and len(msg.msgstr) >= 2:
msgid = msg.msgidp
msgstr = msg.msgstr[1]
self.__process( msg, msgid, msgstr )
class Check_missing_sd( Check ):
# A pattern to find %() without s or d
# Here is a command to use for testing
@ -513,6 +545,7 @@ def analyze_msgs( args, fname, msgs, nr_templates = None, nth = 0 ):
checks.append( Check_fmt( '%s' ) )
checks.append( Check_fmt( '%d' ) )
checks.append( Check_named_fmt() )
checks.append( Check_mapping_fmt() )
checks.append( Check_missing_sd() )
checks.append( Check_runaway() )
checks.append( Check_xml_chars() )

5308
po/cs.po

File diff suppressed because it is too large Load Diff

2161
po/de.po

File diff suppressed because it is too large Load Diff

7499
po/fi.po

File diff suppressed because it is too large Load Diff

2034
po/fr.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2590
po/is.po

File diff suppressed because it is too large Load Diff

View File

@ -6399,23 +6399,20 @@ msgid "Street"
msgstr "Straat"
#: ../gramps/gen/lib/placetype.py:76
#, fuzzy
msgid "Province"
msgstr "Deelstaat/Provincie"
#: ../gramps/gen/lib/placetype.py:77
#, fuzzy
msgid "Region"
msgstr "Religie"
msgstr "Regio"
#: ../gramps/gen/lib/placetype.py:78
#, fuzzy
msgid "Department"
msgstr "Pensioen"
msgstr "Departement"
#: ../gramps/gen/lib/placetype.py:79
msgid "Neighborhood"
msgstr ""
msgstr "Buurt"
#: ../gramps/gen/lib/placetype.py:80
msgid "District"
@ -6426,9 +6423,8 @@ msgid "Borough"
msgstr ""
#: ../gramps/gen/lib/placetype.py:82
#, fuzzy
msgid "Municipality"
msgstr "Plaats"
msgstr "Gemeente"
#: ../gramps/gen/lib/placetype.py:83
msgid "Town"
@ -6436,7 +6432,7 @@ msgstr ""
#: ../gramps/gen/lib/placetype.py:84
msgid "Village"
msgstr ""
msgstr "Dorp"
#: ../gramps/gen/lib/placetype.py:85
msgid "Hamlet"
@ -6444,7 +6440,7 @@ msgstr ""
#: ../gramps/gen/lib/placetype.py:86
msgid "Farm"
msgstr ""
msgstr "Boerderij"
#: ../gramps/gen/lib/placetype.py:87
#, fuzzy
@ -8110,7 +8106,7 @@ msgstr "Onbekend, aangemaakt om een ontbrekend opmerkingsobject te vervangen."
#: ../gramps/gen/utils/unknown.py:149
#, python-format
msgid "Unknown, was missing %(time)s (%(count)d)"
msgstr "Obekend, %(time)s (%(count)d) ontbrak"
msgstr "Onbekend, %(time)s (%(count)d) ontbrak"
#: ../gramps/gen/utils/unknown.py:168
#, python-format
@ -17681,7 +17677,7 @@ msgstr "doopdatum"
#: ../gramps/plugins/export/exportcsv.py:368 ../gramps/plugins/importer/importcsv.py:201
msgid "Baptism place"
msgstr "doopplaats"
msgstr "Doopplaats"
#: ../gramps/plugins/export/exportcsv.py:368 ../gramps/plugins/importer/importcsv.py:206
msgid "Baptism source"

View File

@ -169,9 +169,13 @@ def TipsParse(filename, mark):
tips = open('../data/tips.xml.in.h', 'w')
marklist = root.iter(mark)
for key in marklist:
tip = ElementTree.tostring(key, encoding="UTF-8")
tip = tip.replace("<?xml version='1.0' encoding='UTF-8'?>", "")
tip = tip.replace('\n<_tip number="%(number)s">' % key.attrib, "")
tip = ElementTree.tostring(key, encoding="UTF-8", method="xml")
if sys.version_info[0] < 3:
tip = tip.replace("<?xml version='1.0' encoding='UTF-8'?>", "")
tip = tip.replace('\n<_tip number="%(number)s">' % key.attrib, "")
else: # python3 support
tip = tip.decode("utf-8")
tip = tip.replace('<_tip number="%(number)s">' % key.attrib, "")
tip = tip.replace("<br />", "<br/>")
#tip = tip.replace("\n</_tip>\n", "</_tip>\n") # special case tip 7
#tip = tip.replace("\n<b>", "<b>") # special case tip 18