From cc6820f80c525b9febfec56d671c5db1ce7a2ecc Mon Sep 17 00:00:00 2001 From: Nick Hall Date: Tue, 26 May 2015 19:07:57 +0100 Subject: [PATCH] GEPS 036: Add date and language to place names --- gramps/gen/db/upgrade.py | 35 +++ gramps/gen/db/write.py | 4 +- gramps/gen/filters/rules/place/_hasdata.py | 2 +- gramps/gen/lib/__init__.py | 1 + gramps/gen/lib/place.py | 35 +-- gramps/gen/lib/placename.py | 214 ++++++++++++++++ gramps/gen/utils/location.py | 18 +- gramps/gui/clipboard.py | 19 +- gramps/gui/ddtargets.py | 4 +- gramps/gui/editors/displaytabs/__init__.py | 2 +- ...nameembedlist.py => placenameembedlist.py} | 59 +++-- .../{altnamemodel.py => placenamemodel.py} | 20 +- .../gui/editors/displaytabs/placerefmodel.py | 3 +- gramps/gui/editors/editplace.py | 25 +- gramps/gui/editors/editplacename.py | 84 +++--- gramps/gui/editors/editplaceref.py | 22 +- gramps/gui/glade/editplacename.glade | 239 ++++++++++++++++++ gramps/gui/views/treemodels/placemodel.py | 2 +- gramps/plugins/export/exportxml.py | 23 +- gramps/plugins/gramplet/locations.py | 2 +- gramps/plugins/gramplet/placedetails.py | 6 +- gramps/plugins/importer/importxml.py | 77 ++++-- gramps/plugins/lib/libgrampsxml.py | 2 +- gramps/plugins/lib/libplaceimport.py | 6 +- 24 files changed, 757 insertions(+), 147 deletions(-) create mode 100644 gramps/gen/lib/placename.py rename gramps/gui/editors/displaytabs/{altnameembedlist.py => placenameembedlist.py} (62%) rename gramps/gui/editors/displaytabs/{altnamemodel.py => placenamemodel.py} (73%) create mode 100644 gramps/gui/glade/editplacename.glade diff --git a/gramps/gen/db/upgrade.py b/gramps/gen/db/upgrade.py index 29c5eb8ab..c720a9547 100644 --- a/gramps/gen/db/upgrade.py +++ b/gramps/gen/db/upgrade.py @@ -47,6 +47,7 @@ from ..lib.nameorigintype import NameOriginType from ..lib.place import Place from ..lib.placeref import PlaceRef from ..lib.placetype import PlaceType +from ..lib.placename import PlaceName from ..lib.eventtype import EventType from ..lib.tag import Tag from ..utils.file import create_checksum @@ -80,6 +81,40 @@ def gramps_upgrade_pickle(self): with BSDDBTxn(self.env, self.metadata) as txn: txn.put(b'upgraded', 'Yes') +def gramps_upgrade_18(self): + """ + Upgrade database from version 17 to 18. + """ + length = len(self.place_map) + self.set_total(length) + + # --------------------------------- + # Modify Place + # --------------------------------- + # Convert name fields to use PlaceName. + for handle in self.place_map.keys(): + place = self.place_map[handle] + new_place = list(place) + name = PlaceName() + name.set_value(new_place[6]) + new_place[6] = name.serialize() + alt_names = [] + for name in new_place[7]: + alt_name = PlaceName() + alt_name.set_value(name) + alt_names.append(alt_name.serialize()) + new_place[7] = alt_names + new_place = tuple(new_place) + with BSDDBTxn(self.env, self.place_map) as txn: + if isinstance(handle, str): + handle = handle.encode('utf-8') + txn.put(handle, new_place) + self.update() + + # Bump up database version. Separate transaction to save metadata. + with BSDDBTxn(self.env, self.metadata) as txn: + txn.put(b'version', 18) + def gramps_upgrade_17(self): """ Upgrade database from version 16 to 17. diff --git a/gramps/gen/db/write.py b/gramps/gen/db/write.py index bcf4ece07..607735ec8 100644 --- a/gramps/gen/db/write.py +++ b/gramps/gen/db/write.py @@ -92,7 +92,7 @@ LOG = logging.getLogger(".citation") #_hdlr.setFormatter(logging.Formatter(fmt="%(name)s.%(levelname)s: %(message)s")) #_LOG.addHandler(_hdlr) _MINVERSION = 9 -_DBVERSION = 17 +_DBVERSION = 18 IDTRANS = "person_id" FIDTRANS = "family_id" @@ -2337,6 +2337,8 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): upgrade.gramps_upgrade_16(self) if version < 17: upgrade.gramps_upgrade_17(self) + if version < 18: + upgrade.gramps_upgrade_18(self) self.reset() self.set_total(6) diff --git a/gramps/gen/filters/rules/place/_hasdata.py b/gramps/gen/filters/rules/place/_hasdata.py index 98e3cf2ae..5d19e603f 100644 --- a/gramps/gen/filters/rules/place/_hasdata.py +++ b/gramps/gen/filters/rules/place/_hasdata.py @@ -78,6 +78,6 @@ class HasData(Rule): Match any name in a list of names. """ for name in place.get_all_names(): - if self.match_substring(0, name): + if self.match_substring(0, name.get_value()): return True return False diff --git a/gramps/gen/lib/__init__.py b/gramps/gen/lib/__init__.py index 7654b3e88..93357d62a 100644 --- a/gramps/gen/lib/__init__.py +++ b/gramps/gen/lib/__init__.py @@ -34,6 +34,7 @@ from .eventref import EventRef from .ldsord import LdsOrd from .mediaref import MediaRef from .name import Name +from .placename import PlaceName from .placeref import PlaceRef from .reporef import RepoRef from .surname import Surname diff --git a/gramps/gen/lib/place.py b/gramps/gen/lib/place.py index db09ab4e5..ff9b3b4fc 100644 --- a/gramps/gen/lib/place.py +++ b/gramps/gen/lib/place.py @@ -32,6 +32,7 @@ Place object for Gramps. #------------------------------------------------------------------------- from .primaryobj import PrimaryObject from .placeref import PlaceRef +from .placename import PlaceName from .placetype import PlaceType from .citationbase import CitationBase from .notebase import NoteBase @@ -79,7 +80,7 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): self.long = "" self.lat = "" self.title = "" - self.name = "" + self.name = PlaceName() self.alt_names = [] self.placeref_list = [] self.place_type = PlaceType() @@ -106,7 +107,8 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): """ return (self.handle, self.gramps_id, self.title, self.long, self.lat, [pr.serialize() for pr in self.placeref_list], - self.name, self.alt_names, + self.name.serialize(), + [an.serialize() for an in self.alt_names], self.place_type.serialize(), self.code, [al.serialize() for al in self.alt_loc], UrlBase.serialize(self), @@ -142,8 +144,8 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): "long": self.long, "lat": self.lat, "placeref_list": [pr.to_struct() for pr in self.placeref_list], - "name": self.name, - "alt_names": self.alt_names, + "name": self.name.to_struct(), + "alt_names": [an.to_struct() for an in self.alt_names], "place_type": self.place_type.to_struct(), "code": self.code, "alt_loc": [al.to_struct() for al in self.alt_loc], @@ -169,8 +171,8 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): struct.get("long", default.long), struct.get("lat", default.lat), [PlaceRef.from_struct(pr) for pr in struct.get("placeref_list", default.placeref_list)], - struct.get("name", default.name), - struct.get("alt_names", default.alt_names), + PlaceName.from_struct(struct.get("name", {})), + [PlaceName.from_struct(an) for an in struct.get("alt_names", default.alt_names)], PlaceType.from_struct(struct.get("place_type", {})), struct.get("code", default.code), [Location.from_struct(al) for al in struct.get("alt_loc", default.alt_loc)], @@ -192,7 +194,7 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): :type data: tuple """ (self.handle, self.gramps_id, self.title, self.long, self.lat, - placeref_list, self.name, self.alt_names, the_type, self.code, + placeref_list, name, alt_names, the_type, self.code, alt_loc, urls, media_list, citation_list, note_list, self.change, tag_list, self.private) = data @@ -200,6 +202,8 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): self.place_type.unserialize(the_type) self.alt_loc = [Location().unserialize(al) for al in alt_loc] self.placeref_list = [PlaceRef().unserialize(pr) for pr in placeref_list] + self.name = PlaceName().unserialize(name) + self.alt_names = [PlaceName().unserialize(an) for an in alt_names] UrlBase.unserialize(self, urls) MediaBase.unserialize(self, media_list) CitationBase.unserialize(self, citation_list) @@ -214,7 +218,7 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): :returns: Returns the list of all textual attributes of the object. :rtype: list """ - return [self.long, self.lat, self.title, self.name, self.gramps_id] + return [self.long, self.lat, self.title, self.gramps_id] def get_text_data_child_list(self): """ @@ -224,7 +228,8 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): :rtype: list """ - ret = self.media_list + self.alt_loc + self.urls + ret = (self.media_list + self.alt_loc + self.urls + + self.name + self.alt_names) return ret def get_citation_child_list(self): @@ -306,8 +311,8 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): """ Set the name of the Place object. - :param title: name to assign to the Place - :type title: str + :param name: name to assign to the Place + :type name: PlaceName """ self.name = name @@ -316,7 +321,7 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): Return the name of the Place object. :returns: Returns the name of the Place - :rtype: str + :rtype: PlaceName """ return self.name @@ -325,7 +330,7 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): Return a list of all names of the Place object. :returns: Returns a list of all names of the Place - :rtype: list + :rtype: list of PlaceName """ return [self.name] + self.alt_names @@ -485,7 +490,7 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): Return a list of alternative names for the current Place. :returns: Returns the alternative names for the Place - :rtype: list of strings + :rtype: list of PlaceName """ return self.alt_names @@ -495,7 +500,7 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): :param name_list: The list of names to assign to the Place's internal list. - :type name_list: list of strings + :type name_list: list of PlaceName """ self.alt_names = name_list diff --git a/gramps/gen/lib/placename.py b/gramps/gen/lib/placename.py new file mode 100644 index 000000000..7e381a3aa --- /dev/null +++ b/gramps/gen/lib/placename.py @@ -0,0 +1,214 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2015 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. +# + +""" +Place name class for Gramps +""" + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +from .secondaryobj import SecondaryObject +from .datebase import DateBase +from .const import IDENTICAL, EQUAL, DIFFERENT +from .handle import Handle + +#------------------------------------------------------------------------- +# +# Place Name +# +#------------------------------------------------------------------------- +class PlaceName(SecondaryObject, DateBase): + """ + Place name class. + + This class is for keeping information about place names. + """ + + def __init__(self, source=None): + """ + Create a new PlaceName instance, copying from the source if present. + """ + DateBase.__init__(self, source) + self.value = '' + self.lang = '' + + def serialize(self): + """ + Convert the object to a serialized tuple of data. + """ + return ( + self.value, + DateBase.serialize(self), + self.lang + ) + + def to_struct(self): + """ + Convert the data held in this object to a structure (eg, + struct) that represents all the data elements. + + This method is used to recursively convert the object into a + self-documenting form that can easily be used for various + purposes, including diffs and queries. + + These structures may be primitive Python types (string, + integer, boolean, etc.) or complex Python types (lists, + tuples, or dicts). If the return type is a dict, then the keys + of the dict match the fieldname of the object. If the return + struct (or value of a dict key) is a list, then it is a list + of structs. Otherwise, the struct is just the value of the + attribute. + + :returns: Returns a struct containing the data of the object. + :rtype: dict + """ + return { + "_class": "PlaceName", + "value": self.value, + "date": DateBase.to_struct(self), + "lang": self.lang + } + + @classmethod + def from_struct(cls, struct): + """ + Given a struct data representation, return a serialized object. + + :returns: Returns a serialized object + """ + default = PlaceName() + return ( + struct.get("value", default.value), + DateBase.from_struct(struct.get("date", {})), + struct.get("lang", default.lang) + ) + + def unserialize(self, data): + """ + Convert a serialized tuple of data to an object. + """ + (self.value, date, self.lang) = data + DateBase.unserialize(self, date) + return self + + def get_text_data_list(self): + """ + Return the list of all textual attributes of the object. + + :returns: Returns the list of all textual attributes of the object. + :rtype: list + """ + return [self.value] + + def get_text_data_child_list(self): + """ + Return the list of child objects that may carry textual data. + + :returns: Returns the list of child objects that may carry textual data. + :rtype: list + """ + return [] + + def get_citation_child_list(self): + """ + Return the list of child secondary objects that may refer citations. + + :returns: Returns the list of child secondary child objects that may + refer citations. + :rtype: list + """ + return [] + + def get_note_child_list(self): + """ + Return the list of child secondary objects that may refer notes. + + :returns: Returns the list of child secondary child objects that may + refer notes. + :rtype: list + """ + return [] + + def get_referenced_handles(self): + """ + Return the list of (classname, handle) tuples for all directly + referenced primary objects. + + :returns: Returns the list of (classname, handle) tuples for referenced + objects. + :rtype: list + """ + return [] + + def get_handle_referents(self): + """ + Return the list of child objects which may, directly or through their + children, reference primary objects. + + :returns: Returns the list of objects referencing primary objects. + :rtype: list + """ + return [] + + def is_equivalent(self, other): + """ + Return if this eventref is equivalent, that is agrees in handle and + role, to other. + + :param other: The eventref to compare this one to. + :type other: PlaceName + :returns: Constant indicating degree of equivalence. + :rtype: int + """ + if (self.value != other.value or + self.date != other.date or + self.lang != other.lang): + return DIFFERENT + else: + if self.is_equal(other): + return IDENTICAL + else: + return EQUAL + + def set_value(self, value): + """ + Set the name for the PlaceName instance. + """ + self.value = value + + def get_value(self): + """ + Return the name for the PlaceName instance. + """ + return self.value + + def set_language(self, lang): + """ + Set the language for the PlaceName instance. + """ + self.lang = lang + + def get_language(self): + """Return the language for the PlaceName instance.""" + return self.lang diff --git a/gramps/gen/utils/location.py b/gramps/gen/utils/location.py index 1c7d92d6b..2e7f54d6d 100644 --- a/gramps/gen/utils/location.py +++ b/gramps/gen/utils/location.py @@ -35,22 +35,29 @@ def get_location_list(db, place, date=None): if date is None: date = Today() visited = [place.handle] - lines = [(place.name, place.get_type())] + lines = [(__get_name(place, date), place.get_type())] while True: handle = None for placeref in place.get_placeref_list(): ref_date = placeref.get_date_object() if ref_date.is_empty() or date.match(ref_date): handle = placeref.ref + break if handle is None or handle in visited: break place = db.get_place_from_handle(handle) if place is None: break visited.append(handle) - lines.append((place.name, place.get_type())) + lines.append((__get_name(place, date), place.get_type())) return lines +def __get_name(place, date): + for place_name in place.get_all_names(): + name_date = place_name.get_date_object() + if name_date.is_empty() or date.match(name_date): + return place_name.get_value() + #------------------------------------------------------------------------- # # get_main_location @@ -77,7 +84,7 @@ def get_locations(db, place): containing dictionaries of place types and names. """ locations = [] - todo = [(place, [(int(place.get_type()), place.get_all_names())], + todo = [(place, [(int(place.get_type()), __get_all_names(place))], [place.handle])] while len(todo): place, tree, visited = todo.pop() @@ -86,13 +93,16 @@ def get_locations(db, place): parent_place = db.get_place_from_handle(parent.ref) if parent_place is not None: parent_tree = tree + [(int(parent_place.get_type()), - parent_place.get_all_names())] + __get_all_names(parent_place))] parent_visited = visited + [parent.ref] todo.append((parent_place, parent_tree, parent_visited)) if len(place.get_placeref_list()) == 0: locations.append(dict(tree)) return locations +def __get_all_names(place): + return [name.get_value() for name in place.get_all_names()] + #------------------------------------------------------------------------- # # located_in diff --git a/gramps/gui/clipboard.py b/gramps/gui/clipboard.py index 06f189fd4..e3e552bc3 100644 --- a/gramps/gui/clipboard.py +++ b/gramps/gui/clipboard.py @@ -576,7 +576,7 @@ class ClipPlaceRef(ClipObjWrapper): base = self._db.get_place_from_handle(self._obj.ref) if base: self._title = base.gramps_id - self._value = str(base.get_name()) + self._value = place_displayer.display(self._db, base) class ClipName(ClipObjWrapper): @@ -594,6 +594,22 @@ class ClipName(ClipObjWrapper): self._title = str(self._obj.get_type()) self._value = self._obj.get_name() +class ClipPlaceName(ClipObjWrapper): + + DROP_TARGETS = [DdTargets.PLACENAME] + DRAG_TARGET = DdTargets.PLACENAME + ICON = ICONS['name'] + + def __init__(self, dbstate, obj): + super(ClipPlaceName, self).__init__(dbstate, obj) + self._type = _("Place Name") + self.refresh() + + def refresh(self): + if self._obj: + self._title = get_date(self._obj) + self._value = self._obj.get_value() + class ClipSurname(ClipObjWrapper): DROP_TARGETS = [DdTargets.SURNAME] @@ -1099,6 +1115,7 @@ class ClipboardListView(object): self.register_wrapper_class(ClipAttribute) self.register_wrapper_class(ClipFamilyAttribute) self.register_wrapper_class(ClipName) + self.register_wrapper_class(ClipPlaceName) self.register_wrapper_class(ClipRepositoryLink) self.register_wrapper_class(ClipMediaObj) self.register_wrapper_class(ClipMediaRef) diff --git a/gramps/gui/ddtargets.py b/gramps/gui/ddtargets.py index 6ff7532cc..4f0613b82 100644 --- a/gramps/gui/ddtargets.py +++ b/gramps/gui/ddtargets.py @@ -138,6 +138,7 @@ class _DdTargets(object): self.NAME = _DdType(self, 'name') self.NOTE_LINK = _DdType(self, 'note-link') self.PLACE_LINK = _DdType(self, 'place-link') + self.PLACENAME = _DdType(self, 'placename') self.PLACEREF = _DdType(self, 'placeref') self.REPO_LINK = _DdType(self, 'repo-link') self.REPOREF = _DdType(self, 'reporef') @@ -149,7 +150,7 @@ class _DdTargets(object): self.PERSONREF = _DdType(self, 'personref') self.SOURCEREF = _DdType(self, 'srcref') self.SOURCE_LINK = _DdType(self, 'source-link') - self.SRCATTRIBUTE = _DdType(self, 'sattr') + self.SRCATTRIBUTE = _DdType(self, 'sattr') self.URL = _DdType(self, 'url') self.SURNAME = _DdType(self, 'surname') self.CITATION_LINK = _DdType(self, 'citation-link') @@ -170,6 +171,7 @@ class _DdTargets(object): self.NOTE_LINK, self.PLACE_LINK, self.PLACEREF, + self.PLACENAME, self.PERSON_LINK, self.FAMILY_LINK, self.LINK_LIST, diff --git a/gramps/gui/editors/displaytabs/__init__.py b/gramps/gui/editors/displaytabs/__init__.py index 9017d31ad..2a180ed1f 100644 --- a/gramps/gui/editors/displaytabs/__init__.py +++ b/gramps/gui/editors/displaytabs/__init__.py @@ -35,7 +35,6 @@ from .childmodel import ChildModel from .grampstab import GrampsTab from .embeddedlist import EmbeddedList, TEXT_COL, MARKUP_COL, ICON_COL from .addrembedlist import AddrEmbedList -from .altnameembedlist import AltNameEmbedList from .attrembedlist import AttrEmbedList from .backreflist import BackRefList from .eventattrembedlist import EventAttrEmbedList @@ -57,6 +56,7 @@ from .personeventembedlist import PersonEventEmbedList from .personrefembedlist import PersonRefEmbedList from .personbackreflist import PersonBackRefList from .placebackreflist import PlaceBackRefList +from .placenameembedlist import PlaceNameEmbedList from .placerefembedlist import PlaceRefEmbedList from .repoembedlist import RepoEmbedList from .surnametab import SurnameTab diff --git a/gramps/gui/editors/displaytabs/altnameembedlist.py b/gramps/gui/editors/displaytabs/placenameembedlist.py similarity index 62% rename from gramps/gui/editors/displaytabs/altnameembedlist.py rename to gramps/gui/editors/displaytabs/placenameembedlist.py index 71eb38c12..155c5a7df 100644 --- a/gramps/gui/editors/displaytabs/altnameembedlist.py +++ b/gramps/gui/editors/displaytabs/placenameembedlist.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2015 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 @@ -32,60 +33,86 @@ from gi.repository import GObject, GLib # Gramps classes # #------------------------------------------------------------------------- +from gramps.gen.lib import PlaceName from gramps.gen.errors import WindowActiveError -from .altnamemodel import AltNameModel +from ...ddtargets import DdTargets +from .placenamemodel import PlaceNameModel from .embeddedlist import EmbeddedList, TEXT_COL, MARKUP_COL, ICON_COL #------------------------------------------------------------------------- # -# AltNameEmbedList +# PlaceNameEmbedList # #------------------------------------------------------------------------- -class AltNameEmbedList(EmbeddedList): +class PlaceNameEmbedList(EmbeddedList): - _HANDLE_COL = 0 + _HANDLE_COL = 3 + _DND_TYPE = DdTargets.PLACENAME + + _MSG = { + 'add' : _('Create and add a new place name'), + 'del' : _('Remove the existing place name'), + 'edit' : _('Edit the selected place name'), + 'up' : _('Move the selected place name upwards'), + 'down' : _('Move the selected place name downwards'), + } #index = column in model. Value = # (name, sortcol in model, width, markup/text, weigth_col _column_names = [ - (_('Place Name'), 0, 250, TEXT_COL, -1, None), + (_('Name'), 0, 250, TEXT_COL, -1, None), + (_('Date'), 1, 250, TEXT_COL, -1, None), + (_('Language'), 2, 100, TEXT_COL, -1, None), ] def __init__(self, dbstate, uistate, track, data): self.data = data EmbeddedList.__init__(self, dbstate, uistate, track, - _('Alternative Names'), AltNameModel, + _('Alternative Names'), PlaceNameModel, move_buttons=True) def get_data(self): return self.data def column_order(self): - return ((1, 0),) + return ((1, 0), (1, 1), (1, 2)) def add_button_clicked(self, obj): + """ + Called when the Add button is clicked. + """ + pname = PlaceName() try: from .. import EditPlaceName EditPlaceName(self.dbstate, self.uistate, self.track, - self.get_data(), -1, self.add_callback) + pname, self.add_callback) except WindowActiveError: - pass + return - def add_callback(self, place_name): + def add_callback(self, pname): + """ + Called to update the screen when a new place name is added. + """ data = self.get_data() + data.append(pname) self.rebuild() GLib.idle_add(self.tree.scroll_to_cell, len(data) - 1) def edit_button_clicked(self, obj): - place_name = self.get_selected() - if place_name: + """ + Called with the Edit button is clicked. + """ + pname = self.get_selected() + if pname: try: from .. import EditPlaceName - data = self.get_data() EditPlaceName(self.dbstate, self.uistate, self.track, - data, data.index(place_name), self.edit_callback) + pname, self.edit_callback) except WindowActiveError: - pass + return - def edit_callback(self, place_name): + def edit_callback(self, name): + """ + Called to update the screen when the place name changes. + """ self.rebuild() diff --git a/gramps/gui/editors/displaytabs/altnamemodel.py b/gramps/gui/editors/displaytabs/placenamemodel.py similarity index 73% rename from gramps/gui/editors/displaytabs/altnamemodel.py rename to gramps/gui/editors/displaytabs/placenamemodel.py index 2012481eb..eaacf424d 100644 --- a/gramps/gui/editors/displaytabs/altnamemodel.py +++ b/gramps/gui/editors/displaytabs/placenamemodel.py @@ -1,7 +1,7 @@ # # Gramps - a GTK+/GNOME based genealogy program # -# Copyright (C) 2014 Nick Hall +# Copyright (C) 2014-2015 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 @@ -30,17 +30,21 @@ from gi.repository import Gtk # Gramps classes # #------------------------------------------------------------------------- - +from gramps.gen.datehandler import get_date #------------------------------------------------------------------------- # -# AltNameModel +# PlaceNameModel # #------------------------------------------------------------------------- -class AltNameModel(Gtk.ListStore): +class PlaceNameModel(Gtk.ListStore): - def __init__(self, place_name_list, db): - Gtk.ListStore.__init__(self, str) + def __init__(self, obj_list, db): + Gtk.ListStore.__init__(self, str, str, str, object) self.db = db - for place_name in place_name_list: - self.append(row=[place_name]) + for obj in obj_list: + self.append(row=[obj.get_value(), + get_date(obj), + obj.get_language(), + obj, + ]) diff --git a/gramps/gui/editors/displaytabs/placerefmodel.py b/gramps/gui/editors/displaytabs/placerefmodel.py index 270379bc3..534a61ca8 100644 --- a/gramps/gui/editors/displaytabs/placerefmodel.py +++ b/gramps/gui/editors/displaytabs/placerefmodel.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2015 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 @@ -45,7 +46,7 @@ class PlaceRefModel(Gtk.ListStore): for obj in obj_list: place = self.db.get_place_from_handle(obj.ref) self.append(row=[place.get_gramps_id(), - place.get_name(), + place.get_name().get_value(), str(place.get_type()), displayer.display(obj.date), obj, ]) diff --git a/gramps/gui/editors/editplace.py b/gramps/gui/editors/editplace.py index ce90131c3..2d76acd17 100644 --- a/gramps/gui/editors/editplace.py +++ b/gramps/gui/editors/editplace.py @@ -3,7 +3,7 @@ # # Copyright (C) 2000-2006 Donald N. Allingham # Copyright (C) 2009 Gary Burton -# Copyright (C) 2010 Nick Hall +# Copyright (C) 2010,2015 Nick Hall # Copyright (C) 2011 Tim G L lyons # # This program is free software; you can redistribute it and/or modify @@ -47,7 +47,7 @@ from gramps.gen.lib import NoteType, Place from gramps.gen.db import DbTxn from gramps.gen.utils.location import get_location_list from .editprimary import EditPrimary -from .displaytabs import (PlaceRefEmbedList, AltNameEmbedList, +from .displaytabs import (PlaceRefEmbedList, PlaceNameEmbedList, LocationEmbedList, CitationEmbedList, GalleryTab, NoteTab, WebEmbedList, PlaceBackRefList) from ..widgets import (MonitoredEntry, PrivacyButton, MonitoredTagList, @@ -114,7 +114,8 @@ class EditPlace(EditPrimary): self.db.readonly) self.name = MonitoredEntry(self.top.get_object("name_entry"), - self.obj.set_name, self.obj.get_name, + self.obj.get_name().set_value, + self.obj.get_name().get_value, self.db.readonly, changed=self.name_changed) @@ -195,10 +196,10 @@ class EditPlace(EditPrimary): self._add_tab(notebook, self.placeref_list) self.track_ref_for_deletion("placeref_list") - self.alt_name_list = AltNameEmbedList(self.dbstate, - self.uistate, - self.track, - self.obj.alt_names) + self.alt_name_list = PlaceNameEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.alt_names) self._add_tab(notebook, self.alt_name_list) self.track_ref_for_deletion("alt_name_list") @@ -253,15 +254,9 @@ class EditPlace(EditPrimary): def save(self, *obj): self.ok_button.set_sensitive(False) - if self.object_is_empty(): - ErrorDialog(_("Cannot save place"), - _("No data exists for this place. Please " - "enter data or cancel the edit.")) - self.ok_button.set_sensitive(True) - return - if self.obj.get_name().strip() == '': - msg1 = _("Cannot save location. Name not entered.") + if self.obj.get_name().get_value().strip() == '': + msg1 = _("Cannot save place. Name not entered.") msg2 = _("You must enter a name before saving.") ErrorDialog(msg1, msg2) self.ok_button.set_sensitive(True) diff --git a/gramps/gui/editors/editplacename.py b/gramps/gui/editors/editplacename.py index 1fd28f80a..d4bdf9111 100644 --- a/gramps/gui/editors/editplacename.py +++ b/gramps/gui/editors/editplacename.py @@ -2,7 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham -# Copyright (C) 2014 Nick Hall +# Copyright (C) 2014-2015 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 @@ -31,9 +31,10 @@ from gi.repository import Gtk # Gramps modules # #------------------------------------------------------------------------- -from ..managedwindow import ManagedWindow +from .editsecondary import EditSecondary +from ..glade import Glade +from ..widgets import MonitoredDate, MonitoredEntry from ..dialog import ErrorDialog -from ..display import display_help from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext @@ -42,62 +43,53 @@ _ = glocale.translation.gettext # EditPlaceName class # #------------------------------------------------------------------------- -class EditPlaceName(ManagedWindow): - - def __init__(self, dbstate, uistate, track, data, index, callback): - ManagedWindow.__init__(self, uistate, track, self.__class__) - - self.data = data - self.index = index - self.callback = callback +class EditPlaceName(EditSecondary): + """ + Displays a dialog that allows the user to edit a place name. + """ + def __init__(self, dbstate, uistate, track, pname, callback): + EditSecondary.__init__(self, dbstate, uistate, track, pname, callback) + def _local_init(self): self.width_key = 'interface.place-name-width' self.height_key = 'interface.place-name-height' - window = Gtk.Dialog('', uistate.window, - Gtk.DialogFlags.DESTROY_WITH_PARENT, None) + self.top = Glade() + self.set_window(self.top.toplevel, + self.top.get_object("title"), + _('Place Name Editor')) - self.cancel_button = window.add_button(_('_Cancel'), - Gtk.ResponseType.CANCEL) - self.ok_button = window.add_button(_('_OK'), - Gtk.ResponseType.ACCEPT) - self.help_button = window.add_button(_('_Help'), - Gtk.ResponseType.HELP) + def _setup_fields(self): + self.value = MonitoredEntry( + self.top.get_object("value"), self.obj.set_value, + self.obj.get_value, self.db.readonly) - window.connect('response', self.response) - self.set_window(window, None, _('Place Name Editor')) + self.date = MonitoredDate( + self.top.get_object("date_entry"), + self.top.get_object("date_stat"), + self.obj.get_date_object(), + self.uistate, + self.track, + self.db.readonly) - hbox = Gtk.Box() - label = Gtk.Label(label=_('Place Name:')) - self.entry = Gtk.Entry() - if index >= 0: - self.entry.set_text(data[index]) - hbox.pack_start(label, False, False, 4) - hbox.pack_start(self.entry, True, True, 4) - hbox.show_all() - window.vbox.pack_start(hbox, False, False, 4) + self.language = MonitoredEntry( + self.top.get_object("language"), self.obj.set_language, + self.obj.get_language, self.db.readonly) - self._set_size() - self.show() + def _connect_signals(self): + self.define_help_button(self.top.get_object('help')) + self.define_cancel_button(self.top.get_object('cancel')) + self.define_ok_button(self.top.get_object('ok'),self.save) - def response(self, obj, response_id): - if response_id == Gtk.ResponseType.CANCEL: - self.close() - if response_id == Gtk.ResponseType.ACCEPT: - self.save() - if response_id == Gtk.ResponseType.HELP: - display_help('', '') + def build_menu_names(self, obj): + return (_('Place Name'),_('Place Name Editor')) def save(self, *obj): - place_name = self.entry.get_text() - if not place_name: + if not self.obj.get_value(): ErrorDialog(_("Cannot save place name"), _("The place name cannot be empty")) return - if self.index >= 0: - self.data[self.index] = place_name - else: - self.data.append(place_name) + if self.callback: - self.callback(place_name) + self.callback(self.obj) self.close() diff --git a/gramps/gui/editors/editplaceref.py b/gramps/gui/editors/editplaceref.py index ec3bf3db9..9daddd04a 100644 --- a/gramps/gui/editors/editplaceref.py +++ b/gramps/gui/editors/editplaceref.py @@ -28,7 +28,7 @@ from .editreference import RefTab, EditReference from ..glade import Glade from ..widgets import (MonitoredDate, MonitoredEntry, MonitoredDataType, PrivacyButton, MonitoredTagList) -from .displaytabs import (PlaceRefEmbedList, AltNameEmbedList, +from .displaytabs import (PlaceRefEmbedList, PlaceNameEmbedList, LocationEmbedList, CitationEmbedList, GalleryTab, NoteTab, WebEmbedList, PlaceBackRefList) from gramps.gen.lib import NoteType @@ -37,6 +37,7 @@ from gramps.gen.errors import ValidationError from gramps.gen.utils.place import conv_lat_lon from gramps.gen.display.place import displayer as place_displayer from gramps.gen.config import config +from ..dialog import ErrorDialog from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext @@ -113,7 +114,8 @@ class EditPlaceRef(EditReference): self.db.readonly) self.name = MonitoredEntry(self.top.get_object("name_entry"), - self.source.set_name, self.source.get_name, + self.source.get_name().set_value, + self.source.get_name().get_value, self.db.readonly, changed=self.name_changed) @@ -197,10 +199,10 @@ class EditPlaceRef(EditReference): self._add_tab(notebook, self.placeref_list) self.track_ref_for_deletion("placeref_list") - self.alt_name_list = AltNameEmbedList(self.dbstate, - self.uistate, - self.track, - self.source.alt_names) + self.alt_name_list = PlaceNameEmbedList(self.dbstate, + self.uistate, + self.track, + self.source.alt_names) self._add_tab(notebook, self.alt_name_list) self.track_ref_for_deletion("alt_name_list") @@ -252,6 +254,14 @@ class EditPlaceRef(EditReference): self._setup_notebook_tabs(notebook) def save(self, *obj): + self.ok_button.set_sensitive(False) + + if self.source.get_name().get_value().strip() == '': + msg1 = _("Cannot save place. Name not entered.") + msg2 = _("You must enter a name before saving.") + ErrorDialog(msg1, msg2) + self.ok_button.set_sensitive(True) + return if self.source.handle: with DbTxn(_("Modify Place"), self.db) as trans: diff --git a/gramps/gui/glade/editplacename.glade b/gramps/gui/glade/editplacename.glade new file mode 100644 index 000000000..a34dbf675 --- /dev/null +++ b/gramps/gui/glade/editplacename.glade @@ -0,0 +1,239 @@ + + + + + + + False + dialog + + + True + False + vertical + + + True + False + end + + + _Cancel + True + True + True + True + True + + + False + False + 0 + + + + + _OK + True + True + True + True + True + True + Accept changes and close window + Accept changes and close window + True + + + False + False + 1 + + + + + _Help + True + True + True + True + True + + + False + False + 2 + + + + + False + True + end + 0 + + + + + True + False + vertical + + + True + False + 12 + 6 + 12 + + + True + False + start + _Date: + True + center + date_entry + + + 0 + 1 + + + + + True + False + start + Language: + True + center + language + + + 0 + 2 + + + + + True + True + Mail address. + +Note: Use Residence Event for genealogical address data. + True + + + + 1 + 2 + + + + + True + True + True + True + Invoke date editor + none + + + True + False + gramps-date + + + Date + + + + + + + + + + Date + + + + + + 2 + 1 + + + + + True + True + Date at which the address is valid. + True + + + + 1 + 1 + + + + + True + True + Mail address. + +Note: Use Residence Event for genealogical address data. + True + + + + 1 + 0 + + + + + True + False + start + Name: + True + center + language + + + 0 + 0 + + + + + + + + + + + False + True + 0 + + + + + True + True + 1 + + + + + + cancel + ok + help + + + diff --git a/gramps/gui/views/treemodels/placemodel.py b/gramps/gui/views/treemodels/placemodel.py index e4e115c79..b8eab4000 100644 --- a/gramps/gui/views/treemodels/placemodel.py +++ b/gramps/gui/views/treemodels/placemodel.py @@ -118,7 +118,7 @@ class PlaceBaseModel(object): return str(data[2]) def column_name(self, data): - return str(data[6]) + return str(data[6][0]) def column_longitude(self, data): if not data[3]: diff --git a/gramps/plugins/export/exportxml.py b/gramps/plugins/export/exportxml.py index 043540e5d..991d21ea6 100644 --- a/gramps/plugins/export/exportxml.py +++ b/gramps/plugins/export/exportxml.py @@ -731,6 +731,21 @@ class GrampsXmlWriter(UpdateCallback): self.write_date(date, index+1) self.g.write('%s\n' % sp) + def dump_place_name(self, place_name, index=1): + sp = " " * index + value = place_name.get_value() + date = place_name.get_date_object() + lang = place_name.get_language() + self.g.write('%s\n') + else: + self.g.write('>\n') + self.write_date(date, index+1) + self.g.write('%s\n' % sp) + def write_event(self,event,index=1): if not event: return @@ -1196,9 +1211,7 @@ class GrampsXmlWriter(UpdateCallback): def write_place_obj(self, place, index=1): self.write_primary_tag("placeobj", place, index, close=False) - pname = self.fix(place.get_name()) ptype = self.fix(place.get_type().xml_str()) - self.g.write(' name="%s"' % pname) self.g.write(' type="%s"' % ptype) self.g.write('>\n') @@ -1206,8 +1219,10 @@ class GrampsXmlWriter(UpdateCallback): code = self.fix(place.get_code()) self.write_line_nofix("ptitle", title, index+1) self.write_line_nofix("code", code, index+1) - for name in place.get_alternative_names(): - self.write_line("alt_name", name, index+1) + + self.dump_place_name(place.get_name(), index+1) + for pname in place.get_alternative_names(): + self.dump_place_name(pname, index+1) longitude = self.fix(place.get_longitude()) lat = self.fix(place.get_latitude()) diff --git a/gramps/plugins/gramplet/locations.py b/gramps/plugins/gramplet/locations.py index 4672f7fd7..21f1dac39 100644 --- a/gramps/plugins/gramplet/locations.py +++ b/gramps/plugins/gramplet/locations.py @@ -126,7 +126,7 @@ class Locations(Gramplet, DbGUIElement): place_date = get_date(placeref) place_sort = '%012d' % placeref.get_date_object().get_sort_value() parent_place = self.dbstate.db.get_place_from_handle(placeref.ref) - parent_name = parent_place.get_name() + parent_name = parent_place.get_name().get_value() parent_type = str(parent_place.get_type()) parent_node = self.model.add([placeref.ref, diff --git a/gramps/plugins/gramplet/placedetails.py b/gramps/plugins/gramplet/placedetails.py index f79bf4581..24fd2438d 100644 --- a/gramps/plugins/gramplet/placedetails.py +++ b/gramps/plugins/gramplet/placedetails.py @@ -1,6 +1,6 @@ # Gramps - a GTK+/GNOME based genealogy program # -# Copyright (C) 2011 Nick Hall +# Copyright (C) 2011,2015 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 @@ -108,7 +108,7 @@ class PlaceDetails(Gramplet): self.title.set_text(title) self.clear_grid() - self.add_row(_('Name'), place.get_name()) + self.add_row(_('Name'), place.get_name().get_value()) self.add_row(_('Type'), place.get_type()) self.display_separator() self.display_alt_names(place) @@ -125,7 +125,7 @@ class PlaceDetails(Gramplet): """ Display alternative names for the place. """ - alt_names = place .get_alternative_names() + alt_names = [name.get_value() for name in place.get_alternative_names()] if len(alt_names) > 0: self.add_row(_('Alternative Names'), '\n'.join(alt_names)) diff --git a/gramps/plugins/importer/importxml.py b/gramps/plugins/importer/importxml.py index 4f1a51574..00aa28420 100644 --- a/gramps/plugins/importer/importxml.py +++ b/gramps/plugins/importer/importxml.py @@ -47,15 +47,15 @@ LOG = logging.getLogger(".ImportXML") # #------------------------------------------------------------------------- from gramps.gen.mime import get_type -from gramps.gen.lib import (Address, Attribute, AttributeType, ChildRef, - ChildRefType, Citation, Date, DateError, Event, EventRef, - EventRoleType, EventType, Family, LdsOrd, Location, - MediaObject, MediaRef, Name, NameOriginType, - NameType, Note, NoteType, Person, PersonRef, - Place, RepoRef, Repository, Researcher, Source, - SrcAttribute, SrcAttributeType, PlaceType, - StyledText, StyledTextTag, StyledTextTagType, - Surname, Tag, Url, PlaceRef) +from gramps.gen.lib import (Address, Attribute, AttributeType, ChildRef, + ChildRefType, Citation, Date, DateError, Event, + EventRef, EventRoleType, EventType, Family, LdsOrd, + Location, MediaObject, MediaRef, Name, + NameOriginType, NameType, Note, NoteType, Person, + PersonRef, Place, PlaceName, PlaceRef, PlaceType, + RepoRef, Repository, Researcher, Source, + SrcAttribute, SrcAttributeType, StyledText, + StyledTextTag, StyledTextTagType, Surname, Tag, Url) from gramps.gen.db import DbTxn from gramps.gen.db.write import CLASS_TO_KEY_MAP from gramps.gen.errors import GrampsImportError @@ -539,7 +539,10 @@ class GrampsParser(UpdateCallback): self.attribute = None self.srcattribute = None self.placeobj = None + self.placeref = None + self.place_name = None self.locations = 0 + self.place_names = 0 self.place_map = {} self.place_import = PlaceImport(self.db) @@ -1149,11 +1152,14 @@ class GrampsParser(UpdateCallback): self.inaugurate_id(attrs.get('id'), PLACE_KEY, self.placeobj) self.placeobj.private = bool(attrs.get("priv")) self.placeobj.change = int(attrs.get('change', self.change)) - if self.__xml_version >= (1, 6, 0): - self.placeobj.name = attrs.get('name', '') - if 'type' in attrs: - self.placeobj.place_type.set_from_xml_str(attrs.get('type')) + if self.__xml_version == (1, 6, 0): + place_name = PlaceName() + place_name.set_value(attrs.get('name', '')) + self.placeobj.name = place_name + if 'type' in attrs: + self.placeobj.place_type.set_from_xml_str(attrs.get('type')) self.info.add('new-object', PLACE_KEY, self.placeobj) + self.place_names = 0 # GRAMPS LEGACY: title in the placeobj tag self.placeobj.title = attrs.get('title', '') @@ -1192,8 +1198,9 @@ class GrampsParser(UpdateCallback): for level, name in enumerate(location): if name: break - - self.placeobj.set_name(name) + place_name = PlaceName() + place_name.set_value(name) + self.placeobj.set_name(place_name) type_num = 7 - level if name else PlaceType.UNKNOWN self.placeobj.set_type(PlaceType(type_num)) codes = [attrs.get('postal'), attrs.get('phone')] @@ -1692,6 +1699,23 @@ class GrampsParser(UpdateCallback): self.person.add_family_handle(handle) def start_name(self, attrs): + if self.person: + self.start_person_name(attrs) + else: + self.start_place_name(attrs) + + def start_place_name(self, attrs): + self.place_name = PlaceName() + self.place_name.set_value(attrs["value"]) + if "lang" in attrs: + self.place_name.set_language(attrs["lang"]) + if self.place_names == 0: + self.placeobj.set_name(self.place_name) + else: + self.placeobj.add_alternative_name(self.place_name) + self.place_names += 1 + + def start_person_name(self, attrs): if not self.in_witness: self.name = Name() name_type = attrs.get('type', "Birth Name") @@ -2321,8 +2345,10 @@ class GrampsParser(UpdateCallback): date_value = self.name.get_date_object() elif self.event: date_value = self.event.get_date_object() - else: + elif self.placeref: date_value = self.placeref.get_date_object() + elif self.place_name: + date_value = self.place_name.get_date_object() start = attrs['start'].split('-') stop = attrs['stop'].split('-') @@ -2411,8 +2437,10 @@ class GrampsParser(UpdateCallback): date_value = self.name.get_date_object() elif self.event: date_value = self.event.get_date_object() - else: + elif self.placeref: date_value = self.placeref.get_date_object() + elif self.place_name: + date_value = self.place_name.get_date_object() bce = 1 val = attrs['val'] @@ -2585,7 +2613,9 @@ class GrampsParser(UpdateCallback): self.placeobj.code = tag def stop_alt_name(self, tag): - self.placeobj.add_alternative_name(tag) + place_name = PlaceName() + place_name.set_value(tag) + self.placeobj.add_alternative_name(place_name) def stop_placeobj(self, *tag): self.db.commit_place(self.placeobj, self.trans, @@ -2658,7 +2688,16 @@ class GrampsParser(UpdateCallback): self.event.get_change_time()) self.event = None - def stop_name(self, tag): + def stop_name(self, attrs): + if self.person: + self.stop_person_name(attrs) + else: + self.stop_place_name(attrs) + + def stop_place_name(self, tag): + self.place_name = None + + def stop_person_name(self, tag): if self.in_witness: # Parse witnesses created by older gramps note = Note() diff --git a/gramps/plugins/lib/libgrampsxml.py b/gramps/plugins/lib/libgrampsxml.py index 2c721b86b..63316b32c 100644 --- a/gramps/plugins/lib/libgrampsxml.py +++ b/gramps/plugins/lib/libgrampsxml.py @@ -33,5 +33,5 @@ # Public Constants # #------------------------------------------------------------------------ -GRAMPS_XML_VERSION_TUPLE = (1,6,0) # version for Gramps 4.1 +GRAMPS_XML_VERSION_TUPLE = (1, 7, 0) # version for Gramps 4.2 GRAMPS_XML_VERSION = '.'.join(str(i) for i in GRAMPS_XML_VERSION_TUPLE) diff --git a/gramps/plugins/lib/libplaceimport.py b/gramps/plugins/lib/libplaceimport.py index cdf20886d..ff15acb5c 100644 --- a/gramps/plugins/lib/libplaceimport.py +++ b/gramps/plugins/lib/libplaceimport.py @@ -27,7 +27,7 @@ Helper class for importing places. # GRAMPS modules # #------------------------------------------------------------------------- -from gramps.gen.lib import Place, PlaceType, PlaceRef +from gramps.gen.lib import Place, PlaceName, PlaceType, PlaceRef from gramps.gen.constfunc import handle2internal #------------------------------------------------------------------------- @@ -106,7 +106,9 @@ class PlaceImport(object): Add a missing place to the database. """ place = Place() - place.name = name + place_name = PlaceName() + place_name.set_value(name) + place.name = place_name place.title = title place.place_type = PlaceType(7-type_num) if parent is not None: