Merge branch 'gramps50'

This commit is contained in:
Nick Hall 2017-09-23 17:08:52 +01:00
commit 1482fedb9c
103 changed files with 36752 additions and 40447 deletions

View File

@ -1,12 +1,12 @@
0 HEAD
1 SOUR Gramps
2 VERS 5.0.0-alpha1
2 VERS 5.0.0-alpha2
2 NAME Gramps
1 DATE 29 OCT 2016
2 TIME 15:10:31
1 DATE 1 SEP 2017
2 TIME 12:10:38
1 SUBM @SUBM@
1 FILE C:\Users\prc\AppData\Roaming\gramps\temp\exp_sample_ged.ged
1 COPR Copyright (c) 2016 Alex Roitman,,,.
1 COPR Copyright (c) 2017 Alex Roitman,,,.
1 GEDC
2 VERS 5.5.1
2 FORM LINEAGE-LINKED
@ -17,7 +17,7 @@
1 ADDR Not Provided
2 ADR1 Not Provided
1 PHON 666-555-4444
1 EMAIL an_email@gmail.com
1 EMAIL an_email@@gmail.com
0 @I0000@ INDI
1 NAME Anna /Hansdotter/
2 GIVN Anna
@ -789,7 +789,7 @@
1 OBJE
2 FORM jpeg
2 TITL Michael O'Toole 2015-11
2 FILE c:\grampsaio64-4.9.9\share\gramps\tests\O0.jpg
2 FILE c:\grampsaio64-5.0.0\share\gramps\tests\O0.jpg
2 NOTE @N0019@
1 NOTE @N0007@
1 CHAN
@ -820,7 +820,7 @@
3 CTRY USA
2 PHON 440-871-3400
2 PHON 800-871-3400
2 EMAIL thetester@gmail.com
2 EMAIL thetester@@gmail.com
2 FAX 440-123-4567
2 WWW http://thetester.com
1 EVEN A very bad day
@ -862,7 +862,7 @@
2 DATE 30 DEC 1954
2 PLAC 123 Main St., Winslow, PA, 12345
2 PHON 440-871-3401
2 EMAIL mrstester@gmail.com
2 EMAIL mrstester@@gmail.com
2 FAX 440-321-4568
2 WWW http://mrstester.com
2 NOTE @N0011@
@ -879,7 +879,7 @@
3 POST 12345
1 PHON 440-871-3401
1 PHON 800-871-3401
1 EMAIL mrstester@gmail.com
1 EMAIL mrstester@@gmail.com
1 FAX 440-321-4568
1 WWW http://mrstester.com
1 NOTE @N0010@
@ -904,7 +904,7 @@
3 STAE Colorado
3 CTRY USA
2 PHON 440-871-3402
2 EMAIL tomtester@gmail.com
2 EMAIL tomtester@@gmail.com
2 FAX 440-321-4569
2 WWW http://tomtester.com
1 RESI
@ -1316,10 +1316,10 @@
2 ADR1 360 W 4800 N, Provo, UT 84604
1 PHON (801) 705-7000
1 FAX (801) 705-7001
1 EMAIL help@ancestry.com
1 EMAIL help@@ancestry.com
1 WWW http://www.ancestry.com
0 @R0001@ REPO
1 NAME SUBM (Submitter): (@SUBM@) The Subm /Tester/
1 NAME SUBM (Submitter): (@@SUBM@@) The Subm /Tester/
1 ADDR 123 Main St.
2 CONT Winslow
2 CONT PA
@ -1329,7 +1329,7 @@
2 STAE PA
2 POST 12345
1 PHON 440-871-3401
1 EMAIL mrstester@gmail.com
1 EMAIL mrstester@@gmail.com
1 FAX 440-321-4568
1 WWW http://mrstester.com
1 NOTE @N0009@
@ -1362,7 +1362,7 @@
1 ADDR 123 High St., OSF village, CA, USA
2 ADR1 123 High St., OSF village, CA, USA
1 PHON 988-765-4321
1 EMAIL tester_repo@osf.com
1 EMAIL tester_repo@@osf.com
1 FAX 987-654-3210
1 WWW http://www.tester_repo.com
1 NOTE @N0012@
@ -1401,8 +1401,8 @@
1 CONT
1 CONT Only one phone number supported Lin
1 CONC e 9: 3 PHON (800) 705-7000
0 @N0009@ NOTE Records not imported into SUBM (Submitter): (@SUBM@) The Subm /Test
1 CONC er/:
0 @N0009@ NOTE Records not imported into SUBM (Submitter): (@@SUBM@@) The Subm /Te
1 CONC ster/:
1 CONT
1 CONT Only one phone number supported Lin
1 CONC e 29: 1 PHON 800-871-3401

View File

@ -44,7 +44,7 @@
3 FORM Street, City, County, State, Country, Zip code
2 PHON 440-871-3400
2 PHON 800-871-3400
2 EMAIL thetester@gmail.com
2 EMAIL thetester@@gmail.com
2 FAX 440-123-4567
2 WWW http://thetester.com
0 @I1@ INDI

View File

@ -171,12 +171,19 @@ class CLIDbManager:
retval = {_("Unavailable"): "locked"}
retval.update({_("Family Tree"): name,
_("Path"): dirpath,
_("Database"): dbid,
_("Database"): self.get_backend_name_from_dbid(dbid),
_("Last accessed"): time_val(dirpath)[1],
_("Locked?"): self.is_locked(dirpath),
})
return retval
def get_backend_name_from_dbid(self, dbid):
pmgr = BasePluginManager.get_instance()
for plugin in pmgr.get_reg_databases():
if plugin.id == dbid:
return plugin._name
return _("Unknown")
def print_family_tree_summaries(self, database_names=None):
"""
Prints a detailed list of the known family trees.
@ -184,7 +191,7 @@ class CLIDbManager:
print(_('Gramps Family Trees:'))
for item in self.current_names:
(name, dirpath, path_name, last,
tval, enable, stock_id, backend_type, version) = item
tval, enable, stock_id, backend_type) = item
if (database_names is None or
any([(re.match("^" + dbname + "$", name) or
dbname == name)
@ -206,7 +213,7 @@ class CLIDbManager:
summary_list = []
for item in self.current_names:
(name, dirpath, path_name, last,
tval, enable, stock_id, backend_type, version) = item
tval, enable, stock_id, backend_type) = item
if (database_names is None or
any([(re.match("^" + dbname + "$", name) or
dbname == name)
@ -233,15 +240,6 @@ class CLIDbManager:
backend_type = file.read()
except:
backend_type = "bsddb"
try:
with open(os.path.join(dirpath, "bdbversion.txt")) as file:
version = file.read()
except:
version = "(0, 0, 0)"
try:
version = ast.literal_eval(version)
except:
version = (0, 0, 0)
if os.path.isfile(path_name):
with open(path_name, 'r', encoding='utf8') as file:
name = file.readline().strip()
@ -255,7 +253,7 @@ class CLIDbManager:
self.current_names.append(
(name, os.path.join(dbdir, dpath), path_name,
last, tval, enable, stock_id, backend_type, version))
last, tval, enable, stock_id, backend_type))
self.current_names.sort()

View File

@ -446,7 +446,7 @@ class CommandLineReport:
else:
print(_("Unknown option: %s") % option, file=sys.stderr)
print(_(" Valid options are:") +
", ".join(list(self.options_dict.keys())),
_(", ").join(list(self.options_dict.keys())), # Arabic OK
file=sys.stderr)
print(_(" Use '%(donottranslate)s' to see description "
"and acceptable values"
@ -527,7 +527,7 @@ class CommandLineReport:
else:
print(_("Ignoring unknown option: %s") % opt, file=sys.stderr)
print(_(" Valid options are:"),
", ".join(list(self.options_dict.keys())),
_(", ").join(list(self.options_dict.keys())), # Arabic OK
file=sys.stderr)
print(_(" Use '%(donottranslate)s' to see description "
"and acceptable values"

View File

@ -229,6 +229,7 @@ class DateParserHU(DateParser):
self._numeric = re.compile(
"((\d+)[/\.])?\s*((\d+)[/\.])?\s*(\d+)[/\. ]?$")
# this next RE has the (possibly-slashed) year at the string's start
self._text2 = re.compile('((\d+)(/\d+)?\.)?\s+?%s\.?\s*(\d+\.)?\s*$'
% self._mon_str, re.IGNORECASE)
_span_1 = ['-tó\\)l', '-tól', '-től']

View File

@ -130,6 +130,7 @@ class DateParserLT(DateParser):
def init_strings(self):
DateParser.init_strings(self)
# this next RE has the (possibly-slashed) year at the string's start
self._text2 = re.compile('((\d+)(/\d+)?)?\s+?m\.\s+%s\s*(\d+)?\s*d?\.?$'
% self._mon_str, re.IGNORECASE)
_span_1 = ['nuo']

View File

@ -95,6 +95,10 @@ class DateParserSv(DateParser):
def init_strings(self):
""" Define, in Swedish, span and range regular expressions"""
DateParser.init_strings(self)
self._numeric = re.compile("((\d+)/)?\s*((\d+)/)?\s*(\d+)[/ ]?$")
# this next RE has the (possibly-slashed) year at the string's start
self._text2 = re.compile('((\d+)(/\d+)?)?\s+?%s\s*(\d+)?\s*$'
% self._mon_str, re.IGNORECASE)
self._span = re.compile("(från)?\s*(?P<start>.+)\s*(till|--|)\s*(?P<stop>.+)",
re.IGNORECASE)
self._range = re.compile("(mellan)\s+(?P<start>.+)\s+och\s+(?P<stop>.+)",
@ -109,72 +113,70 @@ class DateDisplaySv(DateDisplay):
"""
Swedish language date display class.
"""
long_months = ( "", "januari", "februari", "mars", "april", "maj",
"juni", "juli", "augusti", "september", "oktober",
"november", "december" )
short_months = ( "", "jan", "feb", "mar", "apr", "maj", "jun",
"jul", "aug", "sep", "okt", "nov", "dec" )
_bce_str = "%s f Kr"
formats = (
"ÅÅÅÅ-MM-DD (ISO)",
"År/mån/dag",
"Månad dag, år",
"MÅN DAG ÅR",
"Dag månad år",
"DAG MÅN ÅR",
"År månad dag",
"År mån dag",
)
# this must agree with DateDisplayEn's "formats" definition
# (since no locale-specific _display_gregorian exists, here)
# this definition must agree with its "_display_calendar" method
calendar = (
"",
"juliansk",
"hebreisk",
"fransk republikansk",
"persisk",
"islamisk",
"svensk"
)
def _display_calendar(self, date_val, long_months, short_months = None,
inflect=""):
# this must agree with its locale-specific "formats" definition
_mod_str = ("", "före ", "efter ", "c:a ", "", "", "")
if short_months is None:
# Let the short formats work the same as long formats
short_months = long_months
_qual_str = ("", "uppskattat ", "beräknat ")
_bce_str = "%s f Kr"
def display(self, date):
"""
Return a text string representing the date.
"""
mod = date.get_modifier()
cal = date.get_calendar()
qual = date.get_quality()
start = date.get_start_date()
newyear = date.get_new_year()
qual_str = self._qual_str[qual]
if mod == Date.MOD_TEXTONLY:
return date.get_text()
elif start == Date.EMPTY:
return ""
elif mod == Date.MOD_SPAN:
d1 = self.display_cal[cal](start)
d2 = self.display_cal[cal](date.get_stop_date())
scal = self.format_extras(cal, newyear)
return "%sfrån %s till %s%s" % (qual_str, d1, d2, scal)
elif mod == Date.MOD_RANGE:
d1 = self.display_cal[cal](start)
d2 = self.display_cal[cal](date.get_stop_date())
scal = self.format_extras(cal, newyear)
return "%smellan %s och %s%s" % (qual_str, d1, d2,
scal)
if self.format == 0:
return self.display_iso(date_val)
elif self.format == 1:
# numerical: year/month/day (with slashes)
value = self.dd_dformat01(date_val)
elif self.format == 2:
# year month_name day
value = self.dd_dformat02_sv(date_val, long_months)
# elif self.format == 3:
else:
text = self.display_cal[date.get_calendar()](start)
scal = self.format_extras(cal, newyear)
return "%s%s%s%s" % (qual_str, self._mod_str[mod],
text, scal)
# year month_abbreviation day
value = self.dd_dformat03_sv(date_val, short_months)
if date_val[2] < 0:
# TODO fix BUG 7064: non-Gregorian calendars wrongly use BCE notation for negative dates
return self._bce_str % value
else:
return value
def dd_dformat02_sv(self, date_val, long_months):
# year month_name day
year = self._slash_year(date_val[2], date_val[3])
if date_val[0] == 0:
if date_val[1] == 0:
return year
else:
return "%s %s" % (year, long_months[date_val[1]])
elif date_val[1] == 0: # month is zero but day is not (see 8477)
return self.display_iso(date_val)
else:
return "%s %s %s" % (year, long_months[date_val[1]], date_val[0])
def dd_dformat03_sv(self, date_val, short_months):
# year month_abbreviation day
year = self._slash_year(date_val[2], date_val[3])
if date_val[0] == 0:
if date_val[1] == 0:
return year
else:
return "%s %s" % (year, short_months[date_val[1]])
elif date_val[1] == 0: # month is zero but day is not (see 8477)
return self.display_iso(date_val)
else:
return "%s %s %s" % (year, short_months[date_val[1]], date_val[0])
display = DateDisplay.display_formatted
#-------------------------------------------------------------------------
#

View File

@ -342,6 +342,7 @@ class DateDisplay:
for item in [scal, snewyear]:
if item:
if retval:
# TODO for Arabic, should the next comma be translated?
retval += ", "
retval += item
if retval:
@ -608,6 +609,9 @@ class DateDisplay:
value = value.replace('%A', self._get_long_weekday(date_val))
if date_val[0] == 0: # ignore the zero day and its delimiter
i_day = value.find('%d')
if len(value) == i_day + 2: # delimiter is left of the day
value = value.replace(value[i_day-1:i_day+2], '')
else: # delimiter is to the right of the day
value = value.replace(value[i_day:i_day+3], '')
value = value.replace('%d', str(date_val[0]))
value = value.replace('%Y', str(abs(date_val[2])))

View File

@ -449,8 +449,9 @@ class DateParser:
self._modifier_after = re.compile('(.*)\s+%s' % self._mod_after_str,
re.IGNORECASE)
self._abt2 = re.compile('<(.*)>', re.IGNORECASE)
self._text = re.compile('%s\.?\s+(\d+)?\s*,?\s*((\d+)(/\d+)?)?\s*$' % self._mon_str,
re.IGNORECASE)
self._text = re.compile('%s\.?(\s+\d+)?\s*,?\s+((\d+)(/\d+)?)?\s*$'
% self._mon_str, re.IGNORECASE)
# this next RE has the (possibly-slashed) year at the string's end
self._text2 = re.compile('(\d+)?\s+?%s\.?\s*((\d+)(/\d+)?)?\s*$' % self._mon_str,
re.IGNORECASE)
self._jtext = re.compile('%s\s+(\d+)?\s*,?\s*((\d+)(/\d+)?)?\s*$' % self._jmon_str,
@ -521,7 +522,7 @@ class DateParser:
def _parse_calendar(self, text, regex1, regex2, mmap, check=None):
match = regex1.match(text.lower())
if match:
if match: # user typed in 'month-name day year' or 'month-name year'
groups = match.groups()
if groups[0] is None:
m = 0
@ -534,19 +535,21 @@ class DateParser:
s = False
else:
d = self._get_int(groups[1])
if groups[4] is not None: # slash year "/80"
if groups[4] is not None:
y = int(groups[3]) + 1 # fullyear + 1
s = True
else: # regular, non-slash date
s = True # slash year
else: # regular year
y = int(groups[3])
s = False
value = (d, m, y, s)
if check and not check((d, m, y)):
if s and julian_valid(value): # slash year
pass
elif check and not check((d, m, y)):
value = Date.EMPTY
return value
match = regex2.match(text.lower())
if match:
if match: # user typed in day month-name year or year month-name day
groups = match.groups()
if self.ymd:
if groups[3] is None:
@ -558,10 +561,10 @@ class DateParser:
y = None
s = False
else:
if groups[2] is not None: # slash year digit
if groups[2] is not None:
y = int(groups[1]) + 1 # fullyear + 1
s = True
else: # regular, non-slash year
s = True # slash year
else: # regular year
y = int(groups[1])
s = False
else:
@ -574,10 +577,10 @@ class DateParser:
y = None
s = False
else:
if groups[4] is not None: # slash year digit
if groups[4] is not None:
y = int(groups[3]) + 1 # fullyear + 1
s = True
else: # regular, non-slash year
s = True # slash year
else: # regular year
y = int(groups[3])
s = False
value = (d, m, y, s)
@ -618,8 +621,8 @@ class DateParser:
y = self._get_int(groups[0])
m = self._get_int(groups[3])
d = self._get_int(groups[4])
if groups[2] and julian_valid((d, m, y + 1)): # slash year digit
return (d, m, y + 1, True)
if groups[2] and julian_valid((d, m, y + 1)):
return (d, m, y + 1, True) # slash year
if check is None or check((d, m, y)):
return (d, m, y, False)
return Date.EMPTY
@ -669,6 +672,15 @@ class DateParser:
y = self._get_int(groups[1])
m = self._get_int(groups[3])
d = self._get_int(groups[4])
if m > 12: # maybe a slash year, not a month (1722/3 is March)
if y % 100 == 99:
modyear = (y + 1) % 1000
elif y % 10 == 9:
modyear = (y + 1) % 100
else:
modyear = (y + 1) % 10
if m == modyear:
return (0, 0, y + 1, True) # slash year
else:
y = self._get_int(groups[4])
if self.dmy:
@ -681,6 +693,15 @@ class DateParser:
else:
m = self._get_int(groups[1])
d = self._get_int(groups[3])
if m > 12: # maybe a slash year, not a month
if m % 100 == 99:
modyear = (m + 1) % 1000
elif m % 10 == 9:
modyear = (m + 1) % 100
else:
modyear = (m + 1) % 10
if y == modyear:
return (0, 0, m + 1, True) # slash year
value = (d, m, y, False)
if check and not check((d, m, y)):
value = Date.EMPTY
@ -906,8 +927,6 @@ class DateParser:
if subdate == Date.EMPTY and text != "":
date.set_as_text(text)
return
#else:
# print 'valid subdate', text, subdate
except:
date.set_as_text(text)
return

View File

@ -601,6 +601,11 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
# run backend-specific code:
self._initialize(directory)
# We use the existence of the person table as a proxy for the database
# being new
if not self.dbapi.table_exists("person"):
self._create_schema()
# Load metadata
self.name_formats = self._get_metadata('name_formats')
self.owner = self._get_metadata('researcher', default=Researcher())
@ -674,7 +679,7 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
Close the database.
if update is False, don't change access times, etc.
"""
if self._directory:
if self._directory != ":memory:":
if update:
# This is just a dummy file to indicate last modified time of
# the database for gramps.cli.clidbman:
@ -2461,7 +2466,7 @@ class DbGeneric(DbWriteBase, DbReadBase, UpdateCallback, Callback):
_("Number of repositories"): self.get_number_of_repositories(),
_("Number of notes"): self.get_number_of_notes(),
_("Number of tags"): self.get_number_of_tags(),
_("Data version"): ".".join([str(v) for v in self.VERSION]),
_("Schema version"): ".".join([str(v) for v in self.VERSION]),
}
def _order_by_person_key(self, person):

View File

@ -129,8 +129,8 @@ def import_as_dict(filename, user, skp_imp_adds=True):
"""
Import the filename into a InMemoryDB and return it.
"""
db = make_database("inmemorydb")
db.load(None)
db = make_database("sqlite")
db.load(":memory:")
db.set_feature("skip-import-additions", skp_imp_adds)
status = import_from_filename(db, filename, user)
return db if status else None

View File

@ -80,6 +80,7 @@ class PlaceDisplay:
if config.get('preferences.place-reverse'):
names.reverse()
# TODO for Arabic, should the next line's comma be translated?
return ", ".join(names)
def _find_populated_place(places):

View File

@ -74,6 +74,7 @@ class HasRepo(Rule):
if self.list[2]:
addr_match = False
for addr in repo.address_list:
# TODO for Arabic, should the next line's comma be translated?
addr_text = ', '.join(addr.get_text_data_list())
if self.match_substring(2, addr_text):
addr_match = True
@ -84,6 +85,7 @@ class HasRepo(Rule):
if self.list[3]:
url_match = False
for url in repo.urls:
# TODO for Arabic, should the next line's comma be translated?
url_text = ', '.join(url.get_text_data_list())
if self.match_substring(3, url_text):
url_match = True

View File

@ -111,7 +111,7 @@ class Note(BasicPrimaryObject):
"_class": {"enum": [cls.__name__]},
"handle": {"type": "string",
"maxLength": 50,
"title": ("Handle")},
"title": _("Handle")},
"gramps_id": {"type": "string",
"title": _("Gramps ID")},
"text": StyledText.get_schema(),

View File

@ -27,6 +27,7 @@
# Gramps modules
#
#-------------------------------------------------------------------------
from copy import copy
from .styledtexttag import StyledTextTag
from ..const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext
@ -77,6 +78,8 @@ class StyledText:
There could be a 'merge_tags' functionality in :py:meth:`__init__`,
however :py:class:`StyledTextBuffer` will merge them automatically if
the text is displayed.
3. Warning: Some of these operations modify the source tag ranges in place
so if you intend to use a source tag more than once, copy it for use.
"""
(POS_TEXT, POS_TAGS) = list(range(2))
@ -198,17 +201,27 @@ class StyledText:
new_string = self._string.join([str(string) for string in seq])
offset = 0
not_first = False
new_tags = []
self_len = len(self._string)
for text in seq:
if not_first: # if not first time through...
# put the joined element tag(s) into place
for tag in self.tags:
ntag = copy(tag)
ntag.ranges = [(start + offset, end + offset)
for (start, end) in tag.ranges]
new_tags += [ntag]
offset += self_len
if isinstance(text, StyledText):
for tag in text.tags:
tag.ranges = [(start + offset, end + offset)
ntag = copy(tag)
ntag.ranges = [(start + offset, end + offset)
for (start, end) in tag.ranges]
new_tags += [tag]
offset = offset + len(str(text)) + self_len
new_tags += [ntag]
offset += len(str(text))
not_first = True
return self.__class__(new_string, new_tags)
@ -366,6 +379,7 @@ if __name__ == '__main__':
from .styledtexttagtype import StyledTextTagType
T1 = StyledTextTag(StyledTextTagType(1), 'v1', [(0, 2), (2, 4), (4, 6)])
T2 = StyledTextTag(StyledTextTagType(2), 'v2', [(1, 3), (3, 5), (0, 7)])
T3 = StyledTextTag(StyledTextTagType(0), 'v3', [(0, 1)])
A = StyledText('123X456', [T1])
B = StyledText("abcXdef", [T2])
@ -376,7 +390,7 @@ if __name__ == '__main__':
C = C.join([A, S, B])
L = C.split()
C = C.replace('X', StyledText('_'))
C = C.replace('X', StyledText('_', [T3]))
A = A + B
print(A)

View File

@ -0,0 +1,82 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2017 Paul Culley
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
""" unittest for styledtext """
import unittest
from copy import deepcopy
from ..styledtext import StyledText
from ..styledtexttag import StyledTextTag
from ..styledtexttagtype import StyledTextTagType
class Test1(unittest.TestCase):
T1 = StyledTextTag(StyledTextTagType(1), 'v1', [(0, 2), (2, 4), (4, 6)])
T2 = StyledTextTag(StyledTextTagType(2), 'v2', [(1, 3), (3, 5), (0, 7)])
T3 = StyledTextTag(StyledTextTagType(0), 'v3', [(0, 1)])
T4 = StyledTextTag(StyledTextTagType(2), 'v2',
[(8, 10), (10, 12), (7, 14)])
T5 = StyledTextTag(StyledTextTagType(2), 'v2',
[(19, 21), (21, 23), (18, 25)])
A = StyledText('123X456', [T1])
B = StyledText("abcXdef", [T2])
C = StyledText('\n')
S = 'cleartext'
# some basic tests
# because the StyledText.__eq__ method doesn't work very well (tags don't
# compare when they are equivalent, but not equal) we have to use
# serialize for comparisons.
def test_join(self):
C = self.C.join([self.A, self.S, deepcopy(self.B)])
_C = StyledText('123X456\ncleartext\nabcXdef', [self.T1, self.T5])
self.assertEqual(C.serialize(), _C.serialize())
def test_split(self):
C = self.C.join([self.A, self.S, deepcopy(self.B)])
L = C.split()
_L = [self.A, self.S, self.B]
self.assertEqual(L[0].serialize(), self.A.serialize())
self.assertEqual(str(L[1]), self.S)
self.assertEqual(L[2].serialize(), self.B.serialize())
def test_replace(self):
C = self.C.join([self.A, self.S, deepcopy(self.B)])
C = C.replace('X', StyledText('_', [self.T3]))
_C = ('123_456\ncleartext\nabc_def',
[((1, ''), 'v1', [(0, 2), (2, 3)]),
((0, ''), 'v3', [(3, 4)]),
((1, ''), 'v1', [(4, 6)]),
((2, ''), 'v2', [(19, 21), (18, 21)]),
((0, ''), 'v3', [(21, 22)]),
((2, ''), 'v2', [(22, 23), (22, 25)])])
self.assertEqual(C.serialize(), _C)
def test_add(self):
A = deepcopy(self.A) + deepcopy(self.B)
_A = StyledText('123X456abcXdef', [self.T1, self.T4])
self.assertEqual(A.serialize(), _A.serialize())
if __name__ == "__main__":
unittest.main()

View File

@ -399,7 +399,6 @@ class Gramplet:
self.update()
def _no_db(self):
if self.dbstate.db.is_open():
self.disconnect_all() # clear the old signals
def get_option_widget(self, label):

View File

@ -25,6 +25,7 @@ Provide a simplified database access interface to the Gramps database.
"""
from ..lib import (Person, Family, Event, Source, Place, Citation,
Media, Repository, Note, Date, Tag)
from ..errors import HandleError
from ..datehandler import displayer
from ..utils.string import gender as gender_map
from ..utils.db import get_birth_or_fallback, get_death_or_fallback
@ -961,8 +962,12 @@ class SimpleAccess:
:param value: gramps_id or handle.
"""
if object_class in self.dbase.get_table_names():
obj = self.dbase.get_table_metadata(object_class)\
[prop + "_func"](value)
try:
obj = self.dbase.get_table_metadata(
object_class)[prop + "_func"](value)
except HandleError:
# Deals with deleted objects referenced in Note Links
obj = None
if obj:
if isinstance(obj, Person):
return "%s: %s [%s]" % (_(object_class),

View File

@ -311,8 +311,7 @@ class Callback:
for key in keymap:
self.__callback_map[signal_name].remove(key)
self.__callback_map[signal_name] = None
self.__callback_map = None
del self.__callback_map
self.__callback_map = {}
def emit(self, signal_name, args=tuple()):
"""

View File

@ -117,6 +117,7 @@ def make_unknown(class_arg, explanation, class_func, commit_func, transaction,
obj.set_type(EventType.UNKNOWN)
elif isinstance(obj, Place):
obj.set_title(_('Unknown'))
obj.name.set_value(_('Unknown'))
elif isinstance(obj, Source):
obj.set_title(_('Unknown'))
elif isinstance(obj, Citation):

View File

@ -288,6 +288,9 @@ class DbManager(CLIDbManager, ManagedWindow):
if not __debug__:
self.convert_btn.set_visible(False)
if not _RCS_FOUND: # it's not in Windows
self.rcs_btn.set_visible(False)
# if nothing is selected
if not node:
self.connect_btn.set_sensitive(False)
@ -315,10 +318,10 @@ class DbManager(CLIDbManager, ManagedWindow):
self.rcs_btn.set_sensitive(True)
else:
self.close_btn.set_sensitive(False)
backend_name = self.get_backend_name_from_dbid("bsddb")
dbid = config.get('database.backend')
backend_type = self.get_backend_name_from_dbid(dbid)
if (store.get_value(node, ICON_COL) in [None, ""] and
store.get_value(node,
BACKEND_COL).startswith(backend_name)):
store.get_value(node, BACKEND_COL) != backend_type):
self.convert_btn.set_sensitive(True)
else:
self.convert_btn.set_sensitive(False)
@ -422,9 +425,7 @@ class DbManager(CLIDbManager, ManagedWindow):
for items in self.current_names:
data = list(items[:8])
backend_type = self.get_backend_name_from_dbid(data[BACKEND_COL])
version = str(".".join([str(v) for v in items[8]]))
node = self.model.append(None, data[:-1] + [backend_type + ", "
+ version])
node = self.model.append(None, data[:-1] + [backend_type])
# For already loaded database, set current_node:
if self.dbstate.is_open() and \
self.dbstate.db.get_save_path() == data[1]:
@ -434,7 +435,7 @@ class DbManager(CLIDbManager, ManagedWindow):
last_accessed_node = node
for rdata in find_revisions(os.path.join(items[1], ARCHIVE_V)):
data = [rdata[2], rdata[0], items[1], rdata[1], 0, False, "",
backend_type + ", " + version]
backend_type]
self.model.append(node, data)
if self._current_node is None:
self._current_node = last_accessed_node
@ -464,6 +465,7 @@ class DbManager(CLIDbManager, ManagedWindow):
or the path and name if something has been selected
"""
self.show()
self.__update_buttons(self.selection)
while True:
value = self.top.run()
if value == Gtk.ResponseType.OK:
@ -762,21 +764,23 @@ class DbManager(CLIDbManager, ManagedWindow):
def __convert_db_ask(self, obj):
"""
Ask to convert a closed BSDDB tree into a new DB-API
tree.
Ask to convert a closed family tree into the default database backend.
"""
store, node = self.selection.get_selected()
name = store[node][0]
dirname = store[node][1]
dbid = config.get('database.backend')
backend_type = self.get_backend_name_from_dbid(dbid)
QuestionDialog(
_("Convert the '%s' database?") % name,
_("You wish to convert this database into the new DB-API format?"),
_("Do you wish to convert this family tree into a "
"%(database_type)s database?") % {'database_type': backend_type},
_("Convert"),
lambda: self.__convert_db(name, dirname), parent=self.top)
def __convert_db(self, name, dirname):
"""
Actually convert the db from BSDDB to DB-API.
Actually convert the family tree into the default database backend.
"""
try:
db = open_database(name)
@ -809,7 +813,7 @@ class DbManager(CLIDbManager, ManagedWindow):
new_text = "%s %s" % (name, _("(Converted #%d)") % count)
new_path, newname = self._create_new_db(new_text, edit_entry=False)
## Create a new database of correct type:
dbase = make_database("dbapi")
dbase = make_database(config.get('database.backend'))
dbase.write_version(new_path)
dbase.load(new_path)
## import from XML
@ -981,13 +985,6 @@ class DbManager(CLIDbManager, ManagedWindow):
parent=self.top)
self.new_btn.set_sensitive(True)
def get_backend_name_from_dbid(self, dbid):
pmgr = GuiPluginManager.get_instance()
for plugin in pmgr.get_reg_databases():
if plugin.id == dbid:
return plugin._name
return _("Unknown")
def _create_new_db(self, title=None, create_db=True, dbid=None,
edit_entry=True):
"""

View File

@ -206,8 +206,8 @@ class ErrorDialog(Gtk.MessageDialog):
Gtk.MessageDialog.__init__(self, transient_for=parent,
modal=True,
message_type=Gtk.MessageType.ERROR,
buttons=Gtk.ButtonsType.CLOSE)
message_type=Gtk.MessageType.ERROR)
self.add_button(_('_Close'), Gtk.ResponseType.CLOSE)
self.set_markup('<span weight="bold" size="larger">%s</span>' % str(msg1))
self.format_secondary_text(msg2)
self.set_icon(ICON)
@ -252,8 +252,8 @@ class WarningDialog(Gtk.MessageDialog):
Gtk.MessageDialog.__init__(self, transient_for=parent,
modal=True,
message_type=Gtk.MessageType.WARNING,
buttons=Gtk.ButtonsType.CLOSE)
message_type=Gtk.MessageType.WARNING)
self.add_button(_('_Close'), Gtk.ResponseType.CLOSE)
self.set_markup('<span weight="bold" size="larger">%s</span>' % msg1)
self.format_secondary_markup(msg2)
# FIXME: Hyper-links in the secondary text display as underlined text,
@ -278,8 +278,8 @@ class OkDialog(Gtk.MessageDialog):
Gtk.MessageDialog.__init__(self, transient_for=parent,
modal=True,
message_type=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.CLOSE)
message_type=Gtk.MessageType.INFO)
self.add_button(_('_Close'), Gtk.ResponseType.CLOSE)
self.set_markup('<span weight="bold" size="larger">%s</span>' % msg1)
self.format_secondary_text(msg2)
self.set_icon(ICON)

View File

@ -196,11 +196,15 @@ class EditPlace(EditPrimary):
def _validate_coordinate(self, widget, text, typedeg):
if (typedeg == 'lat') and not conv_lat_lon(text, "0", "ISO-D"):
return ValidationError(_("Invalid latitude (syntax: 18\u00b09'") +
_('48.21"S, -18.2412 or -18:9:48.21)'))
return ValidationError(
# translators: translate the "S" too (and the "or" of course)
_('Invalid latitude\n(syntax: '
'18\u00b09\'48.21"S, -18.2412 or -18:9:48.21)'))
elif (typedeg == 'lon') and not conv_lat_lon("0", text, "ISO-D"):
return ValidationError(_("Invalid longitude (syntax: 18\u00b09'") +
_('48.21"E, -18.2412 or -18:9:48.21)'))
return ValidationError(
# translators: translate the "E" too (and the "or" of course)
_('Invalid longitude\n(syntax: '
'18\u00b09\'48.21"E, -18.2412 or -18:9:48.21)'))
def update_title(self):
new_title = place_displayer.display(self.db, self.obj)

View File

@ -189,11 +189,15 @@ class EditPlaceRef(EditReference):
def _validate_coordinate(self, widget, text, typedeg):
if (typedeg == 'lat') and not conv_lat_lon(text, "0", "ISO-D"):
return ValidationError(_("Invalid latitude (syntax: 18\u00b09'") +
_('48.21"S, -18.2412 or -18:9:48.21)'))
return ValidationError(
# translators: translate the "S" too (and the "or" of course)
_('Invalid latitude\n(syntax: '
'18\u00b09\'48.21"S, -18.2412 or -18:9:48.21)'))
elif (typedeg == 'lon') and not conv_lat_lon("0", text, "ISO-D"):
return ValidationError(_("Invalid longitude (syntax: 18\u00b09'") +
_('48.21"E, -18.2412 or -18:9:48.21)'))
return ValidationError(
# translators: translate the "E" too (and the "or" of course)
_('Invalid longitude\n(syntax: '
'18\u00b09\'48.21"E, -18.2412 or -18:9:48.21)'))
def update_title(self):
new_title = place_displayer.display(self.db, self.source)

View File

@ -267,6 +267,7 @@ class MergePerson(ManagedWindow):
if len(alist) > 0:
self.add(tobj, title, _("Addresses"))
for addr in alist:
# TODO for Arabic, should the next line's comma be translated?
location = ", ".join([addr.get_street(), addr.get_city(),
addr.get_state(), addr.get_country(),
addr.get_postal_code(), addr.get_phone()])

View File

@ -176,6 +176,7 @@ class PluginDialog(ManagedWindow):
self.title.set_text('<span weight="bold" size="larger">%s</span>' \
% pdata.name)
self.title.set_use_markup(1)
# TODO for Arabic, should the next two lines' commas be translated?
self.author_name.set_text(', '.join(pdata.authors))
self.author_email.set_text(', '.join(pdata.authors_email))
self.item = pdata

View File

@ -1209,6 +1209,7 @@ class UpdateAddons(ManagedWindow):
if errors:
OkDialog(_("Installation Errors"),
_("The following addons had errors: ") +
# TODO for Arabic, should the next comma be translated?
", ".join(errors),
parent=self.parent_window)
if count:

View File

@ -120,10 +120,11 @@ class UndoHistory(ManagedWindow):
scrolled_window.add(self.tree)
self.window.vbox.pack_start(scrolled_window, True, True, 0)
self.sel_chng_hndlr = self.selection.connect('changed',
self._selection_changed)
self._build_model()
self._update_ui()
self.selection.connect('changed', self._selection_changed)
self.show()
def _selection_changed(self, obj):
@ -226,6 +227,7 @@ class UndoHistory(ManagedWindow):
)
def _build_model(self):
self.selection.handler_block(self.sel_chng_hndlr)
self.model.clear()
fg = bg = None
@ -243,6 +245,7 @@ class UndoHistory(ManagedWindow):
mod_text = txn.get_description()
self.model.append(row=[time_text, mod_text, fg, bg])
path = (self.undodb.undo_count,)
self.selection.handler_unblock(self.sel_chng_hndlr)
self.selection.select_path(path)
def update(self):

View File

@ -129,6 +129,7 @@ class CitationBaseModel:
Return the sorted list of tags.
"""
tag_list = list(map(self.get_tag_name, data[COLUMN_TAGS]))
# TODO for Arabic, should the next line's comma be translated?
return ', '.join(sorted(tag_list, key=glocale.sort_key))
def citation_tag_color(self, data):
@ -241,6 +242,7 @@ class CitationBaseModel:
try:
source = self.db.get_source_from_handle(source_handle)
tag_list = list(map(self.get_tag_name, source.get_tag_list()))
# TODO for Arabic, should the next line's comma be translated?
value = ', '.join(sorted(tag_list, key=glocale.sort_key))
except:
value = ''
@ -288,6 +290,7 @@ class CitationBaseModel:
Return the sorted list of tags.
"""
tag_list = list(map(self.get_tag_name, data[COLUMN2_TAGS]))
# TODO for Arabic, should the next line's comma be translated?
return ', '.join(sorted(tag_list, key=glocale.sort_key))
def source_src_tag_color(self, data):

View File

@ -224,4 +224,5 @@ class EventModel(FlatBaseModel):
Return the sorted list of tags.
"""
tag_list = list(map(self.get_tag_name, data[COLUMN_TAGS]))
# TODO for Arabic, should the next line's comma be translated?
return ', '.join(sorted(tag_list, key=glocale.sort_key))

View File

@ -236,4 +236,5 @@ class FamilyModel(FlatBaseModel):
Return the sorted list of tags.
"""
tag_list = list(map(self.get_tag_name, data[13]))
# TODO for Arabic, should the next line's comma be translated?
return ', '.join(sorted(tag_list, key=glocale.sort_key))

View File

@ -187,4 +187,5 @@ class MediaModel(FlatBaseModel):
Return the sorted list of tags.
"""
tag_list = list(map(self.get_tag_name, data[11]))
# TODO for Arabic, should the next line's comma be translated?
return ', '.join(sorted(tag_list, key=glocale.sort_key))

View File

@ -166,4 +166,5 @@ class NoteModel(FlatBaseModel):
Return the sorted list of tags.
"""
tag_list = list(map(self.get_tag_name, data[Note.POS_TAGS]))
# TODO for Arabic, should the next line's comma be translated?
return ', '.join(sorted(tag_list, key=glocale.sort_key))

View File

@ -559,6 +559,7 @@ class PeopleBaseModel(BaseModel):
cached, value = self.get_cached_value(handle, "TAGS")
if not cached:
tag_list = list(map(self.get_tag_name, data[COLUMN_TAGS]))
# TODO for Arabic, should the next line's comma be translated?
value = ', '.join(sorted(tag_list, key=glocale.sort_key))
self.set_cached_value(handle, "TAGS", value)
return value

View File

@ -219,6 +219,7 @@ class PlaceBaseModel:
Return the sorted list of tags.
"""
tag_list = list(map(self.get_tag_name, data[16]))
# TODO for Arabic, should the next line's comma be translated?
return ', '.join(sorted(tag_list, key=glocale.sort_key))
#-------------------------------------------------------------------------

View File

@ -269,4 +269,5 @@ class RepositoryModel(FlatBaseModel):
Return the sorted list of tags.
"""
tag_list = list(map(self.get_tag_name, data[8]))
# TODO for Arabic, should the next line's comma be translated?
return ', '.join(sorted(tag_list, key=glocale.sort_key))

View File

@ -161,4 +161,5 @@ class SourceModel(FlatBaseModel):
Return the sorted list of tags.
"""
tag_list = list(map(self.get_tag_name, data[11]))
# TODO for Arabic, should the next line's comma be translated?
return ', '.join(sorted(tag_list, key=glocale.sort_key))

View File

@ -83,7 +83,7 @@ def gramps_upgrade_19(self):
"""
default_handle = self.metadata.get(b'default')
with BSDDBTxn(self.env, self.metadata) as txn:
if default_handle is not None:
if isinstance(default_handle, bytes):
default_handle = default_handle.decode('utf-8')
txn.put(b'default', default_handle)
txn.put(b'version', 19)
@ -201,6 +201,7 @@ def gramps_upgrade_17(self):
n -= 1
while n > level:
if loc[n]:
# TODO for Arabic, should the next line's comma be translated?
title = ', '.join([item for item in loc[n:] if item])
parent_handle = add_place(self, loc[n], n, parent_handle, title)
locations[tuple([''] * n + loc[n:])] = parent_handle

View File

@ -1655,7 +1655,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
bsddb
"""
self.surname_list = sorted(
[s.decode('utf-8') for s in self.surnames.keys()],
[s.decode('utf-8') for s in set(self.surnames.keys())],
key=glocale.sort_key)
def add_to_surname_list(self, person, batch_transaction):
@ -2290,7 +2290,6 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
else:
bsddb_version = _("Unknown")
return {
_("DB-API version"): "n/a",
_("Number of people"): self.get_number_of_people(),
_("Number of families"): self.get_number_of_families(),
_("Number of sources"): self.get_number_of_sources(),
@ -2301,8 +2300,8 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
_("Number of repositories"): self.get_number_of_repositories(),
_("Number of notes"): self.get_number_of_notes(),
_("Number of tags"): self.get_number_of_tags(),
_("Data version"): schema_version,
_("Database db version"): bsddb_version,
_("Schema version"): schema_version,
_("Database version"): bsddb_version,
}
def _mkname(path, name):

View File

@ -25,11 +25,8 @@
#
#-------------------------------------------------------------------------
import os
import shutil
import time
import sys
import pickle
from operator import itemgetter
import logging
#------------------------------------------------------------------------
@ -82,46 +79,12 @@ class DBAPI(DbGeneric):
version_file.write(str(self.VERSION[0]))
versionpath = os.path.join(directory, str(DBBACKEND))
_LOG.debug("Write database backend file to 'dbapi'")
_LOG.debug("Write database backend file")
with open(versionpath, "w") as version_file:
version_file.write("dbapi")
# Write settings.py and settings.ini:
settings_py = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"settings.py")
settings_ini = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"settings.ini")
LOG.debug("Copy settings.py from: " + settings_py)
LOG.debug("Copy settings.ini from: " + settings_py)
shutil.copy2(settings_py, directory)
shutil.copy2(settings_ini, directory)
version_file.write(self.__class__.__name__.lower())
def _initialize(self, directory):
# Run code from directory
from gramps.gen.utils.configmanager import ConfigManager
config_file = os.path.join(directory, 'settings.ini')
config_mgr = ConfigManager(config_file)
config_mgr.register('database.dbtype', 'sqlite')
config_mgr.register('database.dbname', 'gramps')
config_mgr.register('database.host', 'localhost')
config_mgr.register('database.user', 'user')
config_mgr.register('database.password', 'password')
config_mgr.register('database.port', 'port')
config_mgr.load() # load from settings.ini
settings = {
"__file__":
os.path.join(directory, "settings.py"),
"config": config_mgr
}
settings_file = os.path.join(directory, "settings.py")
with open(settings_file) as fp:
code = compile(fp.read(), settings_file, 'exec')
exec(code, globals(), settings)
self.dbapi = settings["dbapi"]
# We use the existence of the person table as a proxy for the database
# being new
if not self.dbapi.table_exists("person"):
self._create_schema()
raise NotImplementedError
def _create_schema(self):
"""
@ -261,6 +224,7 @@ class DBAPI(DbGeneric):
Lowlevel interface to the backend transaction.
Executes a db BEGIN;
"""
if self.transaction == None:
_LOG.debug(" DBAPI %s transaction begin", hex(id(self)))
self.dbapi.begin()
@ -269,6 +233,7 @@ class DBAPI(DbGeneric):
Lowlevel interface to the backend transaction.
Executes a db END;
"""
if self.transaction == None:
_LOG.debug(" DBAPI %s transaction commit", hex(id(self)))
self.dbapi.commit()
@ -277,6 +242,7 @@ class DBAPI(DbGeneric):
Lowlevel interface to the backend transaction.
Executes a db ROLLBACK;
"""
if self.transaction == None:
self.dbapi.rollback()
def transaction_begin(self, transaction):
@ -995,16 +961,3 @@ class DBAPI(DbGeneric):
in the appropriate type.
"""
return [v if not isinstance(v, bool) else int(v) for v in values]
def get_summary(self):
"""
Returns dictionary of summary item.
Should include, if possible:
_("Number of people")
_("Version")
_("Schema version")
"""
summary = super().get_summary()
summary.update(self.dbapi.__class__.get_summary())
return summary

View File

@ -1,58 +0,0 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2016 Douglas S. Blank <doug.blank@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
from gramps.plugins.db.dbapi.dbapi import DBAPI
from gramps.plugins.db.dbapi.sqlite import Sqlite
from gramps.gen.db import DBBACKEND
from gramps.gen.db.generic import DbGeneric, LOG
import os
import glob
class InMemoryDB(DBAPI):
"""
A DB-API 2.0 In-memory SQL database.
"""
def _initialize(self, directory):
"""
Create an in-memory sqlite database.
"""
self.dbapi = Sqlite(":memory:")
self._create_schema()
def write_version(self, directory):
"""Write files for a newly created DB."""
versionpath = os.path.join(directory, DBBACKEND)
LOG.debug("Write database backend file to 'inmemorydb'")
with open(versionpath, "w") as version_file:
version_file.write("inmemorydb")
def load(self, directory, callback=None, mode=None,
force_schema_upgrade=False,
force_bsddb_upgrade=False,
force_bsddb_downgrade=False,
force_python_upgrade=False,
update=True):
DbGeneric.load(self, directory,
callback,
mode,
force_schema_upgrade,
force_bsddb_upgrade,
force_bsddb_downgrade,
force_python_upgrade)

View File

@ -21,16 +21,23 @@ from gramps.gen.plug._pluginreg import register, STABLE, DATABASE
from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext
try:
import psycopg2
available = True
except (ImportError, ValueError):
available = False
if available:
register(DATABASE,
id = 'inmemorydb',
name = _("In-Memory"),
name_accell = _("In-_Memory Database"),
description = _("In-Memory Database"),
id='postgresql',
name=_('PostgreSQL'),
name_accell=_('_PostgreSQL Database'),
description=_('PostgreSQL Database'),
version='1.0.0',
gramps_target_version = "5.1",
gramps_target_version='5.1',
status=STABLE,
fname = 'inmemorydb.py',
databaseclass = 'InMemoryDB',
fname='postgresql.py',
databaseclass='PostgreSQL',
authors=['Doug Blank'],
authors_email=["doug.blank@gmail.com"],
authors_email=['doug.blank@gmail.com']
)

View File

@ -2,7 +2,7 @@
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2015-2016 Douglas S. Blank <doug.blank@gmail.com>
# Copyright (C) 2016 Nick Hall
# Copyright (C) 2016-2017 Nick Hall
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -19,12 +19,17 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Backend for PostgreSQL database.
"""
#-------------------------------------------------------------------------
#
# Standard python modules
#
#-------------------------------------------------------------------------
import psycopg2
import os
import re
#-------------------------------------------------------------------------
@ -32,27 +37,57 @@ import re
# Gramps modules
#
#-------------------------------------------------------------------------
from gramps.plugins.db.dbapi.dbapi import DBAPI
from gramps.gen.utils.configmanager import ConfigManager
from gramps.gen.db.dbconst import ARRAYSIZE
from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext
psycopg2.paramstyle = 'format'
class Postgresql:
@classmethod
def get_summary(cls):
#-------------------------------------------------------------------------
#
# PostgreSQL class
#
#-------------------------------------------------------------------------
class PostgreSQL(DBAPI):
def get_summary(self):
"""
Return a diction of information about this database
backend.
"""
summary = {
"DB-API version": "2.0",
"Database SQL type": cls.__name__,
"Database SQL module": "psycopg2",
"Database SQL module version": psycopg2.__version__,
"Database SQL module location": psycopg2.__file__,
}
summary = super().get_summary()
summary.update({
_("Database version"): psycopg2.__version__,
_("Database module location"): psycopg2.__file__,
})
return summary
def _initialize(self, directory):
config_file = os.path.join(directory, 'settings.ini')
config_mgr = ConfigManager(config_file)
config_mgr.register('database.dbname', 'gramps')
config_mgr.register('database.host', 'localhost')
config_mgr.register('database.user', 'user')
config_mgr.register('database.password', 'password')
config_mgr.register('database.port', 'port')
config_mgr.load()
dbkwargs = {}
for key in config_mgr.get_section_settings('database'):
dbkwargs[key] = config_mgr.get('database.' + key)
self.dbapi = Connection(**dbkwargs)
#-------------------------------------------------------------------------
#
# Connection class
#
#-------------------------------------------------------------------------
class Connection:
def __init__(self, *args, **kwargs):
self.__connection = psycopg2.connect(*args, **kwargs)
self.__connection.autocommit = True
@ -140,6 +175,11 @@ class Postgresql:
return Cursor(self.__connection)
#-------------------------------------------------------------------------
#
# Cursor class
#
#-------------------------------------------------------------------------
class Cursor:
def __init__(self, connection):
self.__connection = connection

View File

@ -1,11 +0,0 @@
;; Gramps key file
;; Automatically created at 2016/07/12 13:06:48
[database]
;;dbname='gramps'
;;dbtype='sqlite'
;;host='localhost'
;;password='password'
;;port='port'
;;user='user'

View File

@ -1,52 +0,0 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2015-2016 Douglas S. Blank <doug.blank@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
import os
## This file is copied from gramps/plugins/db/dbapi/settings.py
## into each grampsdb/*/ directory. You can edit each copy
## to connect to different databases, or with different
## parameters.
## The database options are saved in settings.ini.
# NOTE: config is predefined
# NOTE: you can override this in settings.ini or here:
#from gramps.gen.config import config
dbtype = config.get('database.dbtype')
if dbtype == "sqlite":
from gramps.plugins.db.dbapi.sqlite import Sqlite
path_to_db = os.path.join(os.path.dirname(os.path.realpath(__file__)),
'sqlite.db')
dbapi = Sqlite(path_to_db)
else:
# NOTE: you can override these settings here or in settings.ini:
dbkwargs = {}
for key in config.get_section_settings('database'):
# Use all parameters except dbtype as keyword arguments
if key == 'dbtype':
continue
dbkwargs[key] = config.get('database.' + key)
if dbtype == "postgresql":
from gramps.plugins.db.dbapi.postgresql import Postgresql
dbapi = Postgresql(**dbkwargs)
else:
raise AttributeError(("invalid DB-API dbtype: '%s'. " +
"Should be 'sqlite' or 'postgresql'") % dbtype)

View File

@ -22,15 +22,15 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext
register(DATABASE,
id = 'dbapi',
name = _("DB-API"),
name_accell = _("DB-_API Database"),
description = _("DB-API Database"),
version = '1.0.32',
gramps_target_version = "5.1",
id='sqlite',
name=_('SQLite'),
name_accell=_('_SQLite Database'),
description=_('SQLite Database'),
version='1.0.0',
gramps_target_version='5.1',
status=STABLE,
fname = 'dbapi.py',
databaseclass = 'DBAPI',
fname='sqlite.py',
databaseclass='SQLite',
authors=['Doug Blank'],
authors_email=["doug.blank@gmail.com"],
authors_email=['doug.blank@gmail.com']
)

View File

@ -2,7 +2,7 @@
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2015-2016 Douglas S. Blank <doug.blank@gmail.com>
# Copyright (C) 2016 Nick Hall
# Copyright (C) 2016-2017 Nick Hall
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -20,7 +20,7 @@
#
"""
Backend for sqlite database.
Backend for SQLite database.
"""
#-------------------------------------------------------------------------
@ -29,43 +29,59 @@ Backend for sqlite database.
#
#-------------------------------------------------------------------------
import sqlite3
import logging
import os
import re
import logging
#-------------------------------------------------------------------------
#
# Gramps modules
#
#-------------------------------------------------------------------------
from gramps.plugins.db.dbapi.dbapi import DBAPI
from gramps.gen.db.dbconst import ARRAYSIZE
from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext
sqlite3.paramstyle = 'qmark'
#-------------------------------------------------------------------------
#
# Sqlite class
# SQLite class
#
#-------------------------------------------------------------------------
class Sqlite:
class SQLite(DBAPI):
def get_summary(self):
"""
Return a dictionary of information about this database backend.
"""
summary = super().get_summary()
summary.update({
_("Database version"): sqlite3.sqlite_version,
_("Database module version"): sqlite3.version,
_("Database module location"): sqlite3.__file__,
})
return summary
def _initialize(self, directory):
if directory == ':memory:':
path_to_db = ':memory:'
else:
path_to_db = os.path.join(directory, 'sqlite.db')
self.dbapi = Connection(path_to_db)
#-------------------------------------------------------------------------
#
# Connection class
#
#-------------------------------------------------------------------------
class Connection:
"""
The Sqlite class is an interface between the DBAPI class which is the Gramps
backend for the DBAPI interface and the sqlite3 python module.
"""
@classmethod
def get_summary(cls):
"""
Return a dictionary of information about this database backend.
"""
summary = {
"DB-API version": "2.0",
"Database SQL type": cls.__name__,
"Database SQL module": "sqlite3",
"Database SQL Python module version": sqlite3.version,
"Database SQL module version": sqlite3.sqlite_version,
"Database SQL module location": sqlite3.__file__,
}
return summary
def __init__(self, *args, **kwargs):
"""

View File

@ -47,8 +47,8 @@ class DbRandomTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.db = make_database("inmemorydb")
cls.db.load(None)
cls.db = make_database("sqlite")
cls.db.load(":memory:")
def setUp(self):
self.handles = {'Person': [], 'Family': [], 'Event': [], 'Place': [],
@ -709,8 +709,8 @@ class DbEmptyTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.db = make_database("inmemorydb")
cls.db.load(None)
cls.db = make_database("sqlite")
cls.db.load(":memory:")
################################################################
#
@ -806,8 +806,8 @@ class DbPersonTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.db = make_database("inmemorydb")
cls.db.load(None)
cls.db = make_database("sqlite")
cls.db.load(":memory:")
def __add_person(self, gender, first_name, surname, trans):
person = Person()

View File

@ -281,6 +281,7 @@ def write_index(index, doc):
doc.start_cell('IDX-Cell')
doc.start_paragraph('IDX-Entry')
pages = [str(page_nr) for page_nr in index[key]]
# TODO for Arabic, should the next line's comma be translated?
doc.write_text(', '.join(pages))
doc.end_paragraph()
doc.end_cell()

View File

@ -359,7 +359,7 @@ class FanChart(Report):
else:
name = p_pn.get_first_name() + p_pn.get_surname()
if (name != "") and (val != ""):
string = name + ", " + val
string = name + self._(", ") + val # Arabic OK
else:
string = name + val
return [string]
@ -375,7 +375,7 @@ class FanChart(Report):
return [name, val]
else:
if (name != "") and (val != ""):
string = name + ", " + val
string = name + self._(", ") + val # Arabic OK
else:
string = name + val
return [string]

View File

@ -44,12 +44,10 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext
from gramps.gen.lib import (AttributeType, ChildRefType, Citation, Date,
EventRoleType, EventType, LdsOrd, NameType,
PlaceType, NoteType, Person, UrlType,
SrcAttributeType)
PlaceType, NoteType, Person, UrlType)
from gramps.version import VERSION
import gramps.plugins.lib.libgedcom as libgedcom
from gramps.gen.errors import DatabaseError
from gramps.gui.plug.export import WriterOptionBox
from gramps.gen.updatecallback import UpdateCallback
from gramps.gen.utils.file import media_path_full
from gramps.gen.utils.place import conv_lat_lon
@ -91,14 +89,12 @@ LDS_STATUS = {
}
LANGUAGES = {
'cs' : 'Czech', 'da' : 'Danish', 'nl' : 'Dutch',
'en' : 'English', 'eo' : 'Esperanto', 'fi' : 'Finnish',
'fr' : 'French', 'de' : 'German', 'hu' : 'Hungarian',
'it' : 'Italian', 'lt' : 'Latvian', 'lv' : 'Lithuanian',
'no' : 'Norwegian', 'po' : 'Polish', 'pt' : 'Portuguese',
'ro' : 'Romanian', 'sk' : 'Slovak', 'es' : 'Spanish',
'sv' : 'Swedish', 'ru' : 'Russian',
}
'cs' : 'Czech', 'da' : 'Danish', 'nl' : 'Dutch', 'en' : 'English',
'eo' : 'Esperanto', 'fi' : 'Finnish', 'fr' : 'French', 'de' : 'German',
'hu' : 'Hungarian', 'it' : 'Italian', 'lt' : 'Latvian',
'lv' : 'Lithuanian', 'no' : 'Norwegian', 'po' : 'Polish',
'pt' : 'Portuguese', 'ro' : 'Romanian', 'sk' : 'Slovak',
'es' : 'Spanish', 'sv' : 'Swedish', 'ru' : 'Russian', }
#-------------------------------------------------------------------------
#
@ -130,6 +126,8 @@ PEDIGREE_TYPES = {
}
NOTES_PER_PERSON = 104 # fudge factor to make progress meter a bit smoother
#-------------------------------------------------------------------------
#
# sort_handles_by_id
@ -152,6 +150,7 @@ def sort_handles_by_id(handle_list, handle_to_object):
sorted_list.sort()
return sorted_list
#-------------------------------------------------------------------------
#
# breakup
@ -170,8 +169,8 @@ def breakup(txt, limit):
# look for non-space pair to break between
# do not break within a UTF-8 byte sequence, i. e. first char >127
idx = limit
while (idx>0 and (txt[idx-1].isspace() or txt[idx].isspace()
or ord(txt[idx-1]) > 127)):
while (idx > 0 and (txt[idx - 1].isspace() or txt[idx].isspace() or
ord(txt[idx - 1]) > 127)):
idx -= 1
if idx == 0:
#no words to break on, just break at limit anyway
@ -191,6 +190,7 @@ def breakup(txt, limit):
#
#-------------------------------------------------------------------------
def event_has_subordinate_data(event, event_ref):
""" determine if event is empty or not """
if event and event_ref:
return (event.get_description().strip() or
not event.get_date_object().is_empty() or
@ -273,11 +273,14 @@ class GedcomWriter(UpdateCallback):
into multiple lines using CONC.
"""
assert(token)
assert token
if textlines:
# break the line into multiple lines if a newline is found
textlines = textlines.replace('\n\r', '\n')
textlines = textlines.replace('\r', '\n')
# Need to double '@' See Gedcom 5.5 spec 'any_char'
if not textlines.startswith('@'): # avoid xrefs
textlines = textlines.replace('@', '@@')
textlist = textlines.split('\n')
token_level = level
for text in textlist:
@ -288,7 +291,8 @@ class GedcomWriter(UpdateCallback):
txt = prefix.join(breakup(text, limit))
else:
txt = text
self.gedcom_file.write("%d %s %s\n" % (token_level, token, txt))
self.gedcom_file.write("%d %s %s\n" %
(token_level, token, txt))
token_level = level + 1
token = "CONT"
else:
@ -551,7 +555,8 @@ class GedcomWriter(UpdateCallback):
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
if not event:
continue
self._process_person_event(person, event, event_ref)
if not adop_written:
self._adoption_records(person, adop_written)
@ -854,7 +859,8 @@ class GedcomWriter(UpdateCallback):
for cref in child_ref_list]
for gid in child_list:
if gid is None: continue
if gid is None:
continue
self._writeln(1, 'CHIL', '@%s@' % gid)
def _family_reference(self, token, person_handle):
@ -880,7 +886,8 @@ class GedcomWriter(UpdateCallback):
"""
for event_ref in family.get_event_ref_list():
event = self.dbase.get_event_from_handle(event_ref.ref)
if event is None: continue
if event is None:
continue
self._process_family_event(event, event_ref)
self._dump_event_stats(event, event_ref)
@ -973,7 +980,8 @@ class GedcomWriter(UpdateCallback):
for (source_id, handle) in sorted_list:
self.update()
source = self.dbase.get_source_from_handle(handle)
if source is None: continue
if source is None:
continue
self._writeln(0, '@%s@' % source_id, 'SOUR')
if source.get_title():
self._writeln(1, 'TITL', source.get_title())
@ -1011,7 +1019,8 @@ class GedcomWriter(UpdateCallback):
self.update()
note_cnt += 1
note = self.dbase.get_note_from_handle(note_handle)
if note is None: continue
if note is None:
continue
self._note_record(note)
def _note_record(self, note):
@ -1025,7 +1034,8 @@ class GedcomWriter(UpdateCallback):
+1 <<CHANGE_DATE>> {0:1}
"""
if note:
self._writeln(0, '@%s@' % note.get_gramps_id(), 'NOTE ' + note.get())
self._writeln(0, '@%s@' % note.get_gramps_id(),
'NOTE ' + note.get())
def _repos(self):
"""
@ -1050,7 +1060,8 @@ class GedcomWriter(UpdateCallback):
for (repo_id, handle) in sorted_list:
self.update()
repo = self.dbase.get_repository_from_handle(handle)
if repo is None: continue
if repo is None:
continue
self._writeln(0, '@%s@' % repo_id, 'REPO')
if repo.get_name():
self._writeln(1, 'NAME', repo.get_name())
@ -1138,8 +1149,7 @@ class GedcomWriter(UpdateCallback):
if int(attr.get_type()) == AttributeType.TIME]
# Not legal, but inserted by PhpGedView
if len(times) > 0:
time = times[0]
self._writeln(3, 'TIME', time)
self._writeln(3, 'TIME', times[0])
place = None
@ -1220,11 +1230,13 @@ class GedcomWriter(UpdateCallback):
family_handle = lds_ord.get_family_handle()
family = self.dbase.get_family_from_handle(family_handle)
if family:
self._writeln(index+1, 'FAMC', '@%s@' % family.get_gramps_id())
self._writeln(index + 1, 'FAMC', '@%s@' %
family.get_gramps_id())
if lds_ord.get_temple():
self._writeln(index + 1, 'TEMP', lds_ord.get_temple())
if lds_ord.get_place_handle():
place = self.dbase.get_place_from_handle(lds_ord.get_place_handle())
place = self.dbase.get_place_from_handle(
lds_ord.get_place_handle())
self._place(place, lds_ord.get_date_object(), 2)
if lds_ord.get_status() != LdsOrd.STATUS_NONE:
self._writeln(2, 'STAT', LDS_STATUS[lds_ord.get_status()])
@ -1358,7 +1370,6 @@ class GedcomWriter(UpdateCallback):
self._writeln(level + 1, 'PAGE', citation.get_page()[0:248],
limit=248)
conf = min(citation.get_confidence_level(),
Citation.CONF_VERY_HIGH)
if conf != Citation.CONF_NORMAL and conf != -1:
@ -1443,7 +1454,8 @@ class GedcomWriter(UpdateCallback):
+2 LONG <PLACE_LONGITUDE> {1:1}
+1 <<NOTE_STRUCTURE>> {0:M}
"""
if place is None: return
if place is None:
return
place_name = _pd.display(self.dbase, place, dateobj)
self._writeln(level, "PLAC", place_name.replace('\r', ' '), limit=120)
longitude = place.get_longitude()
@ -1466,7 +1478,7 @@ class GedcomWriter(UpdateCallback):
country = location.get(PlaceType.COUNTRY)
postal_code = place.get_code()
if (street or locality or city or state or postal_code or country):
if street or locality or city or state or postal_code or country:
self._writeln(level, "ADDR", street)
if street:
self._writeln(level + 1, 'ADR1', street)
@ -1535,6 +1547,7 @@ class GedcomWriter(UpdateCallback):
if addr.get_country():
self._writeln(level + 1, 'CTRY', addr.get_country())
#-------------------------------------------------------------------------
#
#

View File

@ -228,6 +228,7 @@ class PersonDetails(Gramplet):
if attr.get_type() == attr_key:
values.append(attr.get_value())
if values:
# translators: needed for Arabic, ignore otherwise
self.add_row(attr_key, _(', ').join(values))
def display_type(self, active_person, event_type):

View File

@ -367,7 +367,7 @@ class WhatNextGramplet(Gramplet):
if missingbits:
self.link(name, 'Person', person.get_handle())
self.append_text(_(": %(list)s\n") % {
'list': _(", ").join(missingbits)})
'list': _(", ").join(missingbits)}) # Arabic OK
self.__counter += 1
append_list.append(person)
@ -389,7 +389,7 @@ class WhatNextGramplet(Gramplet):
if missingbits:
self.link(name, 'Person', person.get_handle())
self.append_text(_(": %(list)s\n") % {
'list': _(", ").join(missingbits)})
'list': _(", ").join(missingbits)}) # Arabic OK
self.__counter += 1
@ -437,7 +437,7 @@ class WhatNextGramplet(Gramplet):
if missingbits:
self.link(name, 'Family', family.get_handle())
self.append_text(_(": %(list)s\n") % {
'list': _(", ").join(missingbits)})
'list': _(", ").join(missingbits)}) # Arabic OK
self.__counter += 1
append_list.append((family, person1, person2))
@ -470,7 +470,7 @@ class WhatNextGramplet(Gramplet):
if missingbits:
self.link(name, 'Family', family.get_handle())
self.append_text(_(": %(list)s\n") % {
'list': _(", ").join(missingbits)})
'list': _(", ").join(missingbits)}) # Arabic OK
self.__counter += 1
@ -492,7 +492,7 @@ class WhatNextGramplet(Gramplet):
# translators: needed for French, ignore otherwise
return [_("%(str1)s: %(str2)s"
) % {'str1' : event.get_type(),
'str2' : _(", ").join(missingbits)}]
'str2' : _(", ").join(missingbits)}] # Arabic OK
else:
return []

View File

@ -657,7 +657,7 @@ class RelGraphReport(Report):
label += '%s' % desc
if place:
if date or desc:
label += ', '
label += self._(', ') # Arabic OK
label += '%s' % place
label += ')'

View File

@ -676,6 +676,7 @@ class GeneWebParser:
except IndexError: # not all parts are written all the time
pass
if tnth: # Append title numer to title
# TODO for Arabic, should the next comma be translated?
ttitle += ", " + tnth
title = self.create_event(
EventType.NOB_TITLE, ttitle, tstart, tplace)

View File

@ -2515,8 +2515,10 @@ class GrampsParser(UpdateCallback):
date_value = self.name.get_date_object()
elif self.event:
date_value = self.event.get_date_object()
else:
elif self.placeref:
date_value = self.placeref.get_date_object()
else:
date_value = self.place_name.get_date_object()
date_value.set_as_text(attrs['val'])

File diff suppressed because it is too large Load Diff

View File

@ -88,6 +88,7 @@ class PlaceImport:
n -= 1
while n > type_num:
if loc[n]:
# TODO for Arabic, should the next comma be translated?
title = ', '.join([item for item in loc[n:] if item])
parent = self.__add_place(loc[n], n, parent, title, trans)
self.loc2handle[tuple([''] * n + loc[n:])] = parent

View File

@ -1063,6 +1063,7 @@ class GeoGraphyView(OsmGps, NavigationView):
if gids == "":
gids = plce.gramps_id
else:
# TODO for Arabic, should the next comma be translated?
gids = gids + ", " + plce.gramps_id
if nb_places > 1:
from gramps.gui.dialog import WarningDialog

View File

@ -77,10 +77,13 @@ def _build_title(db, place):
if descr:
title_descr += descr.strip()
if parish:
# TODO for Arabic, should the next line's comma be translated?
title_descr += ', ' + parish.strip() + _(" parish")
if city:
# TODO for Arabic, should the next line's comma be translated?
title_descr += ', ' + city.strip()
if state:
# TODO for Arabic, should the next line's comma be translated?
title_descr += ', ' + state.strip() + _(" state")
return _strip_leading_comma(title_descr)
@ -91,6 +94,7 @@ def _build_city(db, place):
# Build a title description string that will work for Eniro
city_descr = _build_area(db, place)
if county:
# TODO for Arabic, should the next line's comma be translated?
city_descr += ', ' + county
return _strip_leading_comma(city_descr)
@ -104,6 +108,7 @@ def _build_area(db, place):
if street:
area_descr += street.strip()
if city:
# TODO for Arabic, should the next line's comma be translated?
area_descr += ', ' + city
return _strip_leading_comma(area_descr)

View File

@ -315,6 +315,7 @@ class AllRelReport:
if isinstance(fam, list):
famstr = str(fam[0]+1)
for val in fam[1:] :
# TODO for Arabic, should the next comma be translated?
famstr += ', ' + str(val+1)
else:
famstr = str(fam+1)

View File

@ -225,6 +225,7 @@ def add_rem(remark, text):
""" Allow for extension of remark, return new remark string
"""
if remark:
# TODO for Arabic, should the next line's comma be translated?
return remark + ', ' + text
else:
return text

View File

@ -361,7 +361,7 @@ class DbTestClassBase(object):
msg="Callback Manager disconnect cb check")
params = [('BsdDb', 'bsddb'), ('DbApi', 'dbapi')]
params = [('BsdDb', 'bsddb'), ('SQLite', 'sqlite')]
for name, param in params:
cls_name = "TestMyTestClass_%s" % (name, )

View File

@ -266,7 +266,7 @@ class Printinfo:
tmp = self.__date_place(
get_death_or_fallback(self.database, person))
if string and tmp:
string += ", "
string += self._(", ") # Arabic OK
string += tmp
if string:
@ -276,13 +276,13 @@ class Printinfo:
tmp = self.__date_place(
get_marriage_or_fallback(self.database, family))
if tmp:
string += ", " + tmp
string += self._(", ") + tmp # Arabic OK
if family and self.showdivorce:
tmp = self.__date_place(
get_divorce_or_fallback(self.database, family))
if tmp:
string += ", " + tmp
string += self._(", ") + tmp # Arabic OK
if family and self.want_ids:
string += ' (%s)' % family.get_gramps_id()

View File

@ -484,6 +484,7 @@ class NotRelated(tool.ActivePersonTool, ManagedWindow) :
tag = self.db.get_tag_from_handle(handle)
tags.append(tag.get_name())
tags.sort(key=glocale.sort_key)
# TODO for Arabic, should the next line's comma be translated?
return ', '.join(tags)
#------------------------------------------------------------------------

View File

@ -232,6 +232,7 @@ class RelCalc(tool.Tool, ManagedWindow):
for person_handle in common:
person = self.db.get_person_from_handle(person_handle)
if index:
# TODO for Arabic, should the next comma be translated?
commontext += ", "
commontext += name_displayer.display(person)
index += 1

View File

@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010-2017 Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
Classe:
AddressBookPage
"""
#------------------------------------------------
# python modules
#------------------------------------------------
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gen.plug.report import Bibliography
from gramps.plugins.lib.libhtml import Html
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
from gramps.plugins.webreport.common import FULLCLEAR
_ = glocale.translation.sgettext
LOG = logging.getLogger(".NarrativeWeb")
getcontext().prec = 8
class AddressBookPage(BasePage):
"""
Create one page for one Address
"""
def __init__(self, report, title, person_handle, has_add, has_res, has_url):
"""
@param: report -- The instance of the main report class
for this report
@param: title -- Is the title of the web page
@param: person_handle -- the url, address and residence to use
for the report
@param: has_add -- the address to use for the report
@param: has_res -- the residence to use for the report
@param: has_url -- the url to use for the report
"""
person = report.database.get_person_from_handle(person_handle)
BasePage.__init__(self, report, title, person.gramps_id)
self.bibli = Bibliography()
self.uplink = True
# set the file name and open file
output_file, sio = self.report.create_file(person_handle, "addr")
addressbookpage, head, body = self.write_header(_("Address Book"))
# begin address book page division and section title
with Html("div", class_="content",
id="AddressBookDetail") as addressbookdetail:
body += addressbookdetail
link = self.new_person_link(person_handle, uplink=True,
person=person)
addressbookdetail += Html("h3", link)
# individual has an address
if has_add:
addressbookdetail += self.display_addr_list(has_add, None)
# individual has a residence
if has_res:
addressbookdetail.extend(
self.dump_residence(res)
for res in has_res
)
# individual has a url
if has_url:
addressbookdetail += self.display_url_list(has_url)
# add fullclear for proper styling
# and footer section to page
footer = self.write_footer(None)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(addressbookpage, output_file, sio, 0)

View File

@ -0,0 +1,170 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010-2017 Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
Classe:
AddressBookListPage
"""
#------------------------------------------------
# python modules
#------------------------------------------------
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.plugins.lib.libhtml import Html
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
from gramps.plugins.webreport.common import FULLCLEAR
LOG = logging.getLogger(".NarrativeWeb")
_ = glocale.translation.sgettext
getcontext().prec = 8
class AddressBookListPage(BasePage):
"""
Create the index for addresses.
"""
def __init__(self, report, title, has_url_addr_res):
"""
@param: report -- The instance of the main report class
for this report
@param: title -- Is the title of the web page
@param: has_url_addr_res -- The url, address and residence to use
for the report
"""
BasePage.__init__(self, report, title)
# Name the file, and create it
output_file, sio = self.report.create_file("addressbook")
# Add xml, doctype, meta and stylesheets
addressbooklistpage, head, body = self.write_header(_("Address Book"))
# begin AddressBookList division
with Html("div", class_="content",
id="AddressBookList") as addressbooklist:
body += addressbooklist
# Address Book Page message
msg = _("This page contains an index of all the individuals in "
"the database, sorted by their surname, with one of the "
"following: Address, Residence, or Web Links. "
"Selecting the person&#8217;s name will take you "
"to their individual Address Book page.")
addressbooklist += Html("p", msg, id="description")
# begin Address Book table
with Html("table",
class_="infolist primobjlist addressbook") as table:
addressbooklist += table
thead = Html("thead")
table += thead
trow = Html("tr")
thead += trow
trow.extend(
Html("th", label, class_=colclass, inline=True)
for (label, colclass) in [
["&nbsp;", "ColumnRowLabel"],
[_("Full Name"), "ColumnName"],
[_("Address"), "ColumnAddress"],
[_("Residence"), "ColumnResidence"],
[_("Web Links"), "ColumnWebLinks"]
]
)
tbody = Html("tbody")
table += tbody
index = 1
for (sort_name, person_handle,
has_add, has_res,
has_url) in has_url_addr_res:
address = None
residence = None
weblinks = None
# has address but no residence event
if has_add and not has_res:
address = "X"
# has residence, but no addresses
elif has_res and not has_add:
residence = "X"
# has residence and addresses too
elif has_add and has_res:
address = "X"
residence = "X"
# has Web Links
if has_url:
weblinks = "X"
trow = Html("tr")
tbody += trow
trow.extend(
Html("td", data or "&nbsp;", class_=colclass,
inline=True)
for (colclass, data) in [
["ColumnRowLabel", index],
["ColumnName",
self.addressbook_link(person_handle)],
["ColumnAddress", address],
["ColumnResidence", residence],
["ColumnWebLinks", weblinks]
]
)
index += 1
# Add footer and clearline
footer = self.write_footer(None)
body += (FULLCLEAR, footer)
# send the page out for processing
# and close the file
self.xhtml_writer(addressbooklistpage, output_file, sio, 0)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010-2017 Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
Classe:
CitationPages - dummy
"""
#------------------------------------------------
# python modules
#------------------------------------------------
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
LOG = logging.getLogger(".NarrativeWeb")
getcontext().prec = 8
#################################################
#
# Passes citations through to the Sources page
#
#################################################
class CitationPages(BasePage):
"""
This class is responsible for displaying information about the 'Citation'
database objects. It passes this information to the 'Sources' tab. It is
told by the 'add_instances' call which 'Citation's to display.
"""
def __init__(self, report):
"""
@param: report -- The instance of the main report class for
this report
"""
BasePage.__init__(self, report, title="")
def display_pages(self, title):
pass

View File

@ -0,0 +1,862 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2010-2017 Serge Noiraud
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
This module is used to share variables, enums and functions between all modules
"""
from unicodedata import normalize
from collections import defaultdict
from hashlib import md5
import re
import logging
from xml.sax.saxutils import escape
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gen.display.name import displayer as _nd
from gramps.gen.display.place import displayer as _pd
from gramps.gen.utils.db import get_death_or_fallback
from gramps.gen.lib import (EventType, Date)
from gramps.gen.plug import BasePluginManager
from gramps.plugins.lib.libgedcom import make_gedcom_date, DATE_QUALITY
from gramps.gen.plug.report import utils
from gramps.plugins.lib.libhtml import Html
LOG = logging.getLogger(".NarrativeWeb")
# define clear blank line for proper styling
FULLCLEAR = Html("div", class_="fullclear", inline=True)
# define all possible web page filename extensions
_WEB_EXT = ['.html', '.htm', '.shtml', '.php', '.php3', '.cgi']
# used to select secured web site or not
HTTP = "http://"
HTTPS = "https://"
GOOGLE_MAPS = 'https://maps.googleapis.com/maps/'
# javascript code for marker path
MARKER_PATH = """
var marker_png = '%s'
"""
# javascript code for Google's FamilyLinks...
FAMILYLINKS = """
var tracelife = %s
function initialize() {
var myLatLng = new google.maps.LatLng(%s, %s);
var mapOptions = {
scaleControl: true,
panControl: true,
backgroundColor: '#000000',
zoom: %d,
center: myLatLng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map_canvas"),
mapOptions);
var flightPath = new google.maps.Polyline({
path: tracelife,
strokeColor: "#FF0000",
strokeOpacity: 1.0,
strokeWeight: 2
});
flightPath.setMap(map);
}"""
# javascript for Google's Drop Markers...
DROPMASTERS = """
var markers = [];
var iterator = 0;
var tracelife = %s
var map;
var myLatLng = new google.maps.LatLng(%s, %s);
function initialize() {
var mapOptions = {
scaleControl: true,
zoomControl: true,
zoom: %d,
mapTypeId: google.maps.MapTypeId.ROADMAP,
center: myLatLng,
};
map = new google.maps.Map(document.getElementById("map_canvas"),
mapOptions);
};
function drop() {
for (var i = 0; i < tracelife.length; i++) {
setTimeout(function() {
addMarker();
}, i * 1000);
}
}
function addMarker() {
var location = tracelife[iterator];
var myLatLng = new google.maps.LatLng(location[1], location[2]);
markers.push(new google.maps.Marker({
position: myLatLng,
map: map,
draggable: true,
title: location[0],
animation: google.maps.Animation.DROP
}));
iterator++;
}"""
# javascript for Google's Markers...
MARKERS = """
var tracelife = %s
var map;
var myLatLng = new google.maps.LatLng(%s, %s);
function initialize() {
var mapOptions = {
scaleControl: true,
panControl: true,
backgroundColor: '#000000',
zoom: %d,
center: myLatLng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("map_canvas"),
mapOptions);
addMarkers();
}
function addMarkers() {
var bounds = new google.maps.LatLngBounds();
for (var i = 0; i < tracelife.length; i++) {
var location = tracelife[i];
var myLatLng = new google.maps.LatLng(location[1], location[2]);
var marker = new google.maps.Marker({
position: myLatLng,
draggable: true,
title: location[0],
map: map,
zIndex: location[3]
});
bounds.extend(myLatLng);
if ( i > 1 ) { map.fitBounds(bounds); };
}
}"""
# javascript for OpenStreetMap's markers...
OSM_MARKERS = """
function initialize(){
var map;
var tracelife = %s;
var iconStyle = new ol.style.Style({
image: new ol.style.Icon(({
opacity: 1.0,
src: marker_png
}))
});
var markerSource = new ol.source.Vector({
});
for (var i = 0; i < tracelife.length; i++) {
var loc = tracelife[i];
var iconFeature = new ol.Feature({
geometry: new ol.geom.Point(ol.proj.transform([loc[0], loc[1]],
'EPSG:4326', 'EPSG:3857')),
name: loc[2],
});
iconFeature.setStyle(iconStyle);
markerSource.addFeature(iconFeature);
}
markerLayer = new ol.layer.Vector({
source: markerSource,
style: iconStyle
});
var centerCoord = new ol.proj.transform([%s, %s], 'EPSG:4326', 'EPSG:3857');
map= new ol.Map({
target: 'map_canvas',
layers: [new ol.layer.Tile({ source: new ol.source.OSM() }),
markerLayer],
view: new ol.View({ center: centerCoord, zoom: %d })
});
var element = document.getElementById('popup');
var tooltip = new ol.Overlay({
element: element,
positioning: 'bottom-center',
stopEvent: false
});
map.addOverlay(tooltip);
var displayFeatureInfo = function(pixel) {
var feature = map.forEachFeatureAtPixel(pixel, function(feature, layer) {
return feature;
});
var info = document.getElementById('popup');
if (feature) {
var geometry = feature.getGeometry();
var coord = geometry.getCoordinates();
tooltip.setPosition(coord);
$(element).siblings('.popover').css({ width: '250px' });
$(element).siblings('.popover').css({ background: '#aaa' });
$(info).popover({
'placement': 'auto',
'html': true,
'content': feature.get('name')
});
$(info).popover('show');
} else {
// TODO : some warning with firebug here
$(info).popover('destroy');
$('.popover').remove();
}
};
map.on('pointermove', function(evt) {
if (evt.dragging) {
return;
}
var pixel = map.getEventPixel(evt.originalEvent);
displayFeatureInfo(pixel);
});
map.on('click', function(evt) {
displayFeatureInfo(evt.pixel);
});
};
"""
# variables for alphabet_navigation()
_KEYPERSON, _KEYPLACE, _KEYEVENT, _ALPHAEVENT = 0, 1, 2, 3
COLLATE_LANG = glocale.collation
_NAME_STYLE_SHORT = 2
_NAME_STYLE_DEFAULT = 1
_NAME_STYLE_FIRST = 0
_NAME_STYLE_SPECIAL = None
PLUGMAN = BasePluginManager.get_instance()
CSS = PLUGMAN.process_plugin_data('WEBSTUFF')
#_NAME_COL = 3
_WRONGMEDIAPATH = []
_HTML_DBL_QUOTES = re.compile(r'([^"]*) " ([^"]*) " (.*)', re.VERBOSE)
_HTML_SNG_QUOTES = re.compile(r"([^']*) ' ([^']*) ' (.*)", re.VERBOSE)
# Events that are usually a family event
_EVENTMAP = set([EventType.MARRIAGE, EventType.MARR_ALT,
EventType.MARR_SETTL, EventType.MARR_LIC,
EventType.MARR_CONTR, EventType.MARR_BANNS,
EventType.ENGAGEMENT, EventType.DIVORCE,
EventType.DIV_FILING])
# Names for stylesheets
_NARRATIVESCREEN = "narrative-screen.css"
_NARRATIVEPRINT = "narrative-print.css"
def sort_people(dbase, handle_list, rlocale=glocale):
"""
will sort the database people by surname
"""
sname_sub = defaultdict(list)
sortnames = {}
for person_handle in handle_list:
person = dbase.get_person_from_handle(person_handle)
primary_name = person.get_primary_name()
if primary_name.group_as:
surname = primary_name.group_as
else:
group_map = _nd.primary_surname(primary_name)
surname = dbase.get_name_group_mapping(group_map)
# Treat people who have no name with those whose name is just
# 'whitespace'
if surname is None or surname.isspace():
surname = ''
sortnames[person_handle] = _nd.sort_string(primary_name)
sname_sub[surname].append(person_handle)
sorted_lists = []
temp_list = sorted(sname_sub, key=rlocale.sort_key)
for name in temp_list:
if isinstance(name, bytes):
name = name.decode('utf-8')
slist = sorted(((sortnames[x], x) for x in sname_sub[name]),
key=lambda x: rlocale.sort_key(x[0]))
entries = [x[1] for x in slist]
sorted_lists.append((name, entries))
return sorted_lists
def sort_event_types(dbase, event_types, event_handle_list, rlocale):
"""
sort a list of event types and their associated event handles
@param: dbase -- report database
@param: event_types -- a dict of event types
@param: event_handle_list -- all event handles in this database
"""
event_dict = dict((evt_type, list()) for evt_type in event_types)
for event_handle in event_handle_list:
event = dbase.get_event_from_handle(event_handle)
event_type = rlocale.translation.sgettext(event.get_type().xml_str())
# add (gramps_id, date, handle) from this event
if event_type in event_dict:
sort_value = event.get_date_object().get_sort_value()
event_dict[event_type].append((sort_value, event_handle))
for tup_list in event_dict.values():
tup_list.sort()
# return a list of sorted tuples, one per event
retval = [(event_type, event_list) for (event_type,
event_list) in event_dict.items()]
retval.sort(key=lambda item: str(item[0]))
return retval
# Modified _get_regular_surname from WebCal.py to get prefix, first name,
# and suffix
def _get_short_name(gender, name):
""" Will get suffix for all people passed through it """
short_name = name.get_first_name()
suffix = name.get_suffix()
if suffix:
# TODO for Arabic, should the next line's comma be translated?
short_name = short_name + ", " + suffix
return short_name
def __get_person_keyname(dbase, handle):
""" .... """
person = dbase.get_person_from_handle(handle)
return _nd.sort_string(person.get_primary_name())
def __get_place_keyname(dbase, handle):
""" ... """
return utils.place_name(dbase, handle)
# See : http://www.gramps-project.org/bugs/view.php?id = 4423
# Contraction data taken from CLDR 22.1. Only the default variant is considered.
# The languages included below are, by no means, all the langauges that have
# contractions - just a sample of langauges that have been supported
# At the time of writing (Feb 2013), the following langauges have greater that
# 50% coverage of translation of Gramps: bg Bulgarian, ca Catalan, cs Czech, da
# Danish, de German, el Greek, en_GB, es Spanish, fi Finish, fr French, he
# Hebrew, hr Croation, hu Hungarian, it Italian, ja Japanese, lt Lithuanian, nb
# Noregian Bokmål, nn Norwegian Nynorsk, nl Dutch, pl Polish, pt_BR Portuguese
# (Brazil), pt_P Portugeuse (Portugal), ru Russian, sk Slovak, sl Slovenian, sv
# Swedish, vi Vietnamese, zh_CN Chinese.
# Key is the language (or language and country), Value is a list of
# contractions. Each contraction consists of a tuple. First element of the
# tuple is the list of characters, second element is the string to use as the
# index entry.
# The DUCET contractions (e.g. LATIN CAPIAL LETTER L, MIDDLE DOT) are ignored,
# as are the supresscontractions in some locales.
CONTRACTIONS_DICT = {
# bg Bulgarian validSubLocales="bg_BG" no contractions
# ca Catalan validSubLocales="ca_AD ca_ES"
"ca" : [(("", ""), "L")],
# Czech, validSubLocales="cs_CZ" Czech_Czech Republic
"cs" : [(("ch", "cH", "Ch", "CH"), "CH")],
# Danish validSubLocales="da_DK" Danish_Denmark
"da" : [(("aa", "Aa", "AA"), "Å")],
# de German validSubLocales="de_AT de_BE de_CH de_DE de_LI de_LU" no
# contractions in standard collation.
# el Greek validSubLocales="el_CY el_GR" no contractions.
# es Spanish validSubLocales="es_419 es_AR es_BO es_CL es_CO es_CR es_CU
# es_DO es_EA es_EC es_ES es_GQ es_GT es_HN es_IC es_MX es_NI es_PA es_PE
# es_PH es_PR es_PY es_SV es_US es_UY es_VE" no contractions in standard
# collation.
# fi Finish validSubLocales="fi_FI" no contractions in default (phonebook)
# collation.
# fr French no collation data.
# he Hebrew validSubLocales="he_IL" no contractions
# hr Croation validSubLocales="hr_BA hr_HR"
"hr" : [(("", ""), ""),
(("lj", "Lj", 'LJ'), "LJ"),
(("Nj", "NJ", "nj"), "NJ")],
# Hungarian hu_HU for two and three character contractions.
"hu" : [(("cs", "Cs", "CS"), "CS"),
(("dzs", "Dzs", "DZS"), "DZS"), # order is important
(("dz", "Dz", "DZ"), "DZ"),
(("gy", "Gy", "GY"), "GY"),
(("ly", "Ly", "LY"), "LY"),
(("ny", "Ny", "NY"), "NY"),
(("sz", "Sz", "SZ"), "SZ"),
(("ty", "Ty", "TY"), "TY"),
(("zs", "Zs", "ZS"), "ZS")
],
# it Italian no collation data.
# ja Japanese unable to process the data as it is too complex.
# lt Lithuanian no contractions.
# Norwegian Bokmål
"nb" : [(("aa", "Aa", "AA"), "Å")],
# nn Norwegian Nynorsk validSubLocales="nn_NO"
"nn" : [(("aa", "Aa", "AA"), "Å")],
# nl Dutch no collation data.
# pl Polish validSubLocales="pl_PL" no contractions
# pt Portuguese no collation data.
# ru Russian validSubLocales="ru_BY ru_KG ru_KZ ru_MD ru_RU ru_UA" no
# contractions
# Slovak, validSubLocales="sk_SK" Slovak_Slovakia
# having DZ in Slovak as a contraction was rejected in
# http://unicode.org/cldr/trac/ticket/2968
"sk" : [(("ch", "cH", "Ch", "CH"), "Ch")],
# sl Slovenian validSubLocales="sl_SI" no contractions
# sv Swedish validSubLocales="sv_AX sv_FI sv_SE" default collation is
# "reformed" no contractions.
# vi Vietnamese validSubLocales="vi_VN" no contractions.
# zh Chinese validSubLocales="zh_Hans zh_Hans_CN zh_Hans_SG" no contractions
# in Latin characters the others are too complex.
}
# The comment below from the glibc locale sv_SE in
# localedata/locales/sv_SE :
#
# % The letter w is normally not present in the Swedish alphabet. It
# % exists in some names in Swedish and foreign words, but is accounted
# % for as a variant of 'v'. Words and names with 'w' are in Swedish
# % ordered alphabetically among the words and names with 'v'. If two
# % words or names are only to be distinguished by 'v' or % 'w', 'v' is
# % placed before 'w'.
#
# See : http://www.gramps-project.org/bugs/view.php?id = 2933
#
# HOWEVER: the characters V and W in Swedish are not considered as a special
# case for several reasons. (1) The default collation for Swedish (called the
# 'reformed' collation type) regards the difference between 'v' and 'w' as a
# primary difference. (2) 'v' and 'w' in the 'standard' (non-default) collation
# type are not a contraction, just a case where the difference is secondary
# rather than primary. (3) There are plenty of other languages where a
# difference that is primary in other languages is secondary, and those are not
# specially handled.
def first_letter(string, rlocale=glocale):
"""
Receives a string and returns the first letter
"""
if string is None or len(string) < 1:
return ' '
norm_unicode = normalize('NFKC', str(string))
contractions = CONTRACTIONS_DICT.get(COLLATE_LANG)
if contractions is None:
contractions = CONTRACTIONS_DICT.get(COLLATE_LANG.split("_")[0])
if contractions is not None:
for contraction in contractions:
count = len(contraction[0][0])
if (len(norm_unicode) >= count and
norm_unicode[:count] in contraction[0]):
return contraction[1]
# no special case
return norm_unicode[0].upper()
try:
import PyICU # pylint : disable=wrong-import-position
PRIM_COLL = PyICU.Collator.createInstance(PyICU.Locale(COLLATE_LANG))
PRIM_COLL.setStrength(PRIM_COLL.PRIMARY)
def primary_difference(prev_key, new_key, rlocale=glocale):
"""
Try to use the PyICU collation.
"""
return PRIM_COLL.compare(prev_key, new_key) != 0
except:
def primary_difference(prev_key, new_key, rlocale=glocale):
"""
The PyICU collation is not available.
Returns true if there is a primary difference between the two parameters
See http://www.gramps-project.org/bugs/view.php?id=2933#c9317 if
letter[i]+'a' < letter[i+1]+'b' and letter[i+1]+'a' < letter[i]+'b' is
true then the letters should be grouped together
The test characters here must not be any that are used in contractions.
"""
return rlocale.sort_key(prev_key + "e") >= \
rlocale.sort_key(new_key + "f") or \
rlocale.sort_key(new_key + "e") >= \
rlocale.sort_key(prev_key + "f")
def get_first_letters(dbase, handle_list, key, rlocale=glocale):
"""
get the first letters of the handle_list
@param: handle_list -- One of a handle list for either person or
place handles or an evt types list
@param: key -- Either a person, place, or event type
The first letter (or letters if there is a contraction) are extracted from
all the objects in the handle list. There may be duplicates, and there may
be letters where there is only a secondary or tertiary difference, not a
primary difference. The list is sorted in collation order. For each group
with secondary or tertiary differences, the first in collation sequence is
retained. For example, assume the default collation sequence (DUCET) and
names Ånström and Apple. These will sort in the order shown. Å and A have a
secondary difference. If the first letter from these names was chosen then
the inex entry would be Å. This is not desirable. Instead, the initial
letters are extracted (Å and A). These are sorted, which gives A and Å. Then
the first of these is used for the index entry.
"""
index_list = []
for handle in handle_list:
if key == _KEYPERSON:
keyname = __get_person_keyname(dbase, handle)
elif key == _KEYPLACE:
keyname = __get_place_keyname(dbase, handle)
else:
if rlocale != glocale:
keyname = rlocale.translation.sgettext(handle)
else:
keyname = handle
ltr = first_letter(keyname)
index_list.append(ltr)
# Now remove letters where there is not a primary difference
index_list.sort(key=rlocale.sort_key)
first = True
prev_index = None
for key in index_list[:]: #iterate over a slice copy of the list
if first or primary_difference(prev_index, key, rlocale):
first = False
prev_index = key
else:
index_list.remove(key)
# return menu set letters for alphabet_navigation
return index_list
def get_index_letter(letter, index_list, rlocale=glocale):
"""
This finds the letter in the index_list that has no primary difference from
the letter provided. See the discussion in get_first_letters above.
Continuing the example, if letter is Å and index_list is A, then this would
return A.
"""
for index in index_list:
if not primary_difference(letter, index, rlocale):
return index
LOG.warning("Initial letter '%s' not found in alphabetic navigation list",
letter)
LOG.debug("filtered sorted index list %s", index_list)
return letter
def alphabet_navigation(index_list, rlocale=glocale):
"""
Will create the alphabet navigation bar for classes IndividualListPage,
SurnameListPage, PlaceListPage, and EventList
@param: index_list -- a dictionary of either letters or words
"""
sorted_set = defaultdict(int)
for menu_item in index_list:
sorted_set[menu_item] += 1
# remove the number of each occurance of each letter
sorted_alpha_index = sorted(sorted_set, key=rlocale.sort_key)
# if no letters, return None to its callers
if not sorted_alpha_index:
return None
num_ltrs = len(sorted_alpha_index)
num_of_cols = 26
num_of_rows = ((num_ltrs // num_of_cols) + 1)
# begin alphabet navigation division
with Html("div", id="alphanav") as alphabetnavigation:
index = 0
for row in range(num_of_rows):
unordered = Html("ul")
cols = 0
while cols <= num_of_cols and index < num_ltrs:
menu_item = sorted_alpha_index[index]
if menu_item == ' ':
menu_item = '&nbsp;'
# adding title to hyperlink menu for screen readers and
# braille writers
title_txt = "Alphabet Menu: %s" % menu_item
title_str = rlocale.translation.sgettext(title_txt)
hyper = Html("a", menu_item, title=title_str,
href="#%s" % menu_item)
unordered.extend(Html("li", hyper, inline=True))
index += 1
cols += 1
num_of_rows -= 1
alphabetnavigation += unordered
return alphabetnavigation
def _has_webpage_extension(url):
"""
determine if a filename has an extension or not...
@param: url -- filename to be checked
"""
return any(url.endswith(ext) for ext in _WEB_EXT)
def add_birthdate(dbase, ppl_handle_list, rlocale):
"""
This will sort a list of child handles in birth order
For each entry in the list, we'll have :
birth date
The transtated birth date for the configured locale
The transtated death date for the configured locale
The handle for the child
@param: dbase -- The database to use
@param: ppl_handle_list -- the handle for the people
@param: rlocale -- the locale for date translation
"""
sortable_individuals = []
for person_handle in ppl_handle_list:
birth_date = 0 # dummy value in case none is found
person = dbase.get_person_from_handle(person_handle)
if person:
birth_ref = person.get_birth_ref()
birth1 = ""
if birth_ref:
birth = dbase.get_event_from_handle(birth_ref.ref)
if birth:
birth1 = rlocale.get_date(birth.get_date_object())
birth_date = birth.get_date_object().get_sort_value()
death_event = get_death_or_fallback(dbase, person)
if death_event:
death = rlocale.get_date(death_event.get_date_object())
else:
death = ""
sortable_individuals.append((birth_date, birth1, death, person_handle))
# return a list of handles with the individual's birthdate attached
return sortable_individuals
def _find_birth_date(dbase, individual):
"""
will look for a birth date within the person's events
@param: dbase -- The database to use
@param: individual -- The individual for who we want to find the birth date
"""
date_out = None
birth_ref = individual.get_birth_ref()
if birth_ref:
birth = dbase.get_event_from_handle(birth_ref.ref)
if birth:
date_out = birth.get_date_object()
date_out.fallback = False
else:
person_evt_ref_list = individual.get_primary_event_ref_list()
if person_evt_ref_list:
for evt_ref in person_evt_ref_list:
event = dbase.get_event_from_handle(evt_ref.ref)
if event:
if event.get_type().is_birth_fallback():
date_out = event.get_date_object()
date_out.fallback = True
LOG.debug("setting fallback to true for '%s'", event)
break
return date_out
def _find_death_date(dbase, individual):
"""
will look for a death date within a person's events
@param: dbase -- The database to use
@param: individual -- The individual for who we want to find the death date
"""
date_out = None
death_ref = individual.get_death_ref()
if death_ref:
death = dbase.get_event_from_handle(death_ref.ref)
if death:
date_out = death.get_date_object()
date_out.fallback = False
else:
person_evt_ref_list = individual.get_primary_event_ref_list()
if person_evt_ref_list:
for evt_ref in person_evt_ref_list:
event = dbase.get_event_from_handle(evt_ref.ref)
if event:
if event.get_type().is_death_fallback():
date_out = event.get_date_object()
date_out.fallback = True
LOG.debug("setting fallback to true for '%s'", event)
break
return date_out
def build_event_data_by_individuals(dbase, ppl_handle_list):
"""
creates a list of event handles and event types for this database
@param: dbase -- The database to use
@param: ppl_handle_list -- the handle for the people
"""
event_handle_list = []
event_types = []
for person_handle in ppl_handle_list:
person = dbase.get_person_from_handle(person_handle)
if person:
evt_ref_list = person.get_event_ref_list()
if evt_ref_list:
for evt_ref in evt_ref_list:
event = dbase.get_event_from_handle(evt_ref.ref)
if event:
event_types.append(str(event.get_type()))
event_handle_list.append(evt_ref.ref)
person_family_handle_list = person.get_family_handle_list()
if person_family_handle_list:
for family_handle in person_family_handle_list:
family = dbase.get_family_from_handle(family_handle)
if family:
family_evt_ref_list = family.get_event_ref_list()
if family_evt_ref_list:
for evt_ref in family_evt_ref_list:
event = dbase.get_event_from_handle(evt_ref.ref)
if event:
event_types.append(str(event.type))
event_handle_list.append(evt_ref.ref)
# return event_handle_list and event types to its caller
return event_handle_list, event_types
def name_to_md5(text):
"""This creates an MD5 hex string to be used as filename."""
return md5(text.encode('utf-8')).hexdigest()
def get_gendex_data(database, event_ref):
"""
Given an event, return the date and place a strings
@param: database -- The database
@param: event_ref -- The event reference
"""
doe = "" # date of event
poe = "" # place of event
if event_ref and event_ref.ref:
event = database.get_event_from_handle(event_ref.ref)
if event:
date = event.get_date_object()
doe = format_date(date)
if event.get_place_handle():
place_handle = event.get_place_handle()
if place_handle:
place = database.get_place_from_handle(place_handle)
if place:
poe = _pd.display(database, place, date)
return doe, poe
def format_date(date):
"""
Format the date
"""
start = date.get_start_date()
if start != Date.EMPTY:
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 = "%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 = "%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
return ""
# This command then defines the 'html_escape' option for escaping
# special characters for presentation in HTML based on the above list.
def html_escape(text):
"""Convert the text and replace some characters with a &# variant."""
# First single characters, no quotes
text = escape(text)
# Deal with double quotes.
match = _HTML_DBL_QUOTES.match(text)
while match:
text = "%s" "&#8220;" "%s" "&#8221;" "%s" % match.groups()
match = _HTML_DBL_QUOTES.match(text)
# Replace remaining double quotes.
text = text.replace('"', '&#34;')
# Deal with single quotes.
text = text.replace("'s ", '&#8217;s ')
match = _HTML_SNG_QUOTES.match(text)
while match:
text = "%s" "&#8216;" "%s" "&#8217;" "%s" % match.groups()
match = _HTML_SNG_QUOTES.match(text)
# Replace remaining single quotes.
text = text.replace("'", '&#39;')
return text

View File

@ -0,0 +1,139 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010-2017 Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
Classe:
ContactPage
"""
#------------------------------------------------
# python modules
#------------------------------------------------
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gen.utils.config import get_researcher
from gramps.plugins.lib.libhtml import Html
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
from gramps.plugins.webreport.common import FULLCLEAR
_ = glocale.translation.sgettext
LOG = logging.getLogger(".NarrativeWeb")
getcontext().prec = 8
class ContactPage(BasePage):
"""
This class is responsible for displaying information about the 'Researcher'
"""
def __init__(self, report, title):
"""
@param: report -- The instance of the main report class for this report
@param: title -- Is the title of the web page
"""
BasePage.__init__(self, report, title)
output_file, sio = self.report.create_file("contact")
contactpage, head, body = self.write_header(self._('Contact'))
# begin contact division
with Html("div", class_="content", id="Contact") as section:
body += section
# begin summaryarea division
with Html("div", id='summaryarea') as summaryarea:
section += summaryarea
contactimg = self.add_image('contactimg', 200)
if contactimg is not None:
summaryarea += contactimg
# get researcher information
res = get_researcher()
with Html("div", id='researcher') as researcher:
summaryarea += researcher
if res.name:
res.name = res.name.replace(',,,', '')
researcher += Html("h3", res.name, inline=True)
if res.addr:
researcher += Html("span", res.addr,
id='streetaddress', inline=True)
if res.locality:
researcher += Html("span", res.locality,
id="locality", inline=True)
text = "".join([res.city, res.state, res.postal])
if text:
city = Html("span", res.city, id='city', inline=True)
state = Html("span", res.state, id='state', inline=True)
postal = Html("span", res.postal, id='postalcode',
inline=True)
researcher += (city, state, postal)
if res.country:
researcher += Html("span", res.country,
id='country', inline=True)
if res.email:
researcher += Html("span", id='email') + (
Html("a", res.email,
href='mailto:%s' % res.email, inline=True)
)
# add clear line for proper styling
summaryarea += FULLCLEAR
note_id = report.options['contactnote']
if note_id:
note = self.r_db.get_note_from_gramps_id(note_id)
note_text = self.get_note_format(note, False)
# attach note
summaryarea += note_text
# add clearline for proper styling
# add footer section
footer = self.write_footer(None)
body += (FULLCLEAR, footer)
# send page out for porcessing
# and close the file
self.xhtml_writer(contactpage, output_file, sio, 0)

View File

@ -0,0 +1,195 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010-2017 Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
Classe:
DownloadPage
"""
#------------------------------------------------
# python modules
#------------------------------------------------
import os
import datetime
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.plugins.lib.libhtml import Html
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
from gramps.plugins.webreport.common import (FULLCLEAR, html_escape)
_ = glocale.translation.sgettext
LOG = logging.getLogger(".NarrativeWeb")
getcontext().prec = 8
class DownloadPage(BasePage):
"""
This class is responsible for displaying information about the Download page
"""
def __init__(self, report, title):
"""
@param: report -- The instance of the main report class for this report
@param: title -- Is the title of the web page
"""
BasePage.__init__(self, report, title)
# do NOT include a Download Page
if not self.report.inc_download:
return
# menu options for class
# download and description #1
dlfname1 = self.report.dl_fname1
dldescr1 = self.report.dl_descr1
# download and description #2
dlfname2 = self.report.dl_fname2
dldescr2 = self.report.dl_descr2
# if no filenames at all, return???
if dlfname1 or dlfname2:
output_file, sio = self.report.create_file("download")
downloadpage, head, body = self.write_header(self._('Download'))
# begin download page and table
with Html("div", class_="content", id="Download") as download:
body += download
msg = self._("This page is for the user/ creator "
"of this Family Tree/ Narrative website "
"to share a couple of files with you "
"regarding their family. If there are "
"any files listed "
"below, clicking on them will allow you "
"to download them. The "
"download page and files have the same "
"copyright as the remainder "
"of these web pages.")
download += Html("p", msg, id="description")
# begin download table and table head
with Html("table", class_="infolist download") as table:
download += table
thead = Html("thead")
table += thead
trow = Html("tr")
thead += trow
trow.extend(
Html("th", label, class_="Column" + colclass,
inline=True)
for (label, colclass) in [
(self._("File Name"), "Filename"),
(self._("Description"), "Description"),
(self._("Last Modified"), "Modified")])
# table body
tbody = Html("tbody")
table += tbody
# if dlfname1 is not None, show it???
if dlfname1:
trow = Html("tr", id='Row01')
tbody += trow
fname = os.path.basename(dlfname1)
# TODO dlfname1 is filename, convert disk path to URL
tcell = Html("td", class_="ColumnFilename") + (
Html("a", fname, href=dlfname1,
title=html_escape(dldescr1))
)
trow += tcell
dldescr1 = dldescr1 or "&nbsp;"
trow += Html("td", dldescr1,
class_="ColumnDescription", inline=True)
tcell = Html("td", class_="ColumnModified", inline=True)
trow += tcell
if os.path.exists(dlfname1):
modified = os.stat(dlfname1).st_mtime
last_mod = datetime.datetime.fromtimestamp(modified)
tcell += last_mod
else:
tcell += "&nbsp;"
# if download filename #2, show it???
if dlfname2:
# begin row #2
trow = Html("tr", id='Row02')
tbody += trow
fname = os.path.basename(dlfname2)
tcell = Html("td", class_="ColumnFilename") + (
Html("a", fname, href=dlfname2,
title=html_escape(dldescr2))
)
trow += tcell
dldescr2 = dldescr2 or "&nbsp;"
trow += Html("td", dldescr2,
class_="ColumnDescription", inline=True)
tcell = Html("td", id='Col04',
class_="ColumnModified", inline=True)
trow += tcell
if os.path.exists(dlfname2):
modified = os.stat(dlfname2).st_mtime
last_mod = datetime.datetime.fromtimestamp(modified)
tcell += last_mod
else:
tcell += "&nbsp;"
# clear line for proper styling
# create footer section
footer = self.write_footer(None)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(downloadpage, output_file, sio, 0)

View File

@ -0,0 +1,448 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010-2017 Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
Classe:
EventPage - Event index page and individual Event pages
"""
#------------------------------------------------
# python modules
#------------------------------------------------
from collections import defaultdict
from operator import itemgetter
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gen.lib import (Date, Event)
from gramps.gen.plug.report import Bibliography
from gramps.plugins.lib.libhtml import Html
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
from gramps.plugins.webreport.common import (get_first_letters, _ALPHAEVENT,
_EVENTMAP, alphabet_navigation,
FULLCLEAR, sort_event_types,
primary_difference,
get_index_letter)
_ = glocale.translation.sgettext
LOG = logging.getLogger(".NarrativeWeb")
getcontext().prec = 8
#################################################
#
# creates the Event List Page and EventPages
#
#################################################
class EventPages(BasePage):
"""
This class is responsible for displaying information about the 'Person'
database objects. It displays this information under the 'Events'
tab. It is told by the 'add_instances' call which 'Person's to display,
and remembers the list of persons. A single call to 'display_pages'
displays both the Event List (Index) page and all the Event
pages.
The base class 'BasePage' is initialised once for each page that is
displayed.
"""
def __init__(self, report):
"""
@param: report -- The instance of the main report class for
this report
"""
BasePage.__init__(self, report, title="")
self.event_handle_list = []
self.event_types = []
self.event_dict = defaultdict(set)
def display_pages(self, title):
"""
Generate and output the pages under the Event tab, namely the event
index and the individual event pages.
@param: title -- Is the title of the web page
"""
LOG.debug("obj_dict[Event]")
for item in self.report.obj_dict[Event].items():
LOG.debug(" %s", str(item))
event_handle_list = self.report.obj_dict[Event].keys()
event_types = []
for event_handle in event_handle_list:
event = self.r_db.get_event_from_handle(event_handle)
event_types.append(self._(event.get_type().xml_str()))
with self.r_user.progress(_("Narrated Web Site Report"),
_("Creating event pages"),
len(event_handle_list) + 1
) as step:
self.eventlistpage(self.report, title, event_types,
event_handle_list)
for event_handle in event_handle_list:
step()
self.eventpage(self.report, title, event_handle)
def eventlistpage(self, report, title, event_types, event_handle_list):
"""
Will create the event list page
@param: report -- The instance of the main report class for
this report
@param: title -- Is the title of the web page
@param: event_types -- A list of the type in the events database
@param: event_handle_list -- A list of event handles
"""
BasePage.__init__(self, report, title)
ldatec = 0
prev_letter = " "
output_file, sio = self.report.create_file("events")
eventslistpage, head, body = self.write_header(self._("Events"))
# begin events list division
with Html("div", class_="content", id="EventList") as eventlist:
body += eventlist
msg = self._("This page contains an index of all the events in the "
"database, sorted by their type and date (if one is "
"present). Clicking on an event&#8217;s Gramps ID "
"will open a page for that event.")
eventlist += Html("p", msg, id="description")
# get alphabet navigation...
index_list = get_first_letters(self.r_db, event_types,
_ALPHAEVENT)
alpha_nav = alphabet_navigation(index_list, self.rlocale)
if alpha_nav:
eventlist += alpha_nav
# begin alphabet event table
with Html("table",
class_="infolist primobjlist alphaevent") as table:
eventlist += table
thead = Html("thead")
table += thead
trow = Html("tr")
thead += trow
trow.extend(
Html("th", label, class_=colclass, inline=True)
for (label, colclass) in [(self._("Letter"),
"ColumnRowLabel"),
(self._("Type"), "ColumnType"),
(self._("Date"), "ColumnDate"),
(self._("Gramps ID"),
"ColumnGRAMPSID"),
(self._("Person"), "ColumnPerson")
]
)
tbody = Html("tbody")
table += tbody
# separate events by their type and then thier event handles
for (evt_type,
data_list) in sort_event_types(self.r_db,
event_types,
event_handle_list,
self.rlocale):
first = True
_event_displayed = []
# sort datalist by date of event and by event handle...
data_list = sorted(data_list, key=itemgetter(0, 1))
first_event = True
for (sort_value, event_handle) in data_list:
event = self.r_db.get_event_from_handle(event_handle)
_type = event.get_type()
gid = event.get_gramps_id()
if event.get_change_time() > ldatec:
ldatec = event.get_change_time()
# check to see if we have listed this gramps_id yet?
if gid not in _event_displayed:
# family event
if int(_type) in _EVENTMAP:
handle_list = set(
self.r_db.find_backlink_handles(
event_handle,
include_classes=['Family', 'Person']))
else:
handle_list = set(
self.r_db.find_backlink_handles(
event_handle,
include_classes=['Person']))
if handle_list:
trow = Html("tr")
tbody += trow
# set up hyperlinked letter for
# alphabet_navigation
tcell = Html("td", class_="ColumnLetter",
inline=True)
trow += tcell
if evt_type and not evt_type.isspace():
letter = get_index_letter(
self._(str(evt_type)[0].capitalize()),
index_list, self.rlocale)
else:
letter = "&nbsp;"
if first or primary_difference(letter,
prev_letter,
self.rlocale):
first = False
prev_letter = letter
t_a = 'class = "BeginLetter BeginType"'
trow.attr = t_a
ttle = self._("Event types beginning "
"with letter %s") % letter
tcell += Html("a", letter, name=letter,
id_=letter, title=ttle,
inline=True)
else:
tcell += "&nbsp;"
# display Event type if first in the list
tcell = Html("td", class_="ColumnType",
title=self._(evt_type),
inline=True)
trow += tcell
if first_event:
tcell += self._(evt_type)
if trow.attr == "":
trow.attr = 'class = "BeginType"'
else:
tcell += "&nbsp;"
# event date
tcell = Html("td", class_="ColumnDate",
inline=True)
trow += tcell
date = Date.EMPTY
if event:
date = event.get_date_object()
if date and date is not Date.EMPTY:
tcell += self.rlocale.get_date(date)
else:
tcell += "&nbsp;"
# Gramps ID
trow += Html("td", class_="ColumnGRAMPSID") + (
self.event_grampsid_link(event_handle,
gid, None)
)
# Person(s) column
tcell = Html("td", class_="ColumnPerson")
trow += tcell
# classname can either be a person or a family
first_person = True
# get person(s) for ColumnPerson
sorted_list = sorted(handle_list)
self.complete_people(tcell, first_person,
sorted_list,
uplink=False)
_event_displayed.append(gid)
first_event = False
# add clearline for proper styling
# add footer section
footer = self.write_footer(ldatec)
body += (FULLCLEAR, footer)
# send page ut for processing
# and close the file
self.xhtml_writer(eventslistpage, output_file, sio, ldatec)
def _geteventdate(self, event_handle):
"""
Get the event date
@param: event_handle -- The handle for the event to use
"""
event_date = Date.EMPTY
event = self.r_db.get_event_from_handle(event_handle)
if event:
date = event.get_date_object()
if date:
# returns the date in YYYY-MM-DD format
return Date(date.get_year_calendar("Gregorian"),
date.get_month(), date.get_day())
# return empty date string
return event_date
def event_grampsid_link(self, handle, grampsid, uplink):
"""
Create a hyperlink from event handle, but show grampsid
@param: handle -- The handle for the event
@param: grampsid -- The gramps ID to display
@param: uplink -- If True, then "../../../" is inserted in front of
the result.
"""
url = self.report.build_url_fname_html(handle, "evt", uplink)
# return hyperlink to its caller
return Html("a", grampsid, href=url, title=grampsid, inline=True)
def eventpage(self, report, title, event_handle):
"""
Creates the individual event page
@param: report -- The instance of the main report class for
this report
@param: title -- Is the title of the web page
@param: event_handle -- The event handle for the database
"""
event = report.database.get_event_from_handle(event_handle)
BasePage.__init__(self, report, title, event.get_gramps_id())
if not event:
return None
ldatec = event.get_change_time()
event_media_list = event.get_media_list()
self.uplink = True
subdirs = True
evt_type = self._(event.get_type().xml_str())
self.page_title = "%(eventtype)s" % {'eventtype' : evt_type}
self.bibli = Bibliography()
output_file, sio = self.report.create_file(event_handle, "evt")
eventpage, head, body = self.write_header(self._("Events"))
# start event detail division
with Html("div", class_="content", id="EventDetail") as eventdetail:
body += eventdetail
thumbnail = self.disp_first_img_as_thumbnail(event_media_list,
event)
if thumbnail is not None:
eventdetail += thumbnail
# display page title
eventdetail += Html("h3", self.page_title, inline=True)
# begin eventdetail table
with Html("table", class_="infolist eventlist") as table:
eventdetail += table
tbody = Html("tbody")
table += tbody
evt_gid = event.get_gramps_id()
if not self.noid and evt_gid:
trow = Html("tr") + (
Html("td", self._("Gramps ID"),
class_="ColumnAttribute", inline=True),
Html("td", evt_gid,
class_="ColumnGRAMPSID", inline=True)
)
tbody += trow
# get event data
#
# for more information: see get_event_data()
#
event_data = self.get_event_data(event, event_handle,
subdirs, evt_gid)
for (label, colclass, data) in event_data:
if data:
trow = Html("tr") + (
Html("td", label, class_="ColumnAttribute",
inline=True),
Html('td', data, class_="Column" + colclass)
)
tbody += trow
# Narrative subsection
notelist = event.get_note_list()
notelist = self.display_note_list(notelist)
if notelist is not None:
eventdetail += notelist
# get attribute list
attrlist = event.get_attribute_list()
if attrlist:
attrsection, attrtable = self.display_attribute_header()
self.display_attr_list(attrlist, attrtable)
eventdetail += attrsection
# event source references
srcrefs = self.display_ind_sources(event)
if srcrefs is not None:
eventdetail += srcrefs
# display additional images as gallery
if self.create_media:
addgallery = self.disp_add_img_as_gallery(event_media_list,
event)
if addgallery:
eventdetail += addgallery
# References list
ref_list = self.display_bkref_list(Event, event_handle)
if ref_list is not None:
eventdetail += ref_list
# add clearline for proper styling
# add footer section
footer = self.write_footer(ldatec)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the page
self.xhtml_writer(eventpage, output_file, sio, ldatec)

View File

@ -0,0 +1,399 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010-2017 Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
Classe:
FamilyPage - Family index page and individual Family pages
"""
#------------------------------------------------
# python modules
#------------------------------------------------
from collections import defaultdict
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gen.lib import (EventType, Family)
from gramps.gen.plug.report import Bibliography
from gramps.plugins.lib.libhtml import Html
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
from gramps.plugins.webreport.common import (get_first_letters, _KEYPERSON,
alphabet_navigation, sort_people,
primary_difference, first_letter,
FULLCLEAR, get_index_letter)
_ = glocale.translation.sgettext
LOG = logging.getLogger(".NarrativeWeb")
getcontext().prec = 8
#################################################
#
# creates the Family List Page and Family Pages
#
#################################################
class FamilyPages(BasePage):
"""
This class is responsible for displaying information about the 'Family'
database objects. It displays this information under the 'Families'
tab. It is told by the 'add_instances' call which 'Family's to display,
and remembers the list of Family. A single call to 'display_pages'
displays both the Family List (Index) page and all the Family
pages.
The base class 'BasePage' is initialised once for each page that is
displayed.
"""
def __init__(self, report):
"""
@param: report -- The instance of the main report class for
this report
"""
BasePage.__init__(self, report, title="")
self.family_dict = defaultdict(set)
self.person = None
self.familymappages = None
def display_pages(self, title):
"""
Generate and output the pages under the Family tab, namely the family
index and the individual family pages.
@param: title -- Is the title of the web page
"""
LOG.debug("obj_dict[Family]")
for item in self.report.obj_dict[Family].items():
LOG.debug(" %s", str(item))
with self.r_user.progress(_("Narrated Web Site Report"),
_("Creating family pages..."),
len(self.report.obj_dict[Family]) + 1
) as step:
self.familylistpage(self.report, title,
self.report.obj_dict[Family].keys())
for family_handle in self.report.obj_dict[Family]:
step()
self.familypage(self.report, title, family_handle)
def familylistpage(self, report, title, fam_list):
"""
Create a family index
@param: report -- The instance of the main report class for
this report
@param: title -- Is the title of the web page
@param: fam_list -- The handle for the place to add
"""
BasePage.__init__(self, report, title)
output_file, sio = self.report.create_file("families")
familieslistpage, head, body = self.write_header(self._("Families"))
ldatec = 0
prev_letter = " "
# begin Family Division
with Html("div", class_="content", id="Relationships") as relationlist:
body += relationlist
# Families list page message
msg = self._("This page contains an index of all the "
"families/ relationships in the "
"database, sorted by their family name/ surname. "
"Clicking on a person&#8217;s "
"name will take you to their "
"family/ relationship&#8217;s page.")
relationlist += Html("p", msg, id="description")
# go through all the families, and construct a dictionary of all the
# people and the families thay are involved in. Note that the people
# in the list may be involved in OTHER families, that are not listed
# because they are not in the original family list.
pers_fam_dict = defaultdict(list)
for family_handle in fam_list:
family = self.r_db.get_family_from_handle(family_handle)
if family:
if family.get_change_time() > ldatec:
ldatec = family.get_change_time()
husband_handle = family.get_father_handle()
spouse_handle = family.get_mother_handle()
if husband_handle:
pers_fam_dict[husband_handle].append(family)
if spouse_handle:
pers_fam_dict[spouse_handle].append(family)
# add alphabet navigation
index_list = get_first_letters(self.r_db, pers_fam_dict.keys(),
_KEYPERSON, rlocale=self.rlocale)
alpha_nav = alphabet_navigation(index_list, self.rlocale)
if alpha_nav:
relationlist += alpha_nav
# begin families table and table head
with Html("table", class_="infolist relationships") as table:
relationlist += table
thead = Html("thead")
table += thead
trow = Html("tr")
thead += trow
# set up page columns
trow.extend(
Html("th", trans, class_=colclass, inline=True)
for trans, colclass in [(self._("Letter"),
"ColumnRowLabel"),
(self._("Person"), "ColumnPartner"),
(self._("Family"), "ColumnPartner"),
(self._("Marriage"), "ColumnDate"),
(self._("Divorce"), "ColumnDate")]
)
tbody = Html("tbody")
table += tbody
# begin displaying index list
ppl_handle_list = sort_people(self.r_db, pers_fam_dict.keys(),
self.rlocale)
first = True
for (surname, handle_list) in ppl_handle_list:
if surname and not surname.isspace():
letter = get_index_letter(first_letter(surname),
index_list,
self.rlocale)
else:
letter = '&nbsp;'
# get person from sorted database list
for person_handle in sorted(
handle_list, key=self.sort_on_name_and_grampsid):
person = self.r_db.get_person_from_handle(person_handle)
if person:
family_list = person.get_family_handle_list()
first_family = True
for family_handle in family_list:
get_family = self.r_db.get_family_from_handle
family = get_family(family_handle)
trow = Html("tr")
tbody += trow
tcell = Html("td", class_="ColumnRowLabel")
trow += tcell
if first or primary_difference(letter,
prev_letter,
self.rlocale):
first = False
prev_letter = letter
trow.attr = 'class="BeginLetter"'
ttle = self._("Families beginning with "
"letter ")
tcell += Html("a", letter, name=letter,
title=ttle + letter,
inline=True)
else:
tcell += '&nbsp;'
tcell = Html("td", class_="ColumnPartner")
trow += tcell
if first_family:
trow.attr = 'class ="BeginFamily"'
tcell += self.new_person_link(
person_handle, uplink=self.uplink)
first_family = False
else:
tcell += '&nbsp;'
tcell = Html("td", class_="ColumnPartner")
trow += tcell
tcell += self.family_link(
family.get_handle(),
self.report.get_family_name(family),
family.get_gramps_id(), self.uplink)
# family events; such as marriage and divorce
# events
fam_evt_ref_list = family.get_event_ref_list()
tcell1 = Html("td", class_="ColumnDate",
inline=True)
tcell2 = Html("td", class_="ColumnDate",
inline=True)
trow += (tcell1, tcell2)
if fam_evt_ref_list:
fam_evt_srt_ref_list = sorted(
fam_evt_ref_list,
key=self.sort_on_grampsid)
for evt_ref in fam_evt_srt_ref_list:
evt = self.r_db.get_event_from_handle(
evt_ref.ref)
if evt:
evt_type = evt.get_type()
if evt_type in [EventType.MARRIAGE,
EventType.DIVORCE]:
cell = self.rlocale.get_date(
evt.get_date_object())
if (evt_type ==
EventType.MARRIAGE):
tcell1 += cell
else:
tcell1 += '&nbsp;'
if (evt_type ==
EventType.DIVORCE):
tcell2 += cell
else:
tcell2 += '&nbsp;'
else:
tcell1 += '&nbsp;'
tcell2 += '&nbsp;'
first_family = False
# add clearline for proper styling
# add footer section
footer = self.write_footer(ldatec)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(familieslistpage, output_file, sio, ldatec)
def familypage(self, report, title, family_handle):
"""
Create a family page
@param: report -- The instance of the main report class for
this report
@param: title -- Is the title of the web page
@param: family_handle -- The handle for the family to add
"""
family = report.database.get_family_from_handle(family_handle)
if not family:
return
BasePage.__init__(self, report, title, family.get_gramps_id())
ldatec = family.get_change_time()
self.bibli = Bibliography()
self.uplink = True
family_name = self.report.get_family_name(family)
self.page_title = family_name
self.familymappages = report.options["familymappages"]
output_file, sio = self.report.create_file(family.get_handle(), "fam")
familydetailpage, head, body = self.write_header(family_name)
# begin FamilyDetaill division
with Html("div", class_="content",
id="RelationshipDetail") as relationshipdetail:
body += relationshipdetail
# family media list for initial thumbnail
if self.create_media:
media_list = family.get_media_list()
# If Event pages are not being created, then we need to display
# the family event media here
if not self.inc_events:
for evt_ref in family.get_event_ref_list():
event = self.r_db.get_event_from_handle(evt_ref.ref)
media_list += event.get_media_list()
thumbnail = self.disp_first_img_as_thumbnail(media_list,
family)
if thumbnail:
relationshipdetail += thumbnail
self.person = None # no longer used
relationshipdetail += Html(
"h2", self.page_title, inline=True) + (
Html('sup') + (Html('small') +
self.get_citation_links(
family.get_citation_list())))
# display relationships
families = self.display_family_relationships(family, None)
if families is not None:
relationshipdetail += families
# display additional images as gallery
if self.create_media and media_list:
addgallery = self.disp_add_img_as_gallery(media_list, family)
if addgallery:
relationshipdetail += addgallery
# Narrative subsection
notelist = family.get_note_list()
if notelist:
relationshipdetail += self.display_note_list(notelist)
# display family LDS ordinance...
family_lds_ordinance_list = family.get_lds_ord_list()
if family_lds_ordinance_list:
relationshipdetail += self.display_lds_ordinance(family)
# get attribute list
attrlist = family.get_attribute_list()
if attrlist:
attrsection, attrtable = self.display_attribute_header()
self.display_attr_list(attrlist, attrtable)
relationshipdetail += attrsection
# source references
srcrefs = self.display_ind_sources(family)
if srcrefs:
relationshipdetail += srcrefs
# add clearline for proper styling
# add footer section
footer = self.write_footer(ldatec)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(familydetailpage, output_file, sio, ldatec)

View File

@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010-2017 Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
Classe:
HomePage
"""
#------------------------------------------------
# python modules
#------------------------------------------------
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.plugins.lib.libhtml import Html
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
from gramps.plugins.webreport.common import FULLCLEAR
_ = glocale.translation.sgettext
LOG = logging.getLogger(".NarrativeWeb")
getcontext().prec = 8
class HomePage(BasePage):
"""
This class is responsible for displaying information about the Home page.
"""
def __init__(self, report, title):
"""
@param: report -- The instance of the main report class for
this report
@param: title -- Is the title of the web page
"""
BasePage.__init__(self, report, title)
ldatec = 0
output_file, sio = self.report.create_file("index")
homepage, head, body = self.write_header(self._('Home'))
# begin home division
with Html("div", class_="content", id="Home") as section:
body += section
homeimg = self.add_image('homeimg')
if homeimg is not None:
section += homeimg
note_id = report.options['homenote']
if note_id:
note = self.r_db.get_note_from_gramps_id(note_id)
note_text = self.get_note_format(note, False)
# attach note
section += note_text
# last modification of this note
ldatec = note.get_change_time()
# create clear line for proper styling
# create footer section
footer = self.write_footer(ldatec)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(homepage, output_file, sio, ldatec)

View File

@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010-2017 Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
Classe:
IntroductionPage
"""
#------------------------------------------------
# python modules
#------------------------------------------------
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.plugins.lib.libhtml import Html
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
from gramps.plugins.webreport.common import FULLCLEAR
_ = glocale.translation.sgettext
LOG = logging.getLogger(".NarrativeWeb")
getcontext().prec = 8
class IntroductionPage(BasePage):
"""
This class is responsible for displaying information
about the introduction page.
"""
def __init__(self, report, title):
"""
@param: report -- The instance of the main report class for
this report
@param: title -- Is the title of the web page
"""
BasePage.__init__(self, report, title)
ldatec = 0
output_file, sio = self.report.create_file(report.intro_fname)
intropage, head, body = self.write_header(self._('Introduction'))
# begin Introduction division
with Html("div", class_="content", id="Introduction") as section:
body += section
introimg = self.add_image('introimg')
if introimg is not None:
section += introimg
note_id = report.options['intronote']
if note_id:
note = self.r_db.get_note_from_gramps_id(note_id)
note_text = self.get_note_format(note, False)
# attach note
section += note_text
# last modification of this note
ldatec = note.get_change_time()
# add clearline for proper styling
# create footer section
footer = self.write_footer(ldatec)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(intropage, output_file, sio, ldatec)

View File

@ -0,0 +1,641 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010-2017 Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
Classe:
MediaPage - Media index page and individual Media pages
"""
#------------------------------------------------
# python modules
#------------------------------------------------
import gc
import os
import shutil
import tempfile
from collections import defaultdict
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gen.lib import (Date, Media)
from gramps.gen.plug.report import Bibliography
from gramps.gen.utils.file import media_path_full
from gramps.gen.utils.thumbnails import run_thumbnailer
from gramps.gen.utils.image import image_size # , resize_to_jpeg_buffer
from gramps.plugins.lib.libhtml import Html
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
from gramps.plugins.webreport.common import (FULLCLEAR, _WRONGMEDIAPATH,
html_escape)
_ = glocale.translation.sgettext
LOG = logging.getLogger(".NarrativeWeb")
getcontext().prec = 8
#################################################
#
# creates the Media List Page and Media Pages
#
#################################################
class MediaPages(BasePage):
"""
This class is responsible for displaying information about the 'Media'
database objects. It displays this information under the 'Individuals'
tab. It is told by the 'add_instances' call which 'Media's to display,
and remembers the list of persons. A single call to 'display_pages'
displays both the Individual List (Index) page and all the Individual
pages.
The base class 'BasePage' is initialised once for each page that is
displayed.
"""
def __init__(self, report):
"""
@param: report -- The instance of the main report class for this report
"""
BasePage.__init__(self, report, title="")
self.media_dict = defaultdict(set)
def display_pages(self, title):
"""
Generate and output the pages under the Media tab, namely the media
index and the individual media pages.
@param: title -- Is the title of the web page
"""
LOG.debug("obj_dict[Media]")
for item in self.report.obj_dict[Media].items():
LOG.debug(" %s", str(item))
with self.r_user.progress(_("Narrated Web Site Report"),
_("Creating media pages"),
len(self.report.obj_dict[Media]) + 1
) as step:
# bug 8950 : it seems it's better to sort on desc + gid.
def sort_by_desc_and_gid(obj):
"""
Sort by media description and gramps ID
"""
return (obj.desc.lower(), obj.gramps_id)
sorted_media_handles = sorted(
self.report.obj_dict[Media].keys(),
key=lambda x: sort_by_desc_and_gid(
self.r_db.get_media_from_handle(x)))
self.medialistpage(self.report, title, sorted_media_handles)
prev = None
total = len(sorted_media_handles)
index = 1
for handle in sorted_media_handles:
gc.collect() # Reduce memory usage when there are many images.
next_ = None if index == total else sorted_media_handles[index]
step()
self.mediapage(self.report, title,
handle, (prev, next_, index, total))
prev = handle
index += 1
def medialistpage(self, report, title, sorted_media_handles):
"""
Generate and output the Media index page.
@param: report -- The instance of the main report class
for this report
@param: title -- Is the title of the web page
@param: sorted_media_handles -- A list of the handles of the media to be
displayed sorted by the media title
"""
BasePage.__init__(self, report, title)
output_file, sio = self.report.create_file("media")
medialistpage, head, body = self.write_header(self._('Media'))
ldatec = 0
# begin gallery division
with Html("div", class_="content", id="Gallery") as medialist:
body += medialist
msg = self._("This page contains an index of all the media objects "
"in the database, sorted by their title. Clicking on "
"the title will take you to that "
"media object&#8217;s page. "
"If you see media size dimensions "
"above an image, click on the "
"image to see the full sized version. ")
medialist += Html("p", msg, id="description")
# begin gallery table and table head
with Html("table",
class_="infolist primobjlist gallerylist") as table:
medialist += table
# begin table head
thead = Html("thead")
table += thead
trow = Html("tr")
thead += trow
trow.extend(
Html("th", trans, class_=colclass, inline=True)
for trans, colclass in [("&nbsp;", "ColumnRowLabel"),
(self._("Media | Name"),
"ColumnName"),
(self._("Date"), "ColumnDate"),
(self._("Mime Type"), "ColumnMime")]
)
# begin table body
tbody = Html("tbody")
table += tbody
index = 1
for media_handle in sorted_media_handles:
media = self.r_db.get_media_from_handle(media_handle)
if media:
if media.get_change_time() > ldatec:
ldatec = media.get_change_time()
title = media.get_description() or "[untitled]"
trow = Html("tr")
tbody += trow
media_data_row = [
[index, "ColumnRowLabel"],
[self.media_ref_link(media_handle,
title), "ColumnName"],
[self.rlocale.get_date(media.get_date_object()),
"ColumnDate"],
[media.get_mime_type(), "ColumnMime"]]
trow.extend(
Html("td", data, class_=colclass)
for data, colclass in media_data_row
)
index += 1
def sort_by_desc_and_gid(obj):
"""
Sort by media description and gramps ID
"""
return (obj.desc, obj.gramps_id)
unused_media_handles = []
if self.create_unused_media:
# add unused media
media_list = self.r_db.get_media_handles()
for media_ref in media_list:
if media_ref not in self.report.obj_dict[Media]:
unused_media_handles.append(media_ref)
unused_media_handles = sorted(
unused_media_handles,
key=lambda x: sort_by_desc_and_gid(
self.r_db.get_media_from_handle(x)))
idx = 1
prev = None
total = len(unused_media_handles)
if total > 0:
trow += Html("tr")
trow.extend(
Html("td", Html("h4", " "), inline=True) +
Html("td",
Html("h4",
self._("Below unused media objects"),
inline=True),
class_="") +
Html("td", Html("h4", " "), inline=True) +
Html("td", Html("h4", " "), inline=True)
)
for media_handle in unused_media_handles:
media = self.r_db.get_media_from_handle(media_handle)
gc.collect() # Reduce memory usage when many images.
next_ = None if idx == total else unused_media_handles[idx]
trow += Html("tr")
media_data_row = [
[index, "ColumnRowLabel"],
[self.media_ref_link(media_handle,
media.get_description()),
"ColumnName"],
[self.rlocale.get_date(media.get_date_object()),
"ColumnDate"],
[media.get_mime_type(), "ColumnMime"]]
trow.extend(
Html("td", data, class_=colclass)
for data, colclass in media_data_row
)
self.mediapage(self.report, title,
media_handle, (prev, next_, index, total))
prev = media_handle
index += 1
idx += 1
# add footer section
# add clearline for proper styling
footer = self.write_footer(ldatec)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(medialistpage, output_file, sio, ldatec)
def media_ref_link(self, handle, name, uplink=False):
"""
Create a reference link to a media
@param: handle -- The media handle
@param: name -- The name to use for the link
@param: uplink -- If True, then "../../../" is inserted in front of the
result.
"""
# get media url
url = self.report.build_url_fname_html(handle, "img", uplink)
# get name
name = html_escape(name)
# begin hyper link
hyper = Html("a", name, href=url, title=name)
# return hyperlink to its callers
return hyper
def mediapage(self, report, title, media_handle, info):
"""
Generate and output an individual Media page.
@param: report -- The instance of the main report class
for this report
@param: title -- Is the title of the web page
@param: media_handle -- The media handle to use
@param: info -- A tuple containing the media handle for the
next and previous media, the current page
number, and the total number of media pages
"""
media = report.database.get_media_from_handle(media_handle)
BasePage.__init__(self, report, title, media.gramps_id)
(prev, next_, page_number, total_pages) = info
ldatec = media.get_change_time()
# get media rectangles
_region_items = self.media_ref_rect_regions(media_handle)
output_file, sio = self.report.create_file(media_handle, "img")
self.uplink = True
self.bibli = Bibliography()
# get media type to be used primarily with "img" tags
mime_type = media.get_mime_type()
#mtype = get_description(mime_type)
if mime_type:
#note_only = False
newpath = self.copy_source_file(media_handle, media)
target_exists = newpath is not None
else:
#note_only = True
target_exists = False
self.copy_thumbnail(media_handle, media)
self.page_title = media.get_description()
esc_page_title = html_escape(self.page_title)
(mediapage, head,
body) = self.write_header("%s - %s" % (self._("Media"),
self.page_title))
# if there are media rectangle regions, attach behaviour style sheet
if _region_items:
fname = "/".join(["css", "behaviour.css"])
url = self.report.build_url_fname(fname, None, self.uplink)
head += Html("link", href=url, type="text/css",
media="screen", rel="stylesheet")
# begin MediaDetail division
with Html("div", class_="content", id="GalleryDetail") as mediadetail:
body += mediadetail
# media navigation
with Html("div", id="GalleryNav", role="navigation") as medianav:
mediadetail += medianav
if prev:
medianav += self.media_nav_link(prev,
self._("Previous"), True)
data = self._('%(strong1_strt)s%(page_number)d%(strong_end)s '
'of %(strong2_strt)s%(total_pages)d%(strong_end)s'
) % {'strong1_strt' :
'<strong id="GalleryCurrent">',
'strong2_strt' : '<strong id="GalleryTotal">',
'strong_end' : '</strong>',
'page_number' : page_number,
'total_pages' : total_pages}
medianav += Html("span", data, id="GalleryPages")
if next_:
medianav += self.media_nav_link(next_, self._("Next"), True)
# missing media error message
errormsg = self._("The file has been moved or deleted.")
# begin summaryarea division
with Html("div", id="summaryarea") as summaryarea:
mediadetail += summaryarea
if mime_type:
if mime_type.startswith("image"):
if not target_exists:
with Html("div", id="MediaDisplay") as mediadisplay:
summaryarea += mediadisplay
mediadisplay += Html("span", errormsg,
class_="MissingImage")
else:
# Check how big the image is relative to the
# requested 'initial' image size.
# If it's significantly bigger, scale it down to
# improve the site's responsiveness. We don't want
# the user to have to await a large download
# unnecessarily. Either way, set the display image
# size as requested.
orig_image_path = media_path_full(self.r_db,
media.get_path())
#mtime = os.stat(orig_image_path).st_mtime
(width, height) = image_size(orig_image_path)
max_width = self.report.options[
'maxinitialimagewidth']
max_height = self.report.options[
'maxinitialimageheight']
if width != 0 and height != 0:
scale_w = (float(max_width)/width) or 1
# the 'or 1' is so that a max of
# zero is ignored
scale_h = (float(max_height)/height) or 1
else:
scale_w = 1.0
scale_h = 1.0
scale = min(scale_w, scale_h, 1.0)
new_width = int(width*scale)
new_height = int(height*scale)
# TODO. Convert disk path to URL.
url = self.report.build_url_fname(orig_image_path,
None, self.uplink)
with Html("div", id="GalleryDisplay",
style='width: %dpx; height: %dpx' % (
new_width,
new_height)) as mediadisplay:
summaryarea += mediadisplay
# Feature #2634; display the mouse-selectable
# regions. See the large block at the top of
# this function where the various regions are
# stored in _region_items
if _region_items:
ordered = Html("ol", class_="RegionBox")
mediadisplay += ordered
while len(_region_items) > 0:
(name, coord_x, coord_y,
width, height, linkurl
) = _region_items.pop()
ordered += Html(
"li",
style="left:%d%%; "
"top:%d%%; "
"width:%d%%; "
"height:%d%%;" % (
coord_x, coord_y,
width, height)) + (
Html("a", name,
href=linkurl)
)
# display the image
if orig_image_path != newpath:
url = self.report.build_url_fname(
newpath, None, self.uplink)
mediadisplay += Html("a", href=url) + (
Html("img", width=new_width,
height=new_height, src=url,
alt=esc_page_title)
)
else:
dirname = tempfile.mkdtemp()
thmb_path = os.path.join(dirname, "document.png")
if run_thumbnailer(mime_type,
media_path_full(self.r_db,
media.get_path()),
thmb_path, 320):
try:
path = self.report.build_path(
"preview", media.get_handle())
npath = os.path.join(path, media.get_handle())
npath += ".png"
self.report.copy_file(thmb_path, npath)
path = npath
os.unlink(thmb_path)
except EnvironmentError:
path = os.path.join("images", "document.png")
else:
path = os.path.join("images", "document.png")
os.rmdir(dirname)
with Html("div", id="GalleryDisplay") as mediadisplay:
summaryarea += mediadisplay
img_url = self.report.build_url_fname(path,
None,
self.uplink)
if target_exists:
# TODO. Convert disk path to URL
url = self.report.build_url_fname(newpath,
None,
self.uplink)
hyper = Html("a", href=url,
title=esc_page_title) + (
Html("img", src=img_url,
alt=esc_page_title)
)
mediadisplay += hyper
else:
mediadisplay += Html("span", errormsg,
class_="MissingImage")
else:
with Html("div", id="GalleryDisplay") as mediadisplay:
summaryarea += mediadisplay
url = self.report.build_url_image("document.png",
"images", self.uplink)
mediadisplay += Html("img", src=url,
alt=esc_page_title,
title=esc_page_title)
# media title
title = Html("h3", html_escape(self.page_title.strip()),
inline=True)
summaryarea += title
# begin media table
with Html("table", class_="infolist gallery") as table:
summaryarea += table
# Gramps ID
media_gid = media.gramps_id
if not self.noid and media_gid:
trow = Html("tr") + (
Html("td", self._("Gramps ID"),
class_="ColumnAttribute",
inline=True),
Html("td", media_gid, class_="ColumnValue",
inline=True)
)
table += trow
# mime type
if mime_type:
trow = Html("tr") + (
Html("td", self._("File Type"),
class_="ColumnAttribute",
inline=True),
Html("td", mime_type, class_="ColumnValue",
inline=True)
)
table += trow
# media date
date = media.get_date_object()
if date and date is not Date.EMPTY:
trow = Html("tr") + (
Html("td", self._("Date"), class_="ColumnAttribute",
inline=True),
Html("td", self.rlocale.get_date(date),
class_="ColumnValue",
inline=True)
)
table += trow
# get media notes
notelist = self.display_note_list(media.get_note_list())
if notelist is not None:
mediadetail += notelist
# get attribute list
attrlist = media.get_attribute_list()
if attrlist:
attrsection, attrtable = self.display_attribute_header()
self.display_attr_list(attrlist, attrtable)
mediadetail += attrsection
# get media sources
srclist = self.display_media_sources(media)
if srclist is not None:
mediadetail += srclist
# get media references
reflist = self.display_bkref_list(Media, media_handle)
if reflist is not None:
mediadetail += reflist
# add clearline for proper styling
# add footer section
footer = self.write_footer(ldatec)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(mediapage, output_file, sio, ldatec)
def media_nav_link(self, handle, name, uplink=False):
"""
Creates the Media Page Navigation hyperlinks for Next and Prev
"""
url = self.report.build_url_fname_html(handle, "img", uplink)
name = html_escape(name)
return Html("a", name, name=name, id=name, href=url,
title=name, inline=True)
def display_media_sources(self, photo):
"""
Display media sources
@param: photo -- The source object (image, pdf, ...)
"""
list(map(
lambda i: self.bibli.add_reference(
self.r_db.get_citation_from_handle(i)),
photo.get_citation_list()))
sourcerefs = self.display_source_refs(self.bibli)
# return source references to its caller
return sourcerefs
def copy_source_file(self, handle, photo):
"""
Copy source file in the web tree.
@param: handle -- Handle of the source
@param: photo -- The source object (image, pdf, ...)
"""
ext = os.path.splitext(photo.get_path())[1]
to_dir = self.report.build_path('images', handle)
newpath = os.path.join(to_dir, handle) + ext
fullpath = media_path_full(self.r_db, photo.get_path())
if not os.path.isfile(fullpath):
_WRONGMEDIAPATH.append([photo.get_gramps_id(), fullpath])
return None
try:
mtime = os.stat(fullpath).st_mtime
if self.report.archive:
self.report.archive.add(fullpath, str(newpath))
else:
to_dir = os.path.join(self.html_dir, to_dir)
if not os.path.isdir(to_dir):
os.makedirs(to_dir)
new_file = os.path.join(self.html_dir, newpath)
shutil.copyfile(fullpath, new_file)
os.utime(new_file, (mtime, mtime))
return newpath
except (IOError, OSError) as msg:
error = _("Missing media object:"
) + "%s (%s)" % (photo.get_description(),
photo.get_gramps_id())
self.r_user.warn(error, str(msg))
return None

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,451 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010-2017 Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
Classe:
PlacePage - Place index page and individual Place pages
"""
#------------------------------------------------
# python modules
#------------------------------------------------
from collections import defaultdict
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gen.lib import (PlaceType, Place)
from gramps.gen.plug.report import Bibliography
from gramps.plugins.lib.libhtml import Html
from gramps.gen.utils.place import conv_lat_lon
from gramps.gen.utils.location import get_main_location
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
from gramps.plugins.webreport.common import (get_first_letters, first_letter,
alphabet_navigation, GOOGLE_MAPS,
primary_difference, _KEYPLACE,
get_index_letter, FULLCLEAR,
MARKER_PATH, OSM_MARKERS, MARKERS,
html_escape)
_ = glocale.translation.sgettext
LOG = logging.getLogger(".NarrativeWeb")
getcontext().prec = 8
######################################################
# #
# Place Pages #
# #
######################################################
class PlacePages(BasePage):
"""
This class is responsible for displaying information about the 'Person'
database objects. It displays this information under the 'Events'
tab. It is told by the 'add_instances' call which 'Person's to display,
and remembers the list of persons. A single call to 'display_pages'
displays both the Event List (Index) page and all the Event
pages.
The base class 'BasePage' is initialised once for each page that is
displayed.
"""
def __init__(self, report):
"""
@param: report -- The instance of the main report class for
this report
"""
BasePage.__init__(self, report, title="")
self.place_dict = defaultdict(set)
self.placemappages = None
self.mapservice = None
self.person = None
self.familymappages = None
self.googlemapkey = None
def display_pages(self, title):
"""
Generate and output the pages under the Place tab, namely the place
index and the individual place pages.
@param: title -- Is the title of the web page
"""
LOG.debug("obj_dict[Place]")
for item in self.report.obj_dict[Place].items():
LOG.debug(" %s", str(item))
with self.r_user.progress(_("Narrated Web Site Report"),
_("Creating place pages"),
len(self.report.obj_dict[Place]) + 1
) as step:
self.placelistpage(self.report, title,
self.report.obj_dict[Place].keys())
for place_handle in self.report.obj_dict[Place]:
step()
self.placepage(self.report, title, place_handle)
def placelistpage(self, report, title, place_handles):
"""
Create a place index
@param: report -- The instance of the main report class for
this report
@param: title -- Is the title of the web page
@param: place_handles -- The handle for the place to add
"""
BasePage.__init__(self, report, title)
output_file, sio = self.report.create_file("places")
placelistpage, head, body = self.write_header(self._("Places"))
ldatec = 0
prev_letter = " "
# begin places division
with Html("div", class_="content", id="Places") as placelist:
body += placelist
# place list page message
msg = self._("This page contains an index of all the places in the "
"database, sorted by their title. "
"Clicking on a place&#8217;s "
"title will take you to that place&#8217;s page.")
placelist += Html("p", msg, id="description")
# begin alphabet navigation
index_list = get_first_letters(self.r_db, place_handles,
_KEYPLACE, rlocale=self.rlocale)
alpha_nav = alphabet_navigation(index_list, self.rlocale)
if alpha_nav is not None:
placelist += alpha_nav
# begin places table and table head
with Html("table",
class_="infolist primobjlist placelist") as table:
placelist += table
# begin table head
thead = Html("thead")
table += thead
trow = Html("tr")
thead += trow
trow.extend(
Html("th", label, class_=colclass, inline=True)
for (label, colclass) in [
[self._("Letter"), "ColumnLetter"],
[self._("Place Name | Name"), "ColumnName"],
[self._("State/ Province"), "ColumnState"],
[self._("Country"), "ColumnCountry"],
[self._("Latitude"), "ColumnLatitude"],
[self._("Longitude"), "ColumnLongitude"]
]
)
# bug 9495 : incomplete display of place hierarchy labels
def sort_by_place_name(obj):
""" sort by lower case place name. """
name = self.report.obj_dict[Place][obj][1]
return name.lower()
handle_list = sorted(place_handles,
key=lambda x: sort_by_place_name(x))
first = True
# begin table body
tbody = Html("tbody")
table += tbody
for place_handle in handle_list:
place = self.r_db.get_place_from_handle(place_handle)
if place:
if place.get_change_time() > ldatec:
ldatec = place.get_change_time()
plc_title = self.report.obj_dict[Place][place_handle][1]
main_location = get_main_location(self.r_db, place)
if plc_title and plc_title != " ":
letter = get_index_letter(first_letter(plc_title),
index_list,
self.rlocale)
else:
letter = '&nbsp;'
trow = Html("tr")
tbody += trow
tcell = Html("td", class_="ColumnLetter", inline=True)
trow += tcell
if first or primary_difference(letter, prev_letter,
self.rlocale):
first = False
prev_letter = letter
trow.attr = 'class = "BeginLetter"'
ttle = self._("Places beginning "
"with letter %s") % letter
tcell += Html("a", letter, name=letter, title=ttle)
else:
tcell += "&nbsp;"
trow += Html("td",
self.place_link(
place.get_handle(),
plc_title, place.get_gramps_id()),
class_="ColumnName")
trow.extend(
Html("td", data or "&nbsp;", class_=colclass,
inline=True)
for (colclass, data) in [
["ColumnState",
main_location.get(PlaceType.STATE, '')],
["ColumnCountry",
main_location.get(PlaceType.COUNTRY, '')]
]
)
tcell1 = Html("td", class_="ColumnLatitude",
inline=True)
tcell2 = Html("td", class_="ColumnLongitude",
inline=True)
trow += (tcell1, tcell2)
if place.lat and place.long:
latitude, longitude = conv_lat_lon(place.lat,
place.long,
"DEG")
tcell1 += latitude
tcell2 += longitude
else:
tcell1 += '&nbsp;'
tcell2 += '&nbsp;'
# add clearline for proper styling
# add footer section
footer = self.write_footer(ldatec)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(placelistpage, output_file, sio, ldatec)
def placepage(self, report, title, place_handle):
"""
Create a place page
@param: report -- The instance of the main report class for
this report
@param: title -- Is the title of the web page
@param: place_handle -- The handle for the place to add
"""
place = report.database.get_place_from_handle(place_handle)
if not place:
return None
BasePage.__init__(self, report, title, place.get_gramps_id())
self.bibli = Bibliography()
place_name = self.report.obj_dict[Place][place_handle][1]
ldatec = place.get_change_time()
output_file, sio = self.report.create_file(place_handle, "plc")
self.uplink = True
self.page_title = place_name
placepage, head, body = self.write_header(_("Places"))
self.placemappages = self.report.options['placemappages']
self.mapservice = self.report.options['mapservice']
self.googlemapkey = self.report.options['googlemapkey']
# begin PlaceDetail Division
with Html("div", class_="content", id="PlaceDetail") as placedetail:
body += placedetail
if self.create_media:
media_list = place.get_media_list()
thumbnail = self.disp_first_img_as_thumbnail(media_list,
place)
if thumbnail is not None:
placedetail += thumbnail
# add section title
placedetail += Html("h3",
html_escape(place_name),
inline=True)
# begin summaryarea division and places table
with Html("div", id='summaryarea') as summaryarea:
placedetail += summaryarea
with Html("table", class_="infolist place") as table:
summaryarea += table
# list the place fields
self.dump_place(place, table)
# place gallery
if self.create_media:
placegallery = self.disp_add_img_as_gallery(media_list, place)
if placegallery is not None:
placedetail += placegallery
# place notes
notelist = self.display_note_list(place.get_note_list())
if notelist is not None:
placedetail += notelist
# place urls
urllinks = self.display_url_list(place.get_url_list())
if urllinks is not None:
placedetail += urllinks
# add place map here
# Link to Gramps marker
fname = "/".join(['images', 'marker.png'])
marker_path = self.report.build_url_image("marker.png",
"images", self.uplink)
if self.placemappages:
if place and (place.lat and place.long):
latitude, longitude = conv_lat_lon(place.get_latitude(),
place.get_longitude(),
"D.D8")
placetitle = place_name
# add narrative-maps CSS...
fname = "/".join(["css", "narrative-maps.css"])
url = self.report.build_url_fname(fname, None, self.uplink)
head += Html("link", href=url, type="text/css",
media="screen", rel="stylesheet")
# add MapService specific javascript code
src_js = GOOGLE_MAPS + "api/js?sensor=false"
if self.mapservice == "Google":
if self.googlemapkey:
src_js += "&key=" + self.googlemapkey
head += Html("script", type="text/javascript",
src=src_js, inline=True)
else:
url = self.secure_mode
url += ("maxcdn.bootstrapcdn.com/bootstrap/3.3.7/"
"css/bootstrap.min.css")
head += Html("link", href=url, type="text/javascript",
rel="stylesheet")
src_js = self.secure_mode
src_js += ("ajax.googleapis.com/ajax/libs/jquery/1.9.1/"
"jquery.min.js")
head += Html("script", type="text/javascript",
src=src_js, inline=True)
src_js = self.secure_mode
src_js += "openlayers.org/en/v3.17.1/build/ol.js"
head += Html("script", type="text/javascript",
src=src_js, inline=True)
url = self.secure_mode
url += "openlayers.org/en/v3.17.1/css/ol.css"
head += Html("link", href=url, type="text/javascript",
rel="stylesheet")
src_js = self.secure_mode
src_js += ("maxcdn.bootstrapcdn.com/bootstrap/3.3.7/"
"js/bootstrap.min.js")
head += Html("script", type="text/javascript",
src=src_js, inline=True)
# section title
placedetail += Html("h4", self._("Place Map"), inline=True)
# begin map_canvas division
with Html("div", id="map_canvas", inline=True) as canvas:
placedetail += canvas
# Begin inline javascript code because jsc is a
# docstring, it does NOT have to be properly indented
if self.mapservice == "Google":
with Html("script", type="text/javascript",
indent=False) as jsc:
head += jsc
# Google adds Latitude/ Longitude to its maps...
plce = placetitle.replace("'", "\\'")
jsc += MARKER_PATH % marker_path
jsc += MARKERS % ([[plce,
latitude,
longitude,
1]],
latitude, longitude,
10)
else:
# OpenStreetMap (OSM) adds Longitude/ Latitude
# to its maps, and needs a country code in
# lowercase letters...
with Html("script", type="text/javascript") as jsc:
canvas += jsc
#param1 = xml_lang()[3:5].lower()
jsc += MARKER_PATH % marker_path
jsc += OSM_MARKERS % ([[float(longitude),
float(latitude),
placetitle]],
longitude, latitude, 10)
# add javascript function call to body element
body.attr += ' onload = "initialize();" '
# add div for popups.
with Html("div", id="popup", inline=True) as popup:
placedetail += popup
# source references
srcrefs = self.display_ind_sources(place)
if srcrefs is not None:
placedetail += srcrefs
# References list
ref_list = self.display_bkref_list(Place, place_handle)
if ref_list is not None:
placedetail += ref_list
# add clearline for proper styling
# add footer section
footer = self.write_footer(ldatec)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(placepage, output_file, sio, ldatec)

View File

@ -0,0 +1,287 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010-2017 Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
Classe:
RepositoryPage - Repository index page and individual Repository pages
"""
#------------------------------------------------
# python modules
#------------------------------------------------
from collections import defaultdict
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gen.lib import Repository
from gramps.plugins.lib.libhtml import Html
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
from gramps.plugins.webreport.common import (FULLCLEAR, html_escape)
_ = glocale.translation.sgettext
LOG = logging.getLogger(".NarrativeWeb")
getcontext().prec = 8
#################################################
#
# creates the Repository List Page and Repository Pages
#
#################################################
class RepositoryPages(BasePage):
"""
This class is responsible for displaying information about the 'Repository'
database objects. It displays this information under the 'Individuals'
tab. It is told by the 'add_instances' call which 'Repository's to display,
and remembers the list of persons. A single call to 'display_pages'
displays both the Individual List (Index) page and all the Individual
pages.
The base class 'BasePage' is initialised once for each page that is
displayed.
"""
def __init__(self, report):
"""
@param: report -- The instance of the main report class for this report
"""
BasePage.__init__(self, report, title="")
self.repos_dict = defaultdict(set)
def display_pages(self, title):
"""
Generate and output the pages under the Repository tab, namely the
repository index and the individual repository pages.
@param: title -- Is the title of the web page
"""
LOG.debug("obj_dict[Person]")
for item in self.report.obj_dict[Repository].items():
LOG.debug(" %s", str(item))
# set progress bar pass for Repositories
with self.r_user.progress(_("Narrated Web Site Report"),
_('Creating repository pages'),
len(self.report.obj_dict[Repository]) + 1
) as step:
# Sort the repositories
repos_dict = {}
for repo_handle in self.report.obj_dict[Repository]:
repository = self.r_db.get_repository_from_handle(repo_handle)
key = repository.get_name() + str(repository.get_gramps_id())
repos_dict[key] = (repository, repo_handle)
keys = sorted(repos_dict, key=self.rlocale.sort_key)
# RepositoryListPage Class
self.repositorylistpage(self.report, title, repos_dict, keys)
for index, key in enumerate(keys):
(repo, handle) = repos_dict[key]
step()
self.repositorypage(self.report, title, repo, handle)
def repositorylistpage(self, report, title, repos_dict, keys):
"""
Create Index for repositories
@param: report -- The instance of the main report class
for this report
@param: title -- Is the title of the web page
@param: repos_dict -- The dictionary for all repositories
@param: keys -- The keys used to access repositories
"""
BasePage.__init__(self, report, title)
#inc_repos = self.report.options["inc_repository"]
output_file, sio = self.report.create_file("repositories")
repolistpage, head, body = self.write_header(_("Repositories"))
ldatec = 0
# begin RepositoryList division
with Html("div", class_="content",
id="RepositoryList") as repositorylist:
body += repositorylist
msg = self._("This page contains an index of "
"all the repositories in the "
"database, sorted by their title. "
"Clicking on a repositories&#8217;s title "
"will take you to that repositories&#8217;s page.")
repositorylist += Html("p", msg, id="description")
# begin repositories table and table head
with Html("table", class_="infolist primobjlist repolist") as table:
repositorylist += table
thead = Html("thead")
table += thead
trow = Html("tr") + (
Html("th", "&nbsp;", class_="ColumnRowLabel", inline=True),
Html("th", self._("Type"), class_="ColumnType",
inline=True),
Html("th", self._("Repository |Name"), class_="ColumnName",
inline=True)
)
thead += trow
# begin table body
tbody = Html("tbody")
table += tbody
for index, key in enumerate(keys):
(repo, handle) = repos_dict[key]
trow = Html("tr")
tbody += trow
# index number
trow += Html("td", index + 1, class_="ColumnRowLabel",
inline=True)
# repository type
rtype = self._(repo.type.xml_str())
trow += Html("td", rtype, class_="ColumnType", inline=True)
# repository name and hyperlink
if repo.get_name():
trow += Html("td",
self.repository_link(handle,
repo.get_name(),
repo.get_gramps_id(),
self.uplink),
class_="ColumnName")
ldatec = repo.get_change_time()
else:
trow += Html("td", "[ untitled ]", class_="ColumnName")
# add clearline for proper styling
# add footer section
footer = self.write_footer(ldatec)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(repolistpage, output_file, sio, ldatec)
def repositorypage(self, report, title, repo, handle):
"""
Create one page for one repository.
@param: report -- The instance of the main report class for this report
@param: title -- Is the title of the web page
@param: repo -- the repository to use
@param: handle -- the handle to use
"""
gid = repo.get_gramps_id()
BasePage.__init__(self, report, title, gid)
ldatec = repo.get_change_time()
output_file, sio = self.report.create_file(handle, 'repo')
self.uplink = True
repositorypage, head, body = self.write_header(_('Repositories'))
# begin RepositoryDetail division and page title
with Html("div", class_="content",
id="RepositoryDetail") as repositorydetail:
body += repositorydetail
# repository name
repositorydetail += Html("h3", html_escape(repo.name),
inline=True)
# begin repository table
with Html("table", class_="infolist repolist") as table:
repositorydetail += table
tbody = Html("tbody")
table += tbody
if not self.noid and gid:
trow = Html("tr") + (
Html("td", self._("Gramps ID"),
class_="ColumnAttribute",
inline=True),
Html("td", gid, class_="ColumnValue", inline=True)
)
tbody += trow
trow = Html("tr") + (
Html("td", self._("Type"), class_="ColumnAttribute",
inline=True),
Html("td", self._(repo.get_type().xml_str()),
class_="ColumnValue",
inline=True)
)
tbody += trow
# repository: address(es)...
# repository addresses do NOT have Sources
repo_address = self.display_addr_list(repo.get_address_list(),
False)
if repo_address is not None:
repositorydetail += repo_address
# repository: urllist
urllist = self.display_url_list(repo.get_url_list())
if urllist is not None:
repositorydetail += urllist
# reposity: notelist
notelist = self.display_note_list(repo.get_note_list())
if notelist is not None:
repositorydetail += notelist
# display Repository Referenced Sources...
ref_list = self.display_bkref_list(Repository, repo.get_handle())
if ref_list is not None:
repositorydetail += ref_list
# add clearline for proper styling
# add footer section
footer = self.write_footer(ldatec)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(repositorypage, output_file, sio, ldatec)

View File

@ -0,0 +1,306 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010-2017 Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
Classe:
SourcePage - Source index page and individual Source pages
"""
#------------------------------------------------
# python modules
#------------------------------------------------
from collections import defaultdict
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gen.lib import Source
from gramps.plugins.lib.libhtml import Html
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
from gramps.plugins.webreport.common import (FULLCLEAR, html_escape)
_ = glocale.translation.sgettext
LOG = logging.getLogger(".NarrativeWeb.source")
getcontext().prec = 8
#################################################
#
# creates the Source List Page and Source Pages
#
#################################################
class SourcePages(BasePage):
"""
This class is responsible for displaying information about the 'Source'
database objects. It displays this information under the 'Sources'
tab. It is told by the 'add_instances' call which 'Source's to display,
and remembers the list of persons. A single call to 'display_pages'
displays both the Individual List (Index) page and all the Individual
pages.
The base class 'BasePage' is initialised once for each page that is
displayed.
"""
def __init__(self, report):
"""
@param: report -- The instance of the main report class for
this report
"""
BasePage.__init__(self, report, title="")
self.source_dict = defaultdict(set)
self.navigation = None
self.citationreferents = None
def display_pages(self, title):
"""
Generate and output the pages under the Sources tab, namely the sources
index and the individual sources pages.
@param: title -- Is the title of the web page
"""
LOG.debug("obj_dict[Source]")
for item in self.report.obj_dict[Source].items():
LOG.debug(" %s", str(item))
with self.r_user.progress(_("Narrated Web Site Report"),
_("Creating source pages"),
len(self.report.obj_dict[Source]) + 1
) as step:
self.sourcelistpage(self.report, title,
self.report.obj_dict[Source].keys())
for source_handle in self.report.obj_dict[Source]:
step()
self.sourcepage(self.report, title, source_handle)
def sourcelistpage(self, report, title, source_handles):
"""
Generate and output the Sources index page.
@param: report -- The instance of the main report class for
this report
@param: title -- Is the title of the web page
@param: source_handles -- A list of the handles of the sources to be
displayed
"""
BasePage.__init__(self, report, title)
source_dict = {}
output_file, sio = self.report.create_file("sources")
sourcelistpage, head, body = self.write_header(self._("Sources"))
# begin source list division
with Html("div", class_="content", id="Sources") as sourceslist:
body += sourceslist
# Sort the sources
for handle in source_handles:
source = self.r_db.get_source_from_handle(handle)
if source is not None:
key = source.get_title() + source.get_author()
key += str(source.get_gramps_id())
source_dict[key] = (source, handle)
keys = sorted(source_dict, key=self.rlocale.sort_key)
msg = self._("This page contains an index of all the sources "
"in the database, sorted by their title. "
"Clicking on a source&#8217;s "
"title will take you to that source&#8217;s page.")
sourceslist += Html("p", msg, id="description")
# begin sourcelist table and table head
with Html("table",
class_="infolist primobjlist sourcelist") as table:
sourceslist += table
thead = Html("thead")
table += thead
trow = Html("tr")
thead += trow
header_row = [
(self._("Number"), "ColumnRowLabel"),
(self._("Author"), "ColumnAuthor"),
(self._("Source Name|Name"), "ColumnName")]
trow.extend(
Html("th", label or "&nbsp;", class_=colclass, inline=True)
for (label, colclass) in header_row
)
# begin table body
tbody = Html("tbody")
table += tbody
for index, key in enumerate(keys):
source, source_handle = source_dict[key]
trow = Html("tr") + (
Html("td", index + 1, class_="ColumnRowLabel",
inline=True)
)
tbody += trow
trow.extend(
Html("td", source.get_author(), class_="ColumnAuthor",
inline=True)
)
trow.extend(
Html("td", self.source_link(source_handle,
source.get_title(),
source.get_gramps_id()),
class_="ColumnName")
)
# add clearline for proper styling
# add footer section
footer = self.write_footer(None)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(sourcelistpage, output_file, sio, 0)
def sourcepage(self, report, title, source_handle):
"""
Generate and output an individual Source page.
@param: report -- The instance of the main report class
for this report
@param: title -- Is the title of the web page
@param: source_handle -- The handle of the source to be output
"""
source = report.database.get_source_from_handle(source_handle)
BasePage.__init__(self, report, title, source.get_gramps_id())
if not source:
return
self.page_title = source.get_title()
inc_repositories = self.report.options["inc_repository"]
self.navigation = self.report.options['navigation']
self.citationreferents = self.report.options['citationreferents']
output_file, sio = self.report.create_file(source_handle, "src")
self.uplink = True
sourcepage, head, body = self.write_header(
"%s - %s" % (self._('Sources'), self.page_title))
ldatec = 0
# begin source detail division
with Html("div", class_="content", id="SourceDetail") as sourcedetail:
body += sourcedetail
media_list = source.get_media_list()
if self.create_media and media_list:
thumbnail = self.disp_first_img_as_thumbnail(media_list,
source)
if thumbnail is not None:
sourcedetail += thumbnail
# add section title
sourcedetail += Html("h3", html_escape(source.get_title()),
inline=True)
# begin sources table
with Html("table", class_="infolist source") as table:
sourcedetail += table
tbody = Html("tbody")
table += tbody
source_gid = False
if not self.noid and self.gid:
source_gid = source.get_gramps_id()
# last modification of this source
ldatec = source.get_change_time()
for (label, value) in [(self._("Gramps ID"), source_gid),
(self._("Author"), source.get_author()),
(self._("Abbreviation"),
source.get_abbreviation()),
(self._("Publication information"),
source.get_publication_info())]:
if value:
trow = Html("tr") + (
Html("td", label, class_="ColumnAttribute",
inline=True),
Html("td", value, class_="ColumnValue", inline=True)
)
tbody += trow
# Source notes
notelist = self.display_note_list(source.get_note_list())
if notelist is not None:
sourcedetail += notelist
# additional media from Source (if any?)
if self.create_media and media_list:
sourcemedia = self.disp_add_img_as_gallery(media_list, source)
if sourcemedia is not None:
sourcedetail += sourcemedia
# Source Data Map...
src_data_map = self.write_srcattr(source.get_attribute_list())
if src_data_map is not None:
sourcedetail += src_data_map
# Source Repository list
if inc_repositories:
repo_list = self.dump_repository_ref_list(
source.get_reporef_list())
if repo_list is not None:
sourcedetail += repo_list
# Source references list
ref_list = self.display_bkref_list(Source, source_handle)
if ref_list is not None:
sourcedetail += ref_list
# add clearline for proper styling
# add footer section
footer = self.write_footer(ldatec)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(sourcepage, output_file, sio, ldatec)

View File

@ -0,0 +1,243 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010-2017 Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
Classe:
StatisticsPage
"""
#------------------------------------------------
# python modules
#------------------------------------------------
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gen.lib import (Person, Family, Event, Place, Source,
Citation, Repository)
from gramps.gen.plug.report import Bibliography
from gramps.gen.utils.file import media_path_full
from gramps.plugins.lib.libhtml import Html
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
from gramps.plugins.webreport.common import FULLCLEAR
LOG = logging.getLogger(".NarrativeWeb")
getcontext().prec = 8
_ = glocale.translation.sgettext
class StatisticsPage(BasePage):
"""
Create one page for statistics
"""
def __init__(self, report, title, step):
"""
@param: report -- The instance of the main report class
for this report
@param: title -- Is the title of the web page
"""
import posixpath
BasePage.__init__(self, report, title)
self.bibli = Bibliography()
self.uplink = False
self.report = report
# set the file name and open file
output_file, sio = self.report.create_file("statistics")
addressbookpage, head, body = self.write_header(_("Statistics"))
(males,
females,
unknown) = self.get_gender(report.database.iter_person_handles())
mobjects = report.database.get_number_of_media()
npersons = report.database.get_number_of_people()
nfamilies = report.database.get_number_of_families()
nsurnames = len(set(report.database.surname_list))
notfound = []
total_media = 0
mbytes = "0"
chars = 0
for media in report.database.iter_media():
total_media += 1
fullname = media_path_full(report.database, media.get_path())
try:
chars += posixpath.getsize(fullname)
length = len(str(chars))
if chars <= 999999:
mbytes = _("less than 1")
else:
mbytes = str(chars)[:(length-6)]
except OSError:
notfound.append(media.get_path())
with Html("div", class_="content", id='EventDetail') as section:
section += Html("h3", self._("Database overview"), inline=True)
body += section
with Html("div", class_="content", id='subsection narrative') as sec11:
sec11 += Html("h4", self._("Individuals"), inline=True)
body += sec11
with Html("div", class_="content", id='subsection narrative') as sec1:
sec1 += Html("br", self._("Number of individuals") + self.colon +
"%d" % npersons, inline=True)
sec1 += Html("br", self._("Males") + self.colon +
"%d" % males, inline=True)
sec1 += Html("br", self._("Females") + self.colon +
"%d" % females, inline=True)
sec1 += Html("br", self._("Individuals with unknown gender") +
self.colon + "%d" % unknown, inline=True)
body += sec1
with Html("div", class_="content", id='subsection narrative') as sec2:
sec2 += Html("h4", self._("Family Information"), inline=True)
sec2 += Html("br", self._("Number of families") + self.colon +
"%d" % nfamilies, inline=True)
sec2 += Html("br", self._("Unique surnames") + self.colon +
"%d" % nsurnames, inline=True)
body += sec2
with Html("div", class_="content", id='subsection narrative') as sec3:
sec3 += Html("h4", self._("Media Objects"), inline=True)
sec3 += Html("br",
self._("Total number of media object references") +
self.colon + "%d" % total_media, inline=True)
sec3 += Html("br", self._("Number of unique media objects") +
self.colon + "%d" % mobjects, inline=True)
sec3 += Html("br", self._("Total size of media objects") +
self.colon +
"%8s %s" % (mbytes, self._("Megabyte|MB")),
inline=True)
sec3 += Html("br", self._("Missing Media Objects") +
self.colon + "%d" % len(notfound), inline=True)
body += sec3
with Html("div", class_="content", id='subsection narrative') as sec4:
sec4 += Html("h4", self._("Miscellaneous"), inline=True)
sec4 += Html("br", self._("Number of events") + self.colon +
"%d" % report.database.get_number_of_events(),
inline=True)
sec4 += Html("br", self._("Number of places") + self.colon +
"%d" % report.database.get_number_of_places(),
inline=True)
nsources = report.database.get_number_of_sources()
sec4 += Html("br", self._("Number of sources") +
self.colon + "%d" % nsources,
inline=True)
ncitations = report.database.get_number_of_citations()
sec4 += Html("br", self._("Number of citations") +
self.colon + "%d" % ncitations,
inline=True)
nrepo = report.database.get_number_of_repositories()
sec4 += Html("br", self._("Number of repositories") +
self.colon + "%d" % nrepo,
inline=True)
body += sec4
(males,
females,
unknown) = self.get_gender(self.report.bkref_dict[Person].keys())
origin = " :<br/>" + report.filter.get_name(self.rlocale)
with Html("div", class_="content", id='EventDetail') as section:
section += Html("h3",
self._("Narrative web content report for") + origin,
inline=True)
body += section
with Html("div", class_="content", id='subsection narrative') as sec5:
sec5 += Html("h4", self._("Individuals"), inline=True)
sec5 += Html("br", self._("Number of individuals") + self.colon +
"%d" % len(self.report.bkref_dict[Person]),
inline=True)
sec5 += Html("br", self._("Males") + self.colon +
"%d" % males, inline=True)
sec5 += Html("br", self._("Females") + self.colon +
"%d" % females, inline=True)
sec5 += Html("br", self._("Individuals with unknown gender") +
self.colon + "%d" % unknown, inline=True)
body += sec5
with Html("div", class_="content", id='subsection narrative') as sec6:
sec6 += Html("h4", self._("Family Information"), inline=True)
sec6 += Html("br", self._("Number of families") + self.colon +
"%d" % len(self.report.bkref_dict[Family]),
inline=True)
body += sec6
with Html("div", class_="content", id='subsection narrative') as sec7:
sec7 += Html("h4", self._("Miscellaneous"), inline=True)
sec7 += Html("br", self._("Number of events") + self.colon +
"%d" % len(self.report.bkref_dict[Event]),
inline=True)
sec7 += Html("br", self._("Number of places") + self.colon +
"%d" % len(self.report.bkref_dict[Place]),
inline=True)
sec7 += Html("br", self._("Number of sources") + self.colon +
"%d" % len(self.report.bkref_dict[Source]),
inline=True)
sec7 += Html("br", self._("Number of citations") + self.colon +
"%d" % len(self.report.bkref_dict[Citation]),
inline=True)
sec7 += Html("br", self._("Number of repositories") + self.colon +
"%d" % len(self.report.bkref_dict[Repository]),
inline=True)
body += sec7
# add fullclear for proper styling
# and footer section to page
footer = self.write_footer(None)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(addressbookpage, output_file, sio, 0)
def get_gender(self, person_list):
"""
This function return the number of males, females and unknown gender
from a person list.
"""
males = 0
females = 0
unknown = 0
for person_handle in person_list:
person = self.report.database.get_person_from_handle(person_handle)
gender = person.get_gender()
if gender == Person.MALE:
males += 1
elif gender == Person.FEMALE:
females += 1
else:
unknown += 1
return (males, females, unknown)

View File

@ -0,0 +1,265 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010-2017 Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
Classe:
SurnamePage - creates list of individuals with same surname
"""
#------------------------------------------------
# python modules
#------------------------------------------------
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gen.plug.report import utils
from gramps.plugins.lib.libhtml import Html
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
from gramps.plugins.webreport.common import (name_to_md5, _NAME_STYLE_FIRST,
_find_birth_date, _find_death_date,
FULLCLEAR, html_escape)
_ = glocale.translation.sgettext
LOG = logging.getLogger(".NarrativeWeb")
getcontext().prec = 8
#################################################
#
# create the page from SurnameListPage
#
#################################################
class SurnamePage(BasePage):
"""
This will create a list of individuals with the same surname
"""
def __init__(self, report, title, surname, ppl_handle_list):
"""
@param: report -- The instance of the main report class for
this report
@param: title -- Is the title of the web page
@param: surname -- The surname to use
@param: ppl_handle_list -- The list of people for whom we need to create
a page.
"""
BasePage.__init__(self, report, title)
# module variables
showbirth = report.options['showbirth']
showdeath = report.options['showdeath']
showpartner = report.options['showpartner']
showparents = report.options['showparents']
if surname == '':
surname = self._("<absent>")
output_file, sio = self.report.create_file(name_to_md5(surname), "srn")
self.uplink = True
(surnamepage, head,
body) = self.write_header("%s - %s" % (self._("Surname"), surname))
ldatec = 0
# begin SurnameDetail division
with Html("div", class_="content", id="SurnameDetail") as surnamedetail:
body += surnamedetail
# section title
surnamedetail += Html("h3", html_escape(surname), inline=True)
# feature request 2356: avoid genitive form
msg = self._("This page contains an index of all the individuals "
"in the database with the surname of %s. "
"Selecting the person&#8217;s name "
"will take you to that person&#8217;s "
"individual page.") % html_escape(surname)
surnamedetail += Html("p", msg, id="description")
# begin surname table and thead
with Html("table", class_="infolist primobjlist surname") as table:
surnamedetail += table
thead = Html("thead")
table += thead
trow = Html("tr")
thead += trow
# Name Column
trow += Html("th", self._("Given Name"), class_="ColumnName",
inline=True)
if showbirth:
trow += Html("th", self._("Birth"), class_="ColumnDate",
inline=True)
if showdeath:
trow += Html("th", self._("Death"), class_="ColumnDate",
inline=True)
if showpartner:
trow += Html("th", self._("Partner"),
class_="ColumnPartner",
inline=True)
if showparents:
trow += Html("th", self._("Parents"),
class_="ColumnParents",
inline=True)
# begin table body
tbody = Html("tbody")
table += tbody
for person_handle in sorted(ppl_handle_list,
key=self.sort_on_name_and_grampsid):
person = self.r_db.get_person_from_handle(person_handle)
if person.get_change_time() > ldatec:
ldatec = person.get_change_time()
trow = Html("tr")
tbody += trow
# firstname column
link = self.new_person_link(person_handle, uplink=True,
person=person,
name_style=_NAME_STYLE_FIRST)
trow += Html("td", link, class_="ColumnName")
# birth column
if showbirth:
tcell = Html("td", class_="ColumnBirth", inline=True)
trow += tcell
birth_date = _find_birth_date(self.r_db, person)
if birth_date is not None:
if birth_date.fallback:
tcell += Html('em',
self.rlocale.get_date(birth_date),
inline=True)
else:
tcell += self.rlocale.get_date(birth_date)
else:
tcell += "&nbsp;"
# death column
if showdeath:
tcell = Html("td", class_="ColumnDeath", inline=True)
trow += tcell
death_date = _find_death_date(self.r_db, person)
if death_date is not None:
if death_date.fallback:
tcell += Html('em',
self.rlocale.get_date(death_date),
inline=True)
else:
tcell += self.rlocale.get_date(death_date)
else:
tcell += "&nbsp;"
# partner column
if showpartner:
tcell = Html("td", class_="ColumnPartner")
trow += tcell
family_list = person.get_family_handle_list()
if family_list:
fam_count = 0
for family_handle in family_list:
fam_count += 1
family = self.r_db.get_family_from_handle(
family_handle)
partner_handle = utils.find_spouse(
person, family)
if partner_handle:
link = self.new_person_link(partner_handle,
uplink=True)
if fam_count < len(family_list):
if isinstance(link, Html):
link.inside += ","
else:
link += ','
tcell += link
else:
tcell += "&nbsp;"
# parents column
if showparents:
parent_hdl_list = person.get_parent_family_handle_list()
if parent_hdl_list:
parent_hdl = parent_hdl_list[0]
fam = self.r_db.get_family_from_handle(parent_hdl)
f_id = fam.get_father_handle()
m_id = fam.get_mother_handle()
mother = father = None
if f_id:
father = self.r_db.get_person_from_handle(f_id)
if father:
father_name = self.get_name(father)
if m_id:
mother = self.r_db.get_person_from_handle(m_id)
if mother:
mother_name = self.get_name(mother)
if mother and father:
tcell = Html("span", father_name,
class_="father fatherNmother")
tcell += Html("span", mother_name,
class_="mother")
elif mother:
tcell = Html("span", mother_name,
class_="mother", inline=True)
elif father:
tcell = Html("span", father_name,
class_="father", inline=True)
samerow = False
else:
tcell = "&nbsp;" # pylint: disable=R0204
samerow = True
trow += Html("td", tcell,
class_="ColumnParents", inline=samerow)
# add clearline for proper styling
# add footer section
footer = self.write_footer(ldatec)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(surnamepage, output_file, sio, ldatec)

View File

@ -0,0 +1,249 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010-2017 Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
Classe:
SurnameListPage - Index for first letters of surname
"""
#------------------------------------------------
# python modules
#------------------------------------------------
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.plugins.lib.libhtml import Html
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
from gramps.plugins.webreport.common import (get_first_letters, _KEYPERSON,
alphabet_navigation, html_escape,
sort_people, name_to_md5,
first_letter, get_index_letter,
primary_difference, FULLCLEAR)
_ = glocale.translation.sgettext
LOG = logging.getLogger(".NarrativeWeb")
getcontext().prec = 8
#################################################
#
# Creates the Surname List page
#
#################################################
class SurnameListPage(BasePage):
"""
This class is responsible for displaying the list of Surnames
"""
ORDER_BY_NAME = 0
ORDER_BY_COUNT = 1
def __init__(self, report, title, ppl_handle_list,
order_by=ORDER_BY_NAME, filename="surnames"):
"""
@param: report -- The instance of the main report class for
this report
@param: title -- Is the title of the web page
@param: ppl_handle_list -- The list of people for whom we need to create
a page.
@param: order_by -- The way to sort surnames :
Surnames or Surnames count
@param: filename -- The name to use for the Surnames page
"""
BasePage.__init__(self, report, title)
prev_surname = ""
prev_letter = " "
if order_by == self.ORDER_BY_NAME:
output_file, sio = self.report.create_file(filename)
surnamelistpage, head, body = self.write_header(self._('Surnames'))
else:
output_file, sio = self.report.create_file("surnames_count")
(surnamelistpage, head,
body) = self.write_header(self._('Surnames by person count'))
# begin surnames division
with Html("div", class_="content", id="surnames") as surnamelist:
body += surnamelist
# page message
msg = self._('This page contains an index of all the '
'surnames in the database. Selecting a link '
'will lead to a list of individuals in the '
'database with this same surname.')
surnamelist += Html("p", msg, id="description")
# add alphabet navigation...
# only if surname list not surname count
if order_by == self.ORDER_BY_NAME:
index_list = get_first_letters(self.r_db, ppl_handle_list,
_KEYPERSON, rlocale=self.rlocale)
alpha_nav = alphabet_navigation(index_list, self.rlocale)
if alpha_nav is not None:
surnamelist += alpha_nav
if order_by == self.ORDER_BY_COUNT:
table_id = 'SortByCount'
else:
table_id = 'SortByName'
# begin surnamelist table and table head
with Html("table", class_="infolist primobjlist surnamelist",
id=table_id) as table:
surnamelist += table
thead = Html("thead")
table += thead
trow = Html("tr")
thead += trow
trow += Html("th", self._("Letter"), class_="ColumnLetter",
inline=True)
# create table header surname hyperlink
fname = self.report.surname_fname + self.ext
tcell = Html("th", class_="ColumnSurname", inline=True)
trow += tcell
hyper = Html("a", self._("Surname"),
href=fname, title=self._("Surnames"))
tcell += hyper
# create table header number of people hyperlink
fname = "surnames_count" + self.ext
tcell = Html("th", class_="ColumnQuantity", inline=True)
trow += tcell
num_people = self._("Number of People")
hyper = Html("a", num_people, href=fname, title=num_people)
tcell += hyper
# begin table body
with Html("tbody") as tbody:
table += tbody
ppl_handle_list = sort_people(self.r_db, ppl_handle_list,
self.rlocale)
if order_by == self.ORDER_BY_COUNT:
temp_list = {}
for (surname, data_list) in ppl_handle_list:
index_val = "%90d_%s" % (999999999-len(data_list),
surname)
temp_list[index_val] = (surname, data_list)
lkey = self.rlocale.sort_key
ppl_handle_list = (temp_list[key]
for key in sorted(temp_list,
key=lkey))
first = True
first_surname = True
for (surname, data_list) in ppl_handle_list:
if surname and not surname.isspace():
letter = first_letter(surname)
if order_by == self.ORDER_BY_NAME:
# There will only be an alphabetic index list if
# the ORDER_BY_NAME page is being generated
letter = get_index_letter(letter, index_list,
self.rlocale)
else:
letter = '&nbsp;'
surname = self._("<absent>")
trow = Html("tr")
tbody += trow
tcell = Html("td", class_="ColumnLetter", inline=True)
trow += tcell
if first or primary_difference(letter, prev_letter,
self.rlocale):
first = False
prev_letter = letter
trow.attr = 'class = "BeginLetter"'
ttle = self._("Surnames beginning with "
"letter %s") % letter
hyper = Html("a", letter, name=letter,
title=ttle, inline=True)
tcell += hyper
elif first_surname or surname != prev_surname:
first_surname = False
tcell += "&nbsp;"
prev_surname = surname
trow += Html("td",
self.surname_link(name_to_md5(surname),
#html_escape(surname)),
surname),
class_="ColumnSurname", inline=True)
trow += Html("td", len(data_list),
class_="ColumnQuantity", inline=True)
# create footer section
# add clearline for proper styling
footer = self.write_footer(None)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(surnamelistpage,
output_file, sio, 0) # 0 => current date modification
def surname_link(self, fname, name, opt_val=None, uplink=False):
"""
Create a link to the surname page.
@param: fname -- Path to the file name
@param: name -- Name to see in the link
@param: opt_val -- Option value to use
@param: uplink -- If True, then "../../../" is inserted in front of
the result.
"""
url = self.report.build_url_fname_html(fname, "srn", uplink)
hyper = Html("a", html_escape(name), href=url,
title=name, inline=True)
if opt_val is not None:
hyper += opt_val
# return hyperlink to its caller
return hyper

View File

@ -0,0 +1,277 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
# Copyright (C) 2008-2009 Brian G. Matherly
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2010-2017 Serge Noiraud
# Copyright (C) 2011 Tim G L Lyons
# Copyright (C) 2013 Benny Malengier
# Copyright (C) 2016 Allen Crider
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Narrative Web Page generator.
Classe:
ThumbnailPreviewPage
"""
#------------------------------------------------
# python modules
#------------------------------------------------
from decimal import getcontext
import logging
#------------------------------------------------
# Gramps module
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gen.lib import Media
from gramps.plugins.lib.libhtml import Html
#------------------------------------------------
# specific narrative web import
#------------------------------------------------
from gramps.plugins.webreport.basepage import BasePage
from gramps.plugins.webreport.common import (FULLCLEAR, html_escape)
_ = glocale.translation.sgettext
LOG = logging.getLogger(".NarrativeWeb")
getcontext().prec = 8
class ThumbnailPreviewPage(BasePage):
"""
This class is responsible for displaying information about
the Thumbnails page.
"""
def __init__(self, report, title, cb_progress):
"""
@param: report -- The instance of the main report class
for this report
@param: title -- Is the title of the web page
@param: cb_progress -- The step used for the progress bar.
"""
BasePage.__init__(self, report, title)
self.create_thumbs_only = report.options['create_thumbs_only']
# bug 8950 : it seems it's better to sort on desc + gid.
def sort_by_desc_and_gid(obj):
"""
Sort by media description and gramps ID
"""
return (obj.desc, obj.gramps_id)
self.photo_keys = sorted(self.report.obj_dict[Media],
key=lambda x: sort_by_desc_and_gid(
self.r_db.get_media_from_handle(x)))
if self.create_unused_media:
# add unused media
media_list = self.r_db.get_media_handles()
for media_ref in media_list:
if media_ref not in self.report.obj_dict[Media]:
self.photo_keys.append(media_ref)
media_list = []
for person_handle in self.photo_keys:
photo = self.r_db.get_media_from_handle(person_handle)
if photo:
if photo.get_mime_type().startswith("image"):
media_list.append((photo.get_description(), person_handle,
photo))
if self.create_thumbs_only:
self.copy_thumbnail(person_handle, photo)
media_list.sort(key=lambda x: self.rlocale.sort_key(x[0]))
# Create thumbnail preview page...
output_file, sio = self.report.create_file("thumbnails")
thumbnailpage, head, body = self.write_header(self._("Thumbnails"))
with Html("div", class_="content", id="Preview") as previewpage:
body += previewpage
msg = self._("This page displays a indexed list "
"of all the media objects "
"in this database. It is sorted by media title. "
"There is an index "
"of all the media objects in this database. "
"Clicking on a thumbnail "
"will take you to that image&#8217;s page.")
previewpage += Html("p", msg, id="description")
with Html("table", class_="calendar") as table:
previewpage += table
thead = Html("thead")
table += thead
# page title...
trow = Html("tr")
thead += trow
trow += Html("th", self._("Thumbnail Preview"),
class_="monthName", colspan=7, inline=True)
# table header cells...
trow = Html("tr")
thead += trow
ltrs = ["&nbsp;", "&nbsp;", "&nbsp;",
"&nbsp;", "&nbsp;", "&nbsp;", "&nbsp;"]
for ltr in ltrs:
trow += Html("th", ltr, class_="weekend", inline=True)
tbody = Html("tbody")
table += tbody
index, indexpos = 1, 0
num_of_images = len(media_list)
num_of_rows = ((num_of_images // 7) + 1)
num_of_cols = 7
grid_row = 0
while grid_row < num_of_rows:
trow = Html("tr", id="RowNumber: %08d" % grid_row)
tbody += trow
cols = 0
while cols < num_of_cols and indexpos < num_of_images:
ptitle = media_list[indexpos][0]
person_handle = media_list[indexpos][1]
photo = media_list[indexpos][2]
# begin table cell and attach to table row(trow)...
tcell = Html("td", class_="highlight weekend")
trow += tcell
# attach index number...
numberdiv = Html("div", class_="date")
tcell += numberdiv
# attach anchor name to date cell in upper right
# corner of grid...
numberdiv += Html("a", index, name=index, title=index,
inline=True)
# begin unordered list and
# attach to table cell(tcell)...
unordered = Html("ul")
tcell += unordered
# create thumbnail
(real_path,
newpath) = self.report.prepare_copy_media(photo)
newpath = self.report.build_url_fname(newpath)
list_html = Html("li")
unordered += list_html
# attach thumbnail to list...
list_html += self.thumb_hyper_image(newpath, "img",
person_handle,
ptitle)
index += 1
indexpos += 1
cols += 1
grid_row += 1
# if last row is incomplete, finish it off?
if grid_row == num_of_rows and cols < num_of_cols:
for emptycols in range(cols, num_of_cols):
trow += Html("td", class_="emptyDays", inline=True)
# begin Thumbnail Reference section...
with Html("div", class_="subsection", id="references") as section:
body += section
section += Html("h4", self._("References"), inline=True)
with Html("table", class_="infolist") as table:
section += table
tbody = Html("tbody")
table += tbody
index = 1
for ptitle, person_handle, photo in media_list:
trow = Html("tr")
tbody += trow
tcell1 = Html("td",
self.thumbnail_link(ptitle, index),
class_="ColumnRowLabel")
tcell2 = Html("td", ptitle, class_="ColumnName")
trow += (tcell1, tcell2)
# increase index for row number...
index += 1
# increase progress meter...
cb_progress()
# add body id element
body.attr = 'id ="ThumbnailPreview"'
# add footer section
# add clearline for proper styling
footer = self.write_footer(None)
body += (FULLCLEAR, footer)
# send page out for processing
# and close the file
self.xhtml_writer(thumbnailpage, output_file, sio, 0)
def thumbnail_link(self, name, index):
"""
creates a hyperlink for Thumbnail Preview Reference...
"""
return Html("a", index, title=html_escape(name),
href="#%d" % index)
def thumb_hyper_image(self, thumbnail_url, subdir, fname, name):
"""
eplaces media_link() because it doesn't work for this instance
"""
name = html_escape(name)
url = "/".join(self.report.build_subdirs(subdir,
fname) + [fname]) + self.ext
with Html("div", class_="content", id="ThumbnailPreview") as section:
with Html("div", class_="snapshot") as snapshot:
section += snapshot
with Html("div", class_="thumbnail") as thumbnail:
snapshot += thumbnail
if not self.create_thumbs_only:
thumbnail_link = Html("a", href=url, title=name) + (
Html("img", src=thumbnail_url, alt=name)
)
else:
thumbnail_link = Html("img", src=thumbnail_url,
alt=name)
thumbnail += thumbnail_link
return section

View File

@ -1901,6 +1901,7 @@ def _regular_surname(sex, name):
surname = name.get_surname()
suffix = name.get_suffix()
if suffix:
# TODO for Arabic, should the next line's comma be translated?
surname = surname + ", " + suffix
return surname

View File

@ -34,13 +34,13 @@ plg.id = 'navwebpage'
plg.name = _("Narrated Web Site")
plg.description = _("Produces web (HTML) pages for individuals, or a set of "
"individuals")
plg.version = '1.0'
plg.version = '2.0'
plg.gramps_target_version = MODULE_VERSION
plg.status = STABLE
plg.fname = 'narrativeweb.py'
plg.ptype = REPORT
plg.authors = ["Donald N. Allingham", "Rob G. Healey"]
plg.authors_email = ["don@gramps-project.org", "robhealey1@gmail.com"]
plg.authors = ["Donald N. Allingham", "Rob G. Healey", "Serge Noiraud"]
plg.authors_email = ["don@gramps-project.org", "serge.noiraud@free.fr"]
plg.category = CATEGORY_WEB
plg.reportclass = 'NavWebReport'
plg.optionclass = 'NavWebOptions'

View File

@ -108,8 +108,8 @@ gramps/gen/filters/rules/family/_hassourcecount.py
gramps/gen/filters/rules/family/_hassourceof.py
gramps/gen/filters/rules/family/_hastag.py
gramps/gen/filters/rules/family/_hastwins.py
gramps/gen/filters/rules/family/_isbookmarked.py
gramps/gen/filters/rules/family/_isancestorof.py
gramps/gen/filters/rules/family/_isbookmarked.py
gramps/gen/filters/rules/family/_isdescendantof.py
gramps/gen/filters/rules/family/_matchesfilter.py
gramps/gen/filters/rules/family/_matchessourceconfidence.py
@ -280,7 +280,10 @@ gramps/gen/filters/rules/source/_matchesrepositoryfilter.py
gramps/gen/filters/rules/source/_matchestitlesubstringof.py
gramps/gen/filters/rules/source/_regexpidof.py
gramps/gen/filters/rules/source/_sourceprivate.py
gramps/gen/lib/address.py
gramps/gen/lib/attribute.py
gramps/gen/lib/attrtype.py
gramps/gen/lib/childref.py
gramps/gen/lib/childreftype.py
gramps/gen/lib/citation.py
gramps/gen/lib/date.py
@ -292,26 +295,35 @@ gramps/gen/lib/family.py
gramps/gen/lib/familyreltype.py
gramps/gen/lib/grampstype.py
gramps/gen/lib/ldsord.py
gramps/gen/lib/location.py
gramps/gen/lib/markertype.py
gramps/gen/lib/media.py
gramps/gen/lib/mediaref.py
gramps/gen/lib/name.py
gramps/gen/lib/nameorigintype.py
gramps/gen/lib/nametype.py
gramps/gen/lib/note.py
gramps/gen/lib/notetype.py
gramps/gen/lib/person.py
gramps/gen/lib/personref.py
gramps/gen/lib/place.py
gramps/gen/lib/placename.py
gramps/gen/lib/placeref.py
gramps/gen/lib/placetype.py
gramps/gen/lib/repo.py
gramps/gen/lib/reporef.py
gramps/gen/lib/repotype.py
gramps/gen/lib/src.py
gramps/gen/lib/srcmediatype.py
gramps/gen/lib/srcattribute.py
gramps/gen/lib/srcattrtype.py
gramps/gen/lib/srcmediatype.py
gramps/gen/lib/styledtext.py
gramps/gen/lib/styledtexttag.py
gramps/gen/lib/styledtexttagtype.py
gramps/gen/lib/surname.py
gramps/gen/lib/surnamebase.py
gramps/gen/lib/tag.py
gramps/gen/lib/url.py
gramps/gen/lib/urltype.py
gramps/gen/merge/diff.py
gramps/gen/merge/mergecitationquery.py
@ -350,8 +362,8 @@ gramps/gen/utils/cast.py
gramps/gen/utils/configmanager.py
gramps/gen/utils/db.py
gramps/gen/utils/docgen/odstab.py
gramps/gen/utils/image.py
gramps/gen/utils/grampslocale.py
gramps/gen/utils/image.py
gramps/gen/utils/keyword.py
gramps/gen/utils/lds.py
gramps/gen/utils/place.py
@ -552,21 +564,23 @@ gramps/gui/widgets/reorderfam.py
gramps/gui/widgets/styledtextbuffer.py
gramps/gui/widgets/styledtexteditor.py
gramps/gui/widgets/validatedmaskedentry.py
gramps/plugins/db/dbapi/dbapi.gpr.py
gramps/plugins/db/bsddb/bsddb.gpr.py
gramps/plugins/db/bsddb/read.py
gramps/plugins/db/bsddb/undoredo.py
gramps/plugins/db/bsddb/upgrade.py
gramps/plugins/db/bsddb/write.py
gramps/plugins/db/dbapi/inmemorydb.gpr.py
gramps/plugins/db/dbapi/postgresql.gpr.py
gramps/plugins/db/dbapi/postgresql.py
gramps/plugins/db/dbapi/sqlite.gpr.py
gramps/plugins/db/dbapi/sqlite.py
gramps/plugins/docgen/asciidoc.py
gramps/plugins/docgen/cairodoc.py
gramps/plugins/docgen/docgen.gpr.py
gramps/plugins/docgen/gtkprint.glade
gramps/plugins/docgen/gtkprint.py
gramps/plugins/docgen/htmldoc.py
gramps/plugins/docgen/latexdoc.py
gramps/plugins/docgen/odfdoc.py
gramps/plugins/docgen/cairodoc.py
gramps/plugins/docgen/rtfdoc.py
gramps/plugins/docgen/svgdrawdoc.py
gramps/plugins/drawreport/ancestortree.py
@ -650,7 +664,6 @@ gramps/plugins/lib/libplugins.gpr.py
gramps/plugins/lib/libprogen.py
gramps/plugins/lib/librecords.py
gramps/plugins/lib/libsubstkeyword.py
#gramps/plugins/lib/libtranslate.py
gramps/plugins/lib/libtreebase.py
gramps/plugins/lib/maps/geography.py
gramps/plugins/lib/maps/osmgps.py
@ -671,8 +684,8 @@ gramps/plugins/quickview/reporef.py
gramps/plugins/quickview/samesurnames.py
gramps/plugins/quickview/siblings.py
gramps/plugins/rel/relplugins.gpr.py
gramps/plugins/sidebar/sidebar.gpr.py
gramps/plugins/sidebar/dropdownsidebar.py
gramps/plugins/sidebar/sidebar.gpr.py
gramps/plugins/textreport/alphabeticalindex.py
gramps/plugins/textreport/ancestorreport.py
gramps/plugins/textreport/birthdayreport.py
@ -758,7 +771,25 @@ gramps/plugins/view/relview.py
gramps/plugins/view/repoview.py
gramps/plugins/view/sourceview.py
gramps/plugins/view/view.gpr.py
gramps/plugins/webreport/addressbook.py
gramps/plugins/webreport/addressbooklist.py
gramps/plugins/webreport/basepage.py
gramps/plugins/webreport/contact.py
gramps/plugins/webreport/download.py
gramps/plugins/webreport/event.py
gramps/plugins/webreport/family.py
gramps/plugins/webreport/home.py
gramps/plugins/webreport/introduction.py
gramps/plugins/webreport/media.py
gramps/plugins/webreport/narrativeweb.py
gramps/plugins/webreport/person.py
gramps/plugins/webreport/place.py
gramps/plugins/webreport/repository.py
gramps/plugins/webreport/source.py
gramps/plugins/webreport/statistics.py
gramps/plugins/webreport/surname.py
gramps/plugins/webreport/surnamelist.py
gramps/plugins/webreport/thumbnail.py
gramps/plugins/webreport/webcal.py
gramps/plugins/webreport/webplugins.gpr.py
gramps/plugins/webstuff/webstuff.gpr.py

View File

@ -3,23 +3,34 @@
# Python files
#
Gramps.py
setup.py
#
# gramps
#
gramps/__init__.py
gramps/version.py
#
# cli
#
gramps/cli/__init__.py
#
# cli.test package
#
gramps/cli/test/argparser_test.py
gramps/cli/test/cli_test.py
gramps/cli/test/user_test.py
#
# gen
#
gramps/gen/__init__.py
gramps/gen/constfunc.py
gramps/gen/dbstate.py
gramps/gen/errors.py
gramps/gen/git_revision.py
gramps/gen/__init__.py
gramps/gen/sort.py
gramps/gen/soundex.py
gramps/gen/updatecallback.py
gramps/gen/ggettext.py
gramps/gen/user.py
#
# gen.datehandler package
#
@ -33,9 +44,11 @@ gramps/gen/datehandler/_date_el.py
gramps/gen/datehandler/_date_es.py
gramps/gen/datehandler/_date_fi.py
gramps/gen/datehandler/_date_fr.py
gramps/gen/datehandler/_datehandler.py
gramps/gen/datehandler/_date_hr.py
gramps/gen/datehandler/_date_hu.py
gramps/gen/datehandler/_date_is.py
gramps/gen/datehandler/_date_it.py
gramps/gen/datehandler/_date_ja.py
gramps/gen/datehandler/_date_lt.py
gramps/gen/datehandler/_date_nb.py
gramps/gen/datehandler/_date_nl.py
@ -46,18 +59,25 @@ gramps/gen/datehandler/_date_sk.py
gramps/gen/datehandler/_date_sl.py
gramps/gen/datehandler/_date_sr.py
gramps/gen/datehandler/_date_sv.py
gramps/gen/datehandler/_date_zh.py
gramps/gen/datehandler/_date_uk.py
gramps/gen/datehandler/_date_zh_CN.py
gramps/gen/datehandler/_date_zh_TW.py
gramps/gen/datehandler/_datehandler.py
gramps/gen/datehandler/_grampslocale.py
#
# gen.datehandler.test package
#
gramps/gen/datehandler/test/datedisplay_test.py
gramps/gen/datehandler/test/datehandler_test.py
gramps/gen/datehandler/test/dateparser_test.py
gramps/gen/datehandler/test/datestrings_test.py
#
# gen db API
#
gramps/gen/db/__init__.py
gramps/gen/db/backup.py
gramps/gen/db/bookmarks.py
gramps/gen/db/bsddbtxn.py
gramps/gen/db/cursor.py
gramps/gen/db/dbconst.py
gramps/gen/db/test/db_test.py
gramps/gen/db/dummydb.py
gramps/gen/db/txn.py
gramps/gen/db/undoredo.py
gramps/gen/db/utils.py
@ -65,18 +85,57 @@ gramps/gen/db/utils.py
# gen.display package
#
gramps/gen/display/__init__.py
gramps/gen/display/place.py
#
# gen.filters package
#
gramps/gen/filters/_filterlist.py
gramps/gen/filters/__init__.py
gramps/gen/filters/_filterlist.py
gramps/gen/filters/_paramfilter.py
gramps/gen/filters/_searchfilter.py
#
# gen.filters.rules package
#
gramps/gen/filters/rules/_hastextmatchingregexpof.py
gramps/gen/filters/rules/__init__.py
gramps/gen/filters/rules/_hastextmatchingregexpof.py
#
# gen.filters.rules.citation package
#
gramps/gen/filters/rules/citation/__init__.py
#
# gen.filters.rules.event package
#
gramps/gen/filters/rules/event/__init__.py
#
# gen.filters.rules.family package
#
gramps/gen/filters/rules/family/__init__.py
gramps/gen/filters/rules/family/_memberbase.py
#
# gen.filters.rules.media package
#
gramps/gen/filters/rules/media/__init__.py
#
# gen.filters.rules.note package
#
gramps/gen/filters/rules/note/__init__.py
#
# gen.filters.rules.person package
#
gramps/gen/filters/rules/person/__init__.py
gramps/gen/filters/rules/person/_hastextmatchingregexpof.py
#
# gen.filters.rules.place package
#
gramps/gen/filters/rules/place/__init__.py
#
# gen.filters.rules.repository package
#
gramps/gen/filters/rules/repository/__init__.py
#
# gen.filters.rules.source package
#
gramps/gen/filters/rules/source/__init__.py
#
# gen.filters.rules.test package
#
@ -88,85 +147,39 @@ gramps/gen/filters/rules/test/person_rules_test.py
gramps/gen/filters/rules/test/place_rules_test.py
gramps/gen/filters/rules/test/repository_rules_test.py
#
# gen.filters.rules.person package
#
gramps/gen/filters/rules/person/__init__.py
gramps/gen/filters/rules/person/_hastextmatchingregexpof.py
#
# gen.filters.rules.family package
#
gramps/gen/filters/rules/family/__init__.py
gramps/gen/filters/rules/family/_memberbase.py
#
# gen.filters.rules.event package
#
gramps/gen/filters/rules/event/__init__.py
#
# gen.filters.rules.place package
#
gramps/gen/filters/rules/place/__init__.py
#
# gen.filters.rules.source package
#
gramps/gen/filters/rules/source/__init__.py
#
# gen.filters.rules.citation package
#
gramps/gen/filters/rules/citation/__init__.py
#
# gen.filters.rules.media package
#
gramps/gen/filters/rules/media/__init__.py
#
# gen.filters.rules.repository package
#
gramps/gen/filters/rules/repository/__init__.py
#
# gen.filters.rules.note package
#
gramps/gen/filters/rules/note/__init__.py
#
# gen lib API
#
gramps/gen/lib/__init__.py
gramps/gen/lib/address.py
gramps/gen/lib/addressbase.py
gramps/gen/lib/attrbase.py
gramps/gen/lib/attribute.py
gramps/gen/lib/baseobj.py
gramps/gen/lib/gcalendar.py
gramps/gen/lib/childref.py
gramps/gen/lib/citationbase.py
gramps/gen/lib/const.py
gramps/gen/lib/datebase.py
gramps/gen/lib/gcalendar.py
gramps/gen/lib/genderstats.py
gramps/gen/lib/ldsordbase.py
gramps/gen/lib/location.py
gramps/gen/lib/locationbase.py
gramps/gen/lib/mediaobj.py
gramps/gen/lib/mediabase.py
gramps/gen/lib/mediaref.py
gramps/gen/lib/notebase.py
gramps/gen/lib/personref.py
gramps/gen/lib/placebase.py
gramps/gen/lib/primaryobj.py
gramps/gen/lib/privacybase.py
gramps/gen/lib/refbase.py
gramps/gen/lib/reporef.py
gramps/gen/lib/researcher.py
gramps/gen/lib/secondaryobj.py
gramps/gen/lib/serialize.py
gramps/gen/lib/srcbase.py
gramps/gen/lib/srcref.py
gramps/gen/lib/styledtexttag.py
gramps/gen/lib/tableobj.py
gramps/gen/lib/tagbase.py
gramps/gen/lib/urlbase.py
#
# gen lib test API
#
gramps/gen/lib/test/date_test.py
gramps/gen/lib/test/grampstype_test.py
gramps/gen/lib/test/merge_test.py
gramps/gen/lib/test/schema_test.py
gramps/gen/lib/url.py
gramps/gen/lib/urlbase.py
gramps/gen/lib/test/serialize_test.py
#
# gen.merge package
#
@ -176,46 +189,58 @@ gramps/gen/merge/__init__.py
#
gramps/gen/mime/__init__.py
#
# gen plugin API
# gen plug API
#
gramps/gen/plug/__init__.py
gramps/gen/plug/_docgenplugin.py
gramps/gen/plug/_export.py
gramps/gen/plug/_import.py
gramps/gen/plug/_plugin.py
gramps/gen/plug/menu/_booleanlist.py
gramps/gen/plug/menu/_boolean.py
gramps/gen/plug/menu/_color.py
gramps/gen/plug/menu/_destination.py
gramps/gen/plug/menu/_family.py
gramps/gen/plug/menu/_filter.py
gramps/gen/plug/menu/__init__.py
gramps/gen/plug/menu/_media.py
gramps/gen/plug/menu/_menu.py
gramps/gen/plug/menu/_note.py
gramps/gen/plug/menu/_number.py
gramps/gen/plug/menu/_option.py
gramps/gen/plug/menu/_personlist.py
gramps/gen/plug/menu/_person.py
gramps/gen/plug/menu/_placelist.py
gramps/gen/plug/menu/_string.py
gramps/gen/plug/menu/_style.py
gramps/gen/plug/menu/_surnamecolor.py
gramps/gen/plug/menu/_text.py
gramps/gen/plug/docbackend/cairobackend.py
#
# gen.plug.docbackend
#
gramps/gen/plug/docbackend/__init__.py
gramps/gen/plug/docbackend/cairobackend.py
#
# gen.plug.docgen
#
gramps/gen/plug/docgen/__init__.py
gramps/gen/plug/docgen/basedoc.py
gramps/gen/plug/docgen/drawdoc.py
gramps/gen/plug/docgen/fontscale.py
gramps/gen/plug/docgen/fontstyle.py
gramps/gen/plug/docgen/graphicstyle.py
gramps/gen/plug/docgen/__init__.py
gramps/gen/plug/docgen/paragraphstyle.py
gramps/gen/plug/docgen/stylesheet.py
gramps/gen/plug/docgen/tablestyle.py
gramps/gen/plug/docgen/textdoc.py
gramps/gen/plug/report/_bibliography.py
#
# gen.plug.menu
#
gramps/gen/plug/menu/__init__.py
gramps/gen/plug/menu/_boolean.py
gramps/gen/plug/menu/_booleanlist.py
gramps/gen/plug/menu/_color.py
gramps/gen/plug/menu/_destination.py
gramps/gen/plug/menu/_family.py
gramps/gen/plug/menu/_filter.py
gramps/gen/plug/menu/_media.py
gramps/gen/plug/menu/_menu.py
gramps/gen/plug/menu/_note.py
gramps/gen/plug/menu/_number.py
gramps/gen/plug/menu/_option.py
gramps/gen/plug/menu/_person.py
gramps/gen/plug/menu/_personlist.py
gramps/gen/plug/menu/_placelist.py
gramps/gen/plug/menu/_string.py
gramps/gen/plug/menu/_style.py
gramps/gen/plug/menu/_surnamecolor.py
gramps/gen/plug/menu/_text.py
#
# gen.plug.report
#
gramps/gen/plug/report/__init__.py
gramps/gen/plug/report/_bibliography.py
gramps/gen/plug/report/_options.py
gramps/gen/plug/report/_paper.py
gramps/gen/plug/report/_reportbase.py
@ -223,6 +248,7 @@ gramps/gen/plug/report/_reportbase.py
# gen proxy API
#
gramps/gen/proxy/__init__.py
gramps/gen/proxy/cache.py
gramps/gen/proxy/filter.py
gramps/gen/proxy/living.py
gramps/gen/proxy/proxybase.py
@ -232,7 +258,11 @@ gramps/gen/proxy/referencedbyselection.py
#
gramps/gen/simple/__init__.py
gramps/gen/simple/_simpledoc.py
#gramps/gen/simple/_simpletable.py
#
# gen test API
#
gramps/gen/test/config_test.py
gramps/gen/test/constfunc_test.py
#
# gen utils API
#
@ -244,8 +274,11 @@ gramps/gen/utils/debug.py
gramps/gen/utils/file.py
gramps/gen/utils/id.py
gramps/gen/utils/libformatting.py
gramps/gen/utils/mactrans.py
gramps/gen/utils/test/callback_test.py
gramps/gen/utils/location.py
gramps/gen/utils/lru.py
gramps/gen/utils/maclocale.py
gramps/gen/utils/resourcepath.py
gramps/gen/utils/thumbnails.py
#
# gen.utils.docgen
#
@ -253,9 +286,18 @@ gramps/gen/utils/docgen/__init__.py
gramps/gen/utils/docgen/csvtab.py
gramps/gen/utils/docgen/tabbeddoc.py
#
# gen.utils.test
#
gramps/gen/utils/test/callback_test.py
gramps/gen/utils/test/file_test.py
gramps/gen/utils/test/grampslocale_test.py
gramps/gen/utils/test/keyword_test.py
gramps/gen/utils/test/place_test.py
#
# gui - GUI code
#
gramps/gui/__init__.py
gramps/gui/actiongroup.py
gramps/gui/basesidebar.py
gramps/gui/dbguielement.py
gramps/gui/ddtargets.py
@ -265,9 +307,8 @@ gramps/gui/listmodel.py
gramps/gui/managedwindow.py
gramps/gui/navigator.py
gramps/gui/pluginmanager.py
gramps/gui/thumbnails.py
gramps/gui/user.py
gramps/gui/glade/catalog/grampswidgets.py
gramps/gui/utilscairo.py
#
# gui/editors - the GUI editors package
#
@ -278,12 +319,10 @@ gramps/gui/editors/editsecondary.py
#
gramps/gui/editors/displaytabs/__init__.py
gramps/gui/editors/displaytabs/addressmodel.py
gramps/gui/editors/displaytabs/altnamemodel.py
gramps/gui/editors/displaytabs/attrmodel.py
gramps/gui/editors/displaytabs/childmodel.py
gramps/gui/editors/displaytabs/citationbackreflist.py
gramps/gui/editors/displaytabs/citationrefmodel.py
gramps/gui/editors/displaytabs/datamodel.py
gramps/gui/editors/displaytabs/eventattrembedlist.py
gramps/gui/editors/displaytabs/eventbackreflist.py
gramps/gui/editors/displaytabs/familyattrembedlist.py
@ -295,38 +334,64 @@ gramps/gui/editors/displaytabs/mediaattrembedlist.py
gramps/gui/editors/displaytabs/mediabackreflist.py
gramps/gui/editors/displaytabs/notebackreflist.py
gramps/gui/editors/displaytabs/notemodel.py
gramps/gui/editors/displaytabs/personbackreflist.py
gramps/gui/editors/displaytabs/personrefmodel.py
gramps/gui/editors/displaytabs/placebackreflist.py
gramps/gui/editors/displaytabs/placenamemodel.py
gramps/gui/editors/displaytabs/placerefmodel.py
gramps/gui/editors/displaytabs/reporefmodel.py
gramps/gui/editors/displaytabs/sourcebackreflist.py
gramps/gui/editors/displaytabs/surnamemodel.py
gramps/gui/editors/displaytabs/webmodel.py
#
# gui.editors.test package
#
gramps/gui/editors/test/editreference_test.py
#
# gui.filters package
#
gramps/gui/filters/__init__.py
gramps/gui/filters/_filtercombobox.py
gramps/gui/filters/_filtermenu.py
#gramps/gui/filters/_filterstore.py
gramps/gui/filters/__init__.py
#
# gui.filters.sidebar package
#
gramps/gui/filters/sidebar/__init__.py
#
# gui.glade.catalog package
#
gramps/gui/glade/catalog/grampswidgets.py
#
# gui.logger package
#
gramps/gui/logger/__init__.py
gramps/gui/logger/_gtkhandler.py
gramps/gui/logger/_rotatehandler.py
#
# gui.logger.test package
#
gramps/gui/logger/test/rotate_handler_test.py
#
# gui.merge package
#
gramps/gui/merge/__init__.py
#
# gui.plug package
#
gramps/gui/plug/export/__init__.py
gramps/gui/plug/__init__.py
#
# gui.plug.export package
#
gramps/gui/plug/export/__init__.py
#
# gui.plug.quick package
#
gramps/gui/plug/quick/__init__.py
gramps/gui/plug/report/_drawreportdialog.py
#
# gui.plug.report package
#
gramps/gui/plug/report/__init__.py
gramps/gui/plug/report/_drawreportdialog.py
gramps/gui/plug/report/_textreportdialog.py
gramps/gui/plug/report/_webreportdialog.py
#
@ -337,6 +402,10 @@ gramps/gui/selectors/baseselector.py
gramps/gui/selectors/selectorexceptions.py
gramps/gui/selectors/selectorfactory.py
#
# gui.test package
#
gramps/gui/test/user_test.py
#
# gui/views - the GUI views package
#
gramps/gui/views/__init__.py
@ -344,25 +413,33 @@ gramps/gui/views/__init__.py
# gui/views/treemodels - the GUI views package
#
gramps/gui/views/treemodels/__init__.py
gramps/gui/views/treemodels/basemodel.py
gramps/gui/views/treemodels/citationlistmodel.py
gramps/gui/views/treemodels/eventmodel.py
gramps/gui/views/treemodels/familymodel.py
gramps/gui/views/treemodels/flatbasemodel.py
gramps/gui/views/treemodels/lru.py
gramps/gui/views/treemodels/notemodel.py
gramps/gui/views/treemodels/repomodel.py
gramps/gui/views/treemodels/sourcemodel.py
#
# gui.views.treemodels.test package
#
gramps/gui/views/treemodels/test/node_test.py
#
# gui/widgets - the GUI widgets package
#
gramps/gui/widgets/__init__.py
gramps/gui/widgets/basicentry.py
gramps/gui/widgets/dateentry.py
gramps/gui/widgets/fanchart2way.py
gramps/gui/widgets/fanchartdesc.py
gramps/gui/widgets/grabbers.py
gramps/gui/widgets/interactivesearchbox.py
gramps/gui/widgets/linkbox.py
gramps/gui/widgets/menuitem.py
gramps/gui/widgets/multitreeview.py
gramps/gui/widgets/placeentry.py
gramps/gui/widgets/selectionwidget.py
gramps/gui/widgets/shortlistcomboentry.py
gramps/gui/widgets/springseparator.py
gramps/gui/widgets/statusbar.py
@ -374,28 +451,72 @@ gramps/gui/widgets/validatedcomboentry.py
gramps/gui/widgets/valueaction.py
gramps/gui/widgets/valuetoolitem.py
#
# plugins .gpr.py
# plugins
#
gramps/plugins/__init__.py
#
# plugins/db directory
# plugins/db/bsddb directory
#
gramps/plugins/db/dbapi/inmemorydb.py
gramps/plugins/db/bsddb/__init__.py
gramps/plugins/db/bsddb/bsddb.py
gramps/plugins/db/bsddb/bsddbtxn.py
gramps/plugins/db/bsddb/cursor.py
gramps/plugins/db/bsddb/summary.py
#
# plugins/db/bsddb/test directory
#
gramps/plugins/db/bsddb/test/cursor_test.py
gramps/plugins/db/bsddb/test/db_test.py
gramps/plugins/db/bsddb/test/grampsdbtestbase.py
gramps/plugins/db/bsddb/test/reference_map_test.py
#
# plugins/db/dbapi directory
#
gramps/plugins/db/dbapi/__init__.py
gramps/plugins/db/dbapi/dbapi.py
#
# plugins/db/dbapi/test directory
#
gramps/plugins/db/dbapi/test/db_test.py
#
# plugins/docgen directory
#
gramps/plugins/docgen/__init__.py
#
# plugins/draw directory
# plugins/drawreport directory
#
gramps/plugins/drawreport/__init__.py
#
# plugins/export directory
#
gramps/plugins/export/exportftree.py
gramps/plugins/export/__init__.py
gramps/plugins/export/exportftree.py
#
# plugins/export/test directory
#
gramps/plugins/export/test/exportvcard_test.py
#
# plugins/gramplet directory
#
gramps/plugins/gramplet/__init__.py
gramps/plugins/gramplet/filter.py
gramps/plugins/gramplet/gallery.py
gramps/plugins/gramplet/mediapreview.py
gramps/plugins/gramplet/metadataviewer.py
#
# plugins/graph directory
#
gramps/plugins/graph/__init__.py
#
# plugins/importer directory
#
gramps/plugins/importer/__init__.py
#
# plugins/importer/test directory
#
gramps/plugins/importer/test/importgeneweb_test.py
gramps/plugins/importer/test/importvcard_test.py
#
# plugins/lib directory
#
gramps/plugins/lib/__init__.py
@ -404,14 +525,19 @@ gramps/plugins/lib/libhtml.py
gramps/plugins/lib/libmapservice.py
gramps/plugins/lib/libmixin.py
gramps/plugins/lib/libodfbackend.py
gramps/plugins/lib/libplaceimport.py
gramps/plugins/lib/librecurse.py
#
# plugins/map directory
# plugins/lib/maps directory
#
gramps/plugins/lib/maps/__init__.py
gramps/plugins/lib/maps/cairoprint.py
gramps/plugins/lib/maps/constants.py
gramps/plugins/lib/maps/datelayer.py
gramps/plugins/lib/maps/dummylayer.py
gramps/plugins/lib/maps/dummynogps.py
gramps/plugins/lib/maps/__init__.py
gramps/plugins/lib/maps/kmllayer.py
gramps/plugins/lib/maps/libkml.py
gramps/plugins/lib/maps/lifewaylayer.py
gramps/plugins/lib/maps/markerlayer.py
gramps/plugins/lib/maps/messagelayer.py
@ -419,35 +545,14 @@ gramps/plugins/lib/maps/selectionlayer.py
#
# plugins/mapservices directory
#
gramps/plugins/mapservices/googlemap.py
gramps/plugins/mapservices/__init__.py
gramps/plugins/mapservices/googlemap.py
gramps/plugins/mapservices/openstreetmap.py
#
# plugins/quickview directory
#
gramps/plugins/quickview/__init__.py
#
# plugins/import directory
#
gramps/plugins/importer/__init__.py
gramps/plugins/importer/test/importvcard_test.py
#
# plugins/gramplet directory
#
gramps/plugins/gramplet/filter.py
gramps/plugins/gramplet/gallery.py
gramps/plugins/gramplet/__init__.py
gramps/plugins/gramplet/mediapreview.py
gramps/plugins/gramplet/metadataviewer.py
gramps/plugins/gramplet/notegramplet.py
gramps/plugins/gramplet/populategramplet.py
gramps/plugins/gramplet/populategramplet.gpr.py
gramps/plugins/gramplet/pythongramplet.py
#
# plugins/graph directory
#
gramps/plugins/graph/__init__.py
#
# plugins/rel directory
#
gramps/plugins/rel/__init__.py
@ -460,6 +565,7 @@ gramps/plugins/rel/rel_fi.py
gramps/plugins/rel/rel_fr.py
gramps/plugins/rel/rel_hr.py
gramps/plugins/rel/rel_hu.py
gramps/plugins/rel/rel_is.py
gramps/plugins/rel/rel_it.py
gramps/plugins/rel/rel_nl.py
gramps/plugins/rel/rel_no.py
@ -469,12 +575,21 @@ gramps/plugins/rel/rel_ru.py
gramps/plugins/rel/rel_sk.py
gramps/plugins/rel/rel_sl.py
gramps/plugins/rel/rel_sv.py
gramps/plugins/rel/rel_uk.py
#
# plugins/sidebar directory
#
gramps/plugins/sidebar/__init__.py
gramps/plugins/sidebar/categorysidebar.py
gramps/plugins/sidebar/expandersidebar.py
gramps/plugins/sidebar/__init__.py
#
# plugins/test directory
#
gramps/plugins/test/db_undo_and_signals_test.py
gramps/plugins/test/exports_test.py
gramps/plugins/test/imports_test.py
gramps/plugins/test/reports_test.py
gramps/plugins/test/tools_test.py
#
# Development tools
#
@ -484,21 +599,25 @@ gramps/plugins/tool/__init__.py
#
gramps/plugins/view/__init__.py
#
# plugins/web directory
# plugins/webreport directory
#
gramps/plugins/webreport/__init__.py
gramps/plugins/webreport/citation.py
gramps/plugins/webreport/common.py
#
# plugins/webstuff directory
#
gramps/plugins/webstuff/__init__.py
#
# test
#
gramps/test/config_test.py
gramps/test/gramps_cli_test.py
gramps/test/__init__.py
gramps/test/regrtest.py
gramps/test/test/gedread_util_test.py
gramps/test/test/test_util_test.py
gramps/test/test_util.py
gramps/test/utils_test.py
#
# test/test
#
gramps/test/test/test_util_test.py
#
# Glade files
#

701
po/da.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -43225,14 +43225,6 @@ msgstr "Nessun foglio di stile"
#~ msgid "Ancestors of \"%s\""
#~ msgstr "Ascendenti di \"%s\""
#~ msgid "Generation %d has 1 individual. (%3.2f%%)\n"
#~ msgstr "La generazione %d comprende 1 persona. (%3.2f%%)\n"
#~ msgid "Total ancestors in generations 2 to %d is %d. (%3.2f%%)\n"
#~ msgstr ""
#~ "Il numero totale di ascendenti dalla generazione 2 alla generazione %d è "
#~ "%d. (%3.2f%%)\n"
#~ msgid "Whether to compress tree."
#~ msgstr "Indica se comprimere l'albero."

4682
po/lt.po

File diff suppressed because it is too large Load Diff

View File

@ -32154,13 +32154,6 @@ msgstr "Неуспешно"
msgid "Number of Ancestors for %s"
msgstr "Број на предци"
#: ../gramps/plugins/textreport/numberofancestorsreport.py:125
#, fuzzy, python-brace-format
msgid "Generation {number} has {count} individual. {percent}"
msgid_plural "Generation {number} has {count} individuals. {percent}"
msgstr[0] "Генерацијата %d има %d лица. (%3.2f%%)\n"
msgstr[1] "Генерацијата %d има %d лица. (%3.2f%%)\n"
#. TC # English return something like:
#. Total ancestors in generations 2 to 3 is 4. (66.67%)
#: ../gramps/plugins/textreport/numberofancestorsreport.py:167

View File

@ -42397,12 +42397,6 @@ msgstr "Bez arkusza stylów"
#~ msgid "Filtering"
#~ msgstr "Filtrowanie"
#~ msgid "Generation %d has 1 individual. (%3.2f%%)"
#~ msgstr "Pokolenie %d ma 1 osobę. (%3.2f%%)"
#~ msgid "Total ancestors in generations 2 to %d is %d. (%3.2f%%)"
#~ msgstr "Łączna ilość przodków w pokoleniach od 2 do %d wynosi %d. (%3.2f%%)"
#~ msgid "Easter"
#~ msgstr "Wielkanoc"

File diff suppressed because it is too large Load Diff

11585
po/pt_PT.po

File diff suppressed because it is too large Load Diff

9385
po/sl.po

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More