From d6ae8cffb48da6d6adcd6d5094b74f97d290750e Mon Sep 17 00:00:00 2001 From: Nick Hall Date: Fri, 1 Nov 2013 19:13:16 +0000 Subject: [PATCH] GEPS 6: Implement place hierarchy svn: r23444 --- gramps/gen/config.py | 2 + gramps/gen/db/read.py | 29 + gramps/gen/db/upgrade.py | 89 ++- gramps/gen/db/write.py | 51 ++ gramps/gen/filters/rules/place/_hasplace.py | 61 +- gramps/gen/lib/__init__.py | 2 + gramps/gen/lib/place.py | 224 +++++--- gramps/gen/lib/placeref.py | 176 ++++++ gramps/gen/lib/placetype.py | 68 +++ gramps/gen/merge/mergeplacequery.py | 8 +- gramps/gen/proxy/private.py | 5 +- gramps/gen/utils/location.py | 80 +++ gramps/gui/clipboard.py | 20 + gramps/gui/ddtargets.py | 2 + gramps/gui/editors/__init__.py | 1 + gramps/gui/editors/displaytabs/__init__.py | 1 + .../editors/displaytabs/placerefembedlist.py | 101 ++++ .../gui/editors/displaytabs/placerefmodel.py | 53 ++ gramps/gui/editors/editplace.py | 128 ++--- gramps/gui/editors/editplaceref.py | 100 ++++ gramps/gui/glade/editplace.glade | 522 +++++------------- gramps/gui/glade/editplaceref.glade | 215 ++++++++ gramps/gui/glade/mergeplace.glade | 408 +++++++++----- gramps/gui/merge/mergeplace.py | 60 +- gramps/gui/selectors/selectplace.py | 17 +- gramps/gui/views/listview.py | 11 +- gramps/gui/views/treemodels/placemodel.py | 184 ++---- gramps/plugins/export/exportgedcom.py | 45 +- gramps/plugins/export/exportxml.py | 34 +- gramps/plugins/gramplet/placedetails.py | 7 +- gramps/plugins/graph/gvfamilylines.py | 45 +- gramps/plugins/importer/importxml.py | 78 ++- gramps/plugins/lib/libgedcom.py | 75 +-- gramps/plugins/lib/libplaceimport.py | 110 ++++ gramps/plugins/lib/libplaceview.py | 67 +-- gramps/plugins/lib/libsubstkeyword.py | 41 +- gramps/plugins/lib/maps/geography.py | 56 +- gramps/plugins/lib/maps/placeselection.py | 75 ++- gramps/plugins/mapservices/eniroswedenmap.py | 36 +- gramps/plugins/mapservices/googlemap.py | 7 +- gramps/plugins/mapservices/openstreetmap.py | 8 +- gramps/plugins/textreport/placereport.py | 21 +- gramps/plugins/view/placetreeview.py | 99 +--- gramps/plugins/webreport/narrativeweb.py | 58 +- 44 files changed, 2208 insertions(+), 1272 deletions(-) create mode 100644 gramps/gen/lib/placeref.py create mode 100644 gramps/gen/lib/placetype.py create mode 100644 gramps/gen/utils/location.py create mode 100644 gramps/gui/editors/displaytabs/placerefembedlist.py create mode 100644 gramps/gui/editors/displaytabs/placerefmodel.py create mode 100644 gramps/gui/editors/editplaceref.py create mode 100644 gramps/gui/glade/editplaceref.glade create mode 100644 gramps/plugins/lib/libplaceimport.py diff --git a/gramps/gen/config.py b/gramps/gen/config.py index 01bc2e245..054472d67 100644 --- a/gramps/gen/config.py +++ b/gramps/gen/config.py @@ -245,6 +245,8 @@ register('interface.person-sel-height', 450) register('interface.person-sel-width', 600) register('interface.person-width', 750) register('interface.place-height', 450) +register('interface.place-ref-height', 450) +register('interface.place-ref-width', 600) register('interface.place-sel-height', 450) register('interface.place-sel-width', 600) register('interface.place-width', 650) diff --git a/gramps/gen/db/read.py b/gramps/gen/db/read.py index 3b3e3de26..d2251aab9 100644 --- a/gramps/gen/db/read.py +++ b/gramps/gen/db/read.py @@ -199,6 +199,32 @@ class DbReadCursor(BsddbBaseCursor): self.cursor = source.db.cursor(txn) self.source = source +#------------------------------------------------------------------------- +# +# DbBsddbTreeCursor +# +#------------------------------------------------------------------------- +class DbBsddbTreeCursor(BsddbBaseCursor): + + def __init__(self, source, txn=None, **kwargs): + BsddbBaseCursor.__init__(self, txn=txn, **kwargs) + self.cursor = source.cursor(txn) + self.source = source + + def __iter__(self): + """ + Iterator + """ + to_do = [''] + while to_do: + data = self.set(str(to_do.pop())) + _n = self.next_dup + while data: + payload = pickle.loads(data[1]) + yield (payload[0], payload) + to_do.append(payload[0]) + data = _n() + class DbBsddbRead(DbReadBase, Callback): """ Read class for the GRAMPS databases. Implements methods necessary to read @@ -488,6 +514,9 @@ class DbBsddbRead(DbReadBase, Callback): def get_place_cursor(self, *args, **kwargs): return self.get_cursor(self.place_map, *args, **kwargs) + def get_place_tree_cursor(self, *args, **kwargs): + return DbBsddbTreeCursor(self.parents, self.txn) + def get_source_cursor(self, *args, **kwargs): return self.get_cursor(self.source_map, *args, **kwargs) diff --git a/gramps/gen/db/upgrade.py b/gramps/gen/db/upgrade.py index cab610443..130457048 100644 --- a/gramps/gen/db/upgrade.py +++ b/gramps/gen/db/upgrade.py @@ -25,8 +25,12 @@ from __future__ import with_statement, unicode_literals import sys import os +import re from ..lib.markertype import MarkerType from ..lib.tag import Tag +from ..lib.place import Place +from ..lib.placeref import PlaceRef +from ..lib.placetype import PlaceType from ..utils.file import create_checksum import time import logging @@ -81,11 +85,65 @@ def gramps_upgrade_17(self): # --------------------------------- # Modify Place # --------------------------------- - # Add new tag_list field. + # Convert to hierarchical structure and add new tag_list field. + locations = {} + self.max_id = 0 + index = re.compile('[0-9]+') for handle in self.place_map.keys(): + place = self.place_map[handle] + match = index.search(place[1]) + if match: + if self.max_id <= int(match.group(0)): + self.max_id = int(match.group(0)) + if place[5] is not None: + locations[get_location(place[5])] = handle + + for handle in list(self.place_map.keys()): place = self.place_map[handle] new_place = list(place) - new_place = new_place[:12] + [[]] + new_place[12:] + + zip_code = '' + if new_place[5]: + zip_code = new_place[5][0][6] + + # find title and type + main_loc = get_location(new_place[5]) + for type_num, name in enumerate(main_loc): + if name: + break + + loc = list(main_loc[:]) + loc[type_num] = '' + + # find top parent + parent_handle = None + for n in range(7): + if loc[n]: + tup = tuple([''] * n + loc[n:]) + parent_handle = locations.get(tup, None) + if parent_handle: + break + + # create nodes + if parent_handle: + n -= 1 + while n > type_num: + if loc[n]: + title = ', '.join([item for item in loc[n:] if item]) + parent_handle = add_place(self, loc[n], n, parent_handle, title) + locations[tuple([''] * n + loc[n:])] = parent_handle + n -= 1 + + if parent_handle is not None: + placeref = PlaceRef() + placeref.ref = parent_handle + placeref_list = [placeref.serialize()] + else: + placeref_list = [] + + new_place = new_place[:5] + [placeref_list, name, + PlaceType(7-type_num).serialize(), zip_code] + \ + new_place[6:12] + [[]] + new_place[12:] new_place = tuple(new_place) with BSDDBTxn(self.env, self.place_map) as txn: if isinstance(handle, UNITYPE): @@ -194,6 +252,33 @@ def gramps_upgrade_17(self): with BSDDBTxn(self.env, self.metadata) as txn: txn.put(b'version', 17) +def get_location(loc): + # (street, locality, parish, city, county, state, country) + if loc is None: + location = ('',) * 7 + else: + location = loc[0][:2] + (loc[1],) + loc[0][2:6] + return location + +def add_place(self, name, type_num, parent, title): + handle = self.create_id() + place = Place() + place.handle = handle + self.max_id += 1 + place.gramps_id = self.place_prefix % self.max_id + place.name = name + place.title = title + place.place_type = PlaceType(7-type_num) + if parent is not None: + placeref = PlaceRef() + placeref.ref = parent + place.set_placeref_list([placeref]) + with BSDDBTxn(self.env, self.place_map) as txn: + if isinstance(handle, UNITYPE): + handle = handle.encode('utf-8') + txn.put(handle, place.serialize()) + return handle + def upgrade_datamap_17(datamap): """ In version 16 key value pairs are stored in source and citation. These become diff --git a/gramps/gen/db/write.py b/gramps/gen/db/write.py index 2ee75eade..0669c3571 100644 --- a/gramps/gen/db/write.py +++ b/gramps/gen/db/write.py @@ -110,6 +110,7 @@ TAGTRANS = "tag_name" SURNAMES = "surnames" NAME_GROUP = "name_group" META = "meta_data" +PPARENT = "place_parent" FAMILY_TBL = "family" PLACES_TBL = "place" @@ -183,6 +184,15 @@ def find_idmap(key, data): val = val.encode('utf-8') return val +def find_parent(key, data): + if len(data[5]) > 0: + val = data[5][0][0] + else: + val = '' + if isinstance(val, UNITYPE): + val = val.encode('utf-8') + return val + # Secondary database key lookups for reference_map table # reference_map data values are of the form: # ((primary_object_class_name, primary_object_handle), @@ -374,6 +384,13 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): return DbBsddbAssocCursor(self.reference_map_referenced_map, self.txn) + @catch_db_error + def get_place_parent_cursor(self): + """ + Returns a reference to a cursor over the place parents + """ + return DbBsddbAssocCursor(self.parents, self.txn) + # These are overriding the DbBsddbRead's methods of saving metadata # because we now have txn-capable metadata table @@ -870,6 +887,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): ("rid_trans", RIDTRANS, db.DB_HASH, 0), ("nid_trans", NIDTRANS, db.DB_HASH, 0), ("tag_trans", TAGTRANS, db.DB_HASH, 0), + ("parents", PPARENT, db.DB_HASH, 0), ("reference_map_primary_map", REF_PRI, db.DB_BTREE, 0), ("reference_map_referenced_map", REF_REF, db.DB_BTREE, db.DB_DUPSORT), ] @@ -887,6 +905,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): (self.family_map, self.fid_trans, find_idmap), (self.event_map, self.eid_trans, find_idmap), (self.place_map, self.pid_trans, find_idmap), + (self.place_map, self.parents, find_parent), (self.source_map, self.sid_trans, find_idmap), (self.citation_map, self.cid_trans, find_idmap), (self.media_map, self.oid_trans, find_idmap), @@ -934,6 +953,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): ( self.nid_trans, NIDTRANS ), ( self.cid_trans, CIDTRANS ), ( self.tag_trans, TAGTRANS ), + ( self.parents, PPARENT ), ( self.reference_map_primary_map, REF_PRI), ( self.reference_map_referenced_map, REF_REF), ] @@ -960,6 +980,36 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): if callback: callback(12) + @catch_db_error + def find_place_child_handles(self, handle): + """ + Find all child places having the given place as the primary parent. + """ + handle = str(handle) + parent_cur = self.get_place_parent_cursor() + + try: + ret = parent_cur.set(handle) + except: + ret = None + + while (ret is not None): + (key, data) = ret + + ### FIXME: this is a dirty hack that works without no + ### sensible explanation. For some reason, for a readonly + ### database, secondary index returns a primary table key + ### corresponding to the data, not the data. + if self.readonly: + data = self.place_map.get(data) + else: + data = pickle.loads(data) + + yield data[0] + ret = parent_cur.next_dup() + + parent_cur.close() + @catch_db_error def find_backlink_handles(self, handle, include_classes=None): """ @@ -1303,6 +1353,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.__close_metadata() self.name_group.close() self.surnames.close() + self.parents.close() self.id_trans.close() self.fid_trans.close() self.eid_trans.close() diff --git a/gramps/gen/filters/rules/place/_hasplace.py b/gramps/gen/filters/rules/place/_hasplace.py index ef30b1b22..f83f88813 100644 --- a/gramps/gen/filters/rules/place/_hasplace.py +++ b/gramps/gen/filters/rules/place/_hasplace.py @@ -36,7 +36,8 @@ _ = glocale.translation.gettext # #------------------------------------------------------------------------- from .. import Rule -from ....lib import Location +from ....lib import PlaceType +from ....utils.location import get_locations #------------------------------------------------------------------------- # @@ -46,7 +47,6 @@ from ....lib import Location class HasPlace(Rule): """Rule that checks for a place with a particular value""" - labels = [ _('Name:'), _('Street:'), _('Locality:'), @@ -62,50 +62,37 @@ class HasPlace(Rule): category = _('General filters') allow_regex = True + TYPE2FIELD = {PlaceType.STREET: 1, + PlaceType.LOCALITY: 2, + PlaceType.CITY: 3, + PlaceType.COUNTY: 4, + PlaceType.STATE: 5, + PlaceType.COUNTRY: 6, + PlaceType.PARISH: 8} + def apply(self, db, place): if not self.match_substring(0, place.get_title()): return False + if not self.match_substring(7, place.get_code()): + return False + # If no location data was given then we're done: match - if not any(self.list[1:]): + if not any(self.list[1:7] + [self.list[8]]): return True - # Something was given, so checking for location until we match - for loc in [place.main_loc] + place.alt_loc: - if self.apply_location(loc): + for location in get_locations(db, place): + if self.check(location): return True - # Nothing matched return False - def apply_location(self, loc): - if not loc: - # Allow regular expressions to match empty fields - loc = Location() - - if not self.match_substring(1, loc.get_street()): - return False - - if not self.match_substring(2, loc.get_locality()): - return False - - if not self.match_substring(3, loc.get_city()): - return False - - if not self.match_substring(4, loc.get_county()): - return False - - if not self.match_substring(5, loc.get_state()): - return False - - if not self.match_substring(6, loc.get_country()): - return False - - if not self.match_substring(7, loc.get_postal_code()): - return False - - if not self.match_substring(8, loc.get_parish()): - return False - - # Nothing contradicted, so we're matching this location + def check(self, location): + """ + Check each location for a match. + """ + for place_type, field in self.TYPE2FIELD.iteritems(): + name = location.get(place_type, '') + if not self.match_substring(field, name): + return False return True diff --git a/gramps/gen/lib/__init__.py b/gramps/gen/lib/__init__.py index 826beca5b..8f9939774 100644 --- a/gramps/gen/lib/__init__.py +++ b/gramps/gen/lib/__init__.py @@ -36,6 +36,7 @@ from .eventref import EventRef from .ldsord import LdsOrd from .mediaref import MediaRef from .name import Name +from .placeref import PlaceRef from .reporef import RepoRef from .surname import Surname from .url import Url @@ -78,6 +79,7 @@ from .markertype import MarkerType from .nameorigintype import NameOriginType from .notetype import NoteType from .styledtexttagtype import StyledTextTagType +from .placetype import PlaceType # Text from .styledtexttag import StyledTextTag diff --git a/gramps/gen/lib/place.py b/gramps/gen/lib/place.py index 091cd5980..82b4821a9 100644 --- a/gramps/gen/lib/place.py +++ b/gramps/gen/lib/place.py @@ -33,6 +33,8 @@ from __future__ import unicode_literals # #------------------------------------------------------------------------- from .primaryobj import PrimaryObject +from .placeref import PlaceRef +from .placetype import PlaceType from .citationbase import CitationBase from .notebase import NoteBase from .mediabase import MediaBase @@ -41,8 +43,6 @@ from .tagbase import TagBase from .location import Location from .handle import Handle -_EMPTY_LOC = Location().serialize() - #------------------------------------------------------------------------- # # Place class @@ -71,13 +71,19 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): self.long = source.long self.lat = source.lat self.title = source.title - self.main_loc = Location(source.main_loc) + self.name = source.name + self.placeref_list = list(map(PlaceRef, source.placeref_list)) + self.place_type = source.place_type + self.code = source.code self.alt_loc = list(map(Location, source.alt_loc)) else: self.long = "" self.lat = "" self.title = "" - self.main_loc = None + self.name = "" + self.placeref_list = [] + self.place_type = PlaceType() + self.code = "" self.alt_loc = [] def serialize(self): @@ -98,14 +104,10 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): be considered persistent. :rtype: tuple """ - - if self.main_loc is None or self.main_loc.serialize() == _EMPTY_LOC: - main_loc = None - else: - main_loc = self.main_loc.serialize() - return (self.handle, self.gramps_id, self.title, self.long, self.lat, - main_loc, [al.serialize() for al in self.alt_loc], + [pr.serialize() for pr in self.placeref_list], + self.name, self.place_type.serialize(), self.code, + [al.serialize() for al in self.alt_loc], UrlBase.serialize(self), MediaBase.serialize(self), CitationBase.serialize(self), @@ -132,17 +134,15 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): :returns: Returns a struct containing the data of the object. :rtype: dict """ - if self.main_loc is None or self.main_loc.serialize() == _EMPTY_LOC: - main_loc = None - else: - main_loc = self.main_loc.to_struct() - return {"handle": Handle("Place", self.handle), "gramps_id": self.gramps_id, "title": self.title, "long": self.long, "lat": self.lat, - "main_loc": main_loc, + "placeref_list": [pr.to_struct() for pr in self.placeref_list], + "name": self.name, + "place_type": self.place_type.to_struct(), + "code": self.code, "alt_loc": [al.to_struct() for al in self.alt_loc], "urls": UrlBase.to_struct(self), "media_list": MediaBase.to_struct(self), @@ -162,14 +162,14 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): :type data: tuple """ (self.handle, self.gramps_id, self.title, self.long, self.lat, - main_loc, alt_loc, urls, media_list, citation_list, note_list, + placeref_list, self.name, the_type, self.code, + alt_loc, urls, media_list, citation_list, note_list, self.change, tag_list, self.private) = data - if main_loc is None: - self.main_loc = None - else: - self.main_loc = Location().unserialize(main_loc) + self.place_type = PlaceType() + 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] UrlBase.unserialize(self, urls) MediaBase.unserialize(self, media_list) CitationBase.unserialize(self, citation_list) @@ -184,7 +184,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.gramps_id] + return [self.long, self.lat, self.title, self.name, self.gramps_id] def get_text_data_child_list(self): """ @@ -195,8 +195,6 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): """ ret = self.media_list + self.alt_loc + self.urls - if self.main_loc: - ret.append(self.main_loc) return ret def get_citation_child_list(self): @@ -226,7 +224,7 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): :returns: Returns the list of objects referencing primary objects. :rtype: list """ - return self.get_citation_child_list() + return self.get_citation_child_list() + self.placeref_list def get_referenced_handles(self): """ @@ -253,6 +251,7 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): self._merge_note_list(acquisition) self._merge_citation_list(acquisition) self._merge_tag_list(acquisition) + self._merge_placeref_list(acquisition) def set_title(self, title): """ @@ -272,6 +271,24 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): """ return self.title + def set_name(self, name): + """ + Set the name of the Place object. + + :param title: name to assign to the Place + :type title: str + """ + self.name = name + + def get_name(self): + """ + Return the name of the Place object. + + :returns: Returns the name of the Place + :rtype: str + """ + return self.name + def set_longitude(self, longitude): """ Set the longitude of the Place object. @@ -308,31 +325,120 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): """ return self.lat - def get_main_location(self): + def set_type(self, place_type): """ - Return the :class:`~gen.lib.location.Location` object representing the primary information for - the Place instance. + Set the type of the Place object. + + :param type: type to assign to the Place + :type type: PlaceType + """ + self.place_type.set(place_type) + + def get_type(self): + """ + Return the type of the Place object. + + :returns: Returns the type of the Place + :rtype: PlaceType + """ + return self.place_type + + def set_code(self, code): + """ + Set the code of the Place object. + + :param code: code to assign to the Place + :type code: str + """ + self.code = code + + def get_code(self): + """ + Return the code of the Place object. + + :returns: Returns the code of the Place + :rtype: str + """ + return self.code + + def add_placeref(self, placeref): + """ + Add a place reference to the list of place references. + + :param code: place reference to append to the list + :type code: PlaceRef + """ + self.placeref_list.append(placeref) + + def get_placeref_list(self): + """ + Return the place reference list for the Place object. + + :returns: Returns the place reference list for the Place + :rtype: list + """ + return self.placeref_list + + def set_placeref_list(self, placeref_list): + """ + Set the place reference list for the Place object. + + :param code: place reference list to assign to the Place + :type code: list + """ + self.placeref_list = placeref_list + + def _merge_placeref_list(self, acquisition): + """ + Add the main and alternate locations of acquisition to the alternate + location list. + + :param acquisition: instance to merge + :type acquisition: :class:'~gen.lib.place.Place + """ + placeref_list = self.placeref_list[:] + add_list = acquisition.placeref_list + for addendum in add_list: + for placeref in placeref_list: + if placeref.is_equal(addendum): + break + else: + self.placeref_list.append(addendum) + + def _has_handle_reference(self, classname, handle): + """ + Return True if the object has reference to a given handle of given + primary object type. - If a :class:`~gen.lib.location.Location` hasn't been assigned yet, an empty one is created. - - :returns: Returns the :class:`~gen.lib.location.Location` instance representing the primary - location information about the Place. - :rtype: :class:`~gen.lib.location.Location` + :param classname: The name of the primary object class. + :type classname: str + :param handle: The handle to be checked. + :type handle: str + :returns: Returns whether the object has reference to this handle of + this object type. + :rtype: bool """ - if not self.main_loc: - self.main_loc = Location() - return self.main_loc + if classname == 'Place': + for placeref in self.placeref_list: + if placeref.ref == handle: + return True + return False - def set_main_location(self, location): + def _replace_handle_reference(self, classname, old_handle, new_handle): """ - Assign the main location information about the Place to the :class:`~gen.lib.location.Location` - object passed. + Replace all references to old handle with those to the new handle. - :param location: :class:`~gen.lib.location.Location` instance to assign to as the main - information for the Place. - :type location: :class:`~gen.lib.location.Location` + :param classname: The name of the primary object class. + :type classname: str + :param old_handle: The handle to be replaced. + :type old_handle: str + :param new_handle: The handle to replace the old one with. + :type new_handle: str """ - self.main_loc = location + if classname == 'Place': + for placeref in self.placeref_list: + if placeref.ref == old_handle: + placeref.ref = new_handle def get_alternate_locations(self): """ @@ -376,40 +482,10 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): :type acquisition: :class:'~gen.lib.place.Place """ altloc_list = self.alt_loc[:] - if self.main_loc and not self.main_loc.is_empty(): - altloc_list.insert(0, self.main_loc) add_list = acquisition.get_alternate_locations() - acq_main_loc = acquisition.get_main_location() - if acq_main_loc and not acq_main_loc.is_empty(): - add_list.insert(0, acquisition.get_main_location()) for addendum in add_list: for altloc in altloc_list: if altloc.is_equal(addendum): break else: self.alt_loc.append(addendum) - - def get_display_info(self): - """ - Get the display information associated with the object. - - This includes the information that is used for display and for sorting. - Returns a list consisting of 13 strings. These are: - - Place Title, Place ID, Main Location Parish, Main Location County, - Main Location City, Main Location State/Province, - Main Location Country, upper case Place Title, upper case Parish, - upper case city, upper case county, upper case state, - upper case country. - """ - - if self.main_loc: - return [self.title, self.gramps_id, self.main_loc.parish, - self.main_loc.city, self.main_loc.county, - self.main_loc.state, self.main_loc.country, - self.title.upper(), self.main_loc.parish.upper(), - self.main_loc.city.upper(), self.main_loc.county.upper(), - self.main_loc.state.upper(), self.main_loc.country.upper()] - else: - return [self.title, self.gramps_id, '', '', '', '', '', - self.title.upper(), '', '', '', '', ''] diff --git a/gramps/gen/lib/placeref.py b/gramps/gen/lib/placeref.py new file mode 100644 index 000000000..394ca5e95 --- /dev/null +++ b/gramps/gen/lib/placeref.py @@ -0,0 +1,176 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2013 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +Place Reference class for Gramps +""" + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +from .secondaryobj import SecondaryObject +from .refbase import RefBase +from .datebase import DateBase +from .const import IDENTICAL, EQUAL, DIFFERENT + +#------------------------------------------------------------------------- +# +# Place References +# +#------------------------------------------------------------------------- +class PlaceRef(RefBase, DateBase, SecondaryObject): + """ + Place reference class. + + This class is for keeping information about how places link to other places + in the place hierarchy. + """ + + def __init__(self, source=None): + """ + Create a new PlaceRef instance, copying from the source if present. + """ + RefBase.__init__(self, source) + DateBase.__init__(self, source) + + def serialize(self): + """ + Convert the object to a serialized tuple of data. + """ + return ( + RefBase.serialize(self), + DateBase.serialize(self) + ) + + 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 { + "ref": RefBase.to_struct(self), + "date": DateBase.to_struct(self) + } + + def unserialize(self, data): + """ + Convert a serialized tuple of data to an object. + """ + (ref, date) = data + RefBase.unserialize(self, ref) + 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 [] + + 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 [('Place', self.ref)] + + 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. + :rtype other: EventRef + :returns: Constant indicating degree of equivalence. + :rtype: int + """ + if self.ref != other.ref or self.date != other.date: + return DIFFERENT + else: + if self.is_equal(other): + return IDENTICAL + else: + return EQUAL diff --git a/gramps/gen/lib/placetype.py b/gramps/gen/lib/placetype.py new file mode 100644 index 000000000..ceeacb438 --- /dev/null +++ b/gramps/gen/lib/placetype.py @@ -0,0 +1,68 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# $Id$ + +""" +Provide the different place types. +""" +#------------------------------------------------------------------------ +# +# Python modules +# +#------------------------------------------------------------------------ +from ..const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +from .grampstype import GrampsType + +class PlaceType(GrampsType): + + UNKNOWN = -1 + CUSTOM = 0 + COUNTRY = 1 + STATE = 2 + COUNTY = 3 + CITY = 4 + PARISH = 5 + LOCALITY = 6 + STREET = 7 + + _CUSTOM = CUSTOM + _DEFAULT = COUNTRY + + _DATAMAP = [ + (UNKNOWN, _("Unknown"), "Unknown"), + (CUSTOM, _("Custom"), "Custom"), + (COUNTRY, _("Country"), "Country"), + (STATE, _("State"), "State"), + (COUNTY, _("County"), "County"), + (CITY, _("City"), "City"), + (PARISH, _("Parish"), "Parish"), + (LOCALITY, _("Locality"), "Locality"), + (STREET, _("Street"), "Street"), + ] + + def __init__(self, value=None): + GrampsType.__init__(self, value) diff --git a/gramps/gen/merge/mergeplacequery.py b/gramps/gen/merge/mergeplacequery.py index 1e7e93151..f8b79aad4 100644 --- a/gramps/gen/merge/mergeplacequery.py +++ b/gramps/gen/merge/mergeplacequery.py @@ -30,7 +30,7 @@ Provide merge capabilities for places. # Gramps modules # #------------------------------------------------------------------------- -from ..lib import Person, Family, Event +from ..lib import Person, Family, Event, Place from ..db import DbTxn from ..const import GRAMPS_LOCALE as glocale _ = glocale.translation.sgettext @@ -81,6 +81,12 @@ class MergePlaceQuery(object): event.replace_handle_reference('Place', old_handle, new_handle) self.database.commit_event(event, trans) + elif class_name == Place.__name__: + place = self.database.get_place_from_handle(handle) + assert(place.has_handle_reference('Place', old_handle)) + place.replace_handle_reference('Place', old_handle, + new_handle) + self.database.commit_place(place, trans) else: raise MergeError("Encounter an object of type %s that has " "a place reference." % class_name) diff --git a/gramps/gen/proxy/private.py b/gramps/gen/proxy/private.py index 3587ccd2a..0c61ee472 100644 --- a/gramps/gen/proxy/private.py +++ b/gramps/gen/proxy/private.py @@ -980,8 +980,11 @@ def sanitize_place(db, place): new_place.set_change_time(place.get_change_time()) new_place.set_longitude(place.get_longitude()) new_place.set_latitude(place.get_latitude()) - new_place.set_main_location(place.get_main_location()) new_place.set_alternate_locations(place.get_alternate_locations()) + new_place.set_name(place.get_name()) + new_place.set_type(place.get_type()) + new_place.set_code(place.get_code()) + new_place.set_placeref_list(place.get_placeref_list()) copy_citation_ref_list(db, place, new_place) copy_notes(db, place, new_place) diff --git a/gramps/gen/utils/location.py b/gramps/gen/utils/location.py new file mode 100644 index 000000000..012ac1d56 --- /dev/null +++ b/gramps/gen/utils/location.py @@ -0,0 +1,80 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2013 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +Location utility functions +""" + +#------------------------------------------------------------------------- +# +# get_location_list +# +#------------------------------------------------------------------------- +def get_location_list(db, place): + """ + Return a list of place names for display. + """ + lines = [place.name] + while len(place.get_placeref_list()) > 0: + place = db.get_place_from_handle(place.get_placeref_list()[0].ref) + lines.append(place.name) + return lines + +#------------------------------------------------------------------------- +# +# get_main_location +# +#------------------------------------------------------------------------- +def get_main_location(db, place): + """ + Find all places in the hierarchy above the given place, and return the + result as a dictionary of place types and names. + """ + items = {int(place.get_type()): place.name} + while len(place.get_placeref_list()) > 0: + place = db.get_place_from_handle(place.get_placeref_list()[0].ref) + items[int(place.get_type())] = place.name + return items + +#------------------------------------------------------------------------- +# +# get_locations +# +#------------------------------------------------------------------------- +def get_locations(db, place): + """ + Determine each possible route up the place hierarchy, and return a list + containing dictionaries of place types and names. + """ + locations = [] + todo = [(place, [(int(place.get_type()), place.get_name())])] + while len(todo): + place, tree = todo.pop() + if len(place.get_placeref_list()): + for parent in place.get_placeref_list(): + parent_place = db.get_place_from_handle(parent.ref) + parent_tree = tree + [(int(parent_place.get_type()), + parent_place.get_name())] + todo.append((parent_place, parent_tree)) + else: + locations.append(dict(tree)) + return locations diff --git a/gramps/gui/clipboard.py b/gramps/gui/clipboard.py index ecf504c66..7d143c2cc 100644 --- a/gramps/gui/clipboard.py +++ b/gramps/gui/clipboard.py @@ -121,6 +121,7 @@ def map2class(target): 'mediaobj': ClipMediaObj, 'mediaref': ClipMediaRef, 'place-link': ClipPlace, + 'placeref': ClipPlaceRef, 'note-link': ClipNote, } return d[target] if target in d else None @@ -568,6 +569,24 @@ class ClipEventRef(ClipObjWrapper): self._title = base.gramps_id self._value = str(base.get_type()) +class ClipPlaceRef(ClipObjWrapper): + + DROP_TARGETS = [DdTargets.PLACEREF] + DRAG_TARGET = DdTargets.PLACEREF + ICON = LINK_PIC + + def __init__(self, dbstate, obj): + super(ClipPlaceRef, self).__init__(dbstate, obj) + self._type = _("Place ref") + self.refresh() + + def refresh(self): + if self._obj: + base = self._db.get_place_from_handle(self._obj.ref) + if base: + self._title = base.gramps_id + self._value = str(base.get_name()) + class ClipName(ClipObjWrapper): DROP_TARGETS = [DdTargets.NAME] @@ -1082,6 +1101,7 @@ class ClipboardListView(object): self.register_wrapper_class(ClipEvent) self.register_wrapper_class(ClipPlace) self.register_wrapper_class(ClipEventRef) + self.register_wrapper_class(ClipPlaceRef) self.register_wrapper_class(ClipRepoRef) self.register_wrapper_class(ClipFamilyEvent) self.register_wrapper_class(ClipUrl) diff --git a/gramps/gui/ddtargets.py b/gramps/gui/ddtargets.py index 5da2651c3..0736bfa02 100644 --- a/gramps/gui/ddtargets.py +++ b/gramps/gui/ddtargets.py @@ -142,6 +142,7 @@ class _DdTargets(object): self.NAME = _DdType(self, 'name') self.NOTE_LINK = _DdType(self, 'note-link') self.PLACE_LINK = _DdType(self, 'place-link') + self.PLACEREF = _DdType(self, 'placeref') self.REPO_LINK = _DdType(self, 'repo-link') self.REPOREF = _DdType(self, 'reporef') self.PERSON_LINK = _DdType(self, 'person-link') @@ -172,6 +173,7 @@ class _DdTargets(object): self.NAME, self.NOTE_LINK, self.PLACE_LINK, + self.PLACEREF, self.PERSON_LINK, self.FAMILY_LINK, self.LINK_LIST, diff --git a/gramps/gui/editors/__init__.py b/gramps/gui/editors/__init__.py index 5a154356e..e680c679f 100644 --- a/gramps/gui/editors/__init__.py +++ b/gramps/gui/editors/__init__.py @@ -39,6 +39,7 @@ from .editnote import EditNote, DeleteNoteQuery from .editperson import EditPerson from .editpersonref import EditPersonRef from .editplace import EditPlace, DeletePlaceQuery +from .editplaceref import EditPlaceRef from .editrepository import EditRepository, DeleteRepositoryQuery from .editreporef import EditRepoRef from .editsource import EditSource, DeleteSrcQuery diff --git a/gramps/gui/editors/displaytabs/__init__.py b/gramps/gui/editors/displaytabs/__init__.py index 2740aea33..88cd4e609 100644 --- a/gramps/gui/editors/displaytabs/__init__.py +++ b/gramps/gui/editors/displaytabs/__init__.py @@ -56,6 +56,7 @@ from .personeventembedlist import PersonEventEmbedList from .personrefembedlist import PersonRefEmbedList from .personbackreflist import PersonBackRefList from .placebackreflist import PlaceBackRefList +from .placerefembedlist import PlaceRefEmbedList from .repoembedlist import RepoEmbedList from .surnametab import SurnameTab from .sourcebackreflist import SourceBackRefList diff --git a/gramps/gui/editors/displaytabs/placerefembedlist.py b/gramps/gui/editors/displaytabs/placerefembedlist.py new file mode 100644 index 000000000..72b64de8b --- /dev/null +++ b/gramps/gui/editors/displaytabs/placerefembedlist.py @@ -0,0 +1,101 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2006 Donald N. Allingham +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +#------------------------------------------------------------------------- +# +# Python classes +# +#------------------------------------------------------------------------- +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext +from gi.repository import GObject + +#------------------------------------------------------------------------- +# +# GRAMPS classes +# +#------------------------------------------------------------------------- +from gramps.gen.lib import PlaceRef +from gramps.gen.errors import WindowActiveError +from ...ddtargets import DdTargets +from .placerefmodel import PlaceRefModel +from .embeddedlist import EmbeddedList, TEXT_COL + +#------------------------------------------------------------------------- +# +# +# +#------------------------------------------------------------------------- +class PlaceRefEmbedList(EmbeddedList): + + _HANDLE_COL = 4 + #_DND_TYPE = DdTargets.PLACEREF + + #index = column in model. Value = + # (name, sortcol in model, width, markup/text, weigth_col + _column_names = [ + (_('ID'), 0, 75, TEXT_COL, -1, None), + (_('Name'), 1, 250, TEXT_COL, -1, None), + (_('Type'), 2, 100, TEXT_COL, -1, None), + (_('Date'), 3, 150, TEXT_COL, -1, None), + ] + + def __init__(self, dbstate, uistate, track, data, handle): + self.data = data + self.handle = handle + EmbeddedList.__init__(self, dbstate, uistate, track, + _('Parents'), PlaceRefModel, + move_buttons=True) + + def get_data(self): + return self.data + + def column_order(self): + return ((1, 0), (1, 1), (1, 2), (1, 3)) + + def add_button_clicked(self, obj): + placeref = PlaceRef() + try: + from .. import EditPlaceRef + EditPlaceRef(self.dbstate, self.uistate, self.track, + placeref, self.handle, self.add_callback) + except WindowActiveError: + pass + + def add_callback(self, name): + data = self.get_data() + data.append(name) + self.rebuild() + GObject.idle_add(self.tree.scroll_to_cell, len(data) - 1) + + def edit_button_clicked(self, obj): + placeref = self.get_selected() + if placeref: + try: + from .. import EditPlaceRef + EditPlaceRef(self.dbstate, self.uistate, self.track, + placeref, self.handle, self.edit_callback) + except WindowActiveError: + pass + + def edit_callback(self, name): + self.rebuild() diff --git a/gramps/gui/editors/displaytabs/placerefmodel.py b/gramps/gui/editors/displaytabs/placerefmodel.py new file mode 100644 index 000000000..06fbddab9 --- /dev/null +++ b/gramps/gui/editors/displaytabs/placerefmodel.py @@ -0,0 +1,53 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2006 Donald N. Allingham +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +#------------------------------------------------------------------------- +# +# GTK libraries +# +#------------------------------------------------------------------------- +from gi.repository import Gtk + +#------------------------------------------------------------------------- +# +# Gramps classes +# +#------------------------------------------------------------------------- +from gramps.gen.datehandler import displayer + +#------------------------------------------------------------------------- +# +# PlaceRefModel +# +#------------------------------------------------------------------------- +class PlaceRefModel(Gtk.ListStore): + + def __init__(self, obj_list, db): + Gtk.ListStore.__init__(self, str, str, str, str, object) + self.db = db + for obj in obj_list: + place = self.db.get_place_from_handle(obj.ref) + self.append(row=[place.get_gramps_id(), + place.get_name(), + str(place.get_type()), + displayer.display(obj.date), + obj, ]) diff --git a/gramps/gui/editors/editplace.py b/gramps/gui/editors/editplace.py index 9e9b0fef9..9d5311c33 100644 --- a/gramps/gui/editors/editplace.py +++ b/gramps/gui/editors/editplace.py @@ -50,57 +50,15 @@ from gi.repository import Gtk from gramps.gen.lib import NoteType, Place from gramps.gen.db import DbTxn from .editprimary import EditPrimary -from .displaytabs import (GrampsTab, LocationEmbedList, CitationEmbedList, +from .displaytabs import (PlaceRefEmbedList, LocationEmbedList, CitationEmbedList, GalleryTab, NoteTab, WebEmbedList, PlaceBackRefList) -from ..widgets import MonitoredEntry, PrivacyButton, MonitoredTagList +from ..widgets import (MonitoredEntry, PrivacyButton, MonitoredTagList, + MonitoredDataType) from gramps.gen.errors import ValidationError from gramps.gen.utils.place import conv_lat_lon from ..dialog import ErrorDialog from ..glade import Glade -#------------------------------------------------------------------------- -# -# Classes -# -#------------------------------------------------------------------------- - -class MainLocTab(GrampsTab): - """ - This class provides the tabpage of the main location tab - """ - - def __init__(self, dbstate, uistate, track, name, widget): - """ - @param dbstate: The database state. Contains a reference to - the database, along with other state information. The GrampsTab - uses this to access the database and to pass to and created - child windows (such as edit dialogs). - @type dbstate: DbState - @param uistate: The UI state. Used primarily to pass to any created - subwindows. - @type uistate: DisplayState - @param track: The window tracking mechanism used to manage windows. - This is only used to pass to generted child windows. - @type track: list - @param name: Notebook label name - @type name: str/unicode - @param widget: widget to be shown in the tab - @type widge: gtk widget - """ - GrampsTab.__init__(self, dbstate, uistate, track, name) - eventbox = Gtk.EventBox() - eventbox.add(widget) - self.pack_start(eventbox, True, True, 0) - self._set_label(show_image=False) - eventbox.connect('key_press_event', self.key_pressed) - self.show_all() - - def is_empty(self): - """ - Override base class - """ - return False - #------------------------------------------------------------------------- # # EditPlace @@ -121,17 +79,7 @@ class EditPlace(EditPrimary): self.height_key = 'interface.place-height' self.top = Glade() - - self.set_window(self.top.toplevel, None, - self.get_menu_title()) - tblmloc = self.top.get_object('table19') - notebook = self.top.get_object('notebook3') - #recreate start page as GrampsTab - notebook.remove_page(0) - self.mloc = MainLocTab(self.dbstate, self.uistate, self.track, - _('_Location'), tblmloc) - self.track_ref_for_deletion("mloc") - + self.set_window(self.top.toplevel, None, self.get_menu_title()) def get_menu_title(self): if self.obj and self.obj.get_handle(): @@ -155,23 +103,14 @@ class EditPlace(EditPrimary): self._add_db_signal('place-delete', self.check_for_close) def _setup_fields(self): - mloc = self.obj.get_main_location() self.title = MonitoredEntry(self.top.get_object("place_title"), self.obj.set_title, self.obj.get_title, self.db.readonly) - self.street = MonitoredEntry(self.top.get_object("street"), - mloc.set_street, mloc.get_street, - self.db.readonly) - - self.locality = MonitoredEntry(self.top.get_object("locality"), - mloc.set_locality, mloc.get_locality, - self.db.readonly) - - self.city = MonitoredEntry(self.top.get_object("city"), - mloc.set_city, mloc.get_city, - self.db.readonly) + self.name = MonitoredEntry(self.top.get_object("name_entry"), + self.obj.set_name, self.obj.get_name, + self.db.readonly) self.gid = MonitoredEntry(self.top.get_object("gid"), self.obj.set_gramps_id, @@ -188,29 +127,14 @@ class EditPlace(EditPrimary): self.privacy = PrivacyButton(self.top.get_object("private"), self.obj, self.db.readonly) - self.parish = MonitoredEntry(self.top.get_object("parish"), - mloc.set_parish, mloc.get_parish, - self.db.readonly) - - self.county = MonitoredEntry(self.top.get_object("county"), - mloc.set_county, mloc.get_county, - self.db.readonly) - - self.state = MonitoredEntry(self.top.get_object("state"), - mloc.set_state, mloc.get_state, - self.db.readonly) + self.place_type = MonitoredDataType(self.top.get_object("place_type"), + self.obj.set_type, + self.obj.get_type) - self.phone = MonitoredEntry(self.top.get_object("phone"), - mloc.set_phone, mloc.get_phone, - self.db.readonly) - - self.postal = MonitoredEntry(self.top.get_object("postal"), - mloc.set_postal_code, - mloc.get_postal_code, self.db.readonly) - - self.country = MonitoredEntry(self.top.get_object("country"), - mloc.set_country, mloc.get_country, - self.db.readonly) + self.code = MonitoredEntry( + self.top.get_object("code_entry"), + self.obj.set_code, self.obj.get_code, + self.db.readonly) self.longitude = MonitoredEntry( self.top.get_object("lon_entry"), @@ -247,8 +171,14 @@ class EditPlace(EditPrimary): """ notebook = self.top.get_object('notebook3') - self._add_tab(notebook, self.mloc) - + self.placeref_list = PlaceRefEmbedList(self.dbstate, + self.uistate, + self.track, + self.obj.get_placeref_list(), + self.obj.handle) + self._add_tab(notebook, self.placeref_list) + self.track_ref_for_deletion("placeref_list") + self.loc_list = LocationEmbedList(self.dbstate, self.uistate, self.track, @@ -306,6 +236,20 @@ class EditPlace(EditPrimary): self.ok_button.set_sensitive(True) return + if self.obj.get_title().strip() == '': + msg1 = _("Cannot save location. Title not entered.") + msg2 = _("You must enter a title before saving.'") + ErrorDialog(msg1, msg2) + self.ok_button.set_sensitive(True) + return + + if self.obj.get_name().strip() == '': + msg1 = _("Cannot save location. Name not entered.") + msg2 = _("You must enter a name before saving.'") + ErrorDialog(msg1, msg2) + self.ok_button.set_sensitive(True) + return + (uses_dupe_id, id) = self._uses_duplicate_id() if uses_dupe_id: prim_object = self.get_from_gramps_id(id) diff --git a/gramps/gui/editors/editplaceref.py b/gramps/gui/editors/editplaceref.py new file mode 100644 index 000000000..22887d356 --- /dev/null +++ b/gramps/gui/editors/editplaceref.py @@ -0,0 +1,100 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2013 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from .editsecondary import EditSecondary +from ..glade import Glade +from ..widgets import MonitoredDate +from ..selectors import SelectorFactory +from ..dialog import ErrorDialog +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext + +SelectPlace = SelectorFactory('Place') + +#------------------------------------------------------------------------- +# +# EditPlaceRef class +# +#------------------------------------------------------------------------- +class EditPlaceRef(EditSecondary): + + def __init__(self, dbstate, uistate, track, placeref, handle, callback): + self.handle = handle + EditSecondary.__init__(self, dbstate, uistate, track, + placeref, callback) + + def _local_init(self): + self.width_key = 'interface.place-ref-width' + self.height_key = 'interface.place-ref-height' + self.top = Glade() + self.set_window(self.top.toplevel, None, _('Place Reference Editor')) + + def _setup_fields(self): + + self.date_field = 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) + + self.parent = self.top.get_object('place_label') + if self.obj.ref is not None: + place = self.db.get_place_from_handle(self.obj.ref) + self.parent.set_text(place.get_name()) + else: + self.parent.set_text(_('Top level place')) + button = self.top.get_object('place_button') + button.connect('clicked', self.select_parent) + + def get_skip_list(self, handle): + todo = [handle] + skip = [handle] + while todo: + handle = todo.pop() + for child in self.dbstate.db.find_place_child_handles(handle): + todo.append(child) + skip.append(child) + return skip + + def select_parent(self, button): + skip = self.get_skip_list(self.handle) + sel = SelectPlace(self.dbstate, self.uistate, self.track, skip=skip) + parent = sel.run() + if parent: + self.parent.set_text(parent.get_name()) + self.obj.ref = parent.get_handle() + + def _connect_signals(self): + self.define_cancel_button(self.top.get_object('cancel_button')) + self.define_ok_button(self.top.get_object('ok_button'), self.save) + self.define_help_button(self.top.get_object('help_button')) + + def save(self, *obj): + if self.callback: + self.callback(self.obj) + self.close() diff --git a/gramps/gui/glade/editplace.glade b/gramps/gui/glade/editplace.glade index a91293850..4816221d5 100644 --- a/gramps/gui/glade/editplace.glade +++ b/gramps/gui/glade/editplace.glade @@ -16,12 +16,10 @@ gtk-cancel - False True True True True - False True @@ -33,13 +31,11 @@ gtk-ok - False True True True True True - False True @@ -51,12 +47,10 @@ gtk-help - False True True True True - False True @@ -82,23 +76,26 @@ True False 12 - 3 + 4 5 6 4 + + + True False 0 - _Place Name: + _Place Title: True center place_title GTK_FILL - + @@ -112,10 +109,10 @@ lat_entry - 1 - 2 + 2 + 3 GTK_FILL - + @@ -131,23 +128,23 @@ 2 3 - 1 - 2 + 2 + 3 GTK_FILL - + True True - Full name of this place. + Full title of this place. 1 5 - + @@ -160,10 +157,10 @@ gid - 2 - 3 + 3 + 4 GTK_FILL - + @@ -178,9 +175,9 @@ You can set these values via the Geography View by searching the place, or via a 1 2 - 1 - 2 - + 2 + 3 + @@ -195,9 +192,9 @@ You can set these values via the Geography View by searching the place, or via a 3 4 - 1 - 2 - + 2 + 3 + @@ -221,11 +218,12 @@ You can set these values via the Geography View by searching the place, or via a - + True False 0 - Tags: + Code: + True False @@ -234,10 +232,12 @@ You can set these values via the Geography View by searching the place, or via a - + True - False - 0 + True + Code associated with this place. Eg Country Code or Postal Code. + + 8 True @@ -248,19 +248,17 @@ You can set these values via the Geography View by searching the place, or via a 1 - 4 - 2 - 3 + 2 + 3 + 4 GTK_FILL - False True True True - False none @@ -284,28 +282,121 @@ You can set these values via the Geography View by searching the place, or via a 4 5 - 1 - 2 - - + 2 + 3 + + - False True True True - False True 4 5 - 2 - 3 - - + 3 + 4 + + + + + + + True + False + 0 + Name: + + + 1 + 2 + GTK_FILL + + + + + + True + False + 0 + Type: + + + 2 + 3 + 1 + 2 + GTK_FILL + + + + + + True + True + The name of this place. + + + + 1 + 2 + 1 + 2 + + + + + + True + False + What type of place this is. Eg 'Country', 'City', ... . + True + + + False + + + + + 3 + 4 + 1 + 2 + + + + + + True + False + 0 + Tags: + + + 2 + 3 + 3 + 4 + GTK_FILL + + + + + + True + False + 0 + + + 3 + 4 + 3 + 4 + @@ -320,345 +411,10 @@ You can set these values via the Geography View by searching the place, or via a True True - - True - False - 12 - 5 - 4 - 12 - 6 - - - True - False - 0 - C_ity: - True - center - city - - - 2 - 3 - GTK_FILL - - - - - - True - False - 0 - S_treet: - True - center - street - - - GTK_FILL - - - - - - True - True - Lowest level of a place division: eg the street name. -Use Alternate Locations tab to store the current name. - - - - 1 - 4 - - - - - - True - True - The town or city where the place is. -Use Alternate Locations tab to store the current name. - - - - 1 - 2 - 2 - 3 - - - - - - True - True - Lowest clergical division of this place. Typically used for church sources that only mention the parish. - - - - 3 - 4 - 1 - 2 - - - - - - True - False - 0 - Ch_urch parish: - True - center - parish - - - 2 - 3 - 1 - 2 - GTK_FILL - - - - - - True - False - 0 - Co_unty: - True - center - county - - - 3 - 4 - GTK_FILL - - - - - - True - True - Third level of place division. Eg., in the USA a county. - - - - 1 - 2 - 3 - 4 - - - - - - True - False - 0 - _State: - True - center - state - - - 2 - 3 - 2 - 3 - GTK_FILL - - - - - - True - True - Second level of place division, eg., in the USA a state, in Germany a Bundesland. - - - - 3 - 4 - 2 - 3 - - - - - - True - False - 0 - Count_ry: - True - center - country - - - 4 - 5 - GTK_FILL - - - - - - True - True - The country where the place is. - - - - - 1 - 2 - 4 - 5 - - - - - - True - False - 0 - _ZIP/Postal code: - True - center - postal - - - 2 - 3 - 3 - 4 - GTK_FILL - - - - - - True - True - - - - 3 - 4 - 3 - 4 - - - - - - True - False - 0 - Phon_e: - True - phone - - - 2 - 3 - 4 - 5 - GTK_FILL - - - - - - True - True - - - - 3 - 4 - 4 - 5 - - - - - - True - False - 0 - _Locality: - True - locality - - - 1 - 2 - GTK_FILL - - - - - - True - True - A district within, or a settlement near to, a town or city. -Use Alternate Locations tab to store the current name. - - - - 1 - 2 - 1 - 2 - - - - + - - True - False - - - True - False - gtk-file - 1 - - - True - True - 0 - - - - - True - False - Location - center - - - - - - False - False - 1 - - - - - False - + diff --git a/gramps/gui/glade/editplaceref.glade b/gramps/gui/glade/editplaceref.glade new file mode 100644 index 000000000..da7dd1842 --- /dev/null +++ b/gramps/gui/glade/editplaceref.glade @@ -0,0 +1,215 @@ + + + + + False + 550 + dialog + + + True + False + + + True + False + end + + + gtk-cancel + True + True + True + True + True + + + False + False + 0 + + + + + gtk-ok + True + True + True + True + True + True + + + False + False + 1 + + + + + gtk-help + True + True + True + True + True + + + False + False + 2 + + + + + False + True + end + 0 + + + + + True + False + + + True + False + 12 + 2 + 3 + 12 + 6 + + + True + False + 0 + Parent Place: + True + right + + + GTK_FILL + + + + + + True + False + 0 + Date: + True + + + 1 + 2 + GTK_FILL + + + + + + ... + True + False + True + + + 2 + 3 + GTK_FILL + + + + + + True + True + True + True + Invoke date editor + none + + + + Date + + + + + True + False + True + Show Date Editor + gramps-date + + + Date + + + + + + + 2 + 3 + 1 + 2 + GTK_FILL + + + + + + True + False + 0 + + + 1 + 2 + + + + + + 27 + True + True + Date range for which the parent is valid. + + + + 1 + 2 + 1 + 2 + + + + + + True + True + 0 + + + + + False + True + 1 + + + + + + cancel_button + ok_button + help_button + + + diff --git a/gramps/gui/glade/mergeplace.glade b/gramps/gui/glade/mergeplace.glade index 1b0417981..6993b1543 100644 --- a/gramps/gui/glade/mergeplace.glade +++ b/gramps/gui/glade/mergeplace.glade @@ -19,12 +19,10 @@ gtk-cancel - False True True True False - False True @@ -36,12 +34,10 @@ gtk-ok - False True True True False - False True @@ -53,12 +49,10 @@ gtk-help - False True True True False - False True @@ -111,11 +105,9 @@ primary data for the merged place. False - False True True False - False 0.5 True @@ -135,11 +127,9 @@ primary data for the merged place. - False True True False - False 0.5 True handle_btn1 @@ -179,7 +169,7 @@ primary data for the merged place. True False 6 - 6 + 8 4 6 6 @@ -194,7 +184,7 @@ primary data for the merged place. GTK_FILL - + @@ -210,17 +200,15 @@ primary data for the merged place. 2 3 GTK_FILL - + Title: - False True True False - False True 0.5 True @@ -229,17 +217,15 @@ primary data for the merged place. 1 2 GTK_FILL - + Title: - False True True False - False True 0.5 True @@ -251,36 +237,32 @@ primary data for the merged place. 1 2 GTK_FILL - + Latitude: - False True True False - False True 0.5 True - 2 - 3 + 5 + 6 GTK_FILL - + Latitude: - False True True False - False True 0.5 True @@ -289,39 +271,35 @@ primary data for the merged place. 2 3 - 2 - 3 + 5 + 6 GTK_FILL - + Longitude: - False True True False - False True 0.5 True - 3 - 4 + 6 + 7 GTK_FILL - + Longitude: - False True True False - False True 0.5 True @@ -330,80 +308,35 @@ primary data for the merged place. 2 3 - 3 - 4 + 6 + 7 GTK_FILL - - - - - - Location: - False - True - True - False - False - True - 0.5 - True - - - 4 - 5 - GTK_FILL - - - - - - Location: - False - True - True - False - False - True - 0.5 - True - loc_btn1 - - - 2 - 3 - 4 - 5 - GTK_FILL - + Gramps ID: - False True True False - False True 0.5 True - 5 - 6 + 7 + 8 GTK_FILL - + Gramps ID: - False True True False - False True 0.5 True @@ -412,10 +345,10 @@ primary data for the merged place. 2 3 - 5 - 6 + 7 + 8 GTK_FILL - + @@ -429,7 +362,7 @@ primary data for the merged place. 2 1 2 - + @@ -443,7 +376,7 @@ primary data for the merged place. 4 1 2 - + @@ -455,9 +388,9 @@ primary data for the merged place. 1 2 - 2 - 3 - + 5 + 6 + @@ -469,9 +402,9 @@ primary data for the merged place. 3 4 - 2 - 3 - + 5 + 6 + @@ -483,9 +416,9 @@ primary data for the merged place. 1 2 - 3 - 4 - + 6 + 7 + @@ -497,49 +430,9 @@ primary data for the merged place. 3 4 - 3 - 4 - - - - - - True - False - - - True - True - False - - - - - 1 - 2 - 4 - 5 - - - - - - True - False - - - True - True - False - - - - - 3 - 4 - 4 - 5 - + 6 + 7 + @@ -551,9 +444,9 @@ primary data for the merged place. 1 2 - 5 - 6 - + 7 + 8 + @@ -568,14 +461,229 @@ primary data for the merged place. 3 4 - 5 - 6 - + 7 + 8 + + + + Name: + True + True + False + half + True + 0 + True + True + + + 2 + 3 + GTK_FILL + + + + + + Name: + True + True + False + half + True + 0 + True + name_btn1 + + + 2 + 3 + 2 + 3 + GTK_FILL + + + + + + True + True + False + + True + + + 1 + 2 + 2 + 3 + + + + + + True + True + False + + True + + + 3 + 4 + 2 + 3 + + + + + + Type: + True + True + False + half + True + 0 + True + True + + + 3 + 4 + GTK_FILL + + + + + + Type: + True + True + False + half + True + 0 + True + type_btn1 + + + 2 + 3 + 3 + 4 + GTK_FILL + + + + + + True + True + False + + True + + + 1 + 2 + 3 + 4 + + + + + + True + True + False + + True + + + 3 + 4 + 3 + 4 + + + + + + Code: + True + True + False + half + True + 0 + True + True + + + 4 + 5 + GTK_FILL + + + + + + Code: + True + True + False + True + 0 + True + code_btn1 + + + 2 + 3 + 4 + 5 + GTK_FILL + + + + + + True + True + False + + True + + + 1 + 2 + 4 + 5 + + + + + + True + True + False + + True + + + 3 + 4 + 4 + 5 + + + True diff --git a/gramps/gui/merge/mergeplace.py b/gramps/gui/merge/mergeplace.py index dee0b8720..ce2446ffc 100644 --- a/gramps/gui/merge/mergeplace.py +++ b/gramps/gui/merge/mergeplace.py @@ -63,10 +63,11 @@ class MergePlace(ManagedWindow): """ Displays a dialog box that allows the places to be combined into one. """ - def __init__(self, dbstate, uistate, handle1, handle2): + def __init__(self, dbstate, uistate, handle1, handle2, callback=None): ManagedWindow.__init__(self, uistate, [], self.__class__) self.dbstate = dbstate database = dbstate.db + self.callback = callback self.pl1 = database.get_place_from_handle(handle1) self.pl2 = database.get_place_from_handle(handle2) @@ -86,6 +87,30 @@ class MergePlace(ManagedWindow): for widget_name in ('title1', 'title2', 'title_btn1', 'title_btn2'): self.get_widget(widget_name).set_sensitive(False) + entry1 = self.get_widget("name1") + entry2 = self.get_widget("name2") + entry1.set_text(self.pl1.get_name()) + entry2.set_text(self.pl2.get_name()) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('name1', 'name2', 'name_btn1', 'name_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("type1") + entry2 = self.get_widget("type2") + entry1.set_text(str(self.pl1.get_type())) + entry2.set_text(str(self.pl2.get_type())) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('type1', 'type2', 'type_btn1', 'type_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("code1") + entry2 = self.get_widget("code2") + entry1.set_text(self.pl1.get_code()) + entry2.set_text(self.pl2.get_code()) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('code1', 'code2', 'code_btn1', 'code_btn2'): + self.get_widget(widget_name).set_sensitive(False) + entry1 = self.get_widget("lat1") entry2 = self.get_widget("lat2") entry1.set_text(self.pl1.get_latitude()) @@ -102,20 +127,6 @@ class MergePlace(ManagedWindow): for widget_name in ('long1', 'long2', 'long_btn1', 'long_btn2'): self.get_widget(widget_name).set_sensitive(False) - loc1 = self.pl1.get_main_location().get_text_data_list() - loc2 = self.pl2.get_main_location().get_text_data_list() - tv1 = self.get_widget("loc1") - tv2 = self.get_widget("loc2") - tb1 = Gtk.TextBuffer() - tb2 = Gtk.TextBuffer() - tv1.set_buffer(tb1) - tv2.set_buffer(tb2) - tb1.set_text("\n".join(loc1)) - tb2.set_text("\n".join(loc2)) - if loc1 == loc2: - for widget_name in ('loc1', 'loc2', 'loc_btn1', 'loc_btn2'): - self.get_widget(widget_name).set_sensitive(False) - gramps1 = self.pl1.get_gramps_id() gramps2 = self.pl2.get_gramps_id() entry1 = self.get_widget("gramps1") @@ -144,12 +155,18 @@ class MergePlace(ManagedWindow): """first chosen place changes""" if obj.get_active(): self.get_widget("title_btn1").set_active(True) + self.get_widget("name_btn1").set_active(True) + self.get_widget("type_btn1").set_active(True) + self.get_widget("code_btn1").set_active(True) self.get_widget("lat_btn1").set_active(True) self.get_widget("long_btn1").set_active(True) self.get_widget("loc_btn1").set_active(True) self.get_widget("gramps_btn1").set_active(True) else: self.get_widget("title_btn2").set_active(True) + self.get_widget("name_btn2").set_active(True) + self.get_widget("type_btn2").set_active(True) + self.get_widget("code_btn2").set_active(True) self.get_widget("lat_btn2").set_active(True) self.get_widget("long_btn2").set_active(True) self.get_widget("loc_btn2").set_active(True) @@ -179,18 +196,23 @@ class MergePlace(ManagedWindow): if self.get_widget("title_btn1").get_active() ^ use_handle1: phoenix.set_title(titanic.get_title()) + if self.get_widget("name_btn1").get_active() ^ use_handle1: + phoenix.set_name(titanic.get_name()) + if self.get_widget("type_btn1").get_active() ^ use_handle1: + phoenix.set_type(titanic.get_type()) + if self.get_widget("code_btn1").get_active() ^ use_handle1: + phoenix.set_code(titanic.get_code()) if self.get_widget("lat_btn1").get_active() ^ use_handle1: phoenix.set_latitude(titanic.get_latitude()) if self.get_widget("long_btn1").get_active() ^ use_handle1: phoenix.set_longitude(titanic.get_longitude()) - if self.get_widget("loc_btn1").get_active() ^ use_handle1: - swaploc = phoenix.get_main_location() - phoenix.set_main_location(titanic.get_main_location()) - titanic.set_main_location(swaploc) if self.get_widget("gramps_btn1").get_active() ^ use_handle1: phoenix.set_gramps_id(titanic.get_gramps_id()) query = MergePlaceQuery(self.dbstate, phoenix, titanic) query.execute() + + if self.callback: + self.callback() self.uistate.set_busy_cursor(False) self.close() diff --git a/gramps/gui/selectors/selectplace.py b/gramps/gui/selectors/selectplace.py index 977ec2826..9f4323bdb 100644 --- a/gramps/gui/selectors/selectplace.py +++ b/gramps/gui/selectors/selectplace.py @@ -35,7 +35,7 @@ _ = glocale.translation.gettext # gramps modules # #------------------------------------------------------------------------- -from ..views.treemodels.placemodel import PlaceListModel +from ..views.treemodels.placemodel import PlaceTreeModel from .baseselector import BaseSelector #------------------------------------------------------------------------- @@ -56,19 +56,14 @@ class SelectPlace(BaseSelector): return _("Select Place") def get_model_class(self): - return PlaceListModel + return PlaceTreeModel def get_column_titles(self): return [ - (_('Title'), 350, BaseSelector.TEXT, 0), - (_('ID'), 75, BaseSelector.TEXT, 1), - (_('Street'), 75, BaseSelector.TEXT, 2), - (_('Locality'), 75, BaseSelector.TEXT, 3), - (_('City'), 75, BaseSelector.TEXT, 4), - (_('County'), 75, BaseSelector.TEXT, 5), - (_('State'), 75, BaseSelector.TEXT, 6), - (_('Country'), 75, BaseSelector.TEXT, 7), - (_('Parish'), 75, BaseSelector.TEXT, 9), + (_('Name'), 200, BaseSelector.TEXT, 0), + (_('ID'), 75, BaseSelector.TEXT, 1), + (_('Type'), 100, BaseSelector.TEXT, 3), + (_('Title'), 300, BaseSelector.TEXT, 2), ] def get_from_handle_func(self): diff --git a/gramps/gui/views/listview.py b/gramps/gui/views/listview.py index 2eb3ff719..bfbb5d4b5 100644 --- a/gramps/gui/views/listview.py +++ b/gramps/gui/views/listview.py @@ -424,11 +424,12 @@ class ListView(NavigationView): parent_iter = self.model.iter_parent(iter_) if parent_iter: parent_path = self.model.get_path(parent_iter) - parent_path_list = parent_path.get_indices() - for i in range(len(parent_path_list)): - expand_path = Gtk.TreePath( - tuple([x for x in parent_path_list[:i+1]])) - self.list.expand_row(expand_path, False) + if parent_path: + parent_path_list = parent_path.get_indices() + for i in range(len(parent_path_list)): + expand_path = Gtk.TreePath( + tuple([x for x in parent_path_list[:i+1]])) + self.list.expand_row(expand_path, False) # Select active object path = self.model.get_path(iter_) diff --git a/gramps/gui/views/treemodels/placemodel.py b/gramps/gui/views/treemodels/placemodel.py index 1e7f68cf5..d34de8f27 100644 --- a/gramps/gui/views/treemodels/placemodel.py +++ b/gramps/gui/views/treemodels/placemodel.py @@ -30,7 +30,6 @@ Place Model. # python modules # #------------------------------------------------------------------------- -import cgi import logging _LOG = logging.getLogger(".gui.views.treemodels.placemodel") @@ -46,6 +45,7 @@ from gi.repository import Gtk # GRAMPS modules # #------------------------------------------------------------------------- +from gramps.gen.lib.placetype import PlaceType from gramps.gen.datehandler import format_time from gramps.gen.utils.place import conv_lat_lon from gramps.gen.constfunc import cuni @@ -60,16 +60,6 @@ from .treebasemodel import TreeBaseModel from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext -#------------------------------------------------------------------------- -# -# Constants -# -#------------------------------------------------------------------------- -COUNTRYLEVELS = { -'default': [_(''), _(''), _(''), - _('')] -} - #------------------------------------------------------------------------- # # PlaceBaseModel @@ -83,39 +73,27 @@ class PlaceBaseModel(object): self.fmap = [ self.column_name, self.column_id, - self.column_street, - self.column_locality, - self.column_city, - self.column_county, - self.column_state, - self.column_country, - self.column_postal_code, - self.column_parish, + self.column_title, + self.column_type, + self.column_code, self.column_latitude, self.column_longitude, self.column_private, self.column_tags, self.column_change, - self.column_place_name, self.column_tag_color ] self.smap = [ self.column_name, self.column_id, - self.column_street, - self.column_locality, - self.column_city, - self.column_county, - self.column_state, - self.column_country, - self.column_postal_code, - self.column_parish, + self.column_title, + self.column_type, + self.column_code, self.sort_latitude, self.sort_longitude, self.column_private, self.column_tags, self.sort_change, - self.column_place_name, self.column_tag_color ] @@ -133,14 +111,17 @@ class PlaceBaseModel(object): """ Return the color column. """ - return 16 + return 10 def on_get_n_columns(self): return len(self.fmap)+1 - def column_place_name(self, data): + def column_title(self, data): return cuni(data[2]) + def column_name(self, data): + return cuni(data[6]) + def column_longitude(self, data): if not data[3]: return '' @@ -176,66 +157,24 @@ class PlaceBaseModel(object): def column_id(self, data): return cuni(data[1]) - def column_parish(self, data): - try: - return data[5][1] - except: - return '' + def column_type(self, data): + return str(PlaceType(data[7])) - def column_street(self, data): - try: - return data[5][0][0] - except: - return '' - - def column_locality(self, data): - try: - return data[5][0][1] - except: - return '' - - def column_city(self, data): - try: - return data[5][0][2] - except: - return '' - - def column_county(self, data): - try: - return data[5][0][3] - except: - return '' - - def column_state(self, data): - try: - return data[5][0][4] - except: - return '' - - def column_country(self, data): - try: - return data[5][0][5] - except: - return '' - - def column_postal_code(self, data): - try: - return data[5][0][6] - except: - return '' + def column_code(self, data): + return cuni(data[8]) def column_private(self, data): - if data[13]: + if data[16]: return 'gramps-lock' else: # There is a problem returning None here. return '' def sort_change(self, data): - return "%012x" % data[11] + return "%012x" % data[14] def column_change(self, data): - return format_time(data[11]) + return format_time(data[14]) def get_tag_name(self, tag_handle): """ @@ -249,7 +188,7 @@ class PlaceBaseModel(object): """ tag_color = "#000000000000" tag_priority = None - for handle in data[12]: + for handle in data[15]: tag = self.db.get_tag_from_handle(handle) if tag: this_priority = tag.get_priority() @@ -262,7 +201,7 @@ class PlaceBaseModel(object): """ Return the sorted list of tags. """ - tag_list = list(map(self.get_tag_name, data[12])) + tag_list = list(map(self.get_tag_name, data[15])) return ', '.join(sorted(tag_list, key=glocale.sort_key)) #------------------------------------------------------------------------- @@ -288,9 +227,6 @@ class PlaceListModel(PlaceBaseModel, FlatBaseModel): PlaceBaseModel.destroy(self) FlatBaseModel.destroy(self) - def column_name(self, data): - return cgi.escape(cuni(data[2])) - #------------------------------------------------------------------------- # # PlaceTreeModel @@ -322,6 +258,7 @@ class PlaceTreeModel(PlaceBaseModel, TreeBaseModel): PlaceBaseModel """ self.number_items = self.db.get_number_of_places + self.gen_cursor = self.db.get_place_tree_cursor def get_tree_levels(self): """ @@ -336,68 +273,19 @@ class PlaceTreeModel(PlaceBaseModel, TreeBaseModel): handle The handle of the gramps object. data The object data. """ - if data[5] is None: - # No primary location - level = [''] * 6 - else: - #country, state, county, city, locality, street - level = [data[5][0][i] for i in range(5,-1,-1)] - - node1 = (level[0], ) - node2 = (level[1], level[0]) - node3 = (level[2], level[1], level[0]) sort_key = self.sort_func(data) - - if not (level[3] or level[4] or level[5]): - if level[2]: - self.add_node(None, node1, level[0], None, add_parent=False) - self.add_node(node1, node2, level[1], None, add_parent=False) - self.add_node(node2, node3, level[2], None, add_parent=False) - self.add_node(node3, handle, sort_key, handle, add_parent=False) - elif level[1]: - self.add_node(None, node1, level[0], None, add_parent=False) - self.add_node(node1, node2, level[1], None, add_parent=False) - self.add_node(node2, handle, level[1], handle, add_parent=False) - elif level[0]: - self.add_node(None, node1, level[0], None, add_parent=False) - self.add_node(node1, handle, level[0], handle, add_parent=False) - else: - self.add_node(None, node1, level[0], None, add_parent=False) - self.add_node(node1, node2, level[1], None, add_parent=False) - self.add_node(node2, node3, level[2], None, add_parent=False) - self.add_node(node3, handle, sort_key, handle, add_parent=False) - - else: - self.add_node(None, node1, level[0], None, add_parent=False) - self.add_node(node1, node2, level[1], None, add_parent=False) - self.add_node(node2, node3, level[2], None, add_parent=False) - self.add_node(node3, handle, sort_key, handle, add_parent=False) - - def column_name(self, data): - name = '' - if data[5] is not None: - level = [data[5][0][i] for i in range(5,-1,-1)] - if not (level[3] or level[4] or level[5]): - name = cuni(level[2] or level[1] or level[0]) - else: - name = ', '.join([item for item in level[3:] if item]) - if not name: - name = cuni(data[2]) - - if name: - return cgi.escape(name) + if len(data[5]) > 0: + parent = data[5][0][0] else: - return "%s" % cgi.escape(_("")) - - def column_header(self, node): - """ - Return a column heading. This is called for nodes with no associated - Gramps handle. - """ - if node.name: - return '%s' % cgi.escape(node.name) - else: - level = len(self.do_get_path(self._get_iter(node)).get_indices()) - heading = '%s' % cgi.escape(COUNTRYLEVELS['default'][level]) - # This causes a problem with Gtk3 unless we cast to str. - return str(heading) + parent = None + + # Add the node as a root node if the parent is not in the tree. This + # will happen when the view is filtered. + if not self._get_node(parent): + parent = None + + self.add_node(parent, handle, sort_key, handle, add_parent=False) + + def column_header(self, data): + # should not get here! + return '????' diff --git a/gramps/plugins/export/exportgedcom.py b/gramps/plugins/export/exportgedcom.py index 8ae152873..5fe8a2e33 100644 --- a/gramps/plugins/export/exportgedcom.py +++ b/gramps/plugins/export/exportgedcom.py @@ -44,7 +44,10 @@ import io #------------------------------------------------------------------------- from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext -from gramps.gen.lib import AttributeType, ChildRefType, Citation, Date, EventRoleType, EventType, LdsOrd, NameType, NoteType, Person, UrlType, SrcAttributeType +from gramps.gen.lib import (AttributeType, ChildRefType, Citation, Date, + EventRoleType, EventType, LdsOrd, NameType, + PlaceType, NoteType, Person, UrlType, + SrcAttributeType) from gramps.version import VERSION import gramps.plugins.lib.libgedcom as libgedcom from gramps.gen.errors import DatabaseError @@ -53,6 +56,7 @@ from gramps.gen.updatecallback import UpdateCallback from gramps.gen.utils.file import media_path_full from gramps.gen.utils.place import conv_lat_lon from gramps.gen.constfunc import cuni +from gramps.gen.utils.location import get_main_location #------------------------------------------------------------------------- # @@ -1373,23 +1377,28 @@ class GedcomWriter(UpdateCallback): # The Gedcom standard shows that an optional address structure can # be written out in the event detail. # http://homepages.rootsweb.com/~pmcbride/gedcom/55gcch2.htm#EVENT_DETAIL - location = place.get_main_location() - if location and not location.is_empty(): - self._writeln(level, "ADDR", location.get_street()) - if location.get_street(): - self._writeln(level + 1, 'ADR1', location.get_street()) - if location.get_locality(): - self._writeln(level + 1, 'ADR2', location.get_locality()) - if location.get_city(): - self._writeln(level + 1, 'CITY', location.get_city()) - if location.get_state(): - self._writeln(level + 1, 'STAE', location.get_state()) - if location.get_postal_code(): - self._writeln(level + 1, 'POST', location.get_postal_code()) - if location.get_country(): - self._writeln(level + 1, 'CTRY', location.get_country()) - if location.get_phone(): - self._writeln(level, 'PHON', location.get_phone()) + location = get_main_location(self.dbase, place) + street = location.get(PlaceType.STREET) + locality = location.get(PlaceType.LOCALITY) + city = location.get(PlaceType.CITY) + state = location.get(PlaceType.STATE) + country = location.get(PlaceType.COUNTRY) + postal_code = place.get_code() + + if (street or locality or city or state or postal_code or country): + self._writeln(level, "ADDR", street) + if street: + self._writeln(level + 1, 'ADR1', street) + if locality: + self._writeln(level + 1, 'ADR2', locality) + if city: + self._writeln(level + 1, 'CITY', city) + if state: + self._writeln(level + 1, 'STAE', state) + if postal_code: + self._writeln(level + 1, 'POST', postal_code) + if country: + self._writeln(level + 1, 'CTRY', country) self._note_references(place.get_note_list(), level+1) diff --git a/gramps/plugins/export/exportxml.py b/gramps/plugins/export/exportxml.py index 17f70f07e..da1061d48 100644 --- a/gramps/plugins/export/exportxml.py +++ b/gramps/plugins/export/exportxml.py @@ -718,6 +718,16 @@ class GrampsXmlWriter(UpdateCallback): self.write_note_list(note_list,index+1) self.g.write('%s\n' % sp) + def dump_place_ref(self, placeref, index=1): + sp = " " * index + date = placeref.get_date_object() + if date.is_empty(): + self.write_ref('placeref', placeref.ref, index, close=True) + else: + self.write_ref('placeref', placeref.ref, index, close=False) + self.write_date(date, index+1) + self.g.write('%s\n' % sp) + def write_event(self,event,index=1): if not event: return @@ -1185,25 +1195,21 @@ class GrampsXmlWriter(UpdateCallback): self.write_primary_tag("placeobj", place, index) title = self.fix(place.get_title()) + name = self.fix(place.get_name()) + ptype = self.fix(place.get_type().xml_str()) + code = self.fix(place.get_code()) + self.write_line_nofix("ptitle", title, index+1) + self.write_line_nofix("pname", name, index+1) + self.write_line_nofix("type", ptype, index+1) + self.write_line_nofix("code", code, index+1) + longitude = self.fix(place.get_longitude()) lat = self.fix(place.get_latitude()) - main_loc = place.get_main_location() - llen = (len(place.get_alternate_locations()) + - len(place.get_url_list()) + - len(place.get_media_list()) + - len(place.get_citation_list()) - ) - - ml_empty = main_loc.is_empty() - - if title == "": - title = self.build_place_title(place.get_main_location()) - self.write_line_nofix("ptitle", title, index+1) - if longitude or lat: self.g.write('%s\n' % (" "*(index+1), longitude, lat)) - self.dump_location(main_loc) + for placeref in place.get_placeref_list(): + self.dump_place_ref(placeref, index+1) list(map(self.dump_location, place.get_alternate_locations())) self.write_media_list(place.get_media_list(), index+1) self.write_url_list(place.get_url_list()) diff --git a/gramps/plugins/gramplet/placedetails.py b/gramps/plugins/gramplet/placedetails.py index 9aa4d708f..6baa85ee8 100644 --- a/gramps/plugins/gramplet/placedetails.py +++ b/gramps/plugins/gramplet/placedetails.py @@ -25,6 +25,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext from gramps.gen.utils.place import conv_lat_lon from gramps.gen.utils.file import media_path_full +from gramps.gen.utils.location import get_location_list from gi.repository import Gtk from gi.repository import Pango @@ -108,7 +109,7 @@ class PlaceDetails(Gramplet): self.title.set_text(place.get_title()) self.clear_table() - self.display_location(place.get_main_location()) + self.display_location(place) self.display_separator() lat, lon = conv_lat_lon(place.get_latitude(), place.get_longitude(), @@ -118,11 +119,11 @@ class PlaceDetails(Gramplet): if lon: self.add_row(_('Longitude'), lon) - def display_location(self, location): + def display_location(self, place): """ Display a location. """ - lines = [line for line in location.get_text_data_list()[:-1] if line] + lines = get_location_list(self.dbstate.db, place) self.add_row(_('Location'), '\n'.join(lines)) def display_empty(self): diff --git a/gramps/plugins/graph/gvfamilylines.py b/gramps/plugins/graph/gvfamilylines.py index dd569ff51..59120b248 100644 --- a/gramps/plugins/graph/gvfamilylines.py +++ b/gramps/plugins/graph/gvfamilylines.py @@ -53,7 +53,7 @@ log = logging.getLogger(".FamilyLines") #------------------------------------------------------------------------ from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext -from gramps.gen.lib import EventRoleType, EventType, Person +from gramps.gen.lib import EventRoleType, EventType, Person, PlaceType from gramps.gen.utils.file import media_path_full from gramps.gui.thumbnails import get_thumbnail_path from gramps.gen.plug.report import Report @@ -65,6 +65,7 @@ from gramps.gen.plug.menu import (NumberOption, ColorOption, BooleanOption, SurnameColorOption) from gramps.gen.utils.db import get_birth_or_fallback, get_death_or_fallback from gramps.gen.display.name import displayer as global_name_display +from gramps.gen.utils.location import get_main_location #------------------------------------------------------------------------ # @@ -772,13 +773,13 @@ class FamilyLinesReport(Report): if not bth_event.private or self._incprivate: place = self._db.get_place_from_handle(bth_event.get_place_handle()) if place: - location = place.get_main_location() - if location.get_city: - birthplace = location.get_city() - elif location.get_state: - birthplace = location.get_state() - elif location.get_country: - birthplace = location.get_country() + location = get_main_location(self._db, place) + if location.get(PlaceType.CITY): + birthplace = location.get(PlaceType.CITY) + elif location.get(PlaceType.STATE): + birthplace = location.get(PlaceType.STATE) + elif location.get(PlaceType.COUNTRY): + birthplace = location.get(PlaceType.COUNTRY) # see if we have a deceased date we can use deathStr = None @@ -796,13 +797,13 @@ class FamilyLinesReport(Report): if not dth_event.private or self._incprivate: place = self._db.get_place_from_handle(dth_event.get_place_handle()) if place: - location = place.get_main_location() - if location.get_city: - deathplace = location.get_city() - elif location.get_state: - deathplace = location.get_state() - elif location.get_country: - deathplace = location.get_country() + location = get_main_location(self._db, place) + if location.get(PlaceType.CITY): + deathplace = location.get(PlaceType.CITY) + elif location.get(PlaceType.STATE): + deathplace = location.get(PlaceType.STATE) + elif location.get(PlaceType.COUNTRY): + deathplace = location.get(PlaceType.COUNTRY) # see if we have an image to use for this person imagePath = None @@ -920,13 +921,13 @@ class FamilyLinesReport(Report): if self._incplaces: place = self._db.get_place_from_handle(event.get_place_handle()) if place: - location = place.get_main_location() - if location.get_city: - weddingPlace = location.get_city() - elif location.get_state: - weddingPlace = location.get_state() - elif location.get_country: - weddingPlace = location.get_country() + location = get_main_location(self._db, place) + if location.get(PlaceType.CITY): + weddingPlace = location.get(PlaceType.CITY) + elif location.get(PlaceType.STATE): + weddingPlace = location.get(PlaceType.STATE) + elif location.get(PlaceType.COUNTRY): + weddingPlace = location.get(PlaceType.COUNTRY) break # figure out the number of children (if any) diff --git a/gramps/plugins/importer/importxml.py b/gramps/plugins/importer/importxml.py index e4174f775..12a8f8a9e 100644 --- a/gramps/plugins/importer/importxml.py +++ b/gramps/plugins/importer/importxml.py @@ -53,9 +53,9 @@ from gramps.gen.lib import (Address, Attribute, AttributeType, ChildRef, MediaObject, MediaRef, Name, NameOriginType, NameType, Note, NoteType, Person, PersonRef, Place, RepoRef, Repository, Researcher, Source, - SrcAttribute, SrcAttributeType, + SrcAttribute, SrcAttributeType, PlaceType, StyledText, StyledTextTag, StyledTextTagType, - Surname, Tag, Url) + Surname, Tag, Url, PlaceRef) from gramps.gen.db import DbTxn from gramps.gen.db.write import CLASS_TO_KEY_MAP from gramps.gen.errors import GrampsImportError @@ -75,6 +75,7 @@ from gramps.gen.config import config #import gramps.plugins.lib.libgrampsxml from gramps.plugins.lib import libgrampsxml from gramps.gen.plug.utils import version_str_to_tup +from gramps.plugins.lib.libplaceimport import PlaceImport #------------------------------------------------------------------------- # @@ -542,6 +543,7 @@ class GrampsParser(UpdateCallback): self.placeobj = None self.locations = 0 self.place_map = {} + self.place_import = PlaceImport(self.db) self.resname = "" self.resaddr = "" @@ -650,6 +652,7 @@ class GrampsParser(UpdateCallback): "phone": (None, self.stop_phone), "date": (None, self.stop_date), "cause": (None, self.stop_cause), + "code": (None, self.stop_code), "description": (None, self.stop_description), "event": (self.start_event, self.stop_event), "type": (None, self.stop_type), @@ -684,6 +687,8 @@ class GrampsParser(UpdateCallback): "datestr": (self.start_datestr, None), "places": (None, self.stop_places), "placeobj": (self.start_placeobj, self.stop_placeobj), + "placeref": (self.start_placeref, self.stop_placeref), + "pname": (None, self.stop_pname), "ptitle": (None, self.stop_ptitle), "location": (self.start_location, None), "lds_ord": (self.start_lds_ord, self.stop_lds_ord), @@ -1166,11 +1171,32 @@ class GrampsParser(UpdateCallback): loc.country = attrs.get('country', '') loc.postal = attrs.get('postal', '') loc.phone = attrs.get('phone', '') - if self.locations > 0: - self.placeobj.add_alternate_locations(loc) + + if self.__xml_version < (1, 6, 0): + if self.locations > 0: + self.placeobj.add_alternate_locations(loc) + else: + location = (attrs.get('street', ''), + attrs.get('locality', ''), + attrs.get('parish', ''), + attrs.get('city', ''), + attrs.get('county', ''), + attrs.get('state', ''), + attrs.get('country', '')) + self.place_import.store_location(location, self.placeobj.handle) + + for type_num, name in enumerate(location): + if name: + break + + self.placeobj.set_name(name) + self.placeobj.set_type(PlaceType(7-type_num)) + codes = [attrs.get('postal'), attrs.get('phone')] + self.placeobj.set_code(' '.join(code for code in codes if code)) else: - self.placeobj.set_main_location(loc) - self.locations = self.locations + 1 + self.placeobj.add_alternate_locations(loc) + + self.locations = self.locations + 1 def start_witness(self, attrs): """ @@ -1293,6 +1319,15 @@ class GrampsParser(UpdateCallback): else: self.person.add_event_ref(self.eventref) + def start_placeref(self, attrs): + """ + Add a place reference to the place currently being processed. + """ + self.placeref = PlaceRef() + handle = self.inaugurate(attrs['hlink'], "place", Place) + self.placeref.ref = handle + self.placeobj.add_placeref(self.placeref) + def start_attribute(self, attrs): self.attribute = Attribute() self.attribute.private = bool(attrs.get("priv")) @@ -2279,8 +2314,10 @@ class GrampsParser(UpdateCallback): date_value = self.address.get_date_object() elif self.name: date_value = self.name.get_date_object() - else: + elif self.event: date_value = self.event.get_date_object() + else: + date_value = self.placeref.get_date_object() start = attrs['start'].split('-') stop = attrs['stop'].split('-') @@ -2361,8 +2398,10 @@ class GrampsParser(UpdateCallback): date_value = self.address.get_date_object() elif self.name: date_value = self.name.get_date_object() - else: + elif self.event: date_value = self.event.get_date_object() + else: + date_value = self.placeref.get_date_object() bce = 1 val = attrs['val'] @@ -2440,8 +2479,10 @@ class GrampsParser(UpdateCallback): date_value = self.address.get_date_object() elif self.name: date_value = self.name.get_date_object() - else: + elif self.event: date_value = self.event.get_date_object() + else: + date_value = self.placeref.get_date_object() date_value.set_as_text(attrs['val']) @@ -2498,6 +2539,9 @@ class GrampsParser(UpdateCallback): def stop_places(self, *tag): self.placeobj = None + + if self.__xml_version < (1, 6, 0): + self.place_import.generate_hierarchy(self.trans) def stop_photo(self, *tag): self.photo = None @@ -2505,11 +2549,13 @@ class GrampsParser(UpdateCallback): def stop_ptitle(self, tag): self.placeobj.title = tag - def stop_placeobj(self, *tag): - if self.placeobj.title == "": - loc = self.placeobj.get_main_location() - self.placeobj.title = build_place_title(loc) + def stop_pname(self, tag): + self.placeobj.name = tag + def stop_code(self, tag): + self.placeobj.code = tag + + def stop_placeobj(self, *tag): self.db.commit_place(self.placeobj, self.trans, self.placeobj.get_change_time()) self.placeobj = None @@ -2526,6 +2572,9 @@ class GrampsParser(UpdateCallback): elif self.repo: # Repository type self.repo.type.set_from_xml_str(tag) + elif self.placeobj: + # Place type + self.placeobj.place_type.set_from_xml_str(tag) def stop_childref(self, tag): self.childref = None @@ -2536,6 +2585,9 @@ class GrampsParser(UpdateCallback): def stop_eventref(self, tag): self.eventref = None + def stop_placeref(self, tag): + self.placeref = None + def stop_event(self, *tag): if self.family: ref = EventRef() diff --git a/gramps/plugins/lib/libgedcom.py b/gramps/plugins/lib/libgedcom.py index df8484f97..0d1c6ca15 100644 --- a/gramps/plugins/lib/libgedcom.py +++ b/gramps/plugins/lib/libgedcom.py @@ -131,7 +131,7 @@ from gramps.gen.lib import (Address, Attribute, AttributeType, ChildRef, MediaRef, Name, NameType, Note, NoteType, Person, PersonRef, Place, RepoRef, Repository, RepositoryType, Researcher, Source, SourceMediaType, SrcAttribute, SrcAttributeType, - Surname, Tag, Url, UrlType) + Surname, Tag, Url, UrlType, PlaceType, PlaceRef) from gramps.gen.db import DbTxn from gramps.gen.updatecallback import UpdateCallback from gramps.gen.mime import get_type @@ -144,6 +144,7 @@ from gramps.gui.dialog import WarningDialog from gramps.gen.lib.const import IDENTICAL, DIFFERENT from gramps.gen.lib import (StyledText, StyledTextTag, StyledTextTagType) from gramps.gen.constfunc import cuni, conv_to_unicode, STRTYPE, UNITYPE +from gramps.plugins.lib.libplaceimport import PlaceImport #------------------------------------------------------------------------- # @@ -1661,7 +1662,7 @@ class PlaceParser(object): fcn = self.__field_map.get(item, lambda x, y: None) self.parse_function.append(fcn) - def load_place(self, place, text): + def load_place(self, place_import, place, text): """ Takes the text string representing a place, splits it into its subcomponents (comma separated), and calls the approriate @@ -1671,12 +1672,31 @@ class PlaceParser(object): items = [item.strip() for item in text.split(',')] if len(items) != len(self.parse_function): return - loc = place.get_main_location() index = 0 + loc = Location() for item in items: self.parse_function[index](loc, item) index += 1 + location = (loc.get_street(), + loc.get_locality(), + loc.get_parish(), + loc.get_city(), + loc.get_county(), + loc.get_state(), + loc.get_country()) + + place_import.store_location(location, place.handle) + + for type_num, name in enumerate(location): + if name: + break + + place.set_name(name) + place.set_type(PlaceType(7-type_num)) + code = loc.get_postal_code() + place.set_code(code) + #------------------------------------------------------------------------- # # IdFinder @@ -1908,6 +1928,8 @@ class GedcomParser(UpdateCallback): self.rid2id = {} self.nid2id = {} + self.place_import = PlaceImport(self.dbase) + # # Parse table for <> below the level 0 SUBM tag # @@ -2675,6 +2697,8 @@ class GedcomParser(UpdateCallback): self.dbase.add_source(src, self.trans) self.__clean_up() + self.place_import.generate_hierarchy(self.trans) + if not self.dbase.get_feature("skip-check-xref"): self.__check_xref() self.dbase.enable_signals() @@ -4370,7 +4394,8 @@ class GedcomParser(UpdateCallback): state.msg += sub_state.msg if sub_state.place: - sub_state.place_fields.load_place(sub_state.place, + sub_state.place_fields.load_place(self.place_import, + sub_state.place, sub_state.place.get_title()) def __lds_temple(self, line, state): @@ -4929,7 +4954,8 @@ class GedcomParser(UpdateCallback): state.msg += sub_state.msg if sub_state.place: - sub_state.place_fields.load_place(sub_state.place, + sub_state.place_fields.load_place(self.place_import, + sub_state.place, sub_state.place.get_title()) def __family_source(self, line, state): @@ -5236,7 +5262,6 @@ class GedcomParser(UpdateCallback): @type state: CurrentState """ - location = None if self.is_ftw and state.event.type in FTW_BAD_PLACE: state.event.set_description(line.data) else: @@ -5247,9 +5272,6 @@ class GedcomParser(UpdateCallback): place_handle = state.event.get_place_handle() if place_handle: place = self.dbase.get_place_from_handle(place_handle) - location = place.get_main_location() - empty_loc = Location() - place.set_main_location(empty_loc) else: place = self.__find_or_create_place(line.data) place.set_title(line.data) @@ -5258,20 +5280,14 @@ class GedcomParser(UpdateCallback): sub_state = CurrentState() sub_state.place = place sub_state.level = state.level+1 - sub_state.pf = self.place_parser + sub_state.pf = PlaceParser() self.__parse_level(sub_state, self.event_place_map, self.__undefined) state.msg += sub_state.msg - sub_state.pf.load_place(place, place.get_title()) - # If we already had a remembered location, we set it into the main - # location if that is empty, else the alternate location - if location and not location.is_empty(): - if place.get_main_location().is_empty(): - place.set_main_location(location) - else: - place.add_alternate_locations(location) + sub_state.pf.load_place(self.place_import, place, place.get_title()) + self.dbase.commit_place(place, self.trans) def __event_place_note(self, line, state): @@ -5393,7 +5409,6 @@ class GedcomParser(UpdateCallback): place_handle = place.handle self.__add_location(place, location) - # place.set_main_location(location) list(map(place.add_note, note_list)) @@ -5407,19 +5422,11 @@ class GedcomParser(UpdateCallback): @param location: A location we want to add to this place @type location: gen.lib.location """ - # If there is no main location, we add the location - if place.main_loc is None: - place.set_main_location(location) - elif place.get_main_location().is_equivalent(location) == IDENTICAL: - # the location is already present as the main location - pass - else: - for loc in place.get_alternate_locations(): - if loc.is_equivalent(location) == IDENTICAL: - return - place.add_alternate_locations(location) - - + for loc in place.get_alternate_locations(): + if loc.is_equivalent(location) == IDENTICAL: + return + place.add_alternate_locations(location) + def __event_phon(self, line, state): """ @param line: The current line in GedLine format @@ -5430,8 +5437,8 @@ class GedcomParser(UpdateCallback): place_handle = state.event.get_place_handle() if place_handle: place = self.dbase.get_place_from_handle(place_handle) - location = place.get_main_location() - location.set_phone(line.data) + codes = [place.get_code(), line.data] + place.set_code(' '.join(code for code in codes if code)) self.dbase.commit_place(place, self.trans) def __event_privacy(self, line, state): diff --git a/gramps/plugins/lib/libplaceimport.py b/gramps/plugins/lib/libplaceimport.py new file mode 100644 index 000000000..516f9fd1f --- /dev/null +++ b/gramps/plugins/lib/libplaceimport.py @@ -0,0 +1,110 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2013 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +Helper class for importing places. +""" + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +from gramps.gen.lib import Place, PlaceType, PlaceRef + +#------------------------------------------------------------------------- +# +# PlaceImport class +# +#------------------------------------------------------------------------- +class PlaceImport(object): + """ + Helper class for importing places. + """ + def __init__(self, db): + self.db = db + self.loc2handle = {} + self.handle2loc = {} + + def store_location(self, location, handle): + """ + Store the location of a place already in the database. + """ + self.loc2handle[location] = handle + self.handle2loc[handle] = location + + def generate_hierarchy(self, trans): + """ + Generate missing places in the place hierarchy. + """ + for handle, location in self.handle2loc.iteritems(): + + # find title and type + for type_num, name in enumerate(location): + if name: + break + + loc = list(location) + loc[type_num] = '' + + # find top parent + parent = None + for n in range(7): + if loc[n]: + tup = tuple([''] * n + loc[n:]) + parent = self.loc2handle.get(tup) + if parent: + break + + # create missing parent places + if parent: + n -= 1 + while n > type_num: + if loc[n]: + title = ', '.join([item for item in loc[n:] if item]) + parent = self.__add_place(loc[n], n, parent, title, trans) + self.loc2handle[tuple([''] * n + loc[n:])] = parent + n -= 1 + + # link to existing place + if parent: + place = self.db.get_place_from_handle(handle) + placeref = PlaceRef() + placeref.ref = parent + place.set_placeref_list([placeref]) + self.db.commit_place(place, trans, place.get_change_time()) + + def __add_place(self, name, type_num, parent, title, trans): + """ + Add a missing place to the database. + """ + place = Place() + place.name = name + place.title = title + place.place_type = PlaceType(7-type_num) + if parent is not None: + placeref = PlaceRef() + placeref.ref = parent + place.set_placeref_list([placeref]) + handle = self.db.add_place(place, trans) + self.db.commit_place(place, trans) + return handle diff --git a/gramps/plugins/lib/libplaceview.py b/gramps/plugins/lib/libplaceview.py index d6d6fb95c..1d0954cc1 100644 --- a/gramps/plugins/lib/libplaceview.py +++ b/gramps/plugins/lib/libplaceview.py @@ -47,7 +47,7 @@ from gi.repository import Gtk # #------------------------------------------------------------------------- from gramps.gen.lib import Place -from gramps.gui.views.listview import ListView, TEXT, MARKUP, ICON +from gramps.gui.views.listview import ListView, TEXT, ICON from gramps.gui.widgets.menuitem import add_menuitem from gramps.gen.errors import WindowActiveError from gramps.gui.views.bookmarks import PlaceBookmarks @@ -79,31 +79,21 @@ class PlaceBaseView(ListView): """ COL_NAME = 0 COL_ID = 1 - COL_STREET = 2 - COL_LOCALITY = 3 - COL_CITY = 4 - COL_COUNTY = 5 - COL_STATE = 6 - COL_COUNTRY = 7 - COL_ZIP = 8 - COL_PARISH = 9 - COL_LAT = 10 - COL_LON = 11 - COL_PRIV = 12 - COL_TAGS = 13 - COL_CHAN = 14 + COL_TITLE = 2 + COL_TYPE = 3 + COL_CODE = 4 + COL_LAT = 5 + COL_LON = 6 + COL_PRIV = 7 + COL_TAGS = 8 + COL_CHAN = 9 # column definitions COLUMNS = [ - (_('Place Name'), MARKUP, None), + (_('Name'), TEXT, None), (_('ID'), TEXT, None), - (_('Street'), TEXT, None), - (_('Locality'), TEXT, None), - (_('City'), TEXT, None), - (_('County'), TEXT, None), - (_('State'), TEXT, None), - (_('Country'), TEXT, None), - (_('ZIP/Postal Code'), TEXT, None), - (_('Church Parish'), TEXT, None), + (_('Title'), TEXT, None), + (_('Type'), TEXT, None), + (_('Code'), TEXT, None), (_('Latitude'), TEXT, None), (_('Longitude'), TEXT, None), (_('Private'), ICON, 'gramps-lock'), @@ -112,14 +102,10 @@ class PlaceBaseView(ListView): ] # default setting with visible columns, order of the col, and their size CONFIGSETTINGS = ( - ('columns.visible', [COL_NAME, COL_ID, COL_STREET, COL_LOCALITY, - COL_CITY, COL_COUNTY, COL_STATE]), - ('columns.rank', [COL_NAME, COL_ID, COL_STREET, COL_LOCALITY, COL_CITY, - COL_COUNTY, COL_STATE, COL_COUNTRY, COL_ZIP, - COL_PARISH, COL_LAT, COL_LON, COL_PRIV, COL_TAGS, - COL_CHAN]), - ('columns.size', [250, 75, 150, 150, 150, 150, 100, 100, 100, - 100, 150, 150, 40, 100, 100]) + ('columns.visible', [COL_TITLE, COL_ID, COL_TYPE, COL_CODE]), + ('columns.rank', [COL_NAME, COL_TITLE, COL_ID, COL_TYPE, COL_CODE, + COL_LAT, COL_LON, COL_PRIV, COL_TAGS, COL_CHAN]), + ('columns.size', [250, 250, 75, 100, 100, 150, 150, 40, 100, 100]) ) ADD_MSG = _("Add a new place") EDIT_MSG = _("Edit the selected place") @@ -380,7 +366,14 @@ class PlaceBaseView(ListView): pass def remove(self, obj): - self.remove_selected_objects() + for handle in self.selected_handles(): + for link in self.dbstate.db.find_backlink_handles(handle,['Place']): + msg = _("Cannot delete place.") + msg2 = _("This place is currently referenced by another place. " + "First remove the places it contains.") + ErrorDialog(msg, msg2) + return + self.remove_selected_objects() def remove_object_from_handle(self, handle): person_list = [ @@ -423,7 +416,15 @@ class PlaceBaseView(ListView): "control key while clicking on the desired place.") ErrorDialog(msg, msg2) else: - MergePlace(self.dbstate, self.uistate, mlist[0], mlist[1]) + MergePlace(self.dbstate, self.uistate, mlist[0], mlist[1], + self.merged) + + def merged(self): + """ + Rebuild the model after a merge to reflect changes in the hierarchy. + """ + if not (self.model.get_flags() & Gtk.TreeModelFlags.LIST_ONLY): + self.build_tree() def get_handle_from_gramps_id(self, gid): obj = self.dbstate.db.get_place_from_gramps_id(gid) diff --git a/gramps/plugins/lib/libsubstkeyword.py b/gramps/plugins/lib/libsubstkeyword.py index cd9774c34..36adb2e63 100644 --- a/gramps/plugins/lib/libsubstkeyword.py +++ b/gramps/plugins/lib/libsubstkeyword.py @@ -41,9 +41,10 @@ from __future__ import print_function #------------------------------------------------------------------------ from gramps.gen.display.name import displayer as name_displayer from gramps.gen.datehandler import displayer -from gramps.gen.lib import EventType +from gramps.gen.lib import EventType, PlaceType, Location from gramps.gen.utils.db import get_birth_or_fallback, get_death_or_fallback from gramps.gen.constfunc import STRTYPE, cuni +from gramps.gen.utils.location import get_main_location #------------------------------------------------------------------------ @@ -159,7 +160,7 @@ class NameFormat(GenericFormat): def parse_format(self, name): """ Parse the name """ if self.is_blank(name): - return + return def common(): """ return the common name of the person """ @@ -310,7 +311,7 @@ class PlaceFormat(GenericFormat): def _default_format(self, place): return place.get_title() - def parse_format(self, place): + def parse_format(self, database, place): """ Parse the place """ if self.is_blank(place): @@ -318,16 +319,28 @@ class PlaceFormat(GenericFormat): code = "elcuspn" + "oitxy" upper = code.upper() - function = [place.get_main_location().get_street, - place.get_main_location().get_locality, - place.get_main_location().get_city, - place.get_main_location().get_county, - place.get_main_location().get_state, - place.get_main_location().get_postal_code, - place.get_main_location().get_country, + + main_loc = get_main_location(database, place) + location = Location() + location.set_street(main_loc.get(PlaceType.STREET, '')) + location.set_locality(main_loc.get(PlaceType.LOCALITY, '')) + location.set_parish(main_loc.get(PlaceType.PARISH, '')) + location.set_city(main_loc.get(PlaceType.CITY, '')) + location.set_county(main_loc.get(PlaceType.COUNTY, '')) + location.set_state(main_loc.get(PlaceType.STATE, '')) + location.set_postal_code(main_loc.get(PlaceType.STREET, '')) + location.set_country(main_loc.get(PlaceType.COUNTRY, '')) - place.get_main_location().get_phone, - place.get_main_location().get_parish, + function = [location.get_street, + location.get_locality, + location.get_city, + location.get_county, + location.get_state, + place.get_code, + location.get_country, + + location.get_phone, + location.get_parish, place.get_title, place.get_longitude, place.get_latitude @@ -382,7 +395,7 @@ class EventFormat(GenericFormat): """ start formatting a place in this event """ place_format = PlaceFormat(self.string_in) place = place_format.get_place(self.database, event) - return place_format.parse_format(place) + return place_format.parse_format(self.database, place) def format_attrib(): """ Get the name and then get the attributes value """ @@ -839,7 +852,7 @@ class VariableParse(object): place = place_f.get_place(self.database, event) if self.empty_item(place): return - return place_f.parse_format(place) + return place_f.parse_format(self.database, place) def __parse_name(self, person): name_format = NameFormat(self._in) diff --git a/gramps/plugins/lib/maps/geography.py b/gramps/plugins/lib/maps/geography.py index 6c47bf3aa..e2ef93fe4 100644 --- a/gramps/plugins/lib/maps/geography.py +++ b/gramps/plugins/lib/maps/geography.py @@ -51,7 +51,7 @@ import cairo # Gramps Modules # #------------------------------------------------------------------------- -from gramps.gen.lib import EventType, Place +from gramps.gen.lib import EventType, Place, PlaceType, PlaceRef from gramps.gen.display.name import displayer as _nd from gramps.gui.views.navigationview import NavigationView from gramps.gen.utils.libformatting import FormattingHelper @@ -61,6 +61,7 @@ from gramps.gui.managedwindow import ManagedWindow from gramps.gen.config import config from gramps.gui.editors import EditPlace, EditEvent, EditFamily, EditPerson from gramps.gui.selectors.selectplace import SelectPlace +from gramps.gen.utils.location import get_main_location from gi.repository import OsmGpsMap as osmgpsmap from . import constants @@ -804,12 +805,14 @@ class GeoGraphyView(OsmGps, NavigationView): """ self.mark = mark place = self.dbstate.db.get_place_from_gramps_id(self.mark[9]) - loc = place.get_main_location() + parent_list = place.get_placeref_list() + if len(parent_list) > 0: + parent = parent_list[0].ref + else: + parent = None self.select_fct = PlaceSelection(self.uistate, self.dbstate, self.osm, self.selection_layer, self.place_list, - lat, lon, self.__edit_place, - (loc.get_country(), loc.get_state(), loc.get_county()) - ) + lat, lon, self.__edit_place, parent) def edit_person(self, menu, event, lat, lon, mark): """ @@ -866,9 +869,11 @@ class GeoGraphyView(OsmGps, NavigationView): selector = SelectPlace(self.dbstate, self.uistate, []) place = selector.run() if place: - loc = place.get_main_location() - oldv = (loc.get_country(), loc.get_state(), - loc.get_county()) if loc else None + parent_list = place.get_placeref_list() + if len(parent_list) > 0: + parent = parent_list[0].ref + else: + parent = None places_handle = self.dbstate.db.iter_place_handles() nb_places = 0 gids = "" @@ -903,9 +908,9 @@ class GeoGraphyView(OsmGps, NavigationView): lat, lon, self.__edit_place, - oldv) + parent) - def __add_place(self, pcountry, pcounty, pstate, plat, plon): + def __add_place(self, parent, plat, plon): """ Add a new place using longitude and latitude of location centered on the map @@ -914,18 +919,17 @@ class GeoGraphyView(OsmGps, NavigationView): new_place = Place() new_place.set_latitude(str(plat)) new_place.set_longitude(str(plon)) - loc = new_place.get_main_location() - loc.set_country(pcountry) - loc.set_county(pcounty) - loc.set_state(pstate) - new_place.set_main_location(loc) + if parent: + placeref = PlaceRef() + placeref.ref = parent + new_place.add_placeref(placeref) try: EditPlace(self.dbstate, self.uistate, [], new_place) self.add_marker(None, None, plat, plon, None, True, 0) except WindowActiveError: pass - def __edit_place(self, pcountry, pcounty, pstate, plat, plon): + def __edit_place(self, parent, plat, plon): """ Edit the selected place at the marker position """ @@ -933,17 +937,16 @@ class GeoGraphyView(OsmGps, NavigationView): place = self.dbstate.db.get_place_from_gramps_id(self.mark[9]) place.set_latitude(str(plat)) place.set_longitude(str(plon)) - loc = place.get_main_location() - loc.set_country(pcountry) - loc.set_county(pcounty) - loc.set_state(pstate) - place.set_main_location(loc) + if parent: + placeref = PlaceRef() + placeref.ref = parent + place.add_placeref(placeref) try: EditPlace(self.dbstate, self.uistate, [], place) except WindowActiveError: pass - def __link_place(self, pcountry, pcounty, pstate, plat, plon): + def __link_place(self, parent, plat, plon): """ Link an existing place using longitude and latitude of location centered on the map @@ -954,11 +957,10 @@ class GeoGraphyView(OsmGps, NavigationView): self.select_fct.close() place.set_latitude(str(plat)) place.set_longitude(str(plon)) - loc = place.get_main_location() - loc.set_country(pcountry) - loc.set_county(pcounty) - loc.set_state(pstate) - place.set_main_location(loc) + if parent: + placeref = PlaceRef() + placeref.ref = parent + place.add_placeref(placeref) try: EditPlace(self.dbstate, self.uistate, [], place) self.add_marker(None, None, plat, plon, None, True, 0) diff --git a/gramps/plugins/lib/maps/placeselection.py b/gramps/plugins/lib/maps/placeselection.py index 4efadce91..e112711b7 100644 --- a/gramps/plugins/lib/maps/placeselection.py +++ b/gramps/plugins/lib/maps/placeselection.py @@ -56,6 +56,8 @@ from gi.repository import Gtk from gramps.gen.errors import WindowActiveError from gramps.gui.managedwindow import ManagedWindow from .osmgps import OsmGps +from gramps.gen.utils.location import get_main_location +from gramps.gen.lib import PlaceType #------------------------------------------------------------------------- # @@ -77,9 +79,9 @@ def match(self, lat, lon, radius): if (math.hypot(lat-float(entry[3]), lon-float(entry[4])) <= rds) == True: # Do we already have this place ? avoid duplicates - self.get_location(entry[9]) - if not [self.country, self.state, self.county] in self.places: - self.places.append([self.country, self.state, self.county]) + country, state, county, place = self.get_location(entry[9]) + if not [country, state, county, place] in self.places: + self.places.append([country, state, county, place]) return self.places #------------------------------------------------------------------------- @@ -100,8 +102,7 @@ class PlaceSelection(ManagedWindow, OsmGps): Place Selection initialization """ try: - ManagedWindow.__init__(self, uistate, [], - PlaceSelection) + ManagedWindow.__init__(self, uistate, [], PlaceSelection) except WindowActiveError: return self.uistate = uistate @@ -109,9 +110,6 @@ class PlaceSelection(ManagedWindow, OsmGps): self.lat = lat self.lon = lon self.osm = maps - self.country = None - self.state = None - self.county = None self.radius = 1.0 self.circle = None self.oldvalue = oldvalue @@ -141,7 +139,7 @@ class PlaceSelection(ManagedWindow, OsmGps): self.scroll = Gtk.ScrolledWindow(self.vadjust) self.scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self.scroll.set_shadow_type(Gtk.ShadowType.IN) - self.plist = Gtk.ListStore(str, str, str) + self.plist = Gtk.ListStore(str, str, str, str) self.choices = Gtk.TreeView(self.plist) self.scroll.add(self.choices) self.renderer = Gtk.CellRendererText() @@ -191,17 +189,19 @@ class PlaceSelection(ManagedWindow, OsmGps): # In this case, we change the color of the row. # display the associated message self.label2.show() - field1, field2, field3 = self.oldvalue - self.plist.append((PLACE_STRING % field1, - PLACE_STRING % field2, - PLACE_STRING % field3) + place = self.dbstate.db.get_place_from_handle(self.oldvalue) + loc = get_main_location(self.dbstate.db, place) + self.plist.append((PLACE_STRING % loc.get(PlaceType.COUNTRY, ''), + PLACE_STRING % loc.get(PlaceType.STATE, ''), + PLACE_STRING % loc.get(PlaceType.COUNTY, ''), + self.oldvalue) ) for place in self.places: self.plist.append(place) # here, we could add value from geography names services ... # if we found no place, we must create a default place. - self.plist.append((_("New place with empty fields"), "", "...")) + self.plist.append((_("New place with empty fields"), "", "...", None)) def hide_the_region(self): """ @@ -220,37 +220,32 @@ class PlaceSelection(ManagedWindow, OsmGps): self.selection_layer = self.add_selection_layer() self.selection_layer.add_circle(rds/2.0, self.lat, self.lon) - def get_location(self, place): + def get_location(self, gramps_id): """ get location values """ - place = self.dbstate.db.get_place_from_gramps_id(place) - loc = place.get_main_location() - data = loc.get_text_data_list() - # new background or font color on gtk fields ? - self.country = data[6] - self.state = data[5] - self.county = data[4] - return(self.country, self.state, self.county) + parent_place = None + country = state = county = '' + place = self.dbstate.db.get_place_from_gramps_id(gramps_id) + parent_list = place.get_placeref_list() + while len(parent_list) > 0: + place = self.dbstate.db.get_place_from_handle(parent_list[0].ref) + if int(place.get_type()) == PlaceType.COUNTY: + county = place.name + if parent_place is None: + parent_place = place.get_handle() + elif int(place.get_type()) == PlaceType.STATE: + state = place.name + if parent_place is None: + parent_place = place.get_handle() + elif int(place.get_type()) == PlaceType.COUNTRY: + country = place.name + if parent_place is None: + parent_place = place.get_handle() + return(country, state, county, parent_place) def selection(self, obj, index, column, function): """ get location values and call the real function : add_place, edit_place """ - if self.plist[index][2] == "...": - # case with blank values ( New place with empty fields ) - self.function( "", "", "", self.lat, self.lon) - elif self.plist[index][0][1:5] == "span": - # case with old values ( keep the old values of the place ) - name = PLACE_REGEXP.search(self.plist[index][0], 0) - country = name.group(1) - name = PLACE_REGEXP.search(self.plist[index][1], 0) - state = name.group(1) - name = PLACE_REGEXP.search(self.plist[index][2], 0) - county = name.group(1) - self.function( country, county, state, self.lat, self.lon) - else: - # Set the new values of the country, county and state fields. - self.function( self.plist[index][0], self.plist[index][2], - self.plist[index][1], self.lat, self.lon) - + self.function(self.plist[index][3], self.lat, self.lon) diff --git a/gramps/plugins/mapservices/eniroswedenmap.py b/gramps/plugins/mapservices/eniroswedenmap.py index e3a540c18..e18412ee6 100644 --- a/gramps/plugins/mapservices/eniroswedenmap.py +++ b/gramps/plugins/mapservices/eniroswedenmap.py @@ -41,6 +41,8 @@ _ = glocale.translation.gettext #------------------------------------------------------------------------ from gramps.plugins.lib.libmapservice import MapService from gramps.gui.dialog import WarningDialog +from gramps.gen.utils.location import get_main_location +from gramps.gen.lib import PlaceType # Make upper case of translaed country so string search works later MAP_NAMES_SWEDEN = [_("Sweden").upper(), @@ -65,12 +67,13 @@ def _strip_leading_comma(descr): descr = descr.strip()[1:] return descr.strip() -def _build_title(self): +def _build_title(db, place): """ Builds descrition string for title parameter in url """ - descr = self.get_title() - parish = self.get_main_location().get_parish() - city = self.get_main_location().get_city() - state = self.get_main_location().get_state() + descr = place.get_title() + location = get_main_location(db, place) + parish = location.get(PlaceType.PARISH) + city = location.get(PlaceType.CITY) + state = location.get(PlaceType.STATE) title_descr = "" if descr: title_descr += descr.strip() @@ -82,19 +85,21 @@ def _build_title(self): title_descr += ', ' + state.strip() + _(" state") return _strip_leading_comma(title_descr) -def _build_city(self): +def _build_city(db, place): """ Builds description string for city parameter in url """ - county = self.get_main_location().get_county() + location = get_main_location(db, place) + county = location.get(PlaceType.COUNTY) # Build a title description string that will work for Eniro - city_descr = _build_area(self) + city_descr = _build_area(db, place) if county: city_descr += ', ' + county return _strip_leading_comma(city_descr) -def _build_area(self): +def _build_area(db, place): """ Builds string for area parameter in url """ - street = self.get_main_location().get_street() - city = self.get_main_location().get_city() + location = get_main_location(db, place) + street = location.get(PlaceType.STREET) + city = location.get(PlaceType.CITY) # Build a title description string that will work for Eniro area_descr = "" if street: @@ -121,7 +126,8 @@ class EniroSVMapService(MapService): path = "" # First see if we are in or near Sweden or Denmark # Change country to upper case - country = place.get_main_location().get_country().upper().strip() + location = get_main_location(self.database, place) + country = location.get(PlaceType.COUNTRY, '').upper().strip() country_given = (country in MAP_NAMES_SWEDEN or \ country in MAP_NAMES_DENMARK) and (country != "") # if no country given, check if we might be in the vicinity defined by @@ -143,8 +149,8 @@ class EniroSVMapService(MapService): return if coord_ok: - place_title = _build_title(place) - place_city = _build_city(place) + place_title = _build_title(self.database, place) + place_city = _build_city(self.database, place) x_coord, y_coord = self._lat_lon(place, format="RT90") # Set zoom level to 5 if Sweden/Denmark, others 3 zoom = 5 @@ -157,7 +163,7 @@ class EniroSVMapService(MapService): self.url = path.replace(" ","%20") return - place_area = _build_area(place) + place_area = _build_area(self.database, place) if country_given and place_area: if country in MAP_NAMES_SWEDEN: path = "http://kartor.eniro.se/query?&what=map_adr&mop=aq" \ diff --git a/gramps/plugins/mapservices/googlemap.py b/gramps/plugins/mapservices/googlemap.py index af17a518c..5d4c00c90 100644 --- a/gramps/plugins/mapservices/googlemap.py +++ b/gramps/plugins/mapservices/googlemap.py @@ -37,6 +37,8 @@ _ = glocale.translation.gettext # #------------------------------------------------------------------------ from gramps.plugins.lib.libmapservice import MapService +from gramps.gen.utils.location import get_main_location +from gramps.gen.lib import PlaceType class GoogleMapService(MapService): """Map service using http://maps.google.com""" @@ -56,8 +58,9 @@ class GoogleMapService(MapService): longitude) return - city = place.get_main_location().get_city() - country = place.get_main_location().get_country() + location = get_main_location(self.database, place) + city = location.get(PlaceType.CITY) + country = location.get(PlaceType.COUNTRY) if city and country: self.url = "http://maps.google.com/maps?q=%s,%s" % (city, country) return diff --git a/gramps/plugins/mapservices/openstreetmap.py b/gramps/plugins/mapservices/openstreetmap.py index 6c8557893..f85c3b8ce 100644 --- a/gramps/plugins/mapservices/openstreetmap.py +++ b/gramps/plugins/mapservices/openstreetmap.py @@ -37,7 +37,8 @@ _ = glocale.translation.gettext # #------------------------------------------------------------------------ from gramps.plugins.lib.libmapservice import MapService - +from gramps.gen.utils.location import get_main_location +from gramps.gen.lib import PlaceType class OpensStreetMapService(MapService): """Map service using http://openstreetmap.org @@ -60,8 +61,9 @@ class OpensStreetMapService(MapService): return - city = place.get_main_location().get_city() - country = place.get_main_location().get_country() + location = get_main_location(self.database, place) + city = location.get(PlaceType.CITY) + country = location.get(PlaceType.COUNTRY) if city and country: self.url = "http://open.mapquestapi.com/nominatim/v1/"\ "search.php?q=%s%%2C%s" % (city, country) diff --git a/gramps/plugins/textreport/placereport.py b/gramps/plugins/textreport/placereport.py index 21c08a08e..c71914b2d 100644 --- a/gramps/plugins/textreport/placereport.py +++ b/gramps/plugins/textreport/placereport.py @@ -51,6 +51,8 @@ from gramps.gen.proxy import PrivateProxyDb from gramps.gen.datehandler import get_date from gramps.gen.sort import Sort from gramps.gen.display.name import displayer as _nd +from gramps.gen.utils.location import get_main_location +from gramps.gen.lib import PlaceType class PlaceReport(Report): """ @@ -148,16 +150,17 @@ class PlaceReport(Report): This procedure writes out the details of a single place """ place = self.database.get_place_from_handle(handle) - location = place.get_main_location() + location = get_main_location(self.database, place) - place_details = [self._("Gramps ID: %s ") % place.get_gramps_id(), - self._("Street: %s ") % location.get_street(), - self._("Parish: %s ") % location.get_parish(), - self._("Locality: %s ") % location.get_locality(), - self._("City: %s ") % location.get_city(), - self._("County: %s ") % location.get_county(), - self._("State: %s") % location.get_state(), - self._("Country: %s ") % location.get_country()] + place_details = [ + self._("Gramps ID: %s ") % place.get_gramps_id(), + self._("Street: %s ") % location.get(PlaceType.STREET, ''), + self._("Parish: %s ") % location.get(PlaceType.PARISH, ''), + self._("Locality: %s ") % location.get(PlaceType.LOCALITY, ''), + self._("City: %s ") % location.get(PlaceType.CITY, ''), + self._("County: %s ") % location.get(PlaceType.COUNTY, ''), + self._("State: %s") % location.get(PlaceType.STATE, ''), + self._("Country: %s ") % location.get(PlaceType.COUNTRY, '')] self.doc.start_paragraph("PLC-PlaceTitle") self.doc.write_text(("%(nbr)s. %(place)s") % {'nbr' : place_nbr, diff --git a/gramps/plugins/view/placetreeview.py b/gramps/plugins/view/placetreeview.py index df2ebc5d7..5231ff583 100644 --- a/gramps/plugins/view/placetreeview.py +++ b/gramps/plugins/view/placetreeview.py @@ -30,10 +30,10 @@ from __future__ import unicode_literals # Gramps modules # #------------------------------------------------------------------------- -from gramps.gui.views.listview import TEXT, MARKUP, ICON +from gramps.gui.views.listview import TEXT, ICON from gramps.plugins.lib.libplaceview import PlaceBaseView -from gramps.gui.views.treemodels.placemodel import PlaceTreeModel, COUNTRYLEVELS -from gramps.gen.lib import Place +from gramps.gui.views.treemodels.placemodel import PlaceTreeModel +from gramps.gen.lib import Place, PlaceRef from gramps.gen.errors import WindowActiveError from gramps.gui.editors import EditPlace @@ -54,51 +54,35 @@ class PlaceTreeView(PlaceBaseView): """ A hierarchical view of the top three levels of places. """ - COL_PLACE = 0 + COL_NAME = 0 COL_ID = 1 - COL_STREET = 2 - COL_LOCALITY = 3 - COL_CITY = 4 - COL_COUNTY = 5 - COL_STATE = 6 - COL_COUNTRY = 7 - COL_ZIP = 8 - COL_PARISH = 9 - COL_LAT = 10 - COL_LON = 11 - COL_PRIV = 12 - COL_TAGS = 13 - COL_CHAN = 14 - COL_NAME = 15 + COL_TITLE = 2 + COL_TYPE = 3 + COL_CODE = 4 + COL_LAT = 5 + COL_LON = 6 + COL_PRIV = 7 + COL_TAGS = 8 + COL_CHAN = 9 # column definitions COLUMNS = [ - (_('Place'), MARKUP, None), + (_('Name'), TEXT, None), (_('ID'), TEXT, None), - (_('Street'), TEXT, None), - (_('Locality'), TEXT, None), - (_('City'), TEXT, None), - (_('County'), TEXT, None), - (_('State'), TEXT, None), - (_('Country'), TEXT, None), - (_('ZIP/Postal Code'), TEXT, None), - (_('Church Parish'), TEXT, None), + (_('Title'), TEXT, None), + (_('Type'), TEXT, None), + (_('Code'), TEXT, None), (_('Latitude'), TEXT, None), (_('Longitude'), TEXT, None), (_('Private'), ICON, 'gramps-lock'), (_('Tags'), TEXT, None), (_('Last Changed'), TEXT, None), - (_('Place Name'), TEXT, None), ] # default setting with visible columns, order of the col, and their size CONFIGSETTINGS = ( - ('columns.visible', [COL_PLACE, COL_ID, COL_STREET, COL_LOCALITY, - COL_CITY, COL_COUNTY, COL_STATE]), - ('columns.rank', [COL_PLACE, COL_ID, COL_STREET, COL_LOCALITY, COL_CITY, - COL_COUNTY, COL_STATE, COL_COUNTRY, COL_ZIP, - COL_PARISH, COL_LAT, COL_LON, COL_PRIV, COL_TAGS, - COL_CHAN, COL_NAME]), - ('columns.size', [250, 75, 150, 150, 150, 150, 100, 100, 100, - 100, 150, 150, 40, 100, 100, 150]) + ('columns.visible', [COL_NAME, COL_ID, COL_TYPE, COL_CODE]), + ('columns.rank', [COL_NAME, COL_ID, COL_TITLE, COL_TYPE, COL_CODE, + COL_LAT, COL_LON, COL_PRIV, COL_TAGS, COL_CHAN]), + ('columns.size', [250, 75, 150, 100, 100, 150, 150, 40, 100, 100]) ) def __init__(self, pdata, dbstate, uistate): @@ -196,41 +180,16 @@ class PlaceTreeView(PlaceBaseView): Add a new place. Attempt to get the top three levels of hierarchy from the currently selected row. """ - place = Place() - - model, pathlist = self.selection.get_selected_rows() - level = ["", "", ""] - level1 = level2 = level3 = "" - if len(pathlist) == 1: - path = pathlist[0] - iter_ = model.get_iter(path) - if iter_: - if len(path) == 1: - level[0] = model.get_node_from_iter(iter_).name - elif len(path) == 2: - level[1] = model.get_node_from_iter(iter_).name - parent = model.iter_parent(iter_) - level[0] = model.get_node_from_iter(parent).name - elif len(path) == 3: - level[2] = model.get_node_from_iter(iter_).name - parent = model.iter_parent(iter_) - level[1] = model.get_node_from_iter(parent).name - parent = model.iter_parent(parent) - level[0] = model.get_node_from_iter(parent).name - else: - parent = model.iter_parent(iter_) - level[2] = model.get_node_from_iter(parent).name - parent = model.iter_parent(parent) - level[1] = model.get_node_from_iter(parent).name - parent = model.iter_parent(parent) - level[0] = model.get_node_from_iter(parent).name + parent_list = [] + for handle in self.selected_handles(): + placeref = PlaceRef() + placeref.ref = handle + parent_list.append(placeref) - for ind in [0, 1, 2]: - if level[ind] and level[ind] == COUNTRYLEVELS['default'][ind+1]: - level[ind] = "" - place.get_main_location().set_country(level[0]) - place.get_main_location().set_state(level[1]) - place.get_main_location().set_county(level[2]) + place = Place() + if len(parent_list) > 0: + place.parent = parent_list + try: EditPlace(self.dbstate, self.uistate, [], place) except WindowActiveError: diff --git a/gramps/plugins/webreport/narrativeweb.py b/gramps/plugins/webreport/narrativeweb.py index f753f8985..661c2e494 100644 --- a/gramps/plugins/webreport/narrativeweb.py +++ b/gramps/plugins/webreport/narrativeweb.py @@ -108,7 +108,7 @@ log = logging.getLogger(".NarrativeWeb") from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.sgettext from gramps.gen.lib import (ChildRefType, Date, EventType, FamilyRelType, Name, - NameType, Person, UrlType, NoteType, + NameType, Person, UrlType, NoteType, PlaceType, EventRoleType, Family, Event, Place, Source, Citation, MediaObject, Repository, Note, Tag) from gramps.gen.lib.date import Today @@ -148,6 +148,7 @@ from gramps.gen.utils.place import conv_lat_lon from gramps.gui.pluginmanager import GuiPluginManager from gramps.gen.relationship import get_relationship_calculator +from gramps.gen.utils.location import get_main_location COLLATE_LANG = glocale.collation SORT_KEY = glocale.sort_key @@ -508,14 +509,11 @@ def get_gendex_data(database, event_ref): if place_handle: place = database.get_place_from_handle(place_handle) if place: - location = place.get_main_location() - if location and not location.is_empty(): - poe = ", ".join(l for l in - [ - location.get_city().strip(), - location.get_state().strip(), - location.get_country().strip() - ] if l) + location = get_main_location(self.dbase_, place) + poe = ", ".join(l for l in [ + location.get(PlaceType.CITY, '').strip(), + location.get(PlaceType.STATE, '').strip(), + location.get(PlaceType.COUNTRY, '').strip()] if l) return doe, poe def format_date(date): @@ -2641,26 +2639,22 @@ class BasePage(object): ) tbody += trow - if place.main_loc: - ml = place.get_main_location() - if ml and not ml.is_empty(): - - for (label, data) in [ - (STREET, ml.street), - (LOCALITY, ml.locality), - (CITY, ml.city), - (PARISH, ml.parish), - (COUNTY, ml.county), - (STATE, ml.state), - (POSTAL, ml.postal), - (COUNTRY, ml.country), - (_("Telephone"), ml.phone) ]: - if data: - trow = Html("tr") + ( - Html("td", label, class_ = "ColumnAttribute", inline = True), - Html("td", data, class_ = "ColumnValue", inline = True) - ) - tbody += trow + ml = get_main_location(self.dbase_, place) + for (label, data) in [ + (STREET, ml.get(PlaceType.STREET, '')), + (LOCALITY, ml.get(PlaceType.LOCALITY, '')), + (CITY, ml.get(PlaceType.CITY, '')), + (PARISH, ml.get(PlaceType.PARISH, '')), + (COUNTY, ml.get(PlaceType.COUNTY, '')), + (STATE, ml.get(PlaceType.STATE, '')), + (POSTAL, place.get_code()), + (COUNTRY, ml.get(PlaceType.COUNTRY, ''))]: + if data: + trow = Html("tr") + ( + Html("td", label, class_ = "ColumnAttribute", inline = True), + Html("td", data, class_ = "ColumnValue", inline = True) + ) + tbody += trow altloc = place.get_alternate_locations() if altloc: @@ -3384,7 +3378,7 @@ class PlacePages(BasePage): place = self.dbase_.get_place_from_handle(place_handle) if place: place_title = place.get_title() - ml = place.get_main_location() + ml = get_main_location(self.dbase_, place) if place_title and not place_title.isspace(): letter = get_index_letter(first_letter(place_title), @@ -3414,8 +3408,8 @@ class PlacePages(BasePage): trow.extend( Html("td", data or " ", class_ =colclass, inline =True) for (colclass, data) in [ - ["ColumnState", ml.state], - ["ColumnCountry", ml.country] ] + ["ColumnState", ml.get(PlaceType.STATE, '')], + ["ColumnCountry", ml.get(PlaceType.COUNTRY, '')] ] ) tcell1 = Html("td", class_ ="ColumnLatitude", inline =True)