diff --git a/data/css/Web_Basic-Blue.css b/data/css/Web_Basic-Blue.css index 44dabde96..3775627bc 100644 --- a/data/css/Web_Basic-Blue.css +++ b/data/css/Web_Basic-Blue.css @@ -288,10 +288,6 @@ table.infolist thead tr th { table.infolist tr td { border-bottom: dashed 1px #000; vertical-align: middle; - padding: 6px 0 6px 10px; -} -table.infolist tr td a { - display: block; } table.infolist tr.BeginLetter td, table.infolist tr.BeginSurname td { border-top: solid 1px #000; @@ -351,15 +347,15 @@ div#Individuals { margin: 0; padding: 0; } -div#Individuals table.individuallist { +div#Individuals table.IndividualList { border-bottom: solid 1px #000; } -div#Individuals table.individuallist tbody tr td.ColumnSurname a:hover, -div#Individuals table.individuallist tbody tr td.ColumnSurname a:active { +div#Individuals table.IndividualList tbody tr td.ColumnSurname a:hover, +div#Individuals table.IndividualList tbody tr td.ColumnSurname a:active { cursor: default; background: none; } -div#Individuals table.individuallist tbody tr td.ColumnName a { +div#Individuals table.IndividualList tbody tr td.ColumnName a { vertical-align: middle; } div#Individuals div table.infolist tr td p { @@ -1129,9 +1125,10 @@ div.narrative { } .narrative p { font: normal .9em/1.4em sans-serif; - margin-top: .5em; - margin-bottom: 0; - padding: 0 20px 1em 20px; + margin: 0.1em 0 0.2em 0; +} +i + div.grampsstylednote p { + margin: 0.1em 0 0.2em 0; } /* Subsections : References diff --git a/data/css/Web_Nebraska.css b/data/css/Web_Nebraska.css index face9451d..9a5141282 100644 --- a/data/css/Web_Nebraska.css +++ b/data/css/Web_Nebraska.css @@ -276,7 +276,6 @@ table.infolist tr th a:hover { table.infolist tr td { font:normal 1.1em/1.4em serif; vertical-align:middle; - padding:.1em 10px; } table.infolist tr td a { display:block; @@ -396,30 +395,30 @@ table.surname thead tr th.ColumnParents, table.surname tbody tr td.ColumnParents /* Individuals ----------------------------------------------------- */ #Individuals { } -#Individuals table.individuallist { +#Individuals table.IndividualList { border-bottom:solid 1px #A97; } -#Individuals table.individuallist tbody tr td { +#Individuals table.IndividualList tbody tr td { border-bottom:dashed 1px #C1B398; } -#Individuals table.individuallist tbody tr td a:hover { +#Individuals table.IndividualList tbody tr td a:hover { text-decoration:none; } -table.individuallist tbody tr td.ColumnSurname a:hover, table.individuallist tbody tr td.ColumnSurname a:active { +table.IndividualList tbody tr td.ColumnSurname a:hover, table.IndividualList tbody tr td.ColumnSurname a:active { cursor:default; color:black; background:none; } -table.individuallist tbody tr td.ColumnName { +table.IndividualList tbody tr td.ColumnName { padding:0; background-color:#FFF; } -table.individuallist tbody tr td.ColumnName a { +table.IndividualList tbody tr td.ColumnName a { display:block; - padding:.6em 10px; + padding:.1em .1em; vertical-align:middle; } -table.individuallist tbody tr td.ColumnName a:hover { +table.IndividualList tbody tr td.ColumnName a:hover { background-color:#C1B398; } #Individuals div table.infolist tr td p { @@ -960,11 +959,12 @@ div#Addresses table.infolist tr td a, div#Addresses table.infolist tr td p a { div.narrative { padding-bottom:0; } +i + div.grampsstylednote p { + margin: 0.1em 0 0.2em 0; +} .narrative p { + margin: 0.1em 0 0.2em 0; font:normal .9em/1.4em sans-serif; - margin-top:.5em; - margin-bottom:0; - padding:0 20px 1em 20px; } /* Subsections : References diff --git a/data/css/Web_Visually.css b/data/css/Web_Visually.css index cea309708..f74a16832 100644 --- a/data/css/Web_Visually.css +++ b/data/css/Web_Visually.css @@ -454,30 +454,30 @@ div#Individuals { margin: 0; padding: 0; } -div#Individuals table.individuallist { +div#Individuals table.IndividualList { border-bottom: solid 1px #5D835F; } -div#Individuals table.individuallist tbody tr td { +div#Individuals table.IndividualList tbody tr td { border-bottom: dashed 1px #5D835F; background-color: #D8F3D6; } -div#Individuals table.individuallist tbody tr td a { +div#Individuals table.IndividualList tbody tr td a { display: block; padding: .6em 10px; } -div#Individuals table.individuallist tbody tr td.ColumnSurname a:hover, -div#Individuals table.individuallist tbody tr td.ColumnSurname a:active { +div#Individuals table.IndividualList tbody tr td.ColumnSurname a:hover, +div#Individuals table.IndividualList tbody tr td.ColumnSurname a:active { cursor:default; color: #000; background:none; } -div#Individuals table.individuallist tbody tr td.ColumnName { +div#Individuals table.IndividualList tbody tr td.ColumnName { background-color: #FFF; } -div#Individuals table.individuallist tbody tr td.ColumnName a { +div#Individuals table.IndividualList tbody tr td.ColumnName a { vertical-align:middle; } -div#Individuals table.individuallist tbody tr td.ColumnPartner { +div#Individuals table.IndividualList tbody tr td.ColumnPartner { background-color: #FFF; } div#Individuals div table.infolist tr td p { diff --git a/data/css/narrative-maps.css b/data/css/narrative-maps.css index 20bd9e767..ae926e390 100644 --- a/data/css/narrative-maps.css +++ b/data/css/narrative-maps.css @@ -33,7 +33,9 @@ body#FamilyMap { border: solid 4px #000; margin: 0px auto; width: 800px; - height: 800px; + height: 400px; + max-width: 90%; + max-height: 90%; } /* Place Maps @@ -43,6 +45,8 @@ div#place_canvas { border: solid 4px #000; width: 500px; height: 400px; + max-width: 90%; + max-height: 90%; } button#drop { background-color: purple; diff --git a/gramps/cli/grampscli.py b/gramps/cli/grampscli.py index 49c656c7f..8514d394c 100644 --- a/gramps/cli/grampscli.py +++ b/gramps/cli/grampscli.py @@ -318,6 +318,7 @@ class CLIManager: and self.dbstate.db.get_total() == 0): self.dbstate.db.set_researcher(owner) + name_displayer.clear_custom_formats() name_displayer.set_name_format(self.dbstate.db.name_formats) fmt_default = config.get('preferences.name-format') name_displayer.set_default_format(fmt_default) diff --git a/gramps/cli/plug/__init__.py b/gramps/cli/plug/__init__.py index cc46e25f2..299bf0add 100644 --- a/gramps/cli/plug/__init__.py +++ b/gramps/cli/plug/__init__.py @@ -47,7 +47,8 @@ LOG = logging.getLogger(".") #------------------------------------------------------------------------- from gramps.gen.plug import BasePluginManager from gramps.gen.plug.docgen import (StyleSheet, StyleSheetList, PaperStyle, - PAPER_PORTRAIT, PAPER_LANDSCAPE, graphdoc) + PAPER_PORTRAIT, PAPER_LANDSCAPE, graphdoc, + treedoc) from gramps.gen.plug.menu import (FamilyOption, PersonOption, NoteOption, MediaOption, PersonListOption, NumberOption, BooleanOption, DestinationOption, Option, @@ -56,8 +57,8 @@ from gramps.gen.plug.menu import (FamilyOption, PersonOption, NoteOption, from gramps.gen.display.name import displayer as name_displayer from gramps.gen.errors import ReportError, FilterError from gramps.gen.plug.report import (CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_BOOK, - CATEGORY_GRAPHVIZ, CATEGORY_CODE, - ReportOptions, append_styles) + CATEGORY_GRAPHVIZ, CATEGORY_TREE, + CATEGORY_CODE, ReportOptions, append_styles) from gramps.gen.plug.report._paper import paper_sizes from gramps.gen.const import USER_HOME, DOCGEN_OPTIONS from gramps.gen.dbstate import DbState @@ -250,6 +251,15 @@ class CommandLineReport: if name not in self.option_class.options_dict: self.option_class.options_dict[ name] = menu.get_option_by_name(name).get_value() + if category == CATEGORY_TREE: + # Need to include Genealogy Tree options + self.__toptions = treedoc.TreeOptions() + menu = self.option_class.menu + self.__toptions.add_menu_options(menu) + for name in menu.get_all_option_names(): + if name not in self.option_class.options_dict: + self.option_class.options_dict[ + name] = menu.get_option_by_name(name).get_value() self.option_class.load_previous_values() _validate_options(self.option_class, database) self.show = options_str_dict.pop('show', None) @@ -320,6 +330,10 @@ class CommandLineReport: for graph_format in graphdoc.FORMATS: self.options_help['off'][2].append( graph_format["type"] + "\t" + graph_format["descr"]) + elif self.category == CATEGORY_TREE: + for tree_format in treedoc.FORMATS: + self.options_help['off'][2].append( + tree_format["type"] + "\t" + tree_format["descr"]) else: self.options_help['off'][2] = "NA" @@ -498,6 +512,15 @@ class CommandLineReport: # Pick the first one as the default. self.format = graphdoc.FORMATS[0]["class"] _chosen_format = graphdoc.FORMATS[0]["type"] + elif self.category == CATEGORY_TREE: + for tree_format in treedoc.FORMATS: + if tree_format['type'] == self.options_dict['off']: + if not self.format: # choose the first one, not the last + self.format = tree_format["class"] + if self.format is None: + # Pick the first one as the default. + self.format = tree_format.FORMATS[0]["class"] + _chosen_format = tree_format.FORMATS[0]["type"] else: self.format = None if _chosen_format and _format_str: @@ -665,7 +688,7 @@ def cl_report(database, name, category, report_class, options_class, clr.selected_style, PaperStyle(clr.paper, clr.orien, clr.marginl, clr.marginr, clr.margint, clr.marginb)) - elif category == CATEGORY_GRAPHVIZ: + elif category in [CATEGORY_GRAPHVIZ, CATEGORY_TREE]: clr.option_class.handler.doc = clr.format( clr.option_class, PaperStyle(clr.paper, clr.orien, clr.marginl, diff --git a/gramps/gen/config.py b/gramps/gen/config.py index a96e3a34e..47ed6322c 100644 --- a/gramps/gen/config.py +++ b/gramps/gen/config.py @@ -274,22 +274,25 @@ register('preferences.last-view', '') register('preferences.last-views', []) register('preferences.family-relation-type', 3) # UNKNOWN register('preferences.age-display-precision', 1) -register('preferences.color-gender-male-alive', '#b8cee6') -register('preferences.color-gender-male-death', '#b8cee6') -register('preferences.color-gender-female-alive', '#feccf0') -register('preferences.color-gender-female-death', '#feccf0') -register('preferences.color-gender-unknown-alive', '#f3dbb6') -register('preferences.color-gender-unknown-death', '#f3dbb6') -#register('preferences.color-gender-other-alive', '#fcaf3e') -#register('preferences.color-gender-other-death', '#fcaf3e') -register('preferences.bordercolor-gender-male-alive', '#1f4986') -register('preferences.bordercolor-gender-male-death', '#000000') -register('preferences.bordercolor-gender-female-alive', '#861f69') -register('preferences.bordercolor-gender-female-death', '#000000') -register('preferences.bordercolor-gender-unknown-alive', '#8e5801') -register('preferences.bordercolor-gender-unknown-death', '#000000') -#register('preferences.bordercolor-gender-other-alive', '#f57900') -#register('preferences.bordercolor-gender-other-death', '#000000') + +register('colors.scheme', 0) +register('colors.male-alive', ['#b8cee6', '#1f344a']) +register('colors.male-dead', ['#b8cee6', '#2d3039']) +register('colors.female-alive', ['#feccf0', '#62242D']) +register('colors.female-dead', ['#feccf0', '#3a292b']) +register('colors.unknown-alive', ['#f3dbb6', '#75507B']) +register('colors.unknown-dead', ['#f3dbb6', '#35103b']) +register('colors.family', ['#eeeeee', '#454545']) +register('colors.family-divorced', ['#ffdede', '#5c3636']) +register('colors.home-person', ['#bbe68a', '#304918']) +register('colors.border-male-alive', ['#1f4986', '#171d26']) +register('colors.border-male-dead', ['#000000', '#000000']) +register('colors.border-female-alive', ['#861f69', '#261111']) +register('colors.border-female-dead', ['#000000', '#000000']) +register('colors.border-unknown-alive', ['#8e5801', '#8e5801']) +register('colors.border-unknown-dead', ['#000000', '#000000']) +register('colors.border-family', ['#cccccc', '#252525']) +register('colors.border-family-divorced', ['#ff7373', '#720b0b']) register('researcher.researcher-addr', '') register('researcher.researcher-locality', '') diff --git a/gramps/gen/datehandler/_date_de.py b/gramps/gen/datehandler/_date_de.py index 3ba9993fa..4bb37cc8f 100644 --- a/gramps/gen/datehandler/_date_de.py +++ b/gramps/gen/datehandler/_date_de.py @@ -292,7 +292,7 @@ class DateDisplayDE(DateDisplay): ) # this definition must agree with its "_display_gregorian" method - def _display_gregorian(self, date_val): + def _display_gregorian(self, date_val, **kwargs): """ display gregorian calendar date in different format """ diff --git a/gramps/gen/datehandler/_date_el.py b/gramps/gen/datehandler/_date_el.py index e1425132e..5668f9a69 100644 --- a/gramps/gen/datehandler/_date_el.py +++ b/gramps/gen/datehandler/_date_el.py @@ -155,7 +155,7 @@ class DateDisplayEL(DateDisplay): ) # this definition must agree with its "_display_gregorian" method - def _display_gregorian(self, date_val): + def _display_gregorian(self, date_val, **kwargs): """ display gregorian calendar date in different format """ diff --git a/gramps/gen/datehandler/_date_lt.py b/gramps/gen/datehandler/_date_lt.py index c706ab5fc..072ee8747 100644 --- a/gramps/gen/datehandler/_date_lt.py +++ b/gramps/gen/datehandler/_date_lt.py @@ -187,7 +187,7 @@ class DateDisplayLT(DateDisplay): "mmmm m. mėnesio diena d.", "Mėn diena, metai") # this definition must agree with its "_display_gregorian" method - def _display_gregorian(self, date_val): + def _display_gregorian(self, date_val, **kwargs): """ display gregorian calendar date in different format """ diff --git a/gramps/gen/datehandler/_date_nl.py b/gramps/gen/datehandler/_date_nl.py index bcdf94bbd..5da036cfd 100644 --- a/gramps/gen/datehandler/_date_nl.py +++ b/gramps/gen/datehandler/_date_nl.py @@ -164,7 +164,7 @@ class DateDisplayNL(DateDisplay): ) # this definition must agree with its "_display_gregorian" method - def _display_gregorian(self, date_val): + def _display_gregorian(self, date_val, **kwargs): """ display gregorian calendar date in different format """ diff --git a/gramps/gen/datehandler/_date_pl.py b/gramps/gen/datehandler/_date_pl.py index acfa6ec5d..3bec0a5e0 100644 --- a/gramps/gen/datehandler/_date_pl.py +++ b/gramps/gen/datehandler/_date_pl.py @@ -215,7 +215,7 @@ class DateDisplayPL(DateDisplay): "XII" ) - def _display_gregorian(self, date_val): + def _display_gregorian(self, date_val, **kwargs): """ display gregorian calendar date in different format """ diff --git a/gramps/gen/datehandler/_date_sr.py b/gramps/gen/datehandler/_date_sr.py index 81e9aceb1..c77c16fb6 100644 --- a/gramps/gen/datehandler/_date_sr.py +++ b/gramps/gen/datehandler/_date_sr.py @@ -240,7 +240,7 @@ class DateDisplaySR_Base(DateDisplay): "VII", "VIII", "IX", "X", "XI", "XII" ) - def _display_gregorian(self, date_val): + def _display_gregorian(self, date_val, **kwargs): """ display gregorian calendar date in different format """ diff --git a/gramps/gen/display/name.py b/gramps/gen/display/name.py index ffa4b94a1..6ab47682f 100644 --- a/gramps/gen/display/name.py +++ b/gramps/gen/display/name.py @@ -418,6 +418,11 @@ class NameDisplay: result = raw_data[_FIRSTNAME] return ' '.join(result.split()) + def clear_custom_formats(self): + self.name_formats = {num: value + for num, value in self.name_formats.items() + if num >= 0} + def set_name_format(self, formats): raw_func_dict = { diff --git a/gramps/gen/filters/rules/person/_isdescendantfamilyof.py b/gramps/gen/filters/rules/person/_isdescendantfamilyof.py index 605f7e044..49777d9c5 100644 --- a/gramps/gen/filters/rules/person/_isdescendantfamilyof.py +++ b/gramps/gen/filters/rules/person/_isdescendantfamilyof.py @@ -82,7 +82,8 @@ class IsDescendantFamilyOf(Rule): while expand: person = expand.pop(0) - if person is None: + if person is None or person.handle in self.matches: + # if we have been here before, skip continue self.matches.add(person.handle) for family_handle in person.get_family_handle_list(): diff --git a/gramps/gen/filters/rules/person/_isdescendantof.py b/gramps/gen/filters/rules/person/_isdescendantof.py index 9b9056aaa..7e7ec7783 100644 --- a/gramps/gen/filters/rules/person/_isdescendantof.py +++ b/gramps/gen/filters/rules/person/_isdescendantof.py @@ -67,7 +67,8 @@ class IsDescendantOf(Rule): return person.handle in self.map def init_list(self, person, first): - if not person: + if not person or person.handle in self.map: + # if we have been here before, skip return if not first: self.map.add(person.handle) diff --git a/gramps/gen/filters/rules/person/_islessthannthgenerationancestorof.py b/gramps/gen/filters/rules/person/_islessthannthgenerationancestorof.py index b95e330d1..847d7294e 100644 --- a/gramps/gen/filters/rules/person/_islessthannthgenerationancestorof.py +++ b/gramps/gen/filters/rules/person/_islessthannthgenerationancestorof.py @@ -61,6 +61,9 @@ class IsLessThanNthGenerationAncestorOf(Rule): queue = [(root_handle, 1)] # generation 1 is root while queue: handle, gen = queue.pop(0) # pop off front of queue + if handle in self.map: + # if we have been here before, skip + continue self.map.add(handle) gen += 1 if gen <= int(self.list[1]): diff --git a/gramps/gen/filters/rules/person/_islessthannthgenerationancestorofbookmarked.py b/gramps/gen/filters/rules/person/_islessthannthgenerationancestorofbookmarked.py index 187f6317d..5e0510cbf 100644 --- a/gramps/gen/filters/rules/person/_islessthannthgenerationancestorofbookmarked.py +++ b/gramps/gen/filters/rules/person/_islessthannthgenerationancestorofbookmarked.py @@ -71,7 +71,8 @@ class IsLessThanNthGenerationAncestorOfBookmarked(Rule): def init_ancestor_list(self, handle, gen): # if p.get_handle() in self.map: # loop_error(self.orig,p) - if not handle: + if not handle or handle in self.map: + # if been here already, skip return if gen: self.map.add(handle) diff --git a/gramps/gen/filters/rules/person/_islessthannthgenerationancestorofdefaultperson.py b/gramps/gen/filters/rules/person/_islessthannthgenerationancestorofdefaultperson.py index 9f2ae9870..cbce57a74 100644 --- a/gramps/gen/filters/rules/person/_islessthannthgenerationancestorofdefaultperson.py +++ b/gramps/gen/filters/rules/person/_islessthannthgenerationancestorofdefaultperson.py @@ -64,7 +64,8 @@ class IsLessThanNthGenerationAncestorOfDefaultPerson(Rule): def init_ancestor_list(self, handle, gen): # if p.get_handle() in self.map: # loop_error(self.orig,p) - if not handle: + if not handle or handle in self.map: + # if we have been here before, skip return if gen: self.map.add(handle) diff --git a/gramps/gen/filters/rules/person/_islessthannthgenerationdescendantof.py b/gramps/gen/filters/rules/person/_islessthannthgenerationdescendantof.py index 74a70e3e5..3d5fb16da 100644 --- a/gramps/gen/filters/rules/person/_islessthannthgenerationdescendantof.py +++ b/gramps/gen/filters/rules/person/_islessthannthgenerationdescendantof.py @@ -65,7 +65,8 @@ class IsLessThanNthGenerationDescendantOf(Rule): return person.handle in self.map def init_list(self,person,gen): - if not person: + if not person or person.handle in self.map: + # if we have been here before, skip return if gen: self.map.add(person.handle) diff --git a/gramps/gen/plug/__init__.py b/gramps/gen/plug/__init__.py index 05a78efcc..a07d1b05a 100644 --- a/gramps/gen/plug/__init__.py +++ b/gramps/gen/plug/__init__.py @@ -27,7 +27,7 @@ The "plug" package for handling plugins in Gramps. from ._plugin import Plugin from ._pluginreg import (PluginData, PluginRegister, REPORT, TOOL, CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_CODE, - CATEGORY_WEB, CATEGORY_BOOK, CATEGORY_GRAPHVIZ, + CATEGORY_WEB, CATEGORY_BOOK, CATEGORY_GRAPHVIZ, CATEGORY_TREE, TOOL_DEBUG, TOOL_ANAL, TOOL_DBPROC, TOOL_DBFIX, TOOL_REVCTL, TOOL_UTILS, CATEGORY_QR_MISC, CATEGORY_QR_PERSON, CATEGORY_QR_FAMILY, CATEGORY_QR_EVENT, CATEGORY_QR_SOURCE, @@ -46,14 +46,14 @@ from ._options import (Options, OptionListCollection, OptionList, OptionHandler, MenuOptions) __all__ = [ "docbackend", "docgen", "menu", "Plugin", "PluginData", - "PluginRegister", "BasePluginManager", - "ImportPlugin", "ExportPlugin", "DocGenPlugin", - "REPORT", "TOOL", "CATEGORY_TEXT", "CATEGORY_DRAW", "CATEGORY_CODE", - "CATEGORY_WEB", "CATEGORY_BOOK", "CATEGORY_GRAPHVIZ", - "TOOL_DEBUG", "TOOL_ANAL", "TOOL_DBPROC", "TOOL_DBFIX", "TOOL_REVCTL", - "TOOL_UTILS", "CATEGORY_QR_MISC", "CATEGORY_QR_PERSON", - "CATEGORY_QR_FAMILY", "CATEGORY_QR_EVENT", "CATEGORY_QR_SOURCE", - "CATEGORY_QR_PLACE", "CATEGORY_QR_REPOSITORY", "CATEGORY_QR_NOTE", - "CATEGORY_QR_DATE", "PTYPE_STR", "CATEGORY_QR_MEDIA", - "CATEGORY_QR_CITATION", "CATEGORY_QR_SOURCE_OR_CITATION", - "START", "END", "make_environment"] + "PluginRegister", "BasePluginManager", "ImportPlugin", + "ExportPlugin", "DocGenPlugin", "REPORT", "TOOL", "CATEGORY_TEXT", + "CATEGORY_DRAW", "CATEGORY_CODE", "CATEGORY_WEB", "CATEGORY_BOOK", + "CATEGORY_GRAPHVIZ", "CATEGORY_TREE", "TOOL_DEBUG", "TOOL_ANAL", + "TOOL_DBPROC", "TOOL_DBFIX", "TOOL_REVCTL","TOOL_UTILS", + "CATEGORY_QR_MISC", "CATEGORY_QR_PERSON", "CATEGORY_QR_FAMILY", + "CATEGORY_QR_EVENT", "CATEGORY_QR_SOURCE", "CATEGORY_QR_PLACE", + "CATEGORY_QR_REPOSITORY", "CATEGORY_QR_NOTE", "CATEGORY_QR_DATE", + "PTYPE_STR", "CATEGORY_QR_MEDIA", "CATEGORY_QR_CITATION", + "CATEGORY_QR_SOURCE_OR_CITATION", "START", "END", + "make_environment"] diff --git a/gramps/gen/plug/_pluginreg.py b/gramps/gen/plug/_pluginreg.py index 6a786c0c0..e637e7ea8 100644 --- a/gramps/gen/plug/_pluginreg.py +++ b/gramps/gen/plug/_pluginreg.py @@ -97,8 +97,10 @@ CATEGORY_CODE = 2 CATEGORY_WEB = 3 CATEGORY_BOOK = 4 CATEGORY_GRAPHVIZ = 5 +CATEGORY_TREE = 6 REPORT_CAT = [ CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_CODE, - CATEGORY_WEB, CATEGORY_BOOK, CATEGORY_GRAPHVIZ] + CATEGORY_WEB, CATEGORY_BOOK, CATEGORY_GRAPHVIZ, + CATEGORY_TREE] #possible tool categories TOOL_DEBUG = -1 TOOL_ANAL = 0 @@ -1043,6 +1045,7 @@ def make_environment(**kwargs): 'CATEGORY_WEB': CATEGORY_WEB, 'CATEGORY_BOOK': CATEGORY_BOOK, 'CATEGORY_GRAPHVIZ': CATEGORY_GRAPHVIZ, + 'CATEGORY_TREE': CATEGORY_TREE, 'TOOL_DEBUG': TOOL_DEBUG, 'TOOL_ANAL': TOOL_ANAL, 'TOOL_DBPROC': TOOL_DBPROC, @@ -1242,8 +1245,7 @@ class PluginRegister: """ Return a list of :class:`PluginData` that are of type ptype """ - return [self.get_plugin(id) for id in - set([x.id for x in self.__plugindata if x.ptype == ptype])] + return [x for x in self.__plugindata if x.ptype == ptype] def report_plugins(self, gui=True): """ @@ -1352,6 +1354,4 @@ class PluginRegister: """ Return a list of :class:`PluginData` that have load_on_reg == True """ - return [self.get_plugin(id) for id in - set([x.id for x in self.__plugindata - if x.load_on_reg == True])] + return [x for x in self.__plugindata if x.load_on_reg == True] diff --git a/gramps/gen/plug/docgen/__init__.py b/gramps/gen/plug/docgen/__init__.py index d2dc8452c..a6fd37ca1 100644 --- a/gramps/gen/plug/docgen/__init__.py +++ b/gramps/gen/plug/docgen/__init__.py @@ -37,3 +37,4 @@ from .textdoc import TextDoc, IndexMark,INDEX_TYPE_ALP, INDEX_TYPE_TOC,\ URL_PATTERN, LOCAL_HYPERLINK, LOCAL_TARGET from .drawdoc import DrawDoc from .graphdoc import GVDoc +from .treedoc import TreeDoc diff --git a/gramps/gen/plug/docgen/treedoc.py b/gramps/gen/plug/docgen/treedoc.py new file mode 100644 index 000000000..41bf666cb --- /dev/null +++ b/gramps/gen/plug/docgen/treedoc.py @@ -0,0 +1,633 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2017-2018 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 +# 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. +# +""" LaTeX Genealogy Tree adapter for Trees """ +#------------------------------------------------------------------------- +# +# Standard Python modules +# +#------------------------------------------------------------------------- +from abc import ABCMeta, abstractmethod +import os +import shutil +from io import StringIO +import tempfile +import logging + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from ...utils.file import search_for +from ...lib import Person, EventType, EventRoleType, Date +from ...display.place import displayer as _pd +from ...utils.file import media_path_full +from . import BaseDoc, PAPER_PORTRAIT +from ..menu import NumberOption, TextOption, EnumeratedListOption +from ...constfunc import win +from ...config import config +from ...const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext + +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +LOG = logging.getLogger(".treedoc") + +#------------------------------------------------------------------------- +# +# Private Constants +# +#------------------------------------------------------------------------- +_DETAIL = [{'name': _("Full"), 'value': "full"}, + {'name': _("Medium"), 'value': "medium"}, + {'name': _("Short"), 'value': "short"}] + +_MARRIAGE = [{'name': _("Default"), 'value': ""}, + {'name': _("Above"), 'value': "marriage above"}, + {'name': _("Below"), 'value': "marriage below"}, + {'name': _("Not shown"), 'value': "no marriage"}] + +_COLOR = [{'name': _("None"), 'value': "none"}, + {'name': _("Default"), 'value': "default"}, + {'name': _("Preferences"), 'value': "preferences"}] + +_TIMEFLOW = [{'name': _("Down (↓)"), 'value': ""}, + {'name': _("Up (↑)"), 'value': "up"}, + {'name': _("Right (→)"), 'value': "right"}, + {'name': _("Left (←)"), 'value': "left"}] + +_EDGES = [{'name': _("Perpendicular"), 'value': ""}, + {'name': _("Rounded"), 'value': "rounded", }, + {'name': _("Swing"), 'value': "swing", }, + {'name': _("Mesh"), 'value': 'mesh'}] + +_NOTELOC = [{'name': _("Top"), 'value': "t"}, + {'name': _("Bottom"), 'value': "b"}] + +_NOTESIZE = [{'name': _("Tiny"), 'value': "tiny"}, + {'name': _("Script"), 'value': "scriptsize"}, + {'name': _("Footnote"), 'value': "footnotesize"}, + {'name': _("Small"), 'value': "small"}, + {'name': _("Normal"), 'value': "normalsize"}, + {'name': _("Large"), 'value': "large"}, + {'name': _("Very large"), 'value': "Large"}, + {'name': _("Extra large"), 'value': "LARGE"}, + {'name': _("Huge"), 'value': "huge"}, + {'name': _("Extra huge"), 'value': "Huge"}] + +if win(): + _LATEX_FOUND = search_for("lualatex.exe") +else: + _LATEX_FOUND = search_for("lualatex") + + +#------------------------------------------------------------------------------ +# +# TreeOptions +# +#------------------------------------------------------------------------------ +class TreeOptions: + """ + Defines all of the controls necessary + to configure the genealogy tree reports. + """ + def add_menu_options(self, menu): + """ + Add all graph related options to the menu. + + :param menu: The menu the options should be added to. + :type menu: :class:`.Menu` + :return: nothing + """ + ################################ + category = _("Node Options") + ################################ + + detail = EnumeratedListOption(_("Node detail"), "full") + for item in _DETAIL: + detail.add_item(item["value"], item["name"]) + detail.set_help(_("Detail of information to be shown in a node.")) + menu.add_option(category, "detail", detail) + + marriage = EnumeratedListOption(_("Marriage"), "") + for item in _MARRIAGE: + marriage.add_item(item["value"], item["name"]) + marriage.set_help(_("Position of marriage information.")) + menu.add_option(category, "marriage", marriage) + + nodesize = NumberOption(_("Node size"), 25, 5, 100, 5) + nodesize.set_help(_("One dimension of a node, in mm. If the timeflow " + "is up or down then this is the width, otherwise " + "it is the height.")) + menu.add_option(category, "nodesize", nodesize) + + levelsize = NumberOption(_("Level size"), 35, 5, 100, 5) + levelsize.set_help(_("One dimension of a node, in mm. If the timeflow " + "is up or down then this is the height, otherwise " + "it is the width.")) + menu.add_option(category, "levelsize", levelsize) + + nodecolor = EnumeratedListOption(_("Color"), "none") + for item in _COLOR: + nodecolor.add_item(item["value"], item["name"]) + nodecolor.set_help(_("Node color.")) + menu.add_option(category, "nodecolor", nodecolor) + + ################################ + category = _("Tree Options") + ################################ + + timeflow = EnumeratedListOption(_("Timeflow"), "") + for item in _TIMEFLOW: + timeflow.add_item(item["value"], item["name"]) + timeflow.set_help(_("Direction that the graph will grow over time.")) + menu.add_option(category, "timeflow", timeflow) + + edges = EnumeratedListOption(_("Edge style"), "") + for item in _EDGES: + edges.add_item(item["value"], item["name"]) + edges.set_help(_("Style of the edges between nodes.")) + menu.add_option(category, "edges", edges) + + leveldist = NumberOption(_("Level distance"), 5, 1, 20, 1) + leveldist.set_help(_("The minimum amount of free space, in mm, " + "between levels. For vertical graphs, this " + "corresponds to spacing between rows. For " + "horizontal graphs, this corresponds to spacing " + "between columns.")) + menu.add_option(category, "leveldist", leveldist) + + ################################ + category = _("Note") + ################################ + + note = TextOption(_("Note to add to the tree"), [""]) + note.set_help(_("This text will be added to the tree.")) + menu.add_option(category, "note", note) + + noteloc = EnumeratedListOption(_("Note location"), 't') + for item in _NOTELOC: + noteloc.add_item(item["value"], item["name"]) + noteloc.set_help(_("Whether note will appear on top " + "or bottom of the page.")) + menu.add_option(category, "noteloc", noteloc) + + notesize = EnumeratedListOption(_("Note size"), 'normalsize') + for item in _NOTESIZE: + notesize.add_item(item["value"], item["name"]) + notesize.set_help(_("The size of note text.")) + menu.add_option(category, "notesize", notesize) + + +#------------------------------------------------------------------------------ +# +# TreeDoc +# +#------------------------------------------------------------------------------ +class TreeDoc(metaclass=ABCMeta): + """ + Abstract Interface for genealogy tree document generators. Output formats + for genealogy tree reports must implement this interface to be used by the + report system. + """ + @abstractmethod + def start_tree(self, option_list): + """ + Write the start of a tree. + """ + + @abstractmethod + def end_tree(self): + """ + Write the end of a tree. + """ + + @abstractmethod + def start_subgraph(self, level, subgraph_type, family, option_list=None): + """ + Write the start of a subgraph. + """ + + @abstractmethod + def end_subgraph(self, level): + """ + Write the end of a subgraph. + """ + + @abstractmethod + def write_node(self, db, level, node_type, person, marriage_flag, + option_list=None): + """ + Write the contents of a node. + """ + + +#------------------------------------------------------------------------------ +# +# TreeDocBase +# +#------------------------------------------------------------------------------ +class TreeDocBase(BaseDoc, TreeDoc): + """ + Base document generator for all Graphviz document generators. Classes that + inherit from this class will only need to implement the close function. + The close function will generate the actual file of the appropriate type. + """ + def __init__(self, options, paper_style): + BaseDoc.__init__(self, None, paper_style) + + self._filename = None + self._tex = StringIO() + self._paper = paper_style + + get_option = options.menu.get_option_by_name + + self.detail = get_option('detail').get_value() + self.marriage = get_option('marriage').get_value() + self.nodesize = get_option('nodesize').get_value() + self.levelsize = get_option('levelsize').get_value() + self.nodecolor = get_option('nodecolor').get_value() + + self.timeflow = get_option('timeflow').get_value() + self.edges = get_option('edges').get_value() + self.leveldist = get_option('leveldist').get_value() + + self.note = get_option('note').get_value() + self.noteloc = get_option('noteloc').get_value() + self.notesize = get_option('notesize').get_value() + + def write_start(self): + """ + Write the start of the document. + """ + paper_size = self._paper.get_size() + name = paper_size.get_name().lower() + if name == 'custom size': + width = str(paper_size.get_width()) + height = str(paper_size.get_width()) + paper = 'papersize={%scm,%scm}' % (width, height) + elif name in ('a', 'b', 'c', 'd', 'e'): + paper = 'ansi' + name + 'paper' + else: + paper = name + 'paper' + + if self._paper.get_orientation() == PAPER_PORTRAIT: + orientation = 'portrait' + else: + orientation = 'landscape' + + lmargin = self._paper.get_left_margin() + rmargin = self._paper.get_right_margin() + tmargin = self._paper.get_top_margin() + bmargin = self._paper.get_bottom_margin() + if lmargin == rmargin == tmargin == bmargin: + margin = 'margin=%scm'% lmargin + else: + if lmargin == rmargin: + margin = 'hmargin=%scm' % lmargin + else: + margin = 'hmargin={%scm,%scm}' % (lmargin, rmargin) + if tmargin == bmargin: + margin += ',vmargin=%scm' % tmargin + else: + margin += ',vmargin={%scm,%scm}' % (tmargin, bmargin) + + self.write(0, '\\documentclass[%s]{article}\n' % orientation) + + self.write(0, '\\IfFileExists{libertine.sty}{\n') + self.write(0, ' \\usepackage{libertine}\n') + self.write(0, '}{}\n') + + self.write(0, '\\usepackage[%s,%s]{geometry}\n' % (paper, margin)) + self.write(0, '\\usepackage[all]{genealogytree}\n') + self.write(0, '\\usepackage{color}\n') + self.write(0, '\\begin{document}\n') + + if self.nodecolor == 'preferences': + scheme = config.get('colors.scheme') + male_bg = config.get('colors.male-dead')[scheme][1:] + female_bg = config.get('colors.female-dead')[scheme][1:] + neuter_bg = config.get('colors.unknown-dead')[scheme][1:] + self.write(0, '\\definecolor{male-bg}{HTML}{%s}\n' % male_bg) + self.write(0, '\\definecolor{female-bg}{HTML}{%s}\n' % female_bg) + self.write(0, '\\definecolor{neuter-bg}{HTML}{%s}\n' % neuter_bg) + + if ''.join(self.note) != '' and self.noteloc == 't': + for line in self.note: + self.write(0, '{\\%s %s}\\par\n' % (self.notesize, line)) + self.write(0, '\\bigskip\n') + + self.write(0, '\\begin{tikzpicture}\n') + + def start_tree(self, option_list): + self.write(0, '\\genealogytree[\n') + self.write(0, 'processing=database,\n') + if self.marriage: + info = self.detail + ' ' + self.marriage + else: + info = self.detail + self.write(0, 'database format=%s,\n' % info) + if self.timeflow: + self.write(0, 'timeflow=%s,\n' % self.timeflow) + if self.edges: + self.write(0, 'edges=%s,\n' % self.edges) + if self.leveldist != 5: + self.write(0, 'level distance=%smm,\n' % self.leveldist) + if self.nodesize != 25: + self.write(0, 'node size=%smm,\n' % self.nodesize) + if self.levelsize != 35: + self.write(0, 'level size=%smm,\n' % self.levelsize) + if self.nodecolor == 'none': + self.write(0, 'tcbset={male/.style={},\n') + self.write(0, ' female/.style={},\n') + self.write(0, ' neuter/.style={}},\n') + if self.nodecolor == 'preferences': + self.write(0, 'tcbset={male/.style={colback=male-bg},\n') + self.write(0, ' female/.style={colback=female-bg},\n') + self.write(0, ' neuter/.style={colback=neuter-bg}},\n') + + for option in option_list: + self.write(0, '%s,\n' % option) + + self.write(0, ']{\n') + + def end_tree(self): + self.write(0, '}\n') + + def write_end(self): + """ + Write the end of the document. + """ + self.write(0, '\\end{tikzpicture}\n') + + if ''.join(self.note) != '' and self.noteloc == 'b': + self.write(0, '\\bigskip\n') + for line in self.note: + self.write(0, '\\par{\\%s %s}\n' % (self.notesize, line)) + + self.write(0, '\\end{document}\n') + + def start_subgraph(self, level, subgraph_type, family, option_list=None): + options = ['id=%s' % family.gramps_id] + if option_list: + options.extend(option_list) + if subgraph_type == 'sandclock': + self.write(level, 'sandclock{\n') + else: + self.write(level, '%s[%s]{\n' % (subgraph_type, ','.join(options))) + + def end_subgraph(self, level): + self.write(level, '}\n') + + def write_node(self, db, level, node_type, person, marriage_flag, + option_list=None): + options = ['id=%s' % person.gramps_id] + if option_list: + options.extend(option_list) + self.write(level, '%s[%s]{\n' % (node_type, ','.join(options))) + if person.gender == Person.MALE: + self.write(level+1, 'male,\n') + elif person.gender == Person.FEMALE: + self.write(level+1, 'female,\n') + elif person.gender == Person.UNKNOWN: + self.write(level+1, 'neuter,\n') + name = person.get_primary_name() + nick = name.get_nick_name() + surn = name.get_surname() + name_parts = [self.format_given_names(name), + '\\nick{{{}}}'.format(nick) if nick else '', + '\\surn{{{}}}'.format(surn) if surn else ''] + self.write(level+1, 'name = {{{}}},\n'.format( + ' '.join([e for e in name_parts if e]))) + for eventref in person.get_event_ref_list(): + if eventref.role == EventRoleType.PRIMARY: + event = db.get_event_from_handle(eventref.ref) + self.write_event(db, level+1, event) + if marriage_flag: + for handle in person.get_family_handle_list(): + family = db.get_family_from_handle(handle) + for eventref in family.get_event_ref_list(): + if eventref.role == EventRoleType.FAMILY: + event = db.get_event_from_handle(eventref.ref) + self.write_event(db, level+1, event) + for attr in person.get_attribute_list(): + if str(attr.get_type()) == 'Occupation': + self.write(level+1, 'profession = {%s},\n' % attr.get_value()) + if str(attr.get_type()) == 'Comment': + self.write(level+1, 'comment = {%s},\n' % attr.get_value()) + for mediaref in person.get_media_list(): + media = db.get_media_from_handle(mediaref.ref) + path = media_path_full(db, media.get_path()) + if os.path.isfile(path): + self.write(level+1, 'image = {%s},\n' % path) + break # first image only + self.write(level, '}\n') + + def write_event(self, db, level, event): + """ + Write an event. + """ + modifier = None + if event.type == EventType.BIRTH: + event_type = 'birth' + if 'died' in event.description.lower(): + modifier = 'died' + if 'stillborn' in event.description.lower(): + modifier = 'stillborn' + # modifier = 'out of wedlock' + elif event.type == EventType.BAPTISM: + event_type = 'baptism' + elif event.type == EventType.ENGAGEMENT: + event_type = 'engagement' + elif event.type == EventType.MARRIAGE: + event_type = 'marriage' + elif event.type == EventType.DIVORCE: + event_type = 'divorce' + elif event.type == EventType.DEATH: + event_type = 'death' + elif event.type == EventType.BURIAL: + event_type = 'burial' + if 'killed' in event.description.lower(): + modifier = 'killed' + elif event.type == EventType.CREMATION: + event_type = 'burial' + modifier = 'cremated' + else: + return + + date = event.get_date_object() + + if date.get_calendar() == Date.CAL_GREGORIAN: + calendar = 'AD' # GR + elif date.get_calendar() == Date.CAL_JULIAN: + calendar = 'JU' + else: + calendar = '' + + if date.get_modifier() == Date.MOD_ABOUT: + calendar = 'ca' + calendar + + date_str = self.format_iso(date.get_ymd(), calendar) + if date.get_modifier() == Date.MOD_BEFORE: + date_str = '/' + date_str + elif date.get_modifier() == Date.MOD_AFTER: + date_str = date_str + '/' + elif date.is_compound(): + stop_date = self.format_iso(date.get_stop_ymd(), calendar) + date_str = date_str + '/' + stop_date + + place = _pd.display_event(db, event) + + if modifier: + event_type += '+' + self.write(level, '%s = {%s}{%s}{%s},\n' % + (event_type, date_str, place, modifier)) + elif place == '': + event_type += '-' + self.write(level, '%s = {%s},\n' % (event_type, date_str)) + else: + self.write(level, '%s = {%s}{%s},\n' % + (event_type, date_str, place)) + + def format_given_names(self, name): + """ + Format given names. + """ + first = name.get_first_name() + call = name.get_call_name() + if call: + if call in first: + where = first.index(call) + return '{before}\\pref{{{call}}}{after}'.format( + before=first[:where], + call=call, + after=first[where+len(call):]) + else: + # ignore erroneous call name + return first + else: + return first + + def format_iso(self, date_tuple, calendar): + """ + Format an iso date. + """ + year, month, day = date_tuple + if year == 0: + iso_date = '' + elif month == 0: + iso_date = str(year) + elif day == 0: + iso_date = '%s-%s' % (year, month) + else: + iso_date = '%s-%s-%s' % (year, month, day) + if calendar and calendar != 'AD': + iso_date = '(%s)%s' % (calendar, iso_date) + return iso_date + + def write(self, level, text): + """ + Write indented text. + """ + self._tex.write(' '*level + text) + + def open(self, filename): + """ Implement TreeDocBase.open() """ + self._filename = os.path.normpath(os.path.abspath(filename)) + self.write_start() + + def close(self): + """ + This isn't useful by itself. Other classes need to override this and + actually generate a file. + """ + self.write_end() + + +#------------------------------------------------------------------------------ +# +# TreeTexDoc +# +#------------------------------------------------------------------------------ +class TreeTexDoc(TreeDocBase): + """ + TreeTexDoc implementation that generates a .tex file. + """ + + def close(self): + """ Implements TreeDocBase.close() """ + TreeDocBase.close(self) + + # Make sure the extension is correct + if self._filename[-4:] != ".tex": + self._filename += ".tex" + + with open(self._filename, "w") as texfile: + texfile.write(self._tex.getvalue()) + + +#------------------------------------------------------------------------------ +# +# TreePdfDoc +# +#------------------------------------------------------------------------------ +class TreePdfDoc(TreeDocBase): + """ + TreePdfDoc implementation that generates a .pdf file. + """ + + def close(self): + """ Implements TreeDocBase.close() """ + TreeDocBase.close(self) + + # Make sure the extension is correct + if self._filename[-4:] != ".pdf": + self._filename += ".pdf" + + with tempfile.TemporaryDirectory() as tmpdir: + with open(os.path.join(tmpdir, 'temp.tex'), "w") as texfile: + texfile.write(self._tex.getvalue()) + os.system('lualatex -output-directory %s temp.tex >/dev/null' + % tmpdir) + shutil.copy(os.path.join(tmpdir, 'temp.pdf'), self._filename) + + +#------------------------------------------------------------------------------ +# +# Various Genealogy Tree formats. +# +#------------------------------------------------------------------------------ +FORMATS = [] + +if _LATEX_FOUND: + FORMATS += [{'type' : "pdf", + 'ext' : "pdf", + 'descr': _("PDF"), + 'mime' : "application/pdf", + 'class': TreePdfDoc}] + +FORMATS += [{'type' : "tex", + 'ext' : "tex", + 'descr': _("LaTeX File"), + 'mime' : "application/x-latex", + 'class': TreeTexDoc}] diff --git a/gramps/gen/plug/report/_book.py b/gramps/gen/plug/report/_book.py index aa867e5ec..9a709043c 100644 --- a/gramps/gen/plug/report/_book.py +++ b/gramps/gen/plug/report/_book.py @@ -495,14 +495,14 @@ class BookList: """ Saves the current BookList to the associated file. """ - with open(self.file, "w") as b_f: + with open(self.file, "w", encoding="utf-8") as b_f: b_f.write("\n") b_f.write('\n') for name in sorted(self.bookmap): # enable a diff of archived copies book = self.get_book(name) - dbname = book.get_dbname() + dbname = escape(book.get_dbname()) b_f.write(' ' - '\n' % (name, dbname)) + '\n' % (escape(name), dbname)) for item in book.get_item_list(): b_f.write(' \n' % ( @@ -566,7 +566,7 @@ class BookList: '\n' % book.get_format_name()) if book.get_output(): b_f.write(' ' - '\n' % book.get_output()) + '\n' % escape(book.get_output())) b_f.write(' \n') b_f.write('\n') @@ -578,8 +578,15 @@ class BookList: try: parser = make_parser() parser.setContentHandler(BookParser(self, self.dbase)) - with open(self.file) as the_file: - parser.parse(the_file) + # bug 10387; XML should be utf8, but was not previously saved + # that way. So try to read utf8, if fails, try with system + # encoding. Only an issue on non-utf8 systems. + try: + with open(self.file, encoding="utf-8") as the_file: + parser.parse(the_file) + except UnicodeDecodeError: + with open(self.file) as the_file: + parser.parse(the_file) except (IOError, OSError, ValueError, SAXParseException, KeyError, AttributeError): LOG.debug("Failed to parse book list", exc_info=True) diff --git a/gramps/gen/plug/report/_constants.py b/gramps/gen/plug/report/_constants.py index 7a7016463..97ca81650 100644 --- a/gramps/gen/plug/report/_constants.py +++ b/gramps/gen/plug/report/_constants.py @@ -39,7 +39,7 @@ import os # Report categories from .. import (CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_CODE, CATEGORY_WEB, - CATEGORY_BOOK, CATEGORY_GRAPHVIZ) + CATEGORY_BOOK, CATEGORY_GRAPHVIZ, CATEGORY_TREE) standalone_categories = { CATEGORY_TEXT : ("RepText", _("Text Reports")), @@ -48,6 +48,7 @@ standalone_categories = { CATEGORY_WEB : ("RepWeb", _("Web Pages")), CATEGORY_BOOK : ("RepBook", _("Books")), CATEGORY_GRAPHVIZ : ("Graphs", _("Graphs")), + CATEGORY_TREE : ("Trees", _("Trees")), } book_categories = { CATEGORY_TEXT : _("Text"), diff --git a/gramps/gen/utils/docgen/csvtab.py b/gramps/gen/utils/docgen/csvtab.py index a9d6bfe51..01822ebf4 100644 --- a/gramps/gen/utils/docgen/csvtab.py +++ b/gramps/gen/utils/docgen/csvtab.py @@ -31,6 +31,7 @@ import csv # #------------------------------------------------------------------------- from .tabbeddoc import * +from ...constfunc import win class CSVTab(TabbedDoc): @@ -48,7 +49,8 @@ class CSVTab(TabbedDoc): else: self.filename = filename - self.f = open(self.filename, "w") + self.f = open(self.filename, "w", + encoding='utf_8_sig' if win() else 'utf_8') self.writer = csv.writer(self.f) def close(self): diff --git a/gramps/gui/configure.py b/gramps/gui/configure.py index d8e7d3ab6..3e301ef28 100644 --- a/gramps/gui/configure.py +++ b/gramps/gui/configure.py @@ -249,13 +249,23 @@ class ConfigureDialog(ManagedWindow): """ self.__config.set(constant, obj.get_text()) - def update_color(self, obj, constant, color_hex_label): + def update_color(self, obj, pspec, constant, color_hex_label): + """ + Called on changing some color. + Either on programmatically color change. + """ rgba = obj.get_rgba() hexval = "#%02x%02x%02x" % (int(rgba.red * 255), int(rgba.green * 255), int(rgba.blue * 255)) color_hex_label.set_text(hexval) - self.__config.set(constant, hexval) + colors = self.__config.get(constant) + if isinstance(colors, list): + scheme = self.__config.get('colors.scheme') + colors[scheme] = hexval + self.__config.set(constant, colors) + else: + self.__config.set(constant, hexval) def update_checkbox(self, obj, constant, config=None): if not config: @@ -383,15 +393,24 @@ class ConfigureDialog(ManagedWindow): grid.attach(entry, col_attach+1, index, 1, 1) def add_color(self, grid, label, index, constant, config=None, col=0): + """ + Add color chooser widget with label to the grid. + """ if not config: config = self.__config lwidget = BasicLabel(_("%s: ") % label) # needed for French, else ignore - hexval = config.get(constant) + colors = config.get(constant) + if isinstance(colors, list): + scheme = config.get('colors.scheme') + hexval = colors[scheme] + else: + hexval = colors color = Gdk.color_parse(hexval) entry = Gtk.ColorButton(color=color) color_hex_label = BasicLabel(hexval) color_hex_label.set_hexpand(True) - entry.connect('color-set', self.update_color, constant, color_hex_label) + entry.connect('notify::color', self.update_color, constant, + color_hex_label) grid.attach(lwidget, col, index, 1, 1) grid.attach(entry, col+1, index, 1, 1) grid.attach(color_hex_label, col+2, index, 1, 1) @@ -554,7 +573,7 @@ class GrampsPreferences(ConfigureDialog): def add_color_panel(self, configdialog): """ - Add the tab to set defaults colors for graph boxes + Add the tab to set defaults colors for graph boxes. """ grid = Gtk.Grid() grid.set_border_width(12) @@ -562,40 +581,62 @@ class GrampsPreferences(ConfigureDialog): grid.set_row_spacing(6) self.add_text(grid, _('Set the colors used for boxes in the graphical views'), 0, line_wrap=False) - self.add_color(grid, _('Gender Male Alive'), 1, - 'preferences.color-gender-male-alive') - self.add_color(grid, _('Border Male Alive'), 2, - 'preferences.bordercolor-gender-male-alive') - self.add_color(grid, _('Gender Male Death'), 3, - 'preferences.color-gender-male-death') - self.add_color(grid, _('Border Male Death'), 4, - 'preferences.bordercolor-gender-male-death') - self.add_color(grid, _('Gender Female Alive'), 1, - 'preferences.color-gender-female-alive', col=4) - self.add_color(grid, _('Border Female Alive'), 2, - 'preferences.bordercolor-gender-female-alive', col=4) - self.add_color(grid, _('Gender Female Death'), 3, - 'preferences.color-gender-female-death', col=4) - self.add_color(grid, _('Border Female Death'), 4, - 'preferences.bordercolor-gender-female-death', col=4) -## self.add_color(grid, _('Gender Other Alive'), 5, -## 'preferences.color-gender-other-alive') -## self.add_color(grid, _('Border Other Alive'), 6, -## 'preferences.bordercolor-gender-other-alive') -## self.add_color(grid, _('Gender Other Death'), 7, -## 'preferences.color-gender-other-death') -## self.add_color(grid, _('Border Other Death'), 8, -## 'preferences.bordercolor-gender-other-death') - self.add_color(grid, _('Gender Unknown Alive'), 5, - 'preferences.color-gender-unknown-alive', col=4) - self.add_color(grid, _('Border Unknown Alive'), 6, - 'preferences.bordercolor-gender-unknown-alive', col=4) - self.add_color(grid, _('Gender Unknown Death'), 7, - 'preferences.color-gender-unknown-death', col=4) - self.add_color(grid, _('Border Unknown Death'), 8, - 'preferences.bordercolor-gender-unknown-death', col=4) + + hbox = Gtk.Box(spacing=12) + self.color_scheme_box = Gtk.ComboBoxText() + formats = [_("Light colors"), + _("Dark colors"),] + list(map(self.color_scheme_box.append_text, formats)) + scheme = config.get('colors.scheme') + self.color_scheme_box.set_active(scheme) + self.color_scheme_box.connect('changed', self.color_scheme_changed) + lwidget = BasicLabel(_("%s: ") % _('Color scheme')) + hbox.pack_start(lwidget, False, False, 0) + hbox.pack_start(self.color_scheme_box, False, False, 0) + + restore_btn = Gtk.Button(_('Restore to defaults')) + restore_btn.connect('clicked', self.restore_colors) + hbox.pack_start(restore_btn, False, False, 0) + grid.attach(hbox, 1, 1, 6, 1) + + color_list = [ + (_('Male Alive'), 'male-alive', 2, 0), + (_('Male Dead'), 'male-dead', 4, 0), + (_('Female Alive'), 'female-alive', 2, 4), + (_('Female Dead'), 'female-dead', 4, 4), + (_('Unknown Alive'), 'unknown-alive', 6, 4), + (_('Unknown Dead'), 'unknown-dead', 8, 4), + (_('Family Node'), 'family', 7, 0), + (_('Family Divorced'), 'family-divorced', 9, 0), + (_('Home Person'), 'home-person', 6, 0), + (_('Border Male Alive'), 'border-male-alive', 3, 0), + (_('Border Male Dead'), 'border-male-dead', 5, 0), + (_('Border Female Alive'), 'border-female-alive', 3, 4), + (_('Border Female Dead'), 'border-female-dead', 5, 4), + (_('Border Unknown Alive'), 'border-unknown-alive', 7, 4), + (_('Border Unknown Dead'), 'border-unknown-dead', 9, 4), + (_('Border Family'), 'border-family', 8, 0), + (_('Border Family Divorced'), 'border-family-divorced', 10, 0), + ] + + self.colors = {} + for color in color_list: + pref_name = 'colors.' + color[1] + self.colors[pref_name] = self.add_color(grid, color[0], color[2], + pref_name, col=color[3]) return _('Colors'), grid + def restore_colors(self, widget=None): + """ + Restore colors of selected scheme to default. + """ + scheme = config.get('colors.scheme') + for key, widget in self.colors.items(): + color = Gdk.RGBA() + hexval = config.get_default(key)[scheme] + Gdk.RGBA.parse(color, hexval) + widget.set_rgba(color) + def add_advanced_panel(self, configdialog): grid = Gtk.Grid() grid.set_border_width(12) @@ -1205,6 +1246,18 @@ class GrampsPreferences(ConfigureDialog): self.old_format = the_list.get_value(the_iter, COL_FMT) win = DisplayNameEditor(self.uistate, self.dbstate, self.track, self) + def color_scheme_changed(self, obj): + """ + Called on swiching color scheme. + """ + scheme = obj.get_active() + config.set('colors.scheme', scheme) + for key, widget in self.colors.items(): + color = Gdk.RGBA() + hexval = config.get(key)[scheme] + Gdk.RGBA.parse(color, hexval) + widget.set_rgba(color) + def check_for_type_changed(self, obj): active = obj.get_active() if active == 0: # update diff --git a/gramps/gui/dialog.py b/gramps/gui/dialog.py index 0bb6d19ae..01f8f2cb9 100644 --- a/gramps/gui/dialog.py +++ b/gramps/gui/dialog.py @@ -35,6 +35,7 @@ _LOG = logging.getLogger(".dialog") #------------------------------------------------------------------------- from gi.repository import GObject from gi.repository import Gtk +from gi.repository import Gdk from gi.repository import GdkPixbuf #------------------------------------------------------------------------- @@ -46,6 +47,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext from gramps.gen.const import ICON, URL_BUGHOME from gramps.gen.config import config +from gramps.gen.constfunc import is_quartz from .glade import Glade from .display import display_url @@ -506,6 +508,12 @@ def main(args): win = Gtk.Window() win.set_title('Dialog test window') win.set_position(Gtk.WindowPosition.CENTER) + #Set the mnemonic modifier on Macs to alt-ctrl so that it + #doesn't interfere with the extended keyboard, see + #https://gramps-project.org/bugs/view.php?id=6943 + if is_quartz(): + win.set_mnemonic_modifier( + Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD1_MASK) def cb(window, event): Gtk.main_quit() win.connect('delete-event', cb) diff --git a/gramps/gui/editors/displaytabs/embeddedlist.py b/gramps/gui/editors/displaytabs/embeddedlist.py index 8798fb1a3..7bbeaf0a3 100644 --- a/gramps/gui/editors/displaytabs/embeddedlist.py +++ b/gramps/gui/editors/displaytabs/embeddedlist.py @@ -42,6 +42,7 @@ from gi.repository import Pango # Gramps classes # #------------------------------------------------------------------------- +from ...widgets.cellrenderertextedit import CellRendererTextEdit from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext from ...utils import is_right_click @@ -482,7 +483,10 @@ class EmbeddedList(ButtonTab): type_col = self._column_names[pair[1]][3] if (type_col in [TEXT_COL, MARKUP_COL, TEXT_EDIT_COL]): - renderer = Gtk.CellRendererText() + if type_col == TEXT_EDIT_COL: + renderer = CellRendererTextEdit() + else: + renderer = Gtk.CellRendererText() renderer.set_property('ellipsize', Pango.EllipsizeMode.END) if type_col == TEXT_COL or type_col == TEXT_EDIT_COL: column = Gtk.TreeViewColumn(name, renderer, text=pair[1]) @@ -519,9 +523,12 @@ class EmbeddedList(ButtonTab): # insert the colum into the tree column.set_resizable(True) column.set_clickable(True) - column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) - #column.set_min_width(self._column_names[pair[1]][2]) - column.set_fixed_width(self._column_names[pair[1]][2]) + if self._column_names[pair[1]][2] != -1: + column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) + #column.set_min_width(self._column_names[pair[1]][2]) + column.set_fixed_width(self._column_names[pair[1]][2]) + else: + column.set_expand(True) column.set_sort_column_id(self._column_names[pair[1]][1]) self.columns.append(column) diff --git a/gramps/gui/editors/displaytabs/surnametab.py b/gramps/gui/editors/displaytabs/surnametab.py index 71f838723..c3e19a474 100644 --- a/gramps/gui/editors/displaytabs/surnametab.py +++ b/gramps/gui/editors/displaytabs/surnametab.py @@ -45,7 +45,7 @@ _ENTER = Gdk.keyval_from_name("Enter") # #------------------------------------------------------------------------- from .surnamemodel import SurnameModel -from .embeddedlist import EmbeddedList, TEXT_COL, MARKUP_COL, ICON_COL +from .embeddedlist import EmbeddedList, TEXT_EDIT_COL from ...ddtargets import DdTargets from gramps.gen.lib import Surname, NameOriginType from ...utils import get_primary_mask @@ -71,9 +71,9 @@ class SurnameTab(EmbeddedList): #index = column in model. Value = # (name, sortcol in model, width, markup/text _column_names = [ - (_('Prefix'), -1, 150, TEXT_COL, -1, None), - (_('Surname'), -1, 250, TEXT_COL, -1, None), - (_('Connector'), -1, 100, TEXT_COL, -1, None), + (_('Prefix'), 0, 150, TEXT_EDIT_COL, -1, None), + (_('Surname'), 1, -1, TEXT_EDIT_COL, -1, None), + (_('Connector'), 2, 100, TEXT_EDIT_COL, -1, None), ] _column_combo = (_('Origin'), -1, 150, 3) # name, sort, width, modelcol _column_toggle = (_('Name|Primary'), -1, 80, 4) @@ -94,14 +94,6 @@ class SurnameTab(EmbeddedList): #first the standard text columns with normal method EmbeddedList.build_columns(self) - # Need to add attributes to renderers - # and connect renderers to the 'edited' signal - for colno in range(len(self.columns)): - for renderer in self.columns[colno].get_cells(): - renderer.set_property('editable', not self.dbstate.db.readonly) - renderer.connect('editing_started', self.on_edit_start, colno) - renderer.connect('edited', self.on_edit_inline, self.column_order()[colno][1]) - # now we add the two special columns # combobox for type colno = len(self.columns) @@ -133,7 +125,7 @@ class SurnameTab(EmbeddedList): column.set_resizable(True) column.set_sort_column_id(self._column_combo[1]) column.set_min_width(self._column_combo[2]) - column.set_expand(True) + column.set_expand(False) self.columns.append(column) self.tree.append_column(column) # toggle box for primary @@ -149,7 +141,7 @@ class SurnameTab(EmbeddedList): column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) column.set_alignment(0.5) column.set_sort_column_id(self._column_toggle[1]) - column.set_min_width(self._column_toggle[2]) + column.set_max_width(self._column_toggle[2]) self.columns.append(column) self.tree.append_column(column) @@ -161,6 +153,24 @@ class SurnameTab(EmbeddedList): ## svalue = self.cmborigmap[second] ## return glocale.strcoll(fvalue, svalue) + def setup_editable_col(self): + """ + inherit this and set the variables needed for editable columns + Variable edit_col_funcs needs to be a dictionary from model col_nr to + function to call for + Example: + self.edit_col_funcs ={1: {'edit_start': self.on_edit_start, + 'edited': self.on_edited + }} + """ + self.edit_col_funcs = { + 0: {'edit_start': self.on_edit_start, + 'edited': self.on_edit_inline}, + 1: {'edit_start': self.on_edit_start, + 'edited': self.on_edit_inline}, + 2: {'edit_start': self.on_edit_start, + 'edited': self.on_edit_inline}} + def get_data(self): return self.obj.get_surname_list() @@ -194,6 +204,16 @@ class SurnameTab(EmbeddedList): if self.on_change: self.on_change() + def post_rebuild(self, prebuildpath): + """ + Called when data model has changed, in particular necessary when row + order is updated. + @param prebuildpath: path selected before rebuild, None if none + @type prebuildpath: tree path + """ + if self.on_change: + self.on_change() + def column_order(self): # order of columns for EmbeddedList. Only the text columns here return ((1, 0), (1, 1), (1, 2)) @@ -239,11 +259,13 @@ class SurnameTab(EmbeddedList): """ self.on_edit_start(cellr, celle, path, colnr) #set up autocomplete + entry = celle.get_child() + entry.set_width_chars(10) completion = Gtk.EntryCompletion() completion.set_model(self.cmborig) completion.set_minimum_key_length(1) completion.set_text_column(1) - celle.get_child().set_completion(completion) + entry.set_completion(completion) # celle.connect('changed', self.on_origcmb_change, path, colnr) diff --git a/gramps/gui/editors/editperson.py b/gramps/gui/editors/editperson.py index 0f0477dc9..cd8276a52 100644 --- a/gramps/gui/editors/editperson.py +++ b/gramps/gui/editors/editperson.py @@ -197,9 +197,6 @@ class EditPerson(EditPrimary): self.singsurnfr = SingSurn(self.top) self.multsurnfr = self.top.get_object("hboxmultsurnames") self.singlesurn_active = True - self.surntab = SurnameTab(self.dbstate, self.uistate, self.track, - self.obj.get_primary_name(), - on_change=self._changed_name) self.set_contexteventbox(self.top.get_object("eventboxtop")) @@ -445,6 +442,9 @@ class EditPerson(EditPrimary): self.preview_name = self.top.get_object("full_name") self.preview_name.override_font(Pango.FontDescription('sans bold 12')) + self.surntab = SurnameTab(self.dbstate, self.uistate, self.track, + self.obj.get_primary_name(), + on_change=self._changed_name) def get_start_date(self): """ @@ -936,7 +936,8 @@ class EditPerson(EditPrimary): msurhbox = self.top.get_object("hboxmultsurnames") msurhbox.remove(self.surntab) self.surntab = SurnameTab(self.dbstate, self.uistate, self.track, - self.obj.get_primary_name()) + self.obj.get_primary_name(), + on_change=self._changed_name) self.multsurnfr.set_size_request(-1, int(config.get('interface.surname-box-height'))) msurhbox.pack_start(self.surntab, True, True, 0) diff --git a/gramps/gui/glade.py b/gramps/gui/glade.py index b6985dea7..1dcc0226e 100644 --- a/gramps/gui/glade.py +++ b/gramps/gui/glade.py @@ -46,6 +46,7 @@ from gi.repository import Gtk # #------------------------------------------------------------------------ from gramps.gen.const import GLADE_DIR, GRAMPS_LOCALE as glocale +from gramps.gen.constfunc import is_quartz #------------------------------------------------------------------------ # @@ -142,11 +143,19 @@ class Glade(Gtk.Builder): # toplevel is given if toplevel: loadlist = [toplevel] + also_load - self.add_objects_from_file(path, loadlist) + with open(path, 'r', encoding='utf-8') as builder_file: + data = builder_file.read().replace('\n', '') + if is_quartz(): + data = data.replace('GDK_CONTROL_MASK', 'GDK_META_MASK') + self.add_objects_from_string(data, loadlist) self.__toplevel = self.get_object(toplevel) # toplevel not given else: - self.add_from_file(path) + with open(path, 'r', encoding='utf-8') as builder_file: + data = builder_file.read().replace('\n', '') + if is_quartz(): + data = data.replace('GDK_CONTROL_MASK', 'GDK_META_MASK') + self.add_from_string(data) # first, use filename as possible toplevel widget name self.__toplevel = self.get_object(filename.rpartition('.')[0]) diff --git a/gramps/gui/glade/editcitation.glade b/gramps/gui/glade/editcitation.glade index a0844298a..4f8d0fa03 100644 --- a/gramps/gui/glade/editcitation.glade +++ b/gramps/gui/glade/editcitation.glade @@ -197,7 +197,7 @@ True - True + False Conveys the submitter's quantitative evaluation of the credibility of a piece of information, based upon its supporting evidence. It is not intended to eliminate the receiver's need to evaluate the evidence for themselves. Very Low =Unreliable evidence or estimated data Low =Questionable reliability of evidence (interviews, census, oral genealogies, or potential for bias for example, an autobiography) diff --git a/gramps/gui/managedwindow.py b/gramps/gui/managedwindow.py index b21a33018..a6b41a1af 100644 --- a/gramps/gui/managedwindow.py +++ b/gramps/gui/managedwindow.py @@ -38,6 +38,7 @@ from io import StringIO # #------------------------------------------------------------------------- from gi.repository import Gtk +from gi.repository import Gdk #------------------------------------------------------------------------- # @@ -47,6 +48,7 @@ from gi.repository import Gtk from gramps.gen.const import GLADE_FILE, ICON from gramps.gen.errors import WindowActiveError from gramps.gen.config import config +from gramps.gen.constfunc import is_quartz from .glade import Glade #------------------------------------------------------------------------- @@ -488,6 +490,13 @@ class ManagedWindow: #closing the Gtk.Window must also close ManagedWindow self.window = window self.window.connect('delete-event', self.close) + #Set the mnemonic modifier on Macs to alt-ctrl so that it + #doesn't interfere with the extended keyboard, see + #https://gramps-project.org/bugs/view.php?id=6943 + if is_quartz(): + self.window.set_mnemonic_modifier( + Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD1_MASK) + if self.modal: self.window.set_modal(True) # The following makes sure that we only have one modal window open; diff --git a/gramps/gui/plug/report/_bookdialog.py b/gramps/gui/plug/report/_bookdialog.py index 146f43de5..19bb45159 100644 --- a/gramps/gui/plug/report/_bookdialog.py +++ b/gramps/gui/plug/report/_bookdialog.py @@ -684,7 +684,7 @@ class BookSelector(ManagedWindow): old_margins = self.book.get_margins() old_format_name = self.book.get_format_name() old_output = self.book.get_output() - BookDialog(self.dbstate, self.uistate, self.book, BookOptions) + BookDialog(self.dbstate, self.uistate, self.book, BookOptions, track=self.track) new_paper_name = self.book.get_paper_name() new_orientation = self.book.get_orientation() new_paper_metric = self.book.get_paper_metric() @@ -918,7 +918,7 @@ class BookDialog(DocReportDialog): Create a dialog selecting target, format, and paper/HTML options. """ - def __init__(self, dbstate, uistate, book, options): + def __init__(self, dbstate, uistate, book, options, track=[]): self.format_menu = None self.options = options self.page_html_added = False @@ -926,7 +926,7 @@ class BookDialog(DocReportDialog): self.title = _('Generate Book') self.database = dbstate.db DocReportDialog.__init__(self, dbstate, uistate, options, - 'book', self.title) + 'book', self.title, track=track) self.options.options_dict['bookname'] = self.book.get_name() response = self.window.run() diff --git a/gramps/gui/plug/report/_docreportdialog.py b/gramps/gui/plug/report/_docreportdialog.py index 4efc9bb35..0725259ad 100644 --- a/gramps/gui/plug/report/_docreportdialog.py +++ b/gramps/gui/plug/report/_docreportdialog.py @@ -63,7 +63,7 @@ class DocReportDialog(ReportDialog): dialogs for docgen derived reports. """ - def __init__(self, dbstate, uistate, option_class, name, trans_name): + def __init__(self, dbstate, uistate, option_class, name, trans_name, track=[]): """Initialize a dialog to request that the user select options for a basic *stand-alone* report.""" @@ -72,7 +72,7 @@ class DocReportDialog(ReportDialog): self.css = PLUGMAN.process_plugin_data('WEBSTUFF') self.dbname = dbstate.db.get_dbname() ReportDialog.__init__(self, dbstate, uistate, option_class, - name, trans_name) + name, trans_name, track=track) self.basedocname = None # keep pylint happy self.css_filename = None diff --git a/gramps/gui/plug/report/_graphreportdialog.py b/gramps/gui/plug/report/_graphreportdialog.py new file mode 100644 index 000000000..9cb240a5c --- /dev/null +++ b/gramps/gui/plug/report/_graphreportdialog.py @@ -0,0 +1,289 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2007-2008 Brian G. Matherly +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2009 Gary Burton +# Contribution 2009 by Bob Ham +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2012-2013 Paul Franklin +# Copyright (C) 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 +# 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. +# + +"""base class for generating dialogs for graph-based reports """ + +#------------------------------------------------------------------------ +# +# python modules +# +#------------------------------------------------------------------------ +from abc import ABCMeta, abstractmethod +import os + +#------------------------------------------------------------------------------- +# +# GTK+ modules +# +#------------------------------------------------------------------------------- +from gi.repository import Gtk +from gi.repository import GObject + +#------------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------------- +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext +from gramps.gen.config import config +from gramps.gen.plug.report import CATEGORY_GRAPHVIZ +from ._reportdialog import ReportDialog +from ._papermenu import PaperFrame +import gramps.gen.plug.docgen.graphdoc as graphdoc +from gramps.gen.plug.menu import Menu + +#------------------------------------------------------------------------------- +# +# GraphvizFormatComboBox +# +#------------------------------------------------------------------------------- +class BaseFormatComboBox(Gtk.ComboBox): + """ + Combo box base class for graph-based report format choices. + """ + FORMATS = [] + + def set(self, active=None): + """ initialize the Graphviz choices """ + store = Gtk.ListStore(GObject.TYPE_STRING) + self.set_model(store) + cell = Gtk.CellRendererText() + self.pack_start(cell, True) + self.add_attribute(cell, 'text', 0) + + index = 0 + active_index = 0 + for item in self.FORMATS: + name = item["descr"] + store.append(row=[name]) + if item['type'] == active: + active_index = index + index += 1 + self.set_active(active_index) + + def get_label(self): + """ get the format description """ + return self.FORMATS[self.get_active()]["descr"] + + def get_reference(self): + """ get the format class """ + return self.FORMATS[self.get_active()]["class"] + + def get_ext(self): + """ get the format extension """ + return '.%s' % self.FORMATS[self.get_active()]['ext'] + + def get_clname(self): + """ get the report's output format type""" + return self.FORMATS[self.get_active()]["type"] + +#----------------------------------------------------------------------- +# +# GraphReportDialog +# +#----------------------------------------------------------------------- +class GraphReportDialog(ReportDialog, metaclass=ABCMeta): + """A base class of ReportDialog customized for graph-based reports.""" + + def __init__(self, dbstate, uistate, opt, name, translated_name): + """Initialize a dialog to request that the user select options + for a Graphviz report. See the ReportDialog class for + more information.""" + self.category = self.get_category() + self._goptions = self.get_options() + self.dbname = dbstate.db.get_dbname() + ReportDialog.__init__(self, dbstate, uistate, opt, + name, translated_name) + + self.doc = None # keep pylint happy + self.format = None + self.paper_label = None + + def init_options(self, option_class): + try: + if issubclass(option_class, object): # Old-style class + self.options = option_class(self.raw_name, + self.dbstate.get_database()) + except TypeError: + self.options = option_class + + menu = Menu() + self._goptions.add_menu_options(menu) + + for category in menu.get_categories(): + for name in menu.get_option_names(category): + option = menu.get_option(category, name) + self.options.add_menu_option(category, name, option) + + self.options.load_previous_values() + + def init_interface(self): + ReportDialog.init_interface(self) + self.doc_type_changed(self.format_menu) + self.notebook.set_current_page(1) # don't start on "Paper Options" + + def setup_format_frame(self): + """Set up the format frame of the dialog.""" + self.make_doc_menu() + self.format_menu.set(self.options.handler.get_format_name()) + self.format_menu.connect('changed', self.doc_type_changed) + label = Gtk.Label(label=_("%s:") % _("Output Format")) + label.set_halign(Gtk.Align.START) + self.grid.attach(label, 1, self.row, 1, 1) + self.format_menu.set_hexpand(True) + self.grid.attach(self.format_menu, 2, self.row, 2, 1) + self.row += 1 + + self.open_with_app = Gtk.CheckButton( + label=_("Open with default viewer")) + self.open_with_app.set_active( + config.get('interface.open-with-default-viewer')) + self.grid.attach(self.open_with_app, 2, self.row, 2, 1) + self.row += 1 + + ext = self.format_menu.get_ext() + if ext is None: + ext = "" + else: + spath = self.get_default_directory() + if self.options.get_output(): + base = os.path.basename(self.options.get_output()) + else: + if self.dbname is None: + default_name = self.raw_name + else: + default_name = self.dbname + "_" + self.raw_name + base = "%s%s" % (default_name, ext) # "ext" already has a dot + spath = os.path.normpath(os.path.join(spath, base)) + self.target_fileentry.set_filename(spath) + + def setup_report_options_frame(self): + self.paper_label = Gtk.Label(label='%s' % _("Paper Options")) + self.paper_label.set_use_markup(True) + handler = self.options.handler + self.paper_frame = PaperFrame( + handler.get_paper_metric(), + handler.get_paper_name(), + handler.get_orientation(), + handler.get_margins(), + handler.get_custom_paper_size()) + self.notebook.insert_page(self.paper_frame, self.paper_label, 0) + self.paper_frame.show_all() + + ReportDialog.setup_report_options_frame(self) + + def doc_type_changed(self, obj): + """ + This routine is called when the user selects a new file + format for the report. It adjusts the various dialog sections + to reflect the appropriate values for the currently selected + file format. For example, a HTML document doesn't need any + paper size/orientation options, but it does need a template + file. Those changes are made here. + """ + self.open_with_app.set_sensitive(True) + + fname = self.target_fileentry.get_full_path(0) + (spath, ext) = os.path.splitext(fname) + + ext_val = obj.get_ext() + if ext_val: + fname = spath + ext_val + else: + fname = spath + self.target_fileentry.set_filename(fname) + + def make_document(self): + """Create a document of the type requested by the user. + """ + pstyle = self.paper_frame.get_paper_style() + + self.doc = self.format(self.options, pstyle) + + self.options.set_document(self.doc) + + def on_ok_clicked(self, obj): + """The user is satisfied with the dialog choices. Validate + the output file name before doing anything else. If there is + a file name, gather the options and create the report.""" + + # Is there a filename? This should also test file permissions, etc. + if not self.parse_target_frame(): + self.window.run() + + # Preparation + self.parse_format_frame() + self.parse_user_options() + + self.options.handler.set_paper_metric( + self.paper_frame.get_paper_metric()) + self.options.handler.set_paper_name( + self.paper_frame.get_paper_name()) + self.options.handler.set_orientation( + self.paper_frame.get_orientation()) + self.options.handler.set_margins( + self.paper_frame.get_paper_margins()) + self.options.handler.set_custom_paper_size( + self.paper_frame.get_custom_paper_size()) + + # Create the output document. + self.make_document() + + # Save options + self.options.handler.save_options() + config.set('interface.open-with-default-viewer', + self.open_with_app.get_active()) + + def parse_format_frame(self): + """Parse the format frame of the dialog. Save the user + selected output format for later use.""" + self.format = self.format_menu.get_reference() + format_name = self.format_menu.get_clname() + self.options.handler.set_format_name(format_name) + + def setup_style_frame(self): + """Required by ReportDialog""" + pass + + @abstractmethod + def make_doc_menu(self): + """ + Build a menu of document types that are appropriate for + this graph report. + """ + + @abstractmethod + def get_category(self): + """ + Return the report category. + """ + + @abstractmethod + def get_options(self): + """ + Return the graph options. + """ diff --git a/gramps/gui/plug/report/_graphvizreportdialog.py b/gramps/gui/plug/report/_graphvizreportdialog.py index 4dd0d26a3..cf8d66ef9 100644 --- a/gramps/gui/plug/report/_graphvizreportdialog.py +++ b/gramps/gui/plug/report/_graphvizreportdialog.py @@ -1,12 +1,7 @@ # # Gramps - a GTK+/GNOME based genealogy program # -# Copyright (C) 2007-2008 Brian G. Matherly -# Copyright (C) 2007-2009 Stephane Charette -# Copyright (C) 2009 Gary Burton -# Contribution 2009 by Bob Ham -# Copyright (C) 2010 Jakim Friant -# Copyright (C) 2012-2013 Paul Franklin +# Copyright (C) 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 @@ -23,174 +18,42 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -""" a ReportDialog customized for Graphviz-based reports """ - -#------------------------------------------------------------------------ -# -# python modules -# -#------------------------------------------------------------------------ -import os - -#------------------------------------------------------------------------------- -# -# GTK+ modules -# -#------------------------------------------------------------------------------- -from gi.repository import Gtk -from gi.repository import GObject +"""class for generating dialogs for graphviz-based reports """ #------------------------------------------------------------------------------- # # Gramps modules # #------------------------------------------------------------------------------- -from gramps.gen.const import GRAMPS_LOCALE as glocale -_ = glocale.translation.gettext -from gramps.gen.config import config +from ._graphreportdialog import GraphReportDialog, BaseFormatComboBox from gramps.gen.plug.report import CATEGORY_GRAPHVIZ -from ._reportdialog import ReportDialog -from ._papermenu import PaperFrame import gramps.gen.plug.docgen.graphdoc as graphdoc -from gramps.gen.plug.menu import Menu - -#------------------------------------------------------------------------------- -# -# GraphvizFormatComboBox -# -#------------------------------------------------------------------------------- -class GraphvizFormatComboBox(Gtk.ComboBox): - """ - Combo box class for Graphviz report format choices. - """ - def set(self, active=None): - """ initialize the Graphviz choices """ - store = Gtk.ListStore(GObject.TYPE_STRING) - self.set_model(store) - cell = Gtk.CellRendererText() - self.pack_start(cell, True) - self.add_attribute(cell, 'text', 0) - - index = 0 - active_index = 0 - for item in graphdoc.FORMATS: - name = item["descr"] - store.append(row=[name]) - if item['type'] == active: - active_index = index - index += 1 - self.set_active(active_index) - - def get_label(self): - """ get the format description """ - return graphdoc.FORMATS[self.get_active()]["descr"] - - def get_reference(self): - """ get the format class """ - return graphdoc.FORMATS[self.get_active()]["class"] - - def get_ext(self): - """ get the format extension """ - return '.%s' % graphdoc.FORMATS[self.get_active()]['ext'] - - def get_clname(self): - """ get the report's output format type""" - return graphdoc.FORMATS[self.get_active()]["type"] #----------------------------------------------------------------------- # # GraphvizReportDialog # #----------------------------------------------------------------------- -class GraphvizReportDialog(ReportDialog): - """A class of ReportDialog customized for Graphviz-based reports.""" +class GraphvizReportDialog(GraphReportDialog): - def __init__(self, dbstate, uistate, opt, name, translated_name): - """Initialize a dialog to request that the user select options - for a Graphviz report. See the ReportDialog class for - more information.""" - self.category = CATEGORY_GRAPHVIZ - self.__gvoptions = graphdoc.GVOptions() - self.dbname = dbstate.db.get_dbname() - ReportDialog.__init__(self, dbstate, uistate, opt, - name, translated_name) - - self.doc = None # keep pylint happy - self.format = None - self.paper_label = None - - def init_options(self, option_class): - try: - if issubclass(option_class, object): # Old-style class - self.options = option_class(self.raw_name, - self.dbstate.get_database()) - except TypeError: - self.options = option_class - - menu = Menu() - self.__gvoptions.add_menu_options(menu) - - for category in menu.get_categories(): - for name in menu.get_option_names(category): - option = menu.get_option(category, name) - self.options.add_menu_option(category, name, option) - - self.options.load_previous_values() - - def init_interface(self): - ReportDialog.init_interface(self) - self.doc_type_changed(self.format_menu) - self.notebook.set_current_page(1) # don't start on "Paper Options" - - def setup_format_frame(self): - """Set up the format frame of the dialog.""" + def make_doc_menu(self): + """ + Build a menu of document types that are appropriate for + this graph report. + """ self.format_menu = GraphvizFormatComboBox() - self.format_menu.set(self.options.handler.get_format_name()) - self.format_menu.connect('changed', self.doc_type_changed) - label = Gtk.Label(label=_("%s:") % _("Output Format")) - label.set_halign(Gtk.Align.START) - self.grid.attach(label, 1, self.row, 1, 1) - self.format_menu.set_hexpand(True) - self.grid.attach(self.format_menu, 2, self.row, 2, 1) - self.row += 1 - self.open_with_app = Gtk.CheckButton( - label=_("Open with default viewer")) - self.open_with_app.set_active( - config.get('interface.open-with-default-viewer')) - self.grid.attach(self.open_with_app, 2, self.row, 2, 1) - self.row += 1 + def get_category(self): + """ + Return the report category. + """ + return CATEGORY_GRAPHVIZ - ext = self.format_menu.get_ext() - if ext is None: - ext = "" - else: - spath = self.get_default_directory() - if self.options.get_output(): - base = os.path.basename(self.options.get_output()) - else: - if self.dbname is None: - default_name = self.raw_name - else: - default_name = self.dbname + "_" + self.raw_name - base = "%s%s" % (default_name, ext) # "ext" already has a dot - spath = os.path.normpath(os.path.join(spath, base)) - self.target_fileentry.set_filename(spath) - - def setup_report_options_frame(self): - self.paper_label = Gtk.Label(label='%s' % _("Paper Options")) - self.paper_label.set_use_markup(True) - handler = self.options.handler - self.paper_frame = PaperFrame( - handler.get_paper_metric(), - handler.get_paper_name(), - handler.get_orientation(), - handler.get_margins(), - handler.get_custom_paper_size()) - self.notebook.insert_page(self.paper_frame, self.paper_label, 0) - self.paper_frame.show_all() - - ReportDialog.setup_report_options_frame(self) + def get_options(self): + """ + Return the graph options. + """ + return graphdoc.GVOptions() def doc_type_changed(self, obj): """ @@ -201,84 +64,31 @@ class GraphvizReportDialog(ReportDialog): paper size/orientation options, but it does need a template file. Those changes are made here. """ - self.open_with_app.set_sensitive(True) - - fname = self.target_fileentry.get_full_path(0) - (spath, ext) = os.path.splitext(fname) - - ext_val = obj.get_ext() - if ext_val: - fname = spath + ext_val - else: - fname = spath - self.target_fileentry.set_filename(fname) + GraphReportDialog.doc_type_changed(self, obj) output_format_str = obj.get_clname() if output_format_str in ['gvpdf', 'gspdf', 'ps']: # Always use 72 DPI for PostScript and PDF files. - self.__gvoptions.dpi.set_value(72) - self.__gvoptions.dpi.set_available(False) + self._goptions.dpi.set_value(72) + self._goptions.dpi.set_available(False) else: - self.__gvoptions.dpi.set_available(True) + self._goptions.dpi.set_available(True) if output_format_str in ['gspdf', 'dot']: # Multiple pages only valid for dot and PDF via GhostsScript. - self.__gvoptions.h_pages.set_available(True) - self.__gvoptions.v_pages.set_available(True) + self._goptions.h_pages.set_available(True) + self._goptions.v_pages.set_available(True) else: - self.__gvoptions.h_pages.set_value(1) - self.__gvoptions.v_pages.set_value(1) - self.__gvoptions.h_pages.set_available(False) - self.__gvoptions.v_pages.set_available(False) + self._goptions.h_pages.set_value(1) + self._goptions.v_pages.set_value(1) + self._goptions.h_pages.set_available(False) + self._goptions.v_pages.set_available(False) - def make_document(self): - """Create a document of the type requested by the user. - """ - pstyle = self.paper_frame.get_paper_style() - self.doc = self.format(self.options, pstyle) - - self.options.set_document(self.doc) - - def on_ok_clicked(self, obj): - """The user is satisfied with the dialog choices. Validate - the output file name before doing anything else. If there is - a file name, gather the options and create the report.""" - - # Is there a filename? This should also test file permissions, etc. - if not self.parse_target_frame(): - self.window.run() - - # Preparation - self.parse_format_frame() - self.parse_user_options() - - self.options.handler.set_paper_metric( - self.paper_frame.get_paper_metric()) - self.options.handler.set_paper_name( - self.paper_frame.get_paper_name()) - self.options.handler.set_orientation( - self.paper_frame.get_orientation()) - self.options.handler.set_margins( - self.paper_frame.get_paper_margins()) - self.options.handler.set_custom_paper_size( - self.paper_frame.get_custom_paper_size()) - - # Create the output document. - self.make_document() - - # Save options - self.options.handler.save_options() - config.set('interface.open-with-default-viewer', - self.open_with_app.get_active()) - - def parse_format_frame(self): - """Parse the format frame of the dialog. Save the user - selected output format for later use.""" - self.format = self.format_menu.get_reference() - format_name = self.format_menu.get_clname() - self.options.handler.set_format_name(format_name) - - def setup_style_frame(self): - """Required by ReportDialog""" - pass +#------------------------------------------------------------------------------- +# +# GraphvizFormatComboBox +# +#------------------------------------------------------------------------------- +class GraphvizFormatComboBox(BaseFormatComboBox): + FORMATS = graphdoc.FORMATS diff --git a/gramps/gui/plug/report/_reportdialog.py b/gramps/gui/plug/report/_reportdialog.py index 73b181c43..fc7634c5d 100644 --- a/gramps/gui/plug/report/_reportdialog.py +++ b/gramps/gui/plug/report/_reportdialog.py @@ -55,7 +55,8 @@ from ...user import User from ...dialog import ErrorDialog, OptionDialog from gramps.gen.plug.report import (CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_BOOK, CATEGORY_CODE, CATEGORY_WEB, - CATEGORY_GRAPHVIZ, standalone_categories) + CATEGORY_GRAPHVIZ, CATEGORY_TREE, + standalone_categories) from gramps.gen.plug.docgen import StyleSheet, StyleSheetList from ...managedwindow import ManagedWindow from ._stylecombobox import StyleComboBox @@ -676,6 +677,9 @@ def report(dbstate, uistate, person, report_class, options_class, elif category == CATEGORY_GRAPHVIZ: from ._graphvizreportdialog import GraphvizReportDialog dialog_class = GraphvizReportDialog + elif category == CATEGORY_TREE: + from ._treereportdialog import TreeReportDialog + dialog_class = TreeReportDialog elif category == CATEGORY_WEB: from ._webreportdialog import WebReportDialog dialog_class = WebReportDialog diff --git a/gramps/gui/plug/report/_treereportdialog.py b/gramps/gui/plug/report/_treereportdialog.py new file mode 100644 index 000000000..94f631219 --- /dev/null +++ b/gramps/gui/plug/report/_treereportdialog.py @@ -0,0 +1,64 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 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 +# 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. +# + +"""class for generating dialogs for graphviz-based reports """ + +#------------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------------- +from ._graphreportdialog import GraphReportDialog, BaseFormatComboBox +from gramps.gen.plug.report import CATEGORY_TREE +import gramps.gen.plug.docgen.treedoc as treedoc + +#----------------------------------------------------------------------- +# +# TreeReportDialog +# +#----------------------------------------------------------------------- +class TreeReportDialog(GraphReportDialog): + + def make_doc_menu(self): + """ + Build a menu of document types that are appropriate for + this graph report. + """ + self.format_menu = TreeFormatComboBox() + + def get_category(self): + """ + Return the report category. + """ + return CATEGORY_TREE + + def get_options(self): + """ + Return the graph options. + """ + return treedoc.TreeOptions() + +#------------------------------------------------------------------------------- +# +# TreeFormatComboBox +# +#------------------------------------------------------------------------------- +class TreeFormatComboBox(BaseFormatComboBox): + FORMATS = treedoc.FORMATS diff --git a/gramps/gui/utils.py b/gramps/gui/utils.py index 4e8072367..4a504a096 100644 --- a/gramps/gui/utils.py +++ b/gramps/gui/utils.py @@ -53,6 +53,7 @@ from gi.repository import Gdk #------------------------------------------------------------------------- from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext +from gramps.gen.lib import EventType, EventRoleType from gramps.gen.lib.person import Person from gramps.gen.constfunc import has_display, is_quartz, mac, win from gramps.gen.config import config @@ -314,6 +315,7 @@ class ProgressMeter: """ Close the progress meter """ + del self.__cancel_callback self.__dialog.destroy() #------------------------------------------------------------------------- @@ -473,33 +475,50 @@ def is_right_click(event): if Gdk.Event.triggers_context_menu(event): return True +def color_graph_family(family, dbstate): + """ + :return: based on the config the color for graph family node in hex + :rtype: tuple (hex color fill, hex color border) + """ + scheme = config.get('colors.scheme') + for event_ref in family.get_event_ref_list(): + event = dbstate.db.get_event_from_handle(event_ref.ref) + if (event.type == EventType.DIVORCE and + event_ref.get_role() in (EventRoleType.FAMILY, + EventRoleType.PRIMARY)): + return (config.get('colors.family-divorced')[scheme], + config.get('colors.border-family-divorced')[scheme]) + return (config.get('colors.family')[scheme], + config.get('colors.border-family')[scheme]) + def color_graph_box(alive=False, gender=Person.MALE): """ :return: based on the config the color for graph boxes in hex If gender is None, an empty box is assumed :rtype: tuple (hex color fill, hex color border) """ + scheme = config.get('colors.scheme') if gender == Person.MALE: if alive: - return (config.get('preferences.color-gender-male-alive'), - config.get('preferences.bordercolor-gender-male-alive')) + return (config.get('colors.male-alive')[scheme], + config.get('colors.border-male-alive')[scheme]) else: - return (config.get('preferences.color-gender-male-death'), - config.get('preferences.bordercolor-gender-male-death')) + return (config.get('colors.male-dead')[scheme], + config.get('colors.border-male-dead')[scheme]) elif gender == Person.FEMALE: if alive: - return (config.get('preferences.color-gender-female-alive'), - config.get('preferences.bordercolor-gender-female-alive')) + return (config.get('colors.female-alive')[scheme], + config.get('colors.border-female-alive')[scheme]) else: - return (config.get('preferences.color-gender-female-death'), - config.get('preferences.bordercolor-gender-female-death')) + return (config.get('colors.female-dead')[scheme], + config.get('colors.border-female-dead')[scheme]) elif gender == Person.UNKNOWN: if alive: - return (config.get('preferences.color-gender-unknown-alive'), - config.get('preferences.bordercolor-gender-unknown-alive')) + return (config.get('colors.unknown-alive')[scheme], + config.get('colors.border-unknown-alive')[scheme]) else: - return (config.get('preferences.color-gender-unknown-death'), - config.get('preferences.bordercolor-gender-unknown-death')) + return (config.get('colors.unknown-dead')[scheme], + config.get('colors.border-unknown-dead')[scheme]) #empty box, no gender return ('#d2d6ce', '#000000') ## print 'male alive', rgb_to_hex((185/256.0, 207/256.0, 231/256.0)) @@ -543,6 +562,21 @@ def rgb_to_hex(rgb): rgbint = (int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255)) return '#%02x%02x%02x' % rgbint +def get_link_color(context): + """ + Find the link color for the current theme. + """ + from gi.repository import Gtk + + if Gtk.get_minor_version() > 11: + col = context.get_color(Gtk.StateFlags.LINK) + else: + found, col = context.lookup_color('link_color') + if not found: + col.parse('blue') + + return rgb_to_hex((col.red, col.green, col.blue)) + def edit_object(dbstate, uistate, reftype, ref): """ Invokes the appropriate editor for an object type and given handle. diff --git a/gramps/gui/viewmanager.py b/gramps/gui/viewmanager.py index ab1177087..46e8dc69b 100644 --- a/gramps/gui/viewmanager.py +++ b/gramps/gui/viewmanager.py @@ -39,6 +39,7 @@ import time import datetime from io import StringIO import posixpath +import gc #------------------------------------------------------------------------- # @@ -54,6 +55,7 @@ LOG = logging.getLogger(".") # #------------------------------------------------------------------------- from gi.repository import Gtk +from gi.repository import Gdk #------------------------------------------------------------------------- # @@ -136,8 +138,6 @@ UIDEFAULT = ''' - - @@ -150,7 +150,6 @@ UIDEFAULT = ''' - @@ -396,7 +395,12 @@ class ViewManager(CLIManager): self.window.set_icon_from_file(ICON) self.window.set_default_size(width, height) self.window.move(horiz_position, vert_position) - + #Set the mnemonic modifier on Macs to alt-ctrl so that it + #doesn't interfere with the extended keyboard, see + #https://gramps-project.org/bugs/view.php?id=6943 + if is_quartz(): + self.window.set_mnemonic_modifier( + Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD1_MASK) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.window.add(vbox) hpane = Gtk.Paned() @@ -599,20 +603,20 @@ class ViewManager(CLIManager): ('Clipboard', 'edit-paste', _('Clip_board'), "b", _("Open the Clipboard dialog"), self.clipboard), ('AddMenu', None, _('_Add')), - ('AddNewMenu', None, _('New')), - ('PersonAdd', None, _('Person'), "p", None, + #('AddNewMenu', None, _('New')), + ('PersonAdd', None, _('Person'), "p", None, self.add_new_person), - ('FamilyAdd', None, _('Family'), "y", None, + ('FamilyAdd', None, _('Family'), "f", None, self.add_new_family), - ('EventAdd', None, _('Event'), "e", None, + ('EventAdd', None, _('Event'), "e", None, self.add_new_event), - ('PlaceAdd', None, _('Place'), "p", None, + ('PlaceAdd', None, _('Place'), "l", None, self.add_new_place), ('SourceAdd', None, _('Source'), "s", None, self.add_new_source), ('CitationAdd', None, _('Citation'), "c", None, self.add_new_citation), - ('RepositoryAdd', None, _('Repository'), "y", None, + ('RepositoryAdd', None, _('Repository'), "r", None, self.add_new_repository), ('MediaAdd', None, _('Media'), "m", None, self.add_new_media), @@ -1769,6 +1773,7 @@ def run_plugin(pdata, dbstate, uistate): name=pdata.id, category=pdata.category, callback=dbstate.db.request_rebuild) + gc.collect(2) def make_plugin_callback(pdata, dbstate, uistate): """ diff --git a/gramps/gui/views/listview.py b/gramps/gui/views/listview.py index 4d43c8bae..106fe4030 100644 --- a/gramps/gui/views/listview.py +++ b/gramps/gui/views/listview.py @@ -278,14 +278,12 @@ class ListView(NavigationView): def foreground_color(self, column, renderer, model, iter_, data=None): ''' Set the foreground color of the cell renderer. We use a cell data - function because we don't want to set the color of untagged rows. + function because there is a problem returning None from a model. ''' fg_color = model.get_value(iter_, model.color_column()) - #for color errors, typically color column is badly set - if fg_color: - renderer.set_property('foreground', fg_color) - else: - LOG.debug('Bad color set: ' + str(fg_color)) + if fg_color == '': + fg_color = None + renderer.set_property('foreground', fg_color) def set_active(self): """ diff --git a/gramps/gui/views/pageview.py b/gramps/gui/views/pageview.py index c0d717a48..6fde0810b 100644 --- a/gramps/gui/views/pageview.py +++ b/gramps/gui/views/pageview.py @@ -156,8 +156,7 @@ class PageView(DbGUIElement, metaclass=ABCMeta): hpane = Gtk.Paned() vpane = Gtk.Paned(orientation=Gtk.Orientation.VERTICAL) hpane.pack1(vpane, resize=True, shrink=False) - hpane.pack2(self.sidebar, resize=False, shrink=True) - self._setup_slider_config(hpane, 'hpane.slider-position') + hpane.pack2(self.sidebar, resize=False, shrink=False) hpane.show() vpane.show() @@ -168,14 +167,31 @@ class PageView(DbGUIElement, metaclass=ABCMeta): self._setup_slider_config(vpane, 'vpane.slider-position') self.sidebar_toggled(self.sidebar.get_property('visible')) + self.hpane_sig = hpane.connect("draw", self.set_page_slider) return hpane - def _setup_slider_config(self, widget, setting): + def set_page_slider(self, widget, dummy): + """ Setup slider. We have the page realized at this point. """ + widget.disconnect(self.hpane_sig) + # get current width of pane + width = widget.get_allocated_width() + # default will use natural size for sidebar until it gets to 400 pix + side_ch = self.sidebar.get_children() # Gtk Notebook + try: + vp_ch = side_ch[0].get_children() # Gtk Viewport child + ch_width = vp_ch[0].get_preferred_width()[0] + 3 + except AttributeError: + ch_width = 300 # needed if no Gramplet installed + pos = width - min(ch_width, 400) + self._setup_slider_config(widget, 'hpane.slider-position', + position=pos) + + def _setup_slider_config(self, widget, setting, position=-1): """ Setup the slider configuration setting. """ - self._config.register(setting, -1) + self._config.register(setting, position) widget.set_position(self._config.get(setting)) widget.connect('notify::position', self._position_changed, setting) diff --git a/gramps/gui/views/treemodels/citationbasemodel.py b/gramps/gui/views/treemodels/citationbasemodel.py index 76991d028..10c953306 100644 --- a/gramps/gui/views/treemodels/citationbasemodel.py +++ b/gramps/gui/views/treemodels/citationbasemodel.py @@ -139,7 +139,7 @@ class CitationBaseModel: tag_handle = data[0] cached, tag_color = self.get_cached_value(tag_handle, "TAG_COLOR") if not cached: - tag_color = "#000000000000" + tag_color = "" tag_priority = None for handle in data[COLUMN_TAGS]: tag = self.db.get_tag_from_handle(handle) @@ -300,7 +300,7 @@ class CitationBaseModel: tag_handle = data[0] cached, tag_color = self.get_cached_value(tag_handle, "TAG_COLOR") if not cached: - tag_color = "#000000000000" + tag_color = "" tag_priority = None for handle in data[COLUMN2_TAGS]: tag = self.db.get_tag_from_handle(handle) diff --git a/gramps/gui/views/treemodels/eventmodel.py b/gramps/gui/views/treemodels/eventmodel.py index 0c43a5e7d..a2ae83096 100644 --- a/gramps/gui/views/treemodels/eventmodel.py +++ b/gramps/gui/views/treemodels/eventmodel.py @@ -208,7 +208,7 @@ class EventModel(FlatBaseModel): tag_handle = data[0] cached, tag_color = self.get_cached_value(tag_handle, "TAG_COLOR") if not cached: - tag_color = "#000000000000" + tag_color = "" tag_priority = None for handle in data[COLUMN_TAGS]: tag = self.db.get_tag_from_handle(handle) diff --git a/gramps/gui/views/treemodels/familymodel.py b/gramps/gui/views/treemodels/familymodel.py index 64d23fb7c..b5de07723 100644 --- a/gramps/gui/views/treemodels/familymodel.py +++ b/gramps/gui/views/treemodels/familymodel.py @@ -220,7 +220,7 @@ class FamilyModel(FlatBaseModel): tag_handle = data[0] cached, tag_color = self.get_cached_value(tag_handle, "TAG_COLOR") if not cached: - tag_color = "#000000000000" + tag_color = "" tag_priority = None for handle in data[13]: tag = self.db.get_tag_from_handle(handle) diff --git a/gramps/gui/views/treemodels/mediamodel.py b/gramps/gui/views/treemodels/mediamodel.py index 9e8c9acd3..aa146da59 100644 --- a/gramps/gui/views/treemodels/mediamodel.py +++ b/gramps/gui/views/treemodels/mediamodel.py @@ -171,7 +171,7 @@ class MediaModel(FlatBaseModel): tag_handle = data[0] cached, tag_color = self.get_cached_value(tag_handle, "TAG_COLOR") if not cached: - tag_color = "#000000000000" + tag_color = "" tag_priority = None for handle in data[11]: tag = self.db.get_tag_from_handle(handle) diff --git a/gramps/gui/views/treemodels/notemodel.py b/gramps/gui/views/treemodels/notemodel.py index 444c82680..82ab7e61e 100644 --- a/gramps/gui/views/treemodels/notemodel.py +++ b/gramps/gui/views/treemodels/notemodel.py @@ -148,7 +148,7 @@ class NoteModel(FlatBaseModel): tag_handle = data[0] cached, value = self.get_cached_value(tag_handle, "TAG_COLOR") if not cached: - tag_color = "#000000000000" + tag_color = "" tag_priority = None for handle in data[Note.POS_TAGS]: tag = self.db.get_tag_from_handle(handle) diff --git a/gramps/gui/views/treemodels/peoplemodel.py b/gramps/gui/views/treemodels/peoplemodel.py index e5b483c1a..8563141bd 100644 --- a/gramps/gui/views/treemodels/peoplemodel.py +++ b/gramps/gui/views/treemodels/peoplemodel.py @@ -538,7 +538,7 @@ class PeopleBaseModel(BaseModel): tag_handle = data[0] cached, value = self.get_cached_value(tag_handle, "TAG_COLOR") if not cached: - tag_color = "#000000000000" + tag_color = "" tag_priority = None for handle in data[COLUMN_TAGS]: tag = self.db.get_tag_from_handle(handle) diff --git a/gramps/gui/views/treemodels/placemodel.py b/gramps/gui/views/treemodels/placemodel.py index f180f01f9..7e87534e7 100644 --- a/gramps/gui/views/treemodels/placemodel.py +++ b/gramps/gui/views/treemodels/placemodel.py @@ -201,7 +201,7 @@ class PlaceBaseModel: tag_handle = data[0] cached, value = self.get_cached_value(tag_handle, "TAG_COLOR") if not cached: - tag_color = "#000000000000" + tag_color = "" tag_priority = None for handle in data[16]: tag = self.db.get_tag_from_handle(handle) diff --git a/gramps/gui/views/treemodels/repomodel.py b/gramps/gui/views/treemodels/repomodel.py index 02790f7d1..01415c7f2 100644 --- a/gramps/gui/views/treemodels/repomodel.py +++ b/gramps/gui/views/treemodels/repomodel.py @@ -253,7 +253,7 @@ class RepositoryModel(FlatBaseModel): tag_handle = data[0] cached, tag_color = self.get_cached_value(tag_handle, "TAG_COLOR") if not cached: - tag_color = "#000000000000" + tag_color = "" tag_priority = None for handle in data[8]: tag = self.db.get_tag_from_handle(handle) diff --git a/gramps/gui/views/treemodels/sourcemodel.py b/gramps/gui/views/treemodels/sourcemodel.py index 62ef58e9a..92996cd3c 100644 --- a/gramps/gui/views/treemodels/sourcemodel.py +++ b/gramps/gui/views/treemodels/sourcemodel.py @@ -143,7 +143,7 @@ class SourceModel(FlatBaseModel): tag_handle = data[0] cached, value = self.get_cached_value(tag_handle, "TAG_COLOR") if not cached: - tag_color = "#000000000000" + tag_color = "" tag_priority = None for handle in data[11]: tag = self.db.get_tag_from_handle(handle) diff --git a/gramps/gui/views/treemodels/treebasemodel.py b/gramps/gui/views/treemodels/treebasemodel.py index ca361f2be..3f6f3fa88 100644 --- a/gramps/gui/views/treemodels/treebasemodel.py +++ b/gramps/gui/views/treemodels/treebasemodel.py @@ -898,7 +898,8 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel): if node.handle is None: # Header rows dont get the foreground color set if col == self.color_column(): - return "#000000000000" + #color must not be utf-8 + return "" # Return the node name for the first column if col == 0: diff --git a/gramps/gui/widgets/__init__.py b/gramps/gui/widgets/__init__.py index 25177d915..71489981f 100644 --- a/gramps/gui/widgets/__init__.py +++ b/gramps/gui/widgets/__init__.py @@ -32,6 +32,7 @@ from .photo import * from .placeentry import * from .monitoredwidgets import * from .selectionwidget import SelectionWidget, Region +from .shadebox import * from .shortlistcomboentry import * from .springseparator import * from .statusbar import Statusbar diff --git a/gramps/gui/widgets/cellrenderertextedit.py b/gramps/gui/widgets/cellrenderertextedit.py new file mode 100644 index 000000000..a2270d365 --- /dev/null +++ b/gramps/gui/widgets/cellrenderertextedit.py @@ -0,0 +1,70 @@ +# +# 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. +# + +#------------------------------------------------------------------------ +# +# Python Modules +# +#------------------------------------------------------------------------ +from gi.repository import Gdk +from gi.repository import Gtk + + +#------------------------------------------------------------------------ +# +# Gramps Modules +# +#------------------------------------------------------------------------ +class CellRendererTextEdit(Gtk.CellRendererText): + """ To be used where you normally use Gtk.CellRendererText and you want to + avoid losing the text if the user clicks outside the cell (Like an 'OK' + button. """ + + __gtype_name__ = 'CellRendererTextEdit' + + def __init__(self): + Gtk.CellRendererText.__init__(self) + + def do_start_editing( + self, event, treeview, path, background_area, cell_area, flags): + if not self.get_property('editable'): + return + entry = Gtk.Entry() + entry.set_has_frame(False) + xalign, yalign = self.get_alignment() + entry.set_alignment(xalign) + entry.set_width_chars(5) + entry.set_text(self.get_property("text")) # get original cell text + entry.add_events(Gdk.EventMask.FOCUS_CHANGE_MASK) + entry.connect('focus-out-event', self.focus_out, path) + entry.connect('key-press-event', self._key_press) + entry.show() + return entry + + def focus_out(self, entry, event, path): + self.emit('edited', path, entry.get_text()) + return False + + def _key_press(self, entry, event): + if event.type == Gdk.EventType.KEY_PRESS: + if event.keyval == Gdk.KEY_Escape: + # get original cell text + entry.set_text(self.get_property("text")) + return False diff --git a/gramps/gui/widgets/fanchart.py b/gramps/gui/widgets/fanchart.py index 259d87633..8b822e0d6 100644 --- a/gramps/gui/widgets/fanchart.py +++ b/gramps/gui/widgets/fanchart.py @@ -1221,7 +1221,9 @@ class FanChartWidget(FanChartBaseWidget): cr.scale(scale, scale) if widget: self.center_xy = self.center_xy_from_delta() - cr.translate(*self.center_xy) + cr.translate(*self.center_xy) + else: + cr.translate(halfdist, halfdist) cr.save() cr.rotate(math.radians(self.rotate_value)) @@ -1596,6 +1598,8 @@ class FanChartGrampsGUI: siblings.append(sib_id) # Collect a list of per-step-family step-siblings for parent_h in [fam.get_father_handle(), fam.get_mother_handle()]: + if not parent_h: + continue parent = self.dbstate.db.get_person_from_handle(parent_h) other_families = [self.dbstate.db.get_family_from_handle(fam_id) for fam_id in parent.get_family_handle_list() diff --git a/gramps/gui/widgets/fanchart2way.py b/gramps/gui/widgets/fanchart2way.py index 47b3faf6a..9ba8331d2 100644 --- a/gramps/gui/widgets/fanchart2way.py +++ b/gramps/gui/widgets/fanchart2way.py @@ -374,7 +374,9 @@ class FanChart2WayWidget(FanChartWidget, FanChartDescWidget): cr.scale(scale, scale) if widget: self.center_xy = self.center_xy_from_delta() - cr.translate(*self.center_xy) + cr.translate(*self.center_xy) + else: + cr.translate(halfdist, halfdist) cr.save() # Draw background diff --git a/gramps/gui/widgets/grampletbar.py b/gramps/gui/widgets/grampletbar.py index 6ec6f8e78..b5cae4bb8 100644 --- a/gramps/gui/widgets/grampletbar.py +++ b/gramps/gui/widgets/grampletbar.py @@ -370,7 +370,7 @@ class GrampletBar(Gtk.Notebook): """ Add a tab to the notebook for the given gramplet. """ - width = min(int(self.uistate.screen_width() * 0.25), 400) + width = -1 # Allow tab width to adjust (smaller) to sidebar height = min(int(self.uistate.screen_height() * 0.20), 400) gramplet.set_size_request(width, height) diff --git a/gramps/gui/widgets/grampletpane.py b/gramps/gui/widgets/grampletpane.py index 0592a0e94..a32160bbd 100644 --- a/gramps/gui/widgets/grampletpane.py +++ b/gramps/gui/widgets/grampletpane.py @@ -48,7 +48,7 @@ from gramps.gen.errors import WindowActiveError from gramps.gen.const import URL_MANUAL_PAGE, VERSION_DIR, COLON from ..editors import EditPerson, EditFamily from ..managedwindow import ManagedWindow -from ..utils import is_right_click, rgb_to_hex, get_primary_mask +from ..utils import is_right_click, get_primary_mask, get_link_color from .menuitem import add_menuitem from ..plug import make_gui_option from ..plug.quick import run_quick_report_by_name @@ -196,12 +196,7 @@ class LinkTag(Gtk.TextTag): lid = 0 #obtaining the theme link color once. Restart needed on theme change! linkcolor = Gtk.Label(label='test') #needed to avoid label destroyed to early - linkcolor = linkcolor.get_style_context().lookup_color('link_color') - if linkcolor[0]: - linkcolor = rgb_to_hex((linkcolor[1].red, linkcolor[1].green, - linkcolor[1].blue)) - else: - linkcolor = 'blue' + linkcolor = get_link_color(linkcolor.get_style_context()) def __init__(self, buffer): LinkTag.lid += 1 diff --git a/gramps/gui/widgets/labels.py b/gramps/gui/widgets/labels.py index 4226a964a..3ff9be1a3 100644 --- a/gramps/gui/widgets/labels.py +++ b/gramps/gui/widgets/labels.py @@ -48,7 +48,7 @@ from gi.repository import Pango # #------------------------------------------------------------------------- from gramps.gen.constfunc import has_display, win -from ..utils import rgb_to_hex +from ..utils import get_link_color #------------------------------------------------------------------------- # @@ -81,11 +81,7 @@ class LinkLabel(Gtk.EventBox): Gtk.EventBox.__init__(self) st_cont = self.get_style_context() - col = st_cont.lookup_color('link_color') - if col[0]: - self.color = rgb_to_hex((col[1].red, col[1].green, col[1].blue)) - else: - self.color = 'blue' + self.color = get_link_color(st_cont) if emph: #emphasize a link diff --git a/gramps/gui/widgets/shadebox.py b/gramps/gui/widgets/shadebox.py new file mode 100644 index 000000000..f839e1665 --- /dev/null +++ b/gramps/gui/widgets/shadebox.py @@ -0,0 +1,58 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2018 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 +# 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. +# + +__all__ = ["ShadeBox"] + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import logging +_LOG = logging.getLogger(".widgets.shadebox") + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +from gi.repository import Gtk + +#------------------------------------------------------------------------- +# +# ShadeBox class +# +#------------------------------------------------------------------------- +class ShadeBox(Gtk.EventBox): + """ + An EventBox with a shaded background. + """ + def __init__(self, use_shade): + Gtk.EventBox.__init__(self) + self.use_shade = use_shade + + def do_draw(self, cr): + if self.use_shade: + tv = Gtk.TextView() + tv_context = tv.get_style_context() + width = self.get_allocated_width() + height = self.get_allocated_height() + Gtk.render_background(tv_context, cr, 0, 0, width, height) + self.get_child().draw(cr) diff --git a/gramps/gui/widgets/styledtexteditor.py b/gramps/gui/widgets/styledtexteditor.py index 8879cb7ae..602d0f556 100644 --- a/gramps/gui/widgets/styledtexteditor.py +++ b/gramps/gui/widgets/styledtexteditor.py @@ -60,9 +60,9 @@ from .toolcomboentry import ToolComboEntry from .springseparator import SpringSeparatorAction from ..spell import Spell from ..display import display_url -from ..utils import SystemFonts, rgb_to_hex, get_primary_mask +from ..utils import SystemFonts, get_primary_mask, get_link_color from gramps.gen.config import config -from gramps.gen.constfunc import has_display +from gramps.gen.constfunc import has_display, mac from ..actiongroup import ActionGroup #------------------------------------------------------------------------- @@ -186,11 +186,7 @@ class StyledTextEditor(Gtk.TextView): self.set_buffer(self.textbuffer) st_cont = self.get_style_context() - col = st_cont.lookup_color('link_color') - if col[0]: - self.linkcolor = rgb_to_hex((col[1].red, col[1].green, col[1].blue)) - else: - self.linkcolor = 'blue' + self.linkcolor = get_link_color(st_cont) self.textbuffer.linkcolor = self.linkcolor self.match = None @@ -319,7 +315,9 @@ class StyledTextEditor(Gtk.TextView): if url.startswith("gramps://"): obj_class, prop, value = url[9:].split("/") display = simple_access.display(obj_class, prop, value) or url - return display + return display + ((_("\nCommand-Click to follow link") if mac() else + _("\nCtrl-Click to follow link")) + if self.get_editable() else '') def on_button_release_event(self, widget, event): """ diff --git a/gramps/gui/widgets/validatedmaskedentry.py b/gramps/gui/widgets/validatedmaskedentry.py index 341a9fbc1..3235b9a52 100644 --- a/gramps/gui/widgets/validatedmaskedentry.py +++ b/gramps/gui/widgets/validatedmaskedentry.py @@ -52,7 +52,7 @@ from gi.repository import Pango #------------------------------------------------------------------------- from gramps.gen.errors import MaskError, ValidationError, WindowActiveError from .undoableentry import UndoableEntry - +from gramps.gen.constfunc import is_quartz #============================================================================ # # MaskedEntry and ValidatableMaskedEntry copied and merged from the Kiwi @@ -1248,6 +1248,12 @@ def main(args): win = Gtk.Window() win.set_title('ValidatableMaskedEntry test window') win.set_position(Gtk.WindowPosition.CENTER) + #Set the mnemonic modifier on Macs to alt-ctrl so that it + #doesn't interfere with the extended keyboard, see + #https://gramps-project.org/bugs/view.php?id=6943 + if is_quartz(): + win.set_mnemonic_modifier( + Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD1_MASK) def cb(window, event): Gtk.main_quit() win.connect('delete-event', cb) diff --git a/gramps/plugins/drawreport/descendtree.py b/gramps/plugins/drawreport/descendtree.py index 2414ff18c..362274c9f 100644 --- a/gramps/plugins/drawreport/descendtree.py +++ b/gramps/plugins/drawreport/descendtree.py @@ -468,8 +468,9 @@ class RecurseDown: #calculate the text. myself.calc_text(self.database, indi_handle, fams_handle) - myself.add_mark(self.database, - self.database.get_person_from_handle(indi_handle)) + if indi_handle: + myself.add_mark(self.database, + self.database.get_person_from_handle(indi_handle)) self.add_to_col(myself) @@ -692,7 +693,8 @@ class MakePersonTree(RecurseDown): family2 = family2_h = None if self.do_parents: family2_h = center1.get_main_parents_family_handle() - family2 = self.database.get_family_from_handle(family2_h) + if family2_h: + family2 = self.database.get_family_from_handle(family2_h) mother2_h = father2_h = None if family2: diff --git a/gramps/plugins/export/exportftree.py b/gramps/plugins/export/exportftree.py index 65d8e2ed4..daea27cac 100644 --- a/gramps/plugins/export/exportftree.py +++ b/gramps/plugins/export/exportftree.py @@ -27,7 +27,6 @@ # standard python modules # #------------------------------------------------------------------------- -import os #------------------------------------------------------------------------ # @@ -43,9 +42,12 @@ log = logging.getLogger(".WriteFtree") # Gramps modules # #------------------------------------------------------------------------- -from gramps.gen.utils.alive import probably_alive +# keep the following line even though not obviously used (works on import) from gramps.gui.plug.export import WriterOptionBox -from gramps.gui.glade import Glade +from gramps.gui.dialog import ErrorDialog +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext + #------------------------------------------------------------------------- # @@ -53,22 +55,25 @@ from gramps.gui.glade import Glade # #------------------------------------------------------------------------- def writeData(database, filename, user, option_box=None): + """ function to export Web Family Tree file """ writer = FtreeWriter(database, filename, user, option_box) return writer.export_data() + #------------------------------------------------------------------------- # # FtreeWriter # #------------------------------------------------------------------------- class FtreeWriter: - + """ Export a Web Family Tree format file """ def __init__(self, database, filename, user, option_box=None): self.db = database self.filename = filename self.user = user self.option_box = option_box - if isinstance(self.user.callback, collections.Callable): # callback is really callable + # is callback is really callable? + if isinstance(self.user.callback, collections.Callable): self.update = self.update_real else: self.update = self.update_empty @@ -78,121 +83,132 @@ class FtreeWriter: self.db = option_box.get_filtered_database(self.db) self.plist = [x for x in self.db.iter_person_handles()] + # the following are used to update the progress meter + self.total = 2 * len(self.plist) + self.count = 0 + self.oldval = 0 # we only update when percentage changes def update_empty(self): + """ used when no callback is present """ pass def update_real(self): + """ Progress update """ self.count += 1 - newval = int(100*self.count/self.total) + newval = int(100 * self.count / self.total) if newval != self.oldval: self.user.callback(newval) self.oldval = newval def export_data(self): + """ main export processing """ name_map = {} id_map = {} id_name = {} - self.count = 0 - self.oldval = 0 - self.total = 2*len(self.plist) for key in self.plist: self.update() - pn = self.db.get_person_from_handle(key).get_primary_name() - sn = pn.get_surname() - items = pn.get_first_name().split() - n = ("%s %s" % (items[0], sn)) if items else sn + pnam = self.db.get_person_from_handle(key).get_primary_name() + snam = pnam.get_surname() + items = pnam.get_first_name().split() + nam = ("%s %s" % (items[0], snam)) if items else snam count = -1 - if n in name_map: + if nam in name_map: count = 0 while 1: - nn = "%s%d" % (n, count) - if nn not in name_map: - break; + nam_num = "%s%d" % (nam, count) + if nam_num not in name_map: + break count += 1 - name_map[nn] = key - id_map[key] = nn + name_map[nam_num] = key + id_map[key] = nam_num else: - name_map[n] = key - id_map[key] = n - id_name[key] = get_name(pn, sn, count) + name_map[nam] = key + id_map[key] = nam + id_name[key] = get_name(pnam, snam, count) - with open(self.filename, "w", encoding='utf_8') as f: + try: + with open(self.filename, "w", encoding='utf_8') as file: + return self._export_data(file, id_name, id_map) + except IOError as msg: + msg2 = _("Could not create %s") % self.filename + ErrorDialog(msg2, str(msg), parent=self.option_box.window) + return False - for key in self.plist: - self.update() - p = self.db.get_person_from_handle(key) - name = id_name[key] - father = mother = email = web = "" + def _export_data(self, file, id_name, id_map): + """ file export processing """ + for key in self.plist: + self.update() + pers = self.db.get_person_from_handle(key) + name = id_name[key] + father = mother = email = web = "" - family_handle = p.get_main_parents_family_handle() - if family_handle: - family = self.db.get_family_from_handle(family_handle) - if family.get_father_handle() and \ - family.get_father_handle() in id_map: - father = id_map[family.get_father_handle()] - if family.get_mother_handle() and \ - family.get_mother_handle() in id_map: - mother = id_map[family.get_mother_handle()] + family_handle = pers.get_main_parents_family_handle() + if family_handle: + family = self.db.get_family_from_handle(family_handle) + if family.get_father_handle() and \ + family.get_father_handle() in id_map: + father = id_map[family.get_father_handle()] + if family.get_mother_handle() and \ + family.get_mother_handle() in id_map: + mother = id_map[family.get_mother_handle()] - # - # Calculate Date - # - birth_ref = p.get_birth_ref() - death_ref = p.get_death_ref() - if birth_ref: - birth_event = self.db.get_event_from_handle(birth_ref.ref) - birth = birth_event.get_date_object() + # + # Calculate Date + # + birth_ref = pers.get_birth_ref() + death_ref = pers.get_death_ref() + if birth_ref: + birth_event = self.db.get_event_from_handle(birth_ref.ref) + birth = birth_event.get_date_object() + else: + birth = None + if death_ref: + death_event = self.db.get_event_from_handle(death_ref.ref) + death = death_event.get_date_object() + else: + death = None + + #if self.restrict: + # alive = probably_alive(pers, self.db) + #else: + # alive = 0 + + if birth: + if death: + dates = "%s-%s" % (fdate(birth), fdate(death)) else: - birth = None - if death_ref: - death_event = self.db.get_event_from_handle(death_ref.ref) - death = death_event.get_date_object() + dates = fdate(birth) + else: + if death: + dates = fdate(death) else: - death = None + dates = "" - #if self.restrict: - # alive = probably_alive(p, self.db) - #else: - # alive = 0 + file.write('%s;%s;%s;%s;%s;%s\n' % + (name, father, mother, email, web, dates)) - if birth: - if death: - dates = "%s-%s" % (fdate(birth), fdate(death)) - else: - dates = fdate(birth) - else: - if death: - dates = fdate(death) - else: - dates = "" + return True - f.write('%s;%s;%s;%s;%s;%s\n' % (name, father, mother, email, web, - dates)) - - return True def fdate(val): + """ return properly formatted date """ if val.get_year_valid(): if val.get_month_valid(): if val.get_day_valid(): return "%d/%d/%d" % (val.get_day(), val.get_month(), val.get_year()) - else: - return "%d/%d" % (val.get_month(), val.get_year()) - else: - return "%d" % val.get_year() - else: - return "" + return "%d/%d" % (val.get_month(), val.get_year()) + return "%d" % val.get_year() + return "" + def get_name(name, surname, count): """returns a name string built from the components of the Name instance, in the form of Firstname Surname""" return (name.first_name + ' ' + - surname + - (str(count) if count != -1 else '') + - (', ' +name.suffix if name.suffix else '') - ) + surname + + (str(count) if count != -1 else '') + + (', ' + name.suffix if name.suffix else '')) diff --git a/gramps/plugins/graph/gvfamilylines.py b/gramps/plugins/graph/gvfamilylines.py index 150deebc7..5507558ea 100644 --- a/gramps/plugins/graph/gvfamilylines.py +++ b/gramps/plugins/graph/gvfamilylines.py @@ -1072,7 +1072,7 @@ class FamilyLinesReport(Report): def get_event_place(self, event): """ get the place of the event """ - place_text = None + place_text = '' place_handle = event.get_place_handle() if place_handle: place = self._db.get_place_from_handle(place_handle) diff --git a/gramps/plugins/lib/libtreebase.py b/gramps/plugins/lib/libtreebase.py index 66d1f8d59..cb6f573dc 100644 --- a/gramps/plugins/lib/libtreebase.py +++ b/gramps/plugins/lib/libtreebase.py @@ -669,7 +669,7 @@ class TitleBox(BoxBase): return #fix me. width should be the printable area self.width = PT2CM(self.doc.string_width(self.font, self.text)) - self.height = PT2CM(self.font.get_size() * 1.2) + self.height = PT2CM(self.font.get_size() * 2) def _get_names(self, persons, name_displayer): """ A helper function that receives a list of persons and diff --git a/gramps/plugins/view/fanchart2wayview.py b/gramps/plugins/view/fanchart2wayview.py index ac68cc6b5..602bef352 100644 --- a/gramps/plugins/view/fanchart2wayview.py +++ b/gramps/plugins/view/fanchart2wayview.py @@ -528,8 +528,6 @@ class CairoPrintSave(): pxwidth = round(context.get_width()) pxheight = round(context.get_height()) scale = min(pxwidth/self.widthpx, pxheight/self.heightpx) - if scale > 1: - scale = 1 self.drawfunc(None, cr, scale=scale) def on_paginate(self, operation, context): diff --git a/gramps/plugins/view/fanchartdescview.py b/gramps/plugins/view/fanchartdescview.py index cdffc7bce..d75f88eb4 100644 --- a/gramps/plugins/view/fanchartdescview.py +++ b/gramps/plugins/view/fanchartdescview.py @@ -516,8 +516,6 @@ class CairoPrintSave: pxwidth = round(context.get_width()) pxheight = round(context.get_height()) scale = min(pxwidth/self.widthpx, pxheight/self.heightpx) - if scale > 1: - scale = 1 self.drawfunc(None, cr, scale=scale) def on_paginate(self, operation, context): diff --git a/gramps/plugins/view/fanchartview.py b/gramps/plugins/view/fanchartview.py index 7f455e3a9..2e3add55c 100644 --- a/gramps/plugins/view/fanchartview.py +++ b/gramps/plugins/view/fanchartview.py @@ -523,8 +523,6 @@ class CairoPrintSave: pxwidth = round(context.get_width()) pxheight = round(context.get_height()) scale = min(pxwidth/self.widthpx, pxheight/self.heightpx) - if scale > 1: - scale = 1 self.drawfunc(None, cr, scale=scale) def on_paginate(self, operation, context): diff --git a/gramps/plugins/view/relview.py b/gramps/plugins/view/relview.py index ea20ed9b3..527a487b2 100644 --- a/gramps/plugins/view/relview.py +++ b/gramps/plugins/view/relview.py @@ -316,15 +316,6 @@ class RelationshipView(NavigationView): self.child = None self.scroll = Gtk.ScrolledWindow() - - st_cont = self.scroll.get_style_context() - col = st_cont.lookup_color('base_color') - if col[0]: - self.color = col[1] - else: - self.color = Gdk.RGBA() - self.color.parse("White") - self.scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.scroll.show() @@ -589,9 +580,7 @@ class RelationshipView(NavigationView): grid.attach(eventbox, 0, 0, 2, 1) - eventbox = Gtk.EventBox() - if self.use_shade: - eventbox.override_background_color(Gtk.StateType.NORMAL, self.color) + eventbox = widgets.ShadeBox(self.use_shade) grid.attach(eventbox, 1, 1, 1, 1) subgrid = Gtk.Grid() subgrid.set_column_spacing(12) @@ -888,9 +877,7 @@ class RelationshipView(NavigationView): box = self.get_people_box(family.get_father_handle(), family.get_mother_handle(), post_msg=childmsg) - eventbox = Gtk.EventBox() - if self.use_shade: - eventbox.override_background_color(Gtk.StateType.NORMAL, self.color) + eventbox = widgets.ShadeBox(self.use_shade) eventbox.add(box) self.child.attach(eventbox, _PDATA_START, self.row, _PDATA_STOP-_PDATA_START, 1) @@ -942,9 +929,7 @@ class RelationshipView(NavigationView): else : childmsg = _(" (only child)") box = self.get_people_box(post_msg=childmsg) - eventbox = Gtk.EventBox() - if self.use_shade: - eventbox.override_background_color(Gtk.StateType.NORMAL, self.color) + eventbox = widgets.ShadeBox(self.use_shade) eventbox.add(box) self.child.attach(eventbox, _PDATA_START, self.row, _PDATA_STOP-_PDATA_START, 1) @@ -972,9 +957,7 @@ class RelationshipView(NavigationView): child_should_be_linked = (child_handle != active) self.write_child(vbox, child_handle, i, child_should_be_linked) i += 1 - eventbox = Gtk.EventBox() - if self.use_shade: - eventbox.override_background_color(Gtk.StateType.NORMAL, self.color) + eventbox = widgets.ShadeBox(self.use_shade) eventbox.add(vbox) self.child.attach(eventbox, _CDATA_START-1, self.row, _CDATA_STOP-_CDATA_START+1, 1) @@ -994,9 +977,6 @@ class RelationshipView(NavigationView): name = self.get_name(handle, True) link_label = widgets.LinkLabel(name, self._button_press, handle, theme=self.theme) - if self.use_shade: - link_label.override_background_color(Gtk.StateType.NORMAL, - self.color) if self._config.get('preferences.releditbtn'): button = widgets.IconButton(self.edit_button_press, handle) @@ -1039,7 +1019,7 @@ class RelationshipView(NavigationView): _PLABEL_STOP-_PLABEL_START, 1) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - eventbox = Gtk.EventBox() + eventbox = widgets.ShadeBox(self.use_shade) if handle: name = self.get_name(handle, True) person = self.dbstate.db.get_person_from_handle(handle) @@ -1053,8 +1033,6 @@ class RelationshipView(NavigationView): emph = False link_label = widgets.LinkLabel(name, self._button_press, handle, emph, theme=self.theme) - if self.use_shade: - link_label.override_background_color(Gtk.StateType.NORMAL, self.color) if self._config.get('preferences.releditbtn'): button = widgets.IconButton(self.edit_button_press, handle) button.set_tooltip_text(_('Edit Person (%s)') % name[0]) @@ -1073,8 +1051,6 @@ class RelationshipView(NavigationView): if value: vbox.pack_start(widgets.MarkupLabel(value), True, True, 0) - if self.use_shade: - eventbox.override_background_color(Gtk.StateType.NORMAL, self.color) eventbox.add(vbox) self.child.attach(eventbox, _PDATA_START, self.row, @@ -1174,9 +1150,6 @@ class RelationshipView(NavigationView): name = self.get_name(handle, True) link_label = widgets.LinkLabel(name, link_func, handle, emph, theme=self.theme) - - if self.use_shade: - link_label.override_background_color(Gtk.StateType.NORMAL, self.color) link_label.set_padding(3, 0) if child_should_be_linked and self._config.get( 'preferences.releditbtn'): @@ -1391,9 +1364,7 @@ class RelationshipView(NavigationView): else : childmsg = _(" (no children)") box = self.get_people_box(handle, post_msg=childmsg) - eventbox = Gtk.EventBox() - if self.use_shade: - eventbox.override_background_color(Gtk.StateType.NORMAL, self.color) + eventbox = widgets.ShadeBox(self.use_shade) eventbox.add(box) self.child.attach(eventbox, _PDATA_START, self.row, _PDATA_STOP-_PDATA_START, 1) @@ -1438,9 +1409,7 @@ class RelationshipView(NavigationView): else : childmsg = _(" (no children)") box = self.get_people_box(post_msg=childmsg) - eventbox = Gtk.EventBox() - if self.use_shade: - eventbox.override_background_color(Gtk.StateType.NORMAL, self.color) + eventbox = widgets.ShadeBox(self.use_shade) eventbox.add(box) self.child.attach(eventbox, _PDATA_START, self.row, _PDATA_STOP-_PDATA_START, 1) @@ -1468,9 +1437,7 @@ class RelationshipView(NavigationView): i += 1 self.row += 1 - eventbox = Gtk.EventBox() - if self.use_shade: - eventbox.override_background_color(Gtk.StateType.NORMAL, self.color) + eventbox = widgets.ShadeBox(self.use_shade) eventbox.add(vbox) self.child.attach(eventbox, _CDATA_START-1, self.row, _CDATA_STOP-_CDATA_START+1, 1) diff --git a/gramps/plugins/webreport/basepage.py b/gramps/plugins/webreport/basepage.py index 1a500791d..b9b8ee575 100644 --- a/gramps/plugins/webreport/basepage.py +++ b/gramps/plugins/webreport/basepage.py @@ -628,17 +628,25 @@ class BasePage: # pylint: disable=C1001 """ creates the event header row for all events """ - trow = Html("tr") + trow = Html("tr", close=None) trow.extend( Html("th", trans, class_=colclass, inline=True) for trans, colclass in [ (self._("Event"), "ColumnEvent"), (self._("Date"), "ColumnDate"), (self._("Place"), "ColumnPlace"), - (self._("Description"), "ColumnDescription"), - (self._("Notes"), "ColumnNotes"), - (self._("Sources"), "ColumnSources")] + (self._("Description"), "ColumnDescription")] ) + trow += Html("/tr", close=None) + trow2 = Html("tr", indent=False) + trow2.extend( + Html("th", trans, class_=colclass, colspan=opt, inline=True) + for trans, colclass, opt in [ + ("", "ColumnEvent", 1), + (self._("Sources"), "ColumnSources", 1), + (self._("Notes"), "ColumnNotes", 2)] + ) + trow.extend(trow2) return trow def display_event_row(self, event, event_ref, place_lat_long, @@ -688,6 +696,12 @@ class BasePage: # pylint: disable=C1001 for (label, colclass, data) in event_data ) + trow2 = Html("tr") + trow2 += Html("td", "", class_="ColumnSources") + # get event source references + srcrefs = self.get_citation_links(event.get_citation_list()) or " " + trow2 += Html("td", srcrefs, class_="ColumnSources") + # get event notes notelist = event.get_note_list() notelist.extend(event_ref.get_note_list()) @@ -709,12 +723,9 @@ class BasePage: # pylint: disable=C1001 if notelist: htmllist.extend(self.dump_notes(notelist)) - trow += Html("td", htmllist, class_="ColumnNotes") - - # get event source references - srcrefs = self.get_citation_links(event.get_citation_list()) or " " - trow += Html("td", srcrefs, class_="ColumnSources") + trow2 += Html("td", htmllist, class_="ColumnNotes", colspan=2) + trow += trow2 # return events table row to its callers return trow @@ -1502,7 +1513,7 @@ class BasePage: # pylint: disable=C1001 ("addressbook", self._("Address Book"), self.report.inc_addressbook), ('contact', self._("Contact"), self.report.use_contact), - ('statistics', self._("Statistics"), True), + ('statistics', self._("Statistics"), self.report.inc_stats), (self.target_cal_uri, self._("Web Calendar"), self.usecal) ] diff --git a/gramps/plugins/webreport/common.py b/gramps/plugins/webreport/common.py index 52f3775c7..5af769e7c 100644 --- a/gramps/plugins/webreport/common.py +++ b/gramps/plugins/webreport/common.py @@ -31,6 +31,7 @@ from unicodedata import normalize from collections import defaultdict from hashlib import md5 import re +import gc import logging from xml.sax.saxutils import escape @@ -859,4 +860,3 @@ def html_escape(text): text = text.replace("'", ''') return text - diff --git a/gramps/plugins/webreport/event.py b/gramps/plugins/webreport/event.py index 6cdc36501..bd61bb4f9 100644 --- a/gramps/plugins/webreport/event.py +++ b/gramps/plugins/webreport/event.py @@ -110,17 +110,18 @@ class EventPages(BasePage): 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"), + message = _("Creating event pages") + with self.r_user.progress(_("Narrated Web Site Report"), message, len(event_handle_list) + 1 ) as step: - self.eventlistpage(self.report, title, event_types, - event_handle_list) - + index = 1 for event_handle in event_handle_list: step() + index += 1 self.eventpage(self.report, title, event_handle) - + step() + self.eventlistpage(self.report, title, event_types, + event_handle_list) def eventlistpage(self, report, title, event_types, event_handle_list): """ diff --git a/gramps/plugins/webreport/family.py b/gramps/plugins/webreport/family.py index d09fc45b6..a7f39f31d 100644 --- a/gramps/plugins/webreport/family.py +++ b/gramps/plugins/webreport/family.py @@ -103,16 +103,18 @@ class FamilyPages(BasePage): 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..."), + message = _("Creating family pages...") + index = 1 + with self.r_user.progress(_("Narrated Web Site Report"), message, 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() + index += 1 self.familypage(self.report, title, family_handle) + step() + self.familylistpage(self.report, title, + self.report.obj_dict[Family].keys()) def familylistpage(self, report, title, fam_list): """ diff --git a/gramps/plugins/webreport/media.py b/gramps/plugins/webreport/media.py index e5c6a1bc2..18142bf45 100644 --- a/gramps/plugins/webreport/media.py +++ b/gramps/plugins/webreport/media.py @@ -94,6 +94,7 @@ class MediaPages(BasePage): """ BasePage.__init__(self, report, title="") self.media_dict = defaultdict(set) + self.unused_media_handles = [] def display_pages(self, title): """ @@ -105,9 +106,13 @@ class MediaPages(BasePage): 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 + if self.create_unused_media: + media_count = len(self.r_db.get_media_handles()) + else: + media_count = len(self.report.obj_dict[Media]) + message = _("Creating media pages") + with self.r_user.progress(_("Narrated Web Site Report"), message, + media_count + 1 ) as step: # bug 8950 : it seems it's better to sort on desc + gid. def sort_by_desc_and_gid(obj): @@ -116,24 +121,62 @@ class MediaPages(BasePage): """ return (obj.desc.lower(), obj.gramps_id) + self.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]: + self.unused_media_handles.append(media_ref) + self.unused_media_handles = sorted( + self.unused_media_handles, + key=lambda x: sort_by_desc_and_gid( + self.r_db.get_media_from_handle(x))) + 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() + if index == media_count: + next_ = None + elif index < total: + next_ = sorted_media_handles[index] + elif len(self.unused_media_handles) > 0: + next_ = self.unused_media_handles[0] + else: + next_ = None self.mediapage(self.report, title, - handle, (prev, next_, index, total)) + handle, (prev, next_, index, media_count)) prev = handle + step() index += 1 + total = len(self.unused_media_handles) + idx = 1 + prev = sorted_media_handles[len(sorted_media_handles)-1] + if total > 0: + for media_handle in self.unused_media_handles: + media = self.r_db.get_media_from_handle(media_handle) + gc.collect() # Reduce memory usage when many images. + if index == media_count: + next_ = None + else: + next_ = self.unused_media_handles[idx] + self.mediapage(self.report, title, + media_handle, + (prev, next_, index, media_count)) + prev = media_handle + step() + index += 1 + idx += 1 + + self.medialistpage(self.report, title, sorted_media_handles) + def medialistpage(self, report, title, sorted_media_handles): """ Generate and output the Media index page. @@ -191,85 +234,84 @@ class MediaPages(BasePage): 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]" + if self.create_unused_media: + media_count = len(self.r_db.get_media_handles()) + else: + media_count = len(self.report.obj_dict[Media]) + message = _("Creating list of media pages") + with self.r_user.progress(_("Narrated Web Site Report"), + message, media_count + 1 + ) as step: + 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 + 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"]] + 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 - ) + trow.extend( + Html("td", data, class_=colclass) + for data, colclass in media_data_row + ) + step() index += 1 - def sort_by_desc_and_gid(obj): - """ - Sort by media description and gramps ID - """ - return (obj.desc, obj.gramps_id) + 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 + idx = 1 + prev = None + total = len(self.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 self.unused_media_handles: + media = self.r_db.get_media_from_handle(media_handle) + gc.collect() # Reduce memory usage when many images. + if idx == total: + next_ = None + else: + self.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 + ) + prev = media_handle + step() + index += 1 + idx += 1 # add footer section # add clearline for proper styling diff --git a/gramps/plugins/webreport/narrativeweb.py b/gramps/plugins/webreport/narrativeweb.py index d8f3adec3..83f4968e7 100644 --- a/gramps/plugins/webreport/narrativeweb.py +++ b/gramps/plugins/webreport/narrativeweb.py @@ -200,6 +200,8 @@ class NavWebReport(Report): self.use_intro = self.options['intronote'] or self.options['introimg'] self.use_home = self.options['homenote'] or self.options['homeimg'] self.use_contact = self.opts['contactnote'] or self.opts['contactimg'] + self.inc_stats = self.opts['inc_stats'] + self.create_unused_media = self.opts['unused'] # Do we need to include this in a cms ? self.usecms = self.options['usecms'] @@ -441,7 +443,8 @@ class NavWebReport(Report): self.tab["Source"].display_pages(self.title) # build classes StatisticsPage - self.statistics_preview_page(self.title) + if self.inc_stats: + self.statistics_preview_page(self.title) # copy all of the neccessary files self.copy_narrated_files() @@ -458,6 +461,7 @@ class NavWebReport(Report): if len(_WRONGMEDIAPATH) > 10: error += '\n ...' self.user.warn(_("Missing media objects:"), error) + self.database.clear_cache() def _build_obj_dict(self): """ @@ -488,12 +492,14 @@ class NavWebReport(Report): ind_list = self._db.iter_person_handles() ind_list = self.filter.apply(self._db, ind_list, user=self.user) - with self.user.progress(_("Narrated Web Site Report"), - _('Constructing list of other objects...'), + message = _('Constructing list of other objects...') + with self.user.progress(_("Narrated Web Site Report"), message, sum(1 for _ in ind_list)) as step: + index = 1 for handle in ind_list: - step() self._add_person(handle, "", "") + step() + index += 1 LOG.debug("final object dictionary \n" + "".join(("%s: %s\n" % item) @@ -1057,13 +1063,15 @@ class NavWebReport(Report): @param: ind_list -- The list of person to use """ if self.inc_gendex: - with self.user.progress(_("Narrated Web Site Report"), - _('Creating GENDEX file'), + message = _('Creating GENDEX file') + with self.user.progress(_("Narrated Web Site Report"), message, len(ind_list)) as step: fp_gendex, gendex_io = self.create_file("gendex", ext=".txt") date = 0 + index = 1 for person_handle in ind_list: step() + index += 1 person = self._db.get_person_from_handle(person_handle) datex = person.get_change_time() if datex > date: @@ -1113,29 +1121,35 @@ class NavWebReport(Report): """ local_list = sort_people(self._db, ind_list, self.rlocale) - with self.user.progress(_("Narrated Web Site Report"), - _("Creating surname pages"), + message = _("Creating surname pages") + with self.user.progress(_("Narrated Web Site Report"), message, len(local_list)) as step: SurnameListPage(self, self.title, ind_list, - SurnameListPage.ORDER_BY_NAME, - self.surname_fname) + SurnameListPage.ORDER_BY_NAME, + self.surname_fname) SurnameListPage(self, self.title, ind_list, - SurnameListPage.ORDER_BY_COUNT, - "surnames_count") + SurnameListPage.ORDER_BY_COUNT, + "surnames_count") + index = 1 for (surname, handle_list) in local_list: SurnamePage(self, self.title, surname, sorted(handle_list)) step() + index += 1 def thumbnail_preview_page(self): """ creates the thumbnail preview page """ + if self.create_unused_media: + media_count = len(self._db.get_media_handles()) + else: + media_count = len(self.obj_dict[Media]) with self.user.progress(_("Narrated Web Site Report"), _("Creating thumbnail preview page..."), - len(self.obj_dict[Media])) as step: + media_count) as step: ThumbnailPreviewPage(self, self.title, step) def statistics_preview_page(self, title): @@ -1144,7 +1158,7 @@ class NavWebReport(Report): """ with self.user.progress(_("Narrated Web Site Report"), _("Creating statistics page..."), - len(self.obj_dict[Media])) as step: + 1) as step: StatisticsPage(self, title, step) def addressbook_pages(self, ind_list): @@ -1184,12 +1198,14 @@ class NavWebReport(Report): # begin Address Book pages addr_size = len(url_addr_res) - with self.user.progress(_("Narrated Web Site Report"), - _("Creating address book pages ..."), + message = _("Creating address book pages ...") + with self.user.progress(_("Narrated Web Site Report"), message, addr_size) as step: + index = 1 for (sort_name, person_handle, add, res, url) in url_addr_res: AddressBookPage(self, self.title, person_handle, add, res, url) step() + index += 1 def base_pages(self): """ @@ -1968,6 +1984,10 @@ class NavWebOptions(MenuReportOptions): "events.")) addopt("inc_addressbook", inc_addressbook) + inc_statistics = BooleanOption(_("Include the statistics page"), False) + inc_statistics.set_help(_("Whether or not to add statistics page")) + addopt("inc_stats", inc_statistics) + def __add_place_map_options(self, menu): """ options for the Place Map tab. diff --git a/gramps/plugins/webreport/person.py b/gramps/plugins/webreport/person.py index 6b00a1cef..6724fadb6 100644 --- a/gramps/plugins/webreport/person.py +++ b/gramps/plugins/webreport/person.py @@ -130,16 +130,19 @@ class PersonPages(BasePage): LOG.debug("obj_dict[Person]") for item in self.report.obj_dict[Person].items(): LOG.debug(" %s", str(item)) - with self.r_user.progress(_("Narrated Web Site Report"), - _('Creating individual pages'), + message = _('Creating individual pages') + with self.r_user.progress(_("Narrated Web Site Report"), message, len(self.report.obj_dict[Person]) + 1 ) as step: - self.individuallistpage(self.report, title, - self.report.obj_dict[Person].keys()) + index = 1 for person_handle in sorted(self.report.obj_dict[Person]): step() + index += 1 person = self.r_db.get_person_from_handle(person_handle) self.individualpage(self.report, title, person) + step() + self.individuallistpage(self.report, title, + self.report.obj_dict[Person].keys()) ################################################# # diff --git a/gramps/plugins/webreport/place.py b/gramps/plugins/webreport/place.py index c91f88399..86a56e79e 100644 --- a/gramps/plugins/webreport/place.py +++ b/gramps/plugins/webreport/place.py @@ -113,17 +113,18 @@ class PlacePages(BasePage): 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"), + message = _("Creating place pages") + with self.r_user.progress(_("Narrated Web Site Report"), message, len(self.report.obj_dict[Place]) + 1 ) as step: - - self.placelistpage(self.report, title, - self.report.obj_dict[Place].keys()) - + index = 1 for place_handle in self.report.obj_dict[Place]: step() + index += 1 self.placepage(self.report, title, place_handle) + step() + self.placelistpage(self.report, title, + self.report.obj_dict[Place].keys()) def placelistpage(self, report, title, place_handles): """ diff --git a/gramps/plugins/webreport/repository.py b/gramps/plugins/webreport/repository.py index a9b3eaca9..1204a55d2 100644 --- a/gramps/plugins/webreport/repository.py +++ b/gramps/plugins/webreport/repository.py @@ -98,8 +98,8 @@ class RepositoryPages(BasePage): LOG.debug(" %s", str(item)) # set progress bar pass for Repositories - with self.r_user.progress(_("Narrated Web Site Report"), - _('Creating repository pages'), + message = _('Creating repository pages') + with self.r_user.progress(_("Narrated Web Site Report"), message, len(self.report.obj_dict[Repository]) + 1 ) as step: # Sort the repositories @@ -114,10 +114,11 @@ class RepositoryPages(BasePage): # RepositoryListPage Class self.repositorylistpage(self.report, title, repos_dict, keys) + idx = 1 for index, key in enumerate(keys): (repo, handle) = repos_dict[key] - step() + idx += 1 self.repositorypage(self.report, title, repo, handle) def repositorylistpage(self, report, title, repos_dict, keys): diff --git a/gramps/plugins/webreport/source.py b/gramps/plugins/webreport/source.py index 17f71eadf..fc6a473c0 100644 --- a/gramps/plugins/webreport/source.py +++ b/gramps/plugins/webreport/source.py @@ -99,15 +99,17 @@ class SourcePages(BasePage): 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"), + message = _("Creating source pages") + with self.r_user.progress(_("Narrated Web Site Report"), message, len(self.report.obj_dict[Source]) + 1 ) as step: self.sourcelistpage(self.report, title, self.report.obj_dict[Source].keys()) + index = 1 for source_handle in self.report.obj_dict[Source]: step() + index += 1 self.sourcepage(self.report, title, source_handle) def sourcelistpage(self, report, title, source_handles): diff --git a/gramps/plugins/webreport/statistics.py b/gramps/plugins/webreport/statistics.py index 67bf86211..134572d93 100644 --- a/gramps/plugins/webreport/statistics.py +++ b/gramps/plugins/webreport/statistics.py @@ -86,6 +86,7 @@ class StatisticsPage(BasePage): females, unknown) = self.get_gender(report.database.iter_person_handles()) + step() mobjects = report.database.get_number_of_media() npersons = report.database.get_number_of_people() nfamilies = report.database.get_number_of_families() diff --git a/gramps/plugins/webreport/thumbnail.py b/gramps/plugins/webreport/thumbnail.py index 1d74d428f..b615e81be 100644 --- a/gramps/plugins/webreport/thumbnail.py +++ b/gramps/plugins/webreport/thumbnail.py @@ -122,7 +122,7 @@ class ThumbnailPreviewPage(BasePage): "will take you to that image’s page.") previewpage += Html("p", msg, id="description") - with Html("table", class_="calendar") as table: + with Html("table", class_="calendar thumbnails") as table: previewpage += table thead = Html("thead") @@ -153,7 +153,7 @@ class ThumbnailPreviewPage(BasePage): num_of_cols = 7 grid_row = 0 while grid_row < num_of_rows: - trow = Html("tr", id="RowNumber: %08d" % grid_row) + trow = Html("tr", class_="thumbnail", id="RowNumber: %08d" % grid_row) tbody += trow cols = 0 @@ -163,7 +163,7 @@ class ThumbnailPreviewPage(BasePage): photo = media_list[indexpos][2] # begin table cell and attach to table row(trow)... - tcell = Html("td", class_="highlight weekend") + tcell = Html("td", class_="highlight weekend thumbnail") trow += tcell # attach index number... @@ -203,6 +203,7 @@ class ThumbnailPreviewPage(BasePage): for emptycols in range(cols, num_of_cols): trow += Html("td", class_="emptyDays", inline=True) + message = _("Creating thumbnail preview page...") # begin Thumbnail Reference section... with Html("div", class_="subsection", id="references") as section: body += section @@ -225,11 +226,12 @@ class ThumbnailPreviewPage(BasePage): tcell2 = Html("td", ptitle, class_="ColumnName") trow += (tcell1, tcell2) + # increase progress meter... + cb_progress() + # increase index for row number... index += 1 - # increase progress meter... - cb_progress() # add body id element body.attr = 'id ="ThumbnailPreview"' diff --git a/mac/gramps.accel b/mac/gramps.accel index 66b7b744f..e22e44a63 100644 --- a/mac/gramps.accel +++ b/mac/gramps.accel @@ -1,200 +1,149 @@ -; gramps.py GtkAccelMap rc-file -*- scheme -*- +; Gramps.py GtkAccelMap rc-file -*- scheme -*- ; this file is an automated accelerator map dump ; - (gtk_accel_path "/People Tree View/PersonAll/Edit" "Return") -; (gtk_accel_path "/FileWindow/PluginStatus" "") -; (gtk_accel_path "/ReportWindow/place_report" "") -; (gtk_accel_path "/FileWindow/ViewMenu" "") - (gtk_accel_path "/Pedigree/Forward/Forward" "Right") -; (gtk_accel_path "/ReportWindow/birthday_report" "") -; (gtk_accel_path "/ReportWindow/marker_report" "") -; (gtk_accel_path "/ReportWindow/Graphs" "") -; (gtk_accel_path "/FileWindow/FAQ" "") -; (gtk_accel_path "/ToolWindow/mediaman" "") - (gtk_accel_path "/Families/Forward/Forward" "Right") - (gtk_accel_path "/Events/ChangeOrder/Add" "i") -; (gtk_accel_path "/ReportWindow/det_descendant_report" "") -; (gtk_accel_path "/ReportWindow/statistics_chart" "") -; (gtk_accel_path "/Person View/PersonAll/FilterEdit" "") - (gtk_accel_path "/Events/Backward/Back" "Left") -; (gtk_accel_path "/ToolWindow/rebuild_refmap" "") -; (gtk_accel_path "/ToolWindow/Database-Processing" "") - (gtk_accel_path "/Events/Bookmark/EditBook" "d") - (gtk_accel_path "/People Tree View/PersonEdit/Remove" "Delete") - (gtk_accel_path "/Notes/Forward/Forward" "Right") - (gtk_accel_path "/Undo/Undo" "z") - (gtk_accel_path "/MainWindow/Import" "i") -; (gtk_accel_path "/FileWindow/Filter" "") -; (gtk_accel_path "/ReportWindow/summary" "") - (gtk_accel_path "/Media/ChangeOrder/Remove" "Delete") -; (gtk_accel_path "/FileWindow/HelpMenu" "") - (gtk_accel_path "/Place View/Bookmark/AddBook" "d") - (gtk_accel_path "/Repositories/Bookmark/EditBook" "d") -; (gtk_accel_path "/ReportWindow/book" "") -; (gtk_accel_path "/FileWindow/FileMenu" "") - (gtk_accel_path "/Person View/Backward/Back" "Left") -; (gtk_accel_path "/ReportWindow/records" "") -; (gtk_accel_path "/ReportWindow/ancestor_report" "") -; (gtk_accel_path "/ToolWindow/chname" "") -; (gtk_accel_path "/Person View/PersonEdit/FastMerge" "") - (gtk_accel_path "/Relationships/Forward/Forward" "Right") -; (gtk_accel_path "/Person View/PersonEdit/ExportTab" "") - (gtk_accel_path "/Notes/ChangeOrder/Remove" "Delete") -; (gtk_accel_path "/ToolWindow/editowner" "") -; (gtk_accel_path "/ReportWindow/hourglass_graph" "") - (gtk_accel_path "/Repositories/ChangeOrder/Add" "i") - (gtk_accel_path "/Media/Forward/Forward" "Right") - (gtk_accel_path "/AllMainWindow/Delete" "Delete") -; (gtk_accel_path "/FileWindow/Toolbar" "") - (gtk_accel_path "/Place Tree View/ChangeOrder/Add" "i") -; (gtk_accel_path "/ReportWindow/number_of_ancestors" "") - (gtk_accel_path "/Families/ChangeOrder/Add" "i") - (gtk_accel_path "/Person View/Bookmark/EditBook" "d") - (gtk_accel_path "/Relationships/Bookmark/AddBook" "d") -; (gtk_accel_path "/ReportWindow/familylines_graph" "") - (gtk_accel_path "/Person View/Forward/Forward" "Right") - (gtk_accel_path "/Place View/ChangeOrder/Remove" "Delete") - (gtk_accel_path "/Sources/ChangeOrder/Remove" "Delete") - (gtk_accel_path "/Person View/ChangeOrder/Remove" "Delete") ; (gtk_accel_path "/ToolWindow/relcalc" "") - (gtk_accel_path "/AllMainWindow/Export" "e") - (gtk_accel_path "/Pedigree/Backward/Back" "Left") - (gtk_accel_path "/Relationships/Bookmark/EditBook" "d") -; (gtk_accel_path "/ToolWindow/reorder_ids" "") - (gtk_accel_path "/Place Tree View/Bookmark/EditBook" "d") -; (gtk_accel_path "/RecentFiles/RecentMenu0" "") - (gtk_accel_path "/Person View/PersonAll/Edit" "Return") -; (gtk_accel_path "/FileWindow/MailingLists" "") - (gtk_accel_path "/Place View/Forward/Forward" "Right") -; (gtk_accel_path "/ReportWindow/kinship_report" "") -; (gtk_accel_path "/MainWindow/BookMenu" "") -; (gtk_accel_path "/ToolWindow/evname" "") -; (gtk_accel_path "/ToolWindow/Analysis-and-Exploration" "") -; (gtk_accel_path "/ReportWindow/indiv_complete" "") -; (gtk_accel_path "/AllMainWindow/F12" "F12") - (gtk_accel_path "/Place Tree View/Bookmark/AddBook" "d") -; (gtk_accel_path "/AllMainWindow/F11" "F11") +; (gtk_accel_path "/ReportWindow/place_report" "") +; (gtk_accel_path "/ToolWindow/mediaman" "") + (gtk_accel_path "/MainWindow/SourceAdd" "s") +; (gtk_accel_path "/ReportWindow/summary" "") +; (gtk_accel_path "/ToolWindow/rebuild_refmap" "") +; (gtk_accel_path "/Redo/Redo" "z") +; (gtk_accel_path "/ToolWindow/ToolAnExp" "") +; (gtk_accel_path "/FileWindow/Toolbar" "") +; (gtk_accel_path "/ToolWindow/editowner" "") +; (gtk_accel_path "/FileWindow/Preferences" "") + (gtk_accel_path "/MainWindow/MediaAdd" "m") ; (gtk_accel_path "/ToolWindow/sortevents" "") - (gtk_accel_path "/Fan Chart/Backward/Back" "Left") -; (gtk_accel_path "/Media/Backward/Back" "Left") -; (gtk_accel_path "/Fan Chart/Bookmark/AddBook" "d") -; (gtk_accel_path "/Person View/PersonEdit/CmpMerge" "") -; (gtk_accel_path "/MainWindow/ToolsMenu" "") - (gtk_accel_path "/Events/Forward/Forward" "Right") - (gtk_accel_path "/Families/Backward/Back" "Left") - (gtk_accel_path "/Place View/Backward/Back" "Left") -; (gtk_accel_path "/ReportWindow/descend_chart" "") -; (gtk_accel_path "/ToolWindow/dupfind" "") -; (gtk_accel_path "/MainWindow/EditMenu" "") - (gtk_accel_path "/UndoHistory/UndoHistory" "h") - (gtk_accel_path "/Sources/Bookmark/EditBook" "d") +; (gtk_accel_path "/AllMainWindow/Delete" "Delete") ; (gtk_accel_path "/FileWindow/ReportBug" "") - (gtk_accel_path "/AllMainWindow/Insert" "i") - (gtk_accel_path "/Notes/Bookmark/AddBook" "d") - (gtk_accel_path "/People Tree View/ChangeOrder/Add" "i") -; (gtk_accel_path "/FileWindow/Sidebar" "") -; (gtk_accel_path "/Person View/PersonAll/Dummy" "") - (gtk_accel_path "/Redo/Redo" "z") - (gtk_accel_path "/Person View/PersonEdit/Remove" "Delete") -; (gtk_accel_path "/ReportWindow/family_group" "") -; (gtk_accel_path "/ToolWindow/excity" "") - (gtk_accel_path "/Repositories/ChangeOrder/Remove" "Delete") - (gtk_accel_path "/Repositories/Forward/Forward" "Right") -; (gtk_accel_path "/FileWindow/UserManual" "F1") -; (gtk_accel_path "/FileWindow/OpenRecent" "") - (gtk_accel_path "/Families/Bookmark/AddBook" "d") - (gtk_accel_path "/Place Tree View/Forward/Forward" "Right") +; (gtk_accel_path "/ReportWindow/hourglass_graph" "") +; (gtk_accel_path "/Undo/Undo" "z") +; (gtk_accel_path "/ToolWindow/reorder_ids" "") ; (gtk_accel_path "/ReportWindow/rel_graph" "") -; (gtk_accel_path "/ReportWindow/Web-Pages" "") -; (gtk_accel_path "/ReportWindow/WebCal" "") -; (gtk_accel_path "/ReportWindow/Text-Reports" "") -; (gtk_accel_path "/Person View/PersonAll/QuickReport" "") +; (gtk_accel_path "/MainWindow/ConfigView" "c") +; (gtk_accel_path "/ReportWindow/descend_report" "") +; (gtk_accel_path "/ReportWindow/ancestor_chart" "") +; (gtk_accel_path "/MainWindow/Clipboard" "b") +; (gtk_accel_path "/FileWindow/MailingLists" "") +; (gtk_accel_path "/AllMainWindow/GoMenu" "") +; (gtk_accel_path "/ToolWindow/verify" "") +; (gtk_accel_path "/ToolWindow/mergecitations" "") ; (gtk_accel_path "/FileWindow/HomePage" "") - (gtk_accel_path "/Relationships/Family/Edit" "Return") -; (gtk_accel_path "/ToolWindow/patchnames" "") - (gtk_accel_path "/Relationships/Backward/Back" "Left") +; (gtk_accel_path "/ToolWindow/populatesources" "") +; (gtk_accel_path "/FileWindow/Open" "o") +; (gtk_accel_path "/ReportWindow/endofline_report" "") + (gtk_accel_path "/MainWindow/CitationAdd" "c") +; (gtk_accel_path "/UndoHistory/UndoHistory" "h") +; (gtk_accel_path "/ReportWindow/family_descend_chart" "") + (gtk_accel_path "/AllMainWindow/Close" "w") +; (gtk_accel_path "/FileWindow/HelpMenu" "") +; (gtk_accel_path "/ToolWindow/loop" "") +; (gtk_accel_path "/ReportWindow/descend_chart" "") + (gtk_accel_path "/MainWindow/EventAdd" "e") +; (gtk_accel_path "/FileWindow/ExtraPlugins" "") + (gtk_accel_path "/MainWindow/RepositoryAdd" "r") +; (gtk_accel_path "/MainWindow/BookMenu" "") +; (gtk_accel_path "/ToolWindow/ToolProc" "") + (gtk_accel_path "/MainWindow/NoteAdd" "n") + (gtk_accel_path "/MainWindow/PlaceAdd" "l") +; (gtk_accel_path "/ReportWindow/navwebpage" "") +; (gtk_accel_path "/ReportWindow/birthday_report" "") +; (gtk_accel_path "/RecentFiles/RecentMenu8" "") +; (gtk_accel_path "/FileWindow/ViewMenu" "") +; (gtk_accel_path "/ReportWindow/fan_chart" "") +; (gtk_accel_path "/RecentFiles/RecentMenu7" "") +; (gtk_accel_path "/ReportWindow/calendar" "") +; (gtk_accel_path "/RecentFiles/RecentMenu6" "") +; (gtk_accel_path "/RecentFiles/RecentMenu5" "") +; (gtk_accel_path "/RecentFiles/RecentMenu4" "") +; (gtk_accel_path "/RecentFiles/RecentMenu3" "") +; (gtk_accel_path "/RecentFiles/RecentMenu2" "") +; (gtk_accel_path "/RecentFiles/RecentMenu1" "") +; (gtk_accel_path "/RecentFiles/RecentMenu0" "") +; (gtk_accel_path "/ReportWindow/RepWeb" "") +; (gtk_accel_path "/ReportWindow/indiv_complete" "") +; (gtk_accel_path "/ToolWindow/rebuild_genstats" "") +; (gtk_accel_path "/FileWindow/FAQ" "") ; (gtk_accel_path "/ReportWindow/det_ancestor_report" "") - (gtk_accel_path "/AllMainWindow/BackSpace" "BackSpace") -; (gtk_accel_path "/ToolWindow/Utilities" "") -; (gtk_accel_path "/AllMainWindow/WindowsMenu" "") - (gtk_accel_path "/Families/Bookmark/EditBook" "d") +; (gtk_accel_path "/ReportWindow/timeline" "") +; (gtk_accel_path "/ToolWindow/eventcmp" "") +; (gtk_accel_path "/AllMainWindow/ReportsMenu" "") +; (gtk_accel_path "/FileWindow/EditMenu" "") + (gtk_accel_path "/MainWindow/FamilyAdd" "f") +; (gtk_accel_path "/ToolWindow/chname" "") +; (gtk_accel_path "/AllMainWindow/Insert" "Insert") ; (gtk_accel_path "/AllMainWindow/F9" "F9") ; (gtk_accel_path "/AllMainWindow/F8" "F8") ; (gtk_accel_path "/AllMainWindow/F7" "F7") ; (gtk_accel_path "/AllMainWindow/F6" "F6") - (gtk_accel_path "/Pedigree/Bookmark/AddBook" "d") ; (gtk_accel_path "/AllMainWindow/F5" "F5") ; (gtk_accel_path "/AllMainWindow/F4" "F4") ; (gtk_accel_path "/AllMainWindow/F3" "F3") ; (gtk_accel_path "/AllMainWindow/F2" "F2") - (gtk_accel_path "/Notes/Bookmark/EditBook" "d") - (gtk_accel_path "/Sources/ChangeOrder/Add" "i") -; (gtk_accel_path "/ReportWindow/Books" "") -; (gtk_accel_path "/FileWindow/About" "") - (gtk_accel_path "/Notes/Backward/Back" "Left") -; (gtk_accel_path "/ReportWindow/endofline_report" "") - (gtk_accel_path "/People Tree View/PersonEdit/Add" "i") -; (gtk_accel_path "/ToolWindow/dbrowse" "") - (gtk_accel_path "/Pedigree/Bookmark/EditBook" "d") -; (gtk_accel_path "/ToolWindow/soundgen" "") -; (gtk_accel_path "/FileWindow/ExtraPlugins" "") -; (gtk_accel_path "/AllMainWindow/ReportsMenu" "") - (gtk_accel_path "/categoryviews/personlistview_0" "1") -; (gtk_accel_path "/ReportWindow/descend_report" "") -; (gtk_accel_path "/ReportWindow/fan_chart" "") - (gtk_accel_path "/MainWindow/ScratchPad" "b") - (gtk_accel_path "/Person View/Edit" "Return") -; (gtk_accel_path "/ToolWindow/rebuild" "") -; (gtk_accel_path "/ToolWindow/eventcmp" "") -; (gtk_accel_path "/AllMainWindow/GoMenu" "") -; (gtk_accel_path "/ReportWindow/timeline" "") -; (gtk_accel_path "/ToolWindow/Database-Repair" "") -; (gtk_accel_path "/ReportWindow/Graphical-Reports" "") - (gtk_accel_path "/Person View/HomePerson" "Home") -; (gtk_accel_path "/ReportWindow/ancestor_chart" "") -; (gtk_accel_path "/ToolWindow/check" "") - (gtk_accel_path "/Fan Chart/Forward/Forward" "Right") -; (gtk_accel_path "/ToolWindow/chtype" "") - (gtk_accel_path "/Notes/ChangeOrder/Add" "i") -; (gtk_accel_path "/ReportWindow/calendar" "") -; (gtk_accel_path "/FileWindow/Fullscreen" "F11") - (gtk_accel_path "/Fan Chart/Bookmark/EditBook" "d") -; (gtk_accel_path "/ReportWindow/navwebpage" "") - (gtk_accel_path "/Repositories/Bookmark/AddBook" "d") - (gtk_accel_path "/Families/ChangeOrder/Remove" "Delete") -; (gtk_accel_path "/ToolWindow/verify" "") - (gtk_accel_path "/People Tree View/Forward/Forward" "Right") - (gtk_accel_path "/Sources/Backward/Back" "Left") -; (gtk_accel_path "/ToolWindow/not_related" "") - (gtk_accel_path "/People Tree View/Bookmark/AddBook" "d") -; (gtk_accel_path "/FileWindow/TipOfDay" "") - (gtk_accel_path "/Media/ChangeOrder/Add" "i") - (gtk_accel_path "/FileWindow/Quit" "q") - (gtk_accel_path "/People Tree View/Bookmark/EditBook" "d") - (gtk_accel_path "/Place Tree View/Backward/Back" "Left") - (gtk_accel_path "/FileWindow/Open" "o") - (gtk_accel_path "/Place View/Bookmark/EditBook" "d") - (gtk_accel_path "/Media/Bookmark/EditBook" "d") - (gtk_accel_path "/MainWindow/ConfigView" "c") - (gtk_accel_path "/People Tree View/Backward/Back" "Left") +; (gtk_accel_path "/FileWindow/Navigator" "m") +; (gtk_accel_path "/FileWindow/PluginStatus" "") +; (gtk_accel_path "/AllMainWindow/F12" "F12") +; (gtk_accel_path "/AllMainWindow/F11" "F11") +; (gtk_accel_path "/ToolWindow/ToolUtil" "") +; (gtk_accel_path "/MainWindow/Import" "i") +; (gtk_accel_path "/ReportWindow/statistics_chart" "") +; (gtk_accel_path "/FileWindow/UserManual" "F1") +; (gtk_accel_path "/AllMainWindow/Books" "") +; (gtk_accel_path "/FileWindow/OpenRecent" "") +; (gtk_accel_path "/Dashboard/RestoreGramplet" "") +; (gtk_accel_path "/WindowManger/4722902520" "") +; (gtk_accel_path "/ReportWindow/RepGraph" "") +; (gtk_accel_path "/ReportWindow/familylines_graph" "") +; (gtk_accel_path "/ReportWindow/kinship_report" "") +; (gtk_accel_path "/ToolWindow/testcasegenerator" "") ; (gtk_accel_path "/FileWindow/KeyBindings" "") - (gtk_accel_path "/Repositories/Backward/Back" "Left") - (gtk_accel_path "/Events/Bookmark/AddBook" "d") - (gtk_accel_path "/People Tree View/ChangeOrder/Remove" "Delete") - (gtk_accel_path "/AllMainWindow/P" "p") - (gtk_accel_path "/Person View/ChangeOrder/Add" "i") - (gtk_accel_path "/AllMainWindow/N" "n") - (gtk_accel_path "/Place Tree View/ChangeOrder/Remove" "Delete") -; (gtk_accel_path "/AllMainWindow/Abandon" "") - (gtk_accel_path "/Events/ChangeOrder/Remove" "Delete") - (gtk_accel_path "/AllMainWindow/J" "j") - (gtk_accel_path "/Media/Bookmark/AddBook" "d") +; (gtk_accel_path "/ReportWindow/tag_report" "") +; (gtk_accel_path "/ToolWindow/dgenstats" "") +; (gtk_accel_path "/FileWindow/Quit" "q") +; (gtk_accel_path "/FileWindow/TipOfDay" "") +; (gtk_accel_path "/ReportWindow/ancestor_report" "") +; (gtk_accel_path "/ToolWindow/check" "") +; (gtk_accel_path "/FileWindow/Fullscreen" "F11") + (gtk_accel_path "/MainWindow/PersonAdd" "p") +; (gtk_accel_path "/AllMainWindow/P" "p") ; (gtk_accel_path "/ToolWindow/remove_unused" "") -; (gtk_accel_path "/Person View/PersonOther/SetActive" "") - (gtk_accel_path "/categoryviews/personview_1" "2") - (gtk_accel_path "/Sources/Bookmark/AddBook" "d") - (gtk_accel_path "/Place View/ChangeOrder/Add" "i") - (gtk_accel_path "/Person View/PersonEdit/Add" "i") - (gtk_accel_path "/Sources/Forward/Forward" "Right") - (gtk_accel_path "/Person View/Bookmark/AddBook" "d") -; (gtk_accel_path "/FileWindow/Preferences" "") +; (gtk_accel_path "/AllMainWindow/N" "n") +; (gtk_accel_path "/ReportWindow/WebCal" "") +; (gtk_accel_path "/ReportWindow/records" "") +; (gtk_accel_path "/AllMainWindow/J" "j") +; (gtk_accel_path "/ReportWindow/number_of_ancestors" "") +; (gtk_accel_path "/ToolWindow/evname" "") +; (gtk_accel_path "/Dashboard/AddGramplet" "") +; (gtk_accel_path "/ReportWindow/notelinkreport" "") +; (gtk_accel_path "/ReportWindow/det_descendant_report" "") +; (gtk_accel_path "/MainWindow/AddMenu" "") +; (gtk_accel_path "/AllMainWindow/WindowsMenu" "") +; (gtk_accel_path "/ToolWindow/test_for_date_parser_and_displayer" "") +; (gtk_accel_path "/AllMainWindow/Abandon" "") +; (gtk_accel_path "/ReportWindow/family_group" "") +; (gtk_accel_path "/AllMainWindow/9" "9") +; (gtk_accel_path "/AllMainWindow/8" "8") +; (gtk_accel_path "/ToolWindow/not_related" "") +; (gtk_accel_path "/AllMainWindow/7" "7") +; (gtk_accel_path "/AllMainWindow/6" "6") +; (gtk_accel_path "/AllMainWindow/5" "5") +; (gtk_accel_path "/AllMainWindow/4" "4") +; (gtk_accel_path "/AllMainWindow/3" "3") +; (gtk_accel_path "/AllMainWindow/2" "2") +; (gtk_accel_path "/AllMainWindow/1" "1") +; (gtk_accel_path "/AllMainWindow/0" "0") +; (gtk_accel_path "/WindowManger/M:4722902520" "") +; (gtk_accel_path "/FileWindow/About" "") +; (gtk_accel_path "/MainWindow/ToolsMenu" "") +; (gtk_accel_path "/FileWindow/FileMenu" "") +; (gtk_accel_path "/AllMainWindow/BackSpace" "BackSpace") +; (gtk_accel_path "/AllMainWindow/Export" "e") +; (gtk_accel_path "/AllMainWindow/Backup" "") +; (gtk_accel_path "/ToolWindow/ToolDebug" "") +; (gtk_accel_path "/ReportWindow/Graphs" "") +; (gtk_accel_path "/ReportWindow/RepText" "") +; (gtk_accel_path "/ToolWindow/dupfind" "") +; (gtk_accel_path "/ToolWindow/rebuild" "") +; (gtk_accel_path "/ToolWindow/patchnames" "") +; (gtk_accel_path "/ToolWindow/chtype" "") +; (gtk_accel_path "/ToolWindow/ToolRep" "") diff --git a/po/POTFILES.in b/po/POTFILES.in index b305ab160..865a4752c 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -345,6 +345,7 @@ gramps/gen/plug/_pluginreg.py gramps/gen/plug/docbackend/docbackend.py gramps/gen/plug/docgen/graphdoc.py gramps/gen/plug/docgen/paperstyle.py +gramps/gen/plug/docgen/treedoc.py gramps/gen/plug/menu/_enumeratedlist.py gramps/gen/plug/report/_book.py gramps/gen/plug/report/_constants.py diff --git a/po/POTFILES.skip b/po/POTFILES.skip index f7d173194..55c0b5a19 100644 --- a/po/POTFILES.skip +++ b/po/POTFILES.skip @@ -431,6 +431,7 @@ gramps/gui/views/treemodels/test/node_test.py # gramps/gui/widgets/__init__.py gramps/gui/widgets/basicentry.py +gramps/gui/widgets/cellrenderertextedit.py gramps/gui/widgets/dateentry.py gramps/gui/widgets/fanchart2way.py gramps/gui/widgets/fanchartdesc.py @@ -441,6 +442,7 @@ gramps/gui/widgets/menuitem.py gramps/gui/widgets/multitreeview.py gramps/gui/widgets/placeentry.py gramps/gui/widgets/selectionwidget.py +gramps/gui/widgets/shadebox.py gramps/gui/widgets/shortlistcomboentry.py gramps/gui/widgets/springseparator.py gramps/gui/widgets/statusbar.py