From 0758e411fdbd64e036cd5e1e6ea7bd7760856f7d Mon Sep 17 00:00:00 2001 From: Nick Hall Date: Sun, 2 Dec 2012 14:23:29 +0000 Subject: [PATCH] Initial version svn: r20746 --- gramps/gen/db/backup.py | 15 +- gramps/gen/db/base.py | 78 +++- gramps/gen/db/dbconst.py | 10 +- gramps/gen/db/read.py | 271 ++++++------ gramps/gen/db/undoredo.py | 63 ++- gramps/gen/db/upgrade.py | 133 ++++-- gramps/gen/db/write.py | 409 ++++++++++-------- gramps/gen/filters/rules/place/__init__.py | 40 +- .../gen/filters/rules/place/_haslocation.py | 63 +++ .../gen/filters/rules/place/_hasnolatorlon.py | 5 +- .../rules/place/_inlatlonneighborhood.py | 4 + gramps/gen/lib/location.py | 170 ++++++-- gramps/gen/lib/place.py | 125 ++---- gramps/gui/dbloader.py | 30 +- .../editors/displaytabs/locationembedlist.py | 24 +- .../gui/editors/displaytabs/locationmodel.py | 20 +- gramps/gui/editors/editlocation.py | 196 ++++++--- gramps/gui/editors/editplace.py | 90 +--- gramps/gui/editors/filtereditor.py | 58 ++- .../filters/sidebar/_placesidebarfilter.py | 59 +-- gramps/gui/glade/editlocation.glade | 291 ++++--------- gramps/gui/glade/editplace.glade | 388 +---------------- gramps/gui/selectors/selectlocation.py | 70 +++ gramps/gui/selectors/selectorfactory.py | 23 +- gramps/gui/selectors/selectplace.py | 14 +- gramps/gui/views/treemodels/__init__.py | 23 +- gramps/gui/views/treemodels/locationmodel.py | 252 +++++++++++ gramps/gui/views/treemodels/placemodel.py | 130 +----- gramps/gui/widgets/__init__.py | 42 +- gramps/gui/widgets/locationentry.py | 196 +++++++++ gramps/gui/widgets/locationentry2.py | 138 ++++++ gramps/plugins/gramplet/placedetails.py | 18 +- gramps/plugins/lib/libmapservice.py | 6 +- gramps/plugins/lib/libplaceview.py | 97 ++--- gramps/plugins/view/geoevents.py | 7 +- gramps/plugins/view/geofamily.py | 7 +- gramps/plugins/view/geoperson.py | 7 +- gramps/plugins/view/geoplaces.py | 7 +- gramps/plugins/view/locationview.py | 281 ++++++++++++ gramps/plugins/view/view.gpr.py | 76 ++-- 40 files changed, 2312 insertions(+), 1624 deletions(-) create mode 100644 gramps/gen/filters/rules/place/_haslocation.py create mode 100644 gramps/gui/selectors/selectlocation.py create mode 100644 gramps/gui/views/treemodels/locationmodel.py create mode 100644 gramps/gui/widgets/locationentry.py create mode 100644 gramps/gui/widgets/locationentry2.py create mode 100644 gramps/plugins/view/locationview.py diff --git a/gramps/gen/db/backup.py b/gramps/gen/db/backup.py index adc511c6d..55b5dc0ed 100644 --- a/gramps/gen/db/backup.py +++ b/gramps/gen/db/backup.py @@ -55,19 +55,15 @@ db. # #------------------------------------------------------------------------- import os -import sys -if sys.version_info[0] < 3: - import cPickle as pickle -else: - import pickle +import cPickle as pickle #------------------------------------------------------------------------ # # Gramps libs # #------------------------------------------------------------------------ -from .exceptions import DbException -from .write import FAMILY_TBL, PLACES_TBL, SOURCES_TBL, MEDIA_TBL, \ +from exceptions import DbException +from write import FAMILY_TBL, PLACES_TBL, LOCATION_TBL, SOURCES_TBL, MEDIA_TBL,\ EVENTS_TBL, PERSON_TBL, REPO_TBL, NOTE_TBL, TAG_TBL, META, CITATIONS_TBL #------------------------------------------------------------------------ @@ -91,7 +87,7 @@ def backup(database): """ try: __do_export(database) - except (OSError, IOError) as msg: + except (OSError, IOError), msg: raise DbException(str(msg)) def __mk_backup_name(database, base): @@ -159,7 +155,7 @@ def restore(database): """ try: __do_restore(database) - except (OSError, IOError) as msg: + except (OSError, IOError), msg: raise DbException(str(msg)) def __do_restore(database): @@ -215,5 +211,6 @@ def __build_tbl_map(database): ( MEDIA_TBL, database.media_map.db), ( EVENTS_TBL, database.event_map.db), ( TAG_TBL, database.tag_map.db), + ( LOCATION_TBL, database.location_map.db), ( META, database.metadata.db), ] diff --git a/gramps/gen/db/base.py b/gramps/gen/db/base.py index 846aa6fb4..30f8bc475 100644 --- a/gramps/gen/db/base.py +++ b/gramps/gen/db/base.py @@ -41,8 +41,8 @@ from ..ggettext import gettext as _ #------------------------------------------------------------------------- from ..lib.childreftype import ChildRefType from ..lib.childref import ChildRef -from .txn import DbTxn -from .exceptions import DbTransactionCancel +from txn import DbTxn +from exceptions import DbTransactionCancel class DbReadBase(object): """ @@ -321,6 +321,27 @@ class DbReadBase(object): """ raise NotImplementedError + def get_location_cursor(self): + """ + Return a reference to a cursor over Location objects + """ + raise NotImplementedError + + def get_location_from_handle(self, handle): + """ + Find a Location in the database from the passed handle. + + If no such Location exists, None is returned. + """ + raise NotImplementedError + + def get_location_handles(self): + """ + Return a list of database handles, one handle for each Location in + the database. + """ + raise NotImplementedError + def get_media_attribute_types(self): """ Return a list of all Attribute types associated with Media and MediaRef @@ -436,6 +457,12 @@ class DbReadBase(object): """ raise NotImplementedError + def get_number_of_locations(self): + """ + Return the number of locations currently in the database. + """ + raise NotImplementedError + def get_number_of_media_objects(self): """ Return the number of media objects currently in the database. @@ -591,6 +618,12 @@ class DbReadBase(object): """ raise NotImplementedError + def get_raw_location_data(self, handle): + """ + Return raw (serialized and pickled) Location object from handle + """ + raise NotImplementedError + def get_raw_note_data(self, handle): """ Return raw (serialized and pickled) Note object from handle @@ -867,6 +900,12 @@ class DbReadBase(object): """ raise NotImplementedError + def has_location_handle(self, handle): + """ + Return True if the handle exists in the current Location database. + """ + raise NotImplementedError + def has_name_group_key(self, name): """ Return if a key exists in the name_group table. @@ -945,6 +984,18 @@ class DbReadBase(object): """ raise NotImplementedError + def iter_location_handles(self): + """ + Return an iterator over handles for Locations in the database + """ + raise NotImplementedError + + def iter_locations(self): + """ + Return an iterator over objects for Locations in the database + """ + raise NotImplementedError + def iter_media_object_handles(self): """ Return an iterator over handles for Media in the database @@ -1235,6 +1286,13 @@ class DbWriteBase(DbReadBase): """ raise NotImplementedError + def add_location(self, location, transaction): + """ + Add a Location to the database, assigning a handle if it has not already + been defined. + """ + raise NotImplementedError + def add_note(self, obj, transaction, set_gid=True): """ Add a Note to the database, assigning internal IDs if they have @@ -1343,6 +1401,13 @@ class DbWriteBase(DbReadBase): """ raise NotImplementedError + def commit_location(self, location, transaction, change_time=None): + """ + Commit the specified Location to the database, storing the changes as + part of the transaction. + """ + raise NotImplementedError + def commit_media_object(self, obj, transaction, change_time=None): """ Commit the specified MediaObject to the database, storing the changes @@ -1460,6 +1525,15 @@ class DbWriteBase(DbReadBase): """ raise NotImplementedError + def remove_location(self, handle, transaction): + """ + Remove the Location specified by the database handle from the + database, preserving the change in the passed transaction. + + This method must be overridden in the derived class. + """ + raise NotImplementedError + def remove_note(self, handle, transaction): """ Remove the Note specified by the database handle from the diff --git a/gramps/gen/db/dbconst.py b/gramps/gen/db/dbconst.py index 2e840dfbf..6eb2d24c4 100644 --- a/gramps/gen/db/dbconst.py +++ b/gramps/gen/db/dbconst.py @@ -30,7 +30,6 @@ Declare constants used by database modules # standard python modules # #------------------------------------------------------------------------- -import sys #------------------------------------------------------------------------- # @@ -45,8 +44,8 @@ __all__ = ( ) + ('PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY', - 'EVENT_KEY', 'MEDIA_KEY', 'PLACE_KEY', 'REPOSITORY_KEY', - 'NOTE_KEY', 'REFERENCE_KEY', 'TAG_KEY' + 'EVENT_KEY', 'MEDIA_KEY', 'PLACE_KEY', 'LOCATION_KEY', + 'REPOSITORY_KEY', 'NOTE_KEY', 'REFERENCE_KEY', 'TAG_KEY' ) + ('TXNADD', 'TXNUPD', 'TXNDEL') @@ -61,14 +60,14 @@ DBLOGNAME = ".Db" # Name of logger DBMODE_R = "r" # Read-only access DBMODE_W = "w" # Full Read/Write access DBPAGE = 16384 # Size of the pages used to hold items in the database -DBMODE = 0o666 # Unix mode for database creation +DBMODE = 0666 # Unix mode for database creation DBCACHE = 0x4000000 # Size of the shared memory buffer pool DBLOCKS = 100000 # Maximum number of locks supported DBOBJECTS = 100000 # Maximum number of simultaneously locked objects DBUNDO = 1000 # Maximum size of undo buffer from ..config import config -if config.get('preferences.use-bsddb3') or sys.version_info[0] >= 3: +if config.get('preferences.use-bsddb3'): from bsddb3.db import DB_CREATE, DB_AUTO_COMMIT, DB_DUP, DB_DUPSORT, DB_RDONLY else: from bsddb.db import DB_CREATE, DB_AUTO_COMMIT, DB_DUP, DB_DUPSORT, DB_RDONLY @@ -87,5 +86,6 @@ REFERENCE_KEY = 7 NOTE_KEY = 8 TAG_KEY = 9 CITATION_KEY = 10 +LOCATION_KEY = 11 TXNADD, TXNUPD, TXNDEL = 0, 1, 2 diff --git a/gramps/gen/db/read.py b/gramps/gen/db/read.py index ed885b0f7..3ed705e00 100644 --- a/gramps/gen/db/read.py +++ b/gramps/gen/db/read.py @@ -25,27 +25,21 @@ """ Read classes for the GRAMPS databases. """ - +from __future__ import with_statement #------------------------------------------------------------------------- # # libraries # #------------------------------------------------------------------------- -from __future__ import print_function, with_statement - -import sys -if sys.version_info[0] < 3: - import cPickle as pickle -else: - import pickle +import cPickle import time import random import locale import os -from sys import maxsize +from sys import maxint from ..config import config -if config.get('preferences.use-bsddb3') or sys.version_info[0] >= 3: +if config.get('preferences.use-bsddb3'): from bsddb3 import db else: from bsddb import db @@ -66,6 +60,7 @@ from ..lib.src import Source from ..lib.citation import Citation from ..lib.event import Event from ..lib.place import Place +from ..lib.location import Location from ..lib.repo import Repository from ..lib.note import Note from ..lib.tag import Tag @@ -73,13 +68,12 @@ from ..lib.genderstats import GenderStats from ..lib.researcher import Researcher from ..lib.nameorigintype import NameOriginType -from .dbconst import * +from dbconst import * from ..utils.callback import Callback from ..utils.cast import conv_dbstr_to_unicode from . import (BsddbBaseCursor, DbReadBase) from ..utils.id import create_id from ..errors import DbError -from ..constfunc import UNITYPE, STRTYPE, cuni LOG = logging.getLogger(DBLOGNAME) LOG = logging.getLogger(".citation") @@ -88,10 +82,10 @@ LOG = logging.getLogger(".citation") # constants # #------------------------------------------------------------------------- -from .dbconst import * +from dbconst import * _SIGBASE = ('person', 'family', 'source', 'citation', - 'event', 'media', 'place', 'repository', + 'event', 'media', 'place', 'location', 'repository', 'reference', 'note', 'tag') DBERRS = (db.DBRunRecoveryError, db.DBAccessError, @@ -105,14 +99,12 @@ DBERRS = (db.DBRunRecoveryError, db.DBAccessError, def find_surname(key, data): """ Creating a surname from raw data of a person, to use for sort and index - returns a byte string """ return __index_surname(data[3][5]) def find_surname_name(key, data): """ Creating a surname from raw name, to use for sort and index - returns a byte string """ return __index_surname(data[5]) @@ -120,13 +112,12 @@ def __index_surname(surn_list): """ All non pa/matronymic surnames are used in indexing. pa/matronymic not as they change for every generation! - returns a byte string """ if surn_list: - surn = " ".join([x[0] for x in surn_list if not (x[3][0] in [ + surn = u" ".join([x[0] for x in surn_list if not (x[3][0] in [ NameOriginType.PATRONYMIC, NameOriginType.MATRONYMIC]) ]) else: - surn = "" + surn = u"" return surn.encode('utf-8') @@ -175,6 +166,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 = [None] + while to_do: + data = self.set(str(to_do.pop())) + _n = self.next_dup + while data: + payload = cPickle.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 @@ -324,6 +341,13 @@ class DbBsddbRead(DbReadBase, Callback): "cursor_func": self.get_tag_cursor, "handles_func": self.get_tag_handles, }, + 'Location': + { + "handle_func": self.get_location_from_handle, + "gramps_id_func": None, + "class_func": Location, + "cursor_func": self.get_location_cursor, + }, } self.set_person_id_prefix('I%04d') @@ -378,6 +402,7 @@ class DbBsddbRead(DbReadBase, Callback): self.nid_trans = {} self.eid_trans = {} self.tag_trans = {} + self.loc_trans = {} self.env = None self.person_map = {} self.family_map = {} @@ -436,7 +461,7 @@ class DbBsddbRead(DbReadBase, Callback): def get_table_names(self): """Return a list of valid table names.""" - return list(self._tables.keys()) + return self._tables.keys() def get_table_metadata(self, table_name): """Return the metadata for a valid table name.""" @@ -447,7 +472,7 @@ class DbBsddbRead(DbReadBase, Callback): def get_cursor(self, table, *args, **kwargs): try: return DbReadCursor(table, self.txn) - except DBERRS as msg: + except DBERRS, msg: self.__log_error() raise DbError(msg) @@ -481,6 +506,9 @@ class DbBsddbRead(DbReadBase, Callback): def get_tag_cursor(self, *args, **kwargs): return self.get_cursor(self.tag_map, *args, **kwargs) + def get_location_cursor(self, *args, **kwargs): + return DbBsddbTreeCursor(self.parents, self.txn) + def close(self): """ Close the specified database. @@ -529,18 +557,16 @@ class DbBsddbRead(DbReadBase, Callback): self.emit('repository-rebuild') self.emit('note-rebuild') self.emit('tag-rebuild') + self.emit('location-rebuild') def __find_next_gramps_id(self, prefix, map_index, trans): """ Helper function for find_next__gramps_id methods """ index = prefix % map_index - #in bytes - bindex = index.encode('utf-8') - while trans.get(bindex, txn=self.txn) is not None: + while trans.get(str(index), txn=self.txn) is not None: map_index += 1 index = prefix % map_index - bindex = index.encode('utf-8') map_index += 1 return (map_index, index) @@ -626,9 +652,7 @@ class DbBsddbRead(DbReadBase, Callback): return gid def get_from_handle(self, handle, class_type, data_map): - if isinstance(handle, UNITYPE): - handle = handle.encode('utf-8') - data = data_map.get(handle) + data = data_map.get(str(handle)) if data: newobj = class_type() newobj.unserialize(data) @@ -744,11 +768,17 @@ class DbBsddbRead(DbReadBase, Callback): """ return self.get_from_handle(handle, Tag, self.tag_map) + def get_location_from_handle(self, handle): + """ + Find a Location in the database from the passed handle. + + If no such Location exists, None is returned. + """ + return self.get_from_handle(handle, Location, self.location_map) + def __get_obj_from_gramps_id(self, val, tbl, class_, prim_tbl): - if isinstance(val, UNITYPE): - val = val.encode('utf-8') try: - data = tbl.get(val, txn=self.txn) + data = tbl.get(str(val), txn=self.txn) if data is not None: obj = class_() ### FIXME: this is a dirty hack that works without no @@ -758,12 +788,12 @@ class DbBsddbRead(DbReadBase, Callback): if self.readonly: tuple_data = prim_tbl.get(data, txn=self.txn) else: - tuple_data = pickle.loads(data) + tuple_data = cPickle.loads(data) obj.unserialize(tuple_data) return obj else: return None - except DBERRS as msg: + except DBERRS, msg: self.__log_error() raise DbError(msg) @@ -862,15 +892,17 @@ class DbBsddbRead(DbReadBase, Callback): Return the default grouping name for a surname. Return type is a unicode object """ - if isinstance(surname, UNITYPE): - surname = surname.encode('utf-8') - return conv_dbstr_to_unicode(self.name_group.get(surname, surname)) + if isinstance(surname, unicode): + ssurname = surname.encode('utf-8') + return conv_dbstr_to_unicode(self.name_group.get(ssurname, ssurname)) + else: + return conv_dbstr_to_unicode(self.name_group.get(surname, surname)) def get_name_group_keys(self): """ Return the defined names that have been assigned to a default grouping. """ - return list(map(conv_dbstr_to_unicode, list(self.name_group.keys()))) + return map(conv_dbstr_to_unicode, self.name_group.keys()) def has_name_group_key(self, name): """ @@ -878,9 +910,10 @@ class DbBsddbRead(DbReadBase, Callback): """ # The use of has_key seems allright because there is no write lock # on the name_group table when this is called. - if isinstance(name, UNITYPE): - name = name.encode('utf-8') - return name in self.name_group + if isinstance(name, unicode): + return self.name_group.has_key(name.encode('utf-8')) + else: + return self.name_group.has_key(name) def get_number_of_records(self, table): if not self.db_is_open: @@ -950,6 +983,12 @@ class DbBsddbRead(DbReadBase, Callback): """ return self.get_number_of_records(self.tag_map) + def get_number_of_locations(self): + """ + Return the number of locations currently in the database. + """ + return self.get_number_of_records(self.location_map) + def all_handles(self, table): return table.keys(txn=self.txn) @@ -1074,6 +1113,15 @@ class DbBsddbRead(DbReadBase, Callback): return handle_list return [] + def get_location_handles(self): + """ + Return a list of database handles, one handle for each Location in the + database. + """ + if self.db_is_open: + return self.all_handles(self.location_map) + return [] + def _f(curs_): """ Closure that returns an iterator over handles in the database. @@ -1096,6 +1144,7 @@ class DbBsddbRead(DbReadBase, Callback): iter_repository_handles = _f(get_repository_cursor) iter_note_handles = _f(get_note_cursor) iter_tag_handles = _f(get_tag_cursor) + iter_location_handles = _f(get_location_cursor) del _f def _f(curs_, obj_): @@ -1122,6 +1171,7 @@ class DbBsddbRead(DbReadBase, Callback): iter_repositories = _f(get_repository_cursor, Repository) iter_notes = _f(get_note_cursor, Note) iter_tags = _f(get_tag_cursor, Tag) + iter_locations = _f(get_location_cursor, Location) del _f def get_gramps_ids(self, obj_key): @@ -1138,7 +1188,7 @@ class DbBsddbRead(DbReadBase, Callback): } table = key2table[obj_key] - return list(table.keys()) + return table.keys() def has_gramps_id(self, obj_key, gramps_id): key2table = { @@ -1154,9 +1204,8 @@ class DbBsddbRead(DbReadBase, Callback): } table = key2table[obj_key] - if isinstance(gramps_id, UNITYPE): - gramps_id = gramps_id.encode('utf-8') - return table.get(gramps_id, txn=self.txn) is not None + #return str(gramps_id) in table + return table.get(str(gramps_id), txn=self.txn) is not None def find_initial_person(self): person = self.get_default_person() @@ -1168,7 +1217,7 @@ class DbBsddbRead(DbReadBase, Callback): @staticmethod def _validated_id_prefix(val, default): - if isinstance(val, STRTYPE) and val: + if isinstance(val, basestring) and val: try: str_ = val % 1 except TypeError: # missing conversion specifier @@ -1190,24 +1239,23 @@ class DbBsddbRead(DbReadBase, Callback): pattern_match = re.match(r"(.*)%[0 ](\d+)[diu]$", id_pattern) if pattern_match: str_prefix = pattern_match.group(1) - ##nr_width = pattern_match.group(2) + nr_width = pattern_match.group(2) def closure_func(gramps_id): if gramps_id and gramps_id.startswith(str_prefix): id_number = gramps_id[len(str_prefix):] if id_number.isdigit(): id_value = int(id_number, 10) - ## this code never ran, as an int compared to str with > is False! -## if len(cuni(id_value)) > nr_width: -## # The ID to be imported is too large to fit in the -## # users format. For now just create a new ID, -## # because that is also what happens with IDs that -## # are identical to IDs already in the database. If -## # the problem of colliding import and already -## # present IDs is solved the code here also needs -## # some solution. -## gramps_id = id_pattern % 1 -## else: - gramps_id = id_pattern % id_value + if len(str(id_value)) > nr_width: + # The ID to be imported is too large to fit in the + # users format. For now just create a new ID, + # because that is also what happens with IDs that + # are identical to IDs already in the database. If + # the problem of colliding import and already + # present IDs is solved the code here also needs + # some solution. + gramps_id = id_pattern % 1 + else: + gramps_id = id_pattern % id_value return gramps_id else: def closure_func(gramps_id): @@ -1391,13 +1439,13 @@ class DbBsddbRead(DbReadBase, Callback): if person: return person elif (self.metadata is not None) and (not self.readonly): - self.metadata[b'default'] = None + self.metadata['default'] = None return None def get_default_handle(self): """Return the default Person of the database.""" if self.metadata is not None: - return self.metadata.get(b'default') + return self.metadata.get('default') return None def get_save_path(self): @@ -1513,11 +1561,9 @@ class DbBsddbRead(DbReadBase, Callback): """ Helper method for get_raw__data methods """ - if isinstance(handle, UNITYPE): - handle = handle.encode('utf-8') try: - return table.get(handle, txn=self.txn) - except DBERRS as msg: + return table.get(str(handle), txn=self.txn) + except DBERRS, msg: self.__log_error() raise DbError(msg) @@ -1551,15 +1597,16 @@ class DbBsddbRead(DbReadBase, Callback): def get_raw_tag_data(self, handle): return self.__get_raw_data(self.tag_map, handle) + def get_raw_location_data(self, handle): + return self.__get_raw_data(self.location_map, handle) + def __has_handle(self, table, handle): """ Helper function for has__handle methods """ - if isinstance(handle, UNITYPE): - handle = handle.encode('utf-8') try: - return table.get(handle, txn=self.txn) is not None - except DBERRS as msg: + return table.get(str(handle), txn=self.txn) is not None + except DBERRS, msg: self.__log_error() raise DbError(msg) @@ -1623,94 +1670,68 @@ class DbBsddbRead(DbReadBase, Callback): """ return self.__has_handle(self.tag_map, handle) - def __sortbyperson_key(self, handle): - if isinstance(handle, UNITYPE): - handle = handle.encode('utf-8') - return locale.strxfrm(find_surname(handle, - self.person_map.get(handle))) + def has_location_handle(self, handle): + """ + Return True if the handle exists in the current Location database. + """ + return self.__has_handle(self.location_map, handle) + + def __sortbyperson_key(self, person): + return locale.strxfrm(find_surname(str(person), + self.person_map.get(str(person)))) def __sortbyplace(self, first, second): - if isinstance(first, UNITYPE): - first = first.encode('utf-8') - if isinstance(second, UNITYPE): - second = second.encode('utf-8') - return locale.strcoll(self.place_map.get(first)[2], - self.place_map.get(second)[2]) + return locale.strcoll(self.place_map.get(str(first))[2], + self.place_map.get(str(second))[2]) def __sortbyplace_key(self, place): - if isinstance(place, UNITYPE): - place = place.encode('utf-8') - return locale.strxfrm(self.place_map.get(place)[2]) + return locale.strxfrm(self.place_map.get(str(place))[2]) def __sortbysource(self, first, second): - if isinstance(first, UNITYPE): - first = first.encode('utf-8') - if isinstance(second, UNITYPE): - second = second.encode('utf-8') - source1 = cuni(self.source_map[first][2]) - source2 = cuni(self.source_map[second][2]) + source1 = unicode(self.source_map[str(first)][2]) + source2 = unicode(self.source_map[str(second)][2]) return locale.strcoll(source1, source2) def __sortbysource_key(self, key): - if isinstance(key, UNITYPE): - key = key.encode('utf-8') - source = cuni(self.source_map[key][2]) + source = unicode(self.source_map[str(key)][2]) return locale.strxfrm(source) def __sortbycitation(self, first, second): - if isinstance(first, UNITYPE): - first = first.encode('utf-8') - if isinstance(second, UNITYPE): - second = second.encode('utf-8') - citation1 = cuni(self.citation_map[first][3]) - citation2 = cuni(self.citation_map[second][3]) + citation1 = unicode(self.citation_map[str(first)][3]) + citation2 = unicode(self.citation_map[str(second)][3]) return locale.strcoll(citation1, citation2) def __sortbycitation_key(self, key): - if isinstance(key, UNITYPE): - key = key.encode('utf-8') - citation = cuni(self.citation_map[key][3]) + citation = unicode(self.citation_map[str(key)][3]) return locale.strxfrm(citation) def __sortbymedia(self, first, second): - if isinstance(first, UNITYPE): - first = first.encode('utf-8') - if isinstance(second, UNITYPE): - second = second.encode('utf-8') - media1 = self.media_map[first][4] - media2 = self.media_map[second][4] + media1 = self.media_map[str(first)][4] + media2 = self.media_map[str(second)][4] return locale.strcoll(media1, media2) def __sortbymedia_key(self, key): - if isinstance(key, UNITYPE): - key = key.encode('utf-8') - media = self.media_map[key][4] + media = self.media_map[str(key)][4] return locale.strxfrm(media) def __sortbytag(self, first, second): - if isinstance(first, UNITYPE): - first = first.encode('utf-8') - if isinstance(second, UNITYPE): - second = second.encode('utf-8') - tag1 = self.tag_map[first][1] - tag2 = self.tag_map[second][1] + tag1 = self.tag_map[str(first)][1] + tag2 = self.tag_map[str(second)][1] return locale.strcoll(tag1, tag2) def __sortbytag_key(self, key): - if isinstance(key, UNITYPE): - key = key.encode('utf-8') - tag = self.tag_map[key][1] + tag = self.tag_map[str(key)][1] return locale.strxfrm(tag) def set_mediapath(self, path): """Set the default media path for database, path should be utf-8.""" if (self.metadata is not None) and (not self.readonly): - self.metadata[b'mediapath'] = path + self.metadata['mediapath'] = path def get_mediapath(self): """Return the default media path of the database.""" if self.metadata is not None: - return self.metadata.get(b'mediapath', None) + return self.metadata.get('mediapath', None) return None def find_backlink_handles(self, handle, include_classes=None): @@ -1779,17 +1800,21 @@ class DbBsddbRead(DbReadBase, Callback): 'cursor_func': self.get_tag_cursor, 'class_func': Tag, }, + 'Location': { + 'cursor_func': self.get_location_cursor, + 'class_func': Location, + }, } # Find which tables to iterate over if (include_classes is None): - the_tables = list(primary_tables.keys()) + the_tables = primary_tables.keys() else: the_tables = include_classes # Now we use the functions and classes defined above to loop through # each of the existing primary object tables - for primary_table_name, funcs in the_tables.items(): + for primary_table_name, funcs in the_tables.iteritems(): with funcs['cursor_func']() as cursor: # Grab the real object class here so that the lookup does @@ -1834,7 +1859,7 @@ class DbBsddbRead(DbReadBase, Callback): name_file = open(filepath, "r") name = name_file.read() name_file.close() - except (OSError, IOError) as msg: + except (OSError, IOError), msg: self.__log_error() name = None return name diff --git a/gramps/gen/db/undoredo.py b/gramps/gen/db/undoredo.py index ec5ecd6db..6b117b3e4 100644 --- a/gramps/gen/db/undoredo.py +++ b/gramps/gen/db/undoredo.py @@ -31,18 +31,13 @@ undos and redos. # Standard python modules # #------------------------------------------------------------------------- -from __future__ import print_function, with_statement - +from __future__ import with_statement import time, os -import sys -if sys.version_info[0] < 3: - import cPickle as pickle -else: - import pickle +import cPickle as pickle from collections import deque from ..config import config -if config.get('preferences.use-bsddb3') or sys.version_info[0] >= 3: +if config.get('preferences.use-bsddb3'): from bsddb3 import db else: from bsddb import db @@ -53,7 +48,7 @@ from ..ggettext import gettext as _ # Gramps modules # #------------------------------------------------------------------------- -from .dbconst import * +from dbconst import * from . import BSDDBTxn from ..errors import DbError @@ -65,8 +60,8 @@ from ..errors import DbError DBERRS = (db.DBRunRecoveryError, db.DBAccessError, db.DBPageNotFoundError, db.DBInvalidArgError) -_SIGBASE = ('person', 'family', 'source', 'event', 'media', - 'place', 'repository', 'reference', 'note', 'tag', 'citation') +_SIGBASE = ('person', 'family', 'source', 'event', 'media', 'place', + 'location', 'repository', 'reference', 'note', 'tag', 'citation') #------------------------------------------------------------------------- # # DbUndo class @@ -104,6 +99,7 @@ class DbUndo(object): self.db.note_map, self.db.tag_map, self.db.citation_map, + self.db.location_map, ) def clear(self): @@ -212,7 +208,7 @@ class DbUndo(object): self.db.txn = None return status - except DBERRS as msg: + except DBERRS, msg: self.db._log_error() raise DbError(msg) @@ -305,7 +301,7 @@ class DbUndo(object): else: db_map.put(handle, data, txn=self.txn) - except DBERRS as msg: + except DBERRS, msg: self.db._log_error() raise DbError(msg) @@ -326,7 +322,7 @@ class DbUndo(object): db_map.put(handle, data, txn=self.txn) emit(signal, ([handle],)) - except DBERRS as msg: + except DBERRS, msg: self.db._log_error() raise DbError(msg) @@ -459,7 +455,7 @@ class DbUndoBSDDB(DbUndo): data = cursor.first() while data: yield data - data = next(cursor) + data = cursor.next() def testundo(): class T: @@ -479,35 +475,36 @@ def testundo(): self.place_map = {} self.note_map = {} self.tag_map = {} + self.location_map = {} self.repository_map = {} self.reference_map = {} - print("list tests") + print "list tests" undo = DbUndoList(D()) - print(undo.append('foo')) - print(undo.append('bar')) - print(undo[0]) + print undo.append('foo') + print undo.append('bar') + print undo[0] undo[0] = 'foobar' - print(undo[0]) - print("len", len(undo)) - print("iter") + print undo[0] + print "len", len(undo) + print "iter" for data in undo: - print(data) - print() - print("bsddb tests") + print data + print + print "bsddb tests" undo = DbUndoBSDDB(D(), '/tmp/testundo') undo.open() - print(undo.append('foo')) - print(undo.append('fo2')) - print(undo.append('fo3')) - print(undo[1]) + print undo.append('foo') + print undo.append('fo2') + print undo.append('fo3') + print undo[1] undo[1] = 'bar' - print(undo[1]) + print undo[1] for data in undo: - print(data) - print("len", len(undo)) + print data + print "len", len(undo) - print("test commit") + print "test commit" undo.commit(T(), msg="test commit") undo.close() diff --git a/gramps/gen/db/upgrade.py b/gramps/gen/db/upgrade.py index 9984adc62..593e01f34 100644 --- a/gramps/gen/db/upgrade.py +++ b/gramps/gen/db/upgrade.py @@ -21,9 +21,8 @@ # $Id$ -from __future__ import with_statement, unicode_literals +from __future__ import with_statement -import sys from ..lib.markertype import MarkerType from ..lib.tag import Tag import time @@ -31,23 +30,87 @@ import logging LOG = logging.getLogger(".citation") from ..ggettext import gettext as _ -from ..constfunc import cuni """ methods to upgrade a database from version 13 to current version """ from ..config import config -if config.get('preferences.use-bsddb3') or sys.version_info[0] >= 3: +if config.get('preferences.use-bsddb3'): from bsddb3 import db else: from bsddb import db from . import BSDDBTxn from ..lib.nameorigintype import NameOriginType -from .write import _mkname, SURNAMES -from .dbconst import (PERSON_KEY, FAMILY_KEY, EVENT_KEY, - MEDIA_KEY, PLACE_KEY, REPOSITORY_KEY) +from write import _mkname, SURNAMES +from dbconst import (PERSON_KEY, FAMILY_KEY, EVENT_KEY, + MEDIA_KEY, PLACE_KEY, LOCATION_KEY, REPOSITORY_KEY) from gramps.gui.dialog import (InfoDialog) +def gramps_upgrade_17(self): + self.set_total(len(self.place_map)) + self.children = {None: []} + for handle in self.place_map.keys(): + place = self.place_map[handle] + new_place = list(place) + lat_long = (new_place[4], new_place[3]) + if new_place[5] is not None: + new_place[5] = process_location(self, new_place[5], lat_long) + else: + new_place[5] = process_location(self, None, lat_long) + add_reference(self, handle, new_place[5]) + alt_locs = [] + for alt_loc in new_place[6]: + ref_handle = process_location(self, alt_loc, lat_long) + add_reference(self, handle, ref_handle) + alt_locs.append(ref_handle) + new_place[6] = alt_locs + new_place = tuple(new_place[:3] + new_place[5:]) + with BSDDBTxn(self.env, self.place_map) as txn: + txn.put(str(handle), new_place) + self.update() + + with BSDDBTxn(self.env, self.metadata) as txn: + txn.put('version', 17) + +def add_reference(self, pri_handle, ref_handle): + key = (PLACE_KEY, pri_handle) + data = ((PLACE_KEY, pri_handle), (LOCATION_KEY, ref_handle)) + with BSDDBTxn(self.env, self.reference_map) as txn: + txn.put(str(key), data) + +def process_location(self, loc, lat_long): + if loc is None: + location = ['Unknown'] + else: + # (street, locality, parish, city, county, state, country) + # We need to think about where to put ZIP code and Phone number + location = loc[0][:2] + (loc[1],) + loc[0][2:6] + location = list(location) + location.reverse() + items = [x for x in enumerate(location) if x[1]] + parent = None + for item in items: + parent = match_location(self, parent, item, lat_long) + return parent + +def match_location(self, parent, item, lat_long): + for handle in self.children[parent]: + if self.location_map[handle][2] == item[1]: + return handle + handle = self.create_id() + self.children[handle] = [] + self.children[parent].append(handle) + new_location = (handle, + parent, + item[1], # Name + item[0]+1, # Type + lat_long[0], + lat_long[1], + int(time.time())) + with BSDDBTxn(self.env, self.location_map) as txn: + txn.put(str(handle), new_location) + return handle + def gramps_upgrade_16(self): """Upgrade database from version 15 to 16. This upgrade converts all SourceRef child objects to Citation Primary objects. @@ -148,10 +211,10 @@ def gramps_upgrade_16(self): self.update() LOG.debug("%d persons upgraded with %d citations in %d seconds. " % - (len(list(self.person_map.keys())), + (len(self.person_map.keys()), self.cmap_index - start_num_citations, time.time() - start_time)) - data_upgradeobject[key2data[PERSON_KEY]] = (len(list(self.person_map.keys())), + data_upgradeobject[key2data[PERSON_KEY]] = (len(self.person_map.keys()), self.cmap_index - start_num_citations, time.time() - start_time) @@ -184,7 +247,7 @@ def gramps_upgrade_16(self): LOG.debug("Media upgrade %d citations upgraded in %d seconds" % (self.cmap_index - start_num_citations, int(time.time() - start_time))) - data_upgradeobject[key2data[MEDIA_KEY]] = (len(list(self.media_map.keys())), + data_upgradeobject[key2data[MEDIA_KEY]] = (len(self.media_map.keys()), self.cmap_index - start_num_citations, time.time() - start_time) @@ -195,7 +258,7 @@ def gramps_upgrade_16(self): start_time = time.time() for place_handle in self.place_map.keys(): place = self.place_map[place_handle] - (handle, gramps_id, title, int, lat, + (handle, gramps_id, title, long, lat, main_loc, alt_loc, urls, media_list, source_list, note_list, change, private) = place if source_list: @@ -208,7 +271,7 @@ def gramps_upgrade_16(self): self, media_list) if source_list or media_list: new_place = (handle, gramps_id, title, - int, lat, main_loc, alt_loc, urls, + long, lat, main_loc, alt_loc, urls, media_list, new_citation_list, note_list, change, private) with BSDDBTxn(self.env, self.place_map) as txn: @@ -216,10 +279,10 @@ def gramps_upgrade_16(self): self.update() LOG.debug("%d places upgraded with %d citations in %d seconds. " % - (len(list(self.place_map.keys())), + (len(self.place_map.keys()), self.cmap_index - start_num_citations, time.time() - start_time)) - data_upgradeobject[key2data[PLACE_KEY]] = (len(list(self.place_map.keys())), + data_upgradeobject[key2data[PLACE_KEY]] = (len(self.place_map.keys()), self.cmap_index - start_num_citations, time.time() - start_time) @@ -264,10 +327,10 @@ def gramps_upgrade_16(self): self.update() LOG.debug("%d familys upgraded with %d citations in %d seconds. " % - (len(list(self.family_map.keys())), + (len(self.family_map.keys()), self.cmap_index - start_num_citations, time.time() - start_time)) - data_upgradeobject[key2data[FAMILY_KEY]] = (len(list(self.family_map.keys())), + data_upgradeobject[key2data[FAMILY_KEY]] = (len(self.family_map.keys()), self.cmap_index - start_num_citations, time.time() - start_time) # --------------------------------- @@ -309,10 +372,10 @@ def gramps_upgrade_16(self): LOG.debug("%d events upgraded with %d citations in %d seconds. " "Backlinks took %d seconds" % - (len(list(self.event_map.keys())), + (len(self.event_map.keys()), self.cmap_index - start_num_citations, int(upgrade_time), int(backlink_time))) - data_upgradeobject[key2data[EVENT_KEY]] = (len(list(self.event_map.keys())), + data_upgradeobject[key2data[EVENT_KEY]] = (len(self.event_map.keys()), self.cmap_index - start_num_citations, time.time() - start_time) @@ -336,10 +399,10 @@ def gramps_upgrade_16(self): self.update() LOG.debug("%d repositorys upgraded with %d citations in %d seconds. " % - (len(list(self.repository_map.keys())), + (len(self.repository_map.keys()), self.cmap_index - start_num_citations, time.time() - start_time)) - data_upgradeobject[key2data[REPOSITORY_KEY]] = (len(list(self.repository_map.keys())), + data_upgradeobject[key2data[REPOSITORY_KEY]] = (len(self.repository_map.keys()), self.cmap_index - start_num_citations, time.time() - start_time) # --------------------------------- @@ -594,9 +657,9 @@ def gramps_upgrade_15(self): tags = [tag_handle] else: tags = [] - address_list = list(map(convert_address, address_list)) + address_list = map(convert_address, address_list) new_primary_name = convert_name_15(primary_name) - new_alternate_names = list(map(convert_name_15, alternate_names)) + new_alternate_names = map(convert_name_15, alternate_names) new_person = (junk_handle, # 0 gramps_id, # 1 gender, # 2 @@ -700,7 +763,7 @@ def gramps_upgrade_15(self): new_place = list(place) if new_place[5] is not None: new_place[5] = convert_location(new_place[5]) - new_place[6] = list(map(convert_location, new_place[6])) + new_place[6] = map(convert_location, new_place[6]) new_place = new_place[:12] + new_place[13:] new_place = tuple(new_place) with BSDDBTxn(self.env, self.place_map) as txn: @@ -728,7 +791,7 @@ def gramps_upgrade_15(self): repository = self.repository_map[handle] new_repository = list(repository) new_repository = new_repository[:8] + new_repository[9:] - new_repository[5] = list(map(convert_address, new_repository[5])) + new_repository[5] = map(convert_address, new_repository[5]) new_repository = tuple(new_repository) with BSDDBTxn(self.env, self.repository_map) as txn: txn.put(str(handle), new_repository) @@ -742,7 +805,7 @@ def convert_marker(self, marker_field): """Convert a marker into a tag.""" marker = MarkerType() marker.unserialize(marker_field) - tag_name = cuni(marker) + tag_name = unicode(marker) if tag_name != '': if tag_name not in self.tags: @@ -761,7 +824,7 @@ def convert_marker(self, marker_field): def convert_locbase(loc): """Convert location base to include an empty locality field.""" - return tuple([loc[0], ''] + list(loc[1:])) + return tuple([loc[0], u''] + list(loc[1:])) def convert_location(loc): """Convert a location into the new format.""" @@ -777,26 +840,26 @@ def convert_name_15(name): name_type, prefix, patronymic, group_as, sort_as, display_as, call) = name - connector = "" - origintype = (NameOriginType.NONE, "") - patorigintype = (NameOriginType.PATRONYMIC, "") + connector = u"" + origintype = (NameOriginType.NONE, u"") + patorigintype = (NameOriginType.PATRONYMIC, u"") - if patronymic.strip() == "": + if patronymic.strip() == u"": #no patronymic, create a single surname surname_list = [(surname, prefix, True, origintype, connector)] else: #a patronymic, if no surname or equal as patronymic, a single surname - if (surname.strip() == "") or (surname == patronymic and prefix == ""): + if (surname.strip() == u"") or (surname == patronymic and prefix == u""): surname_list = [(patronymic, prefix, True, patorigintype, connector)] else: #two surnames, first patronymic, then surname which is primary - surname_list = [(patronymic, "", False, patorigintype, ""), + surname_list = [(patronymic, u"", False, patorigintype, u""), (surname, prefix, True, origintype, connector)] #return new value, add two empty strings for nick and family nick return (privacy, source_list, note_list, date, first_name, surname_list, suffix, title, name_type, - group_as, sort_as, display_as, call, "", "") + group_as, sort_as, display_as, call, u"", u"") def gramps_upgrade_14(self): """Upgrade database from version 13 to 14.""" @@ -1004,12 +1067,12 @@ def gramps_upgrade_14(self): # --------------------------------- for place_handle in self.place_map.keys(): place = self.place_map[place_handle] - (handle, gramps_id, title, int, lat, + (handle, gramps_id, title, long, lat, main_loc, alt_loc, urls, media_list, source_list, note_list, change, marker, private) = place new_media_list = new_media_list_14(media_list) new_source_list = new_source_list_14(source_list) - new_place = (handle, gramps_id, title, int, lat, + new_place = (handle, gramps_id, title, long, lat, main_loc, alt_loc, urls, new_media_list, new_source_list, note_list, change, marker, private) diff --git a/gramps/gen/db/write.py b/gramps/gen/db/write.py index c1f2976c0..97ed6bb2d 100644 --- a/gramps/gen/db/write.py +++ b/gramps/gen/db/write.py @@ -32,23 +32,19 @@ This is used since GRAMPS version 3.0 # Standard python modules # #------------------------------------------------------------------------- -from __future__ import print_function, with_statement -import sys -if sys.version_info[0] < 3: - import cPickle as pickle -else: - import pickle +from __future__ import with_statement +import cPickle as pickle import os import time import locale import bisect from functools import wraps import logging -from sys import maxsize +from sys import maxint from ..ggettext import gettext as _ from ..config import config -if config.get('preferences.use-bsddb3') or sys.version_info[0] >= 3: +if config.get('preferences.use-bsddb3'): from bsddb3 import dbshelve, db else: from bsddb import dbshelve, db @@ -64,6 +60,7 @@ from ..lib.src import Source from ..lib.citation import Citation from ..lib.event import Event from ..lib.place import Place +from ..lib.location import Location from ..lib.repo import Repository from ..lib.mediaobj import MediaObject from ..lib.note import Note @@ -75,17 +72,17 @@ from . import (DbBsddbRead, DbWriteBase, BSDDBTxn, DbTxn, BsddbBaseCursor, BsddbDowngradeError, DbVersionError, DbEnvironmentError, DbUpgradeRequiredError, find_surname, find_surname_name, DbUndoBSDDB as DbUndo) -from .dbconst import * +from dbconst import * from ..utils.callback import Callback from ..utils.cast import (conv_unicode_tosrtkey, conv_dbstr_to_unicode) from ..updatecallback import UpdateCallback from ..errors import DbError -from ..constfunc import win, conv_to_unicode, cuni, UNITYPE +from ..constfunc import win _LOG = logging.getLogger(DBLOGNAME) LOG = logging.getLogger(".citation") _MINVERSION = 9 -_DBVERSION = 16 +_DBVERSION = 17 IDTRANS = "person_id" FIDTRANS = "family_id" @@ -97,6 +94,8 @@ NIDTRANS = "note_id" SIDTRANS = "source_id" CIDTRANS = "citation_id" TAGTRANS = "tag_name" +LPARENTS = "location_parent" +LNAMES = "location_name" SURNAMES = "surnames" NAME_GROUP = "name_group" META = "meta_data" @@ -111,6 +110,7 @@ PERSON_TBL = "person" REPO_TBL = "repo" NOTE_TBL = "note" TAG_TBL = "tag" +LOCATION_TBL = "location" REF_MAP = "reference_map" REF_PRI = "primary_map" @@ -134,7 +134,8 @@ CLASS_TO_KEY_MAP = {Person.__name__: PERSON_KEY, Place.__name__: PLACE_KEY, Repository.__name__:REPOSITORY_KEY, Note.__name__: NOTE_KEY, - Tag.__name__: TAG_KEY} + Tag.__name__: TAG_KEY, + Location.__name__: LOCATION_KEY} KEY_TO_CLASS_MAP = {PERSON_KEY: Person.__name__, FAMILY_KEY: Family.__name__, @@ -145,7 +146,8 @@ KEY_TO_CLASS_MAP = {PERSON_KEY: Person.__name__, PLACE_KEY: Place.__name__, REPOSITORY_KEY: Repository.__name__, NOTE_KEY: Note.__name__, - TAG_KEY: Tag.__name__} + TAG_KEY: Tag.__name__, + LOCATION_KEY: Location.__name__} KEY_TO_NAME_MAP = {PERSON_KEY: 'person', FAMILY_KEY: 'family', @@ -157,7 +159,8 @@ KEY_TO_NAME_MAP = {PERSON_KEY: 'person', REPOSITORY_KEY: 'repository', #REFERENCE_KEY: 'reference', NOTE_KEY: 'note', - TAG_KEY: 'tag'} + TAG_KEY: 'tag', + LOCATION_KEY: 'location'} #------------------------------------------------------------------------- # # Helper functions @@ -165,13 +168,13 @@ KEY_TO_NAME_MAP = {PERSON_KEY: 'person', #------------------------------------------------------------------------- def find_idmap(key, data): - """ return id for association of secondary index. - returns a byte string - """ - val = data[1] - if isinstance(val, UNITYPE): - val = val.encode('utf-8') - return val + return str(data[1]) + +def find_parent(key, data): + return str(data[1]) + +def find_name(key, data): + return str(data[2]).upper() # Secondary database key lookups for reference_map table # reference_map data values are of the form: @@ -179,22 +182,10 @@ def find_idmap(key, data): # (referenced_object_class_name, referenced_object_handle)) def find_primary_handle(key, data): - """ return handle for association of indexes - returns byte string - """ - val = (data)[0][1] - if isinstance(val, UNITYPE): - val = val.encode('utf-8') - return val + return str((data)[0][1]) def find_referenced_handle(key, data): - """ return handle for association of indexes - returns byte string - """ - val = (data)[1][1] - if isinstance(val, UNITYPE): - val = val.encode('utf-8') - return val + return str((data)[1][1]) #------------------------------------------------------------------------- # @@ -219,7 +210,7 @@ class DbBsddbAssocCursor(BsddbBaseCursor): BsddbBaseCursor.__init__(self, txn=txn, **kwargs) self.cursor = source.cursor(txn) self.source = source - + #------------------------------------------------------------------------- # # DbBsddb @@ -235,7 +226,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): # 1. Signals for primary objects __signals__ = dict((obj+'-'+op, signal) for obj in - ['person', 'family', 'event', 'place', + ['person', 'family', 'event', 'place', 'location', 'source', 'citation', 'media', 'note', 'repository', 'tag'] for op, signal in zip( ['add', 'update', 'delete', 'rebuild'], @@ -252,11 +243,8 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): # 3. Special signal for change in home person __signals__['home-person-changed'] = None - # 4. Signal for change in person group name, parameters are - if sys.version_info[0] < 3: - __signals__['person-groupname-rebuild'] = (unicode, unicode) - else: - __signals__['person-groupname-rebuild'] = (str, str) + # 4. Signal for change in person group name, parameters are + __signals__['person-groupname-rebuild'] = (unicode, unicode) def __init__(self): """Create a new GrampsDB.""" @@ -280,7 +268,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): def try_(self, *args, **kwargs): try: return func(self, *args, **kwargs) - except DBERRS as msg: + except DBERRS, msg: self.__log_error() raise DbError(msg) return try_ @@ -363,19 +351,30 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): return DbBsddbAssocCursor(self.reference_map_referenced_map, self.txn) + @catch_db_error + def get_location_parent_cursor(self): + """ + Returns a reference to a cursor over the location parents + """ + return DbBsddbAssocCursor(self.parents, self.txn) + + @catch_db_error + def get_location_name_cursor(self): + """ + Returns a reference to a cursor over the location names + """ + return DbBsddbAssocCursor(self.names, self.txn) + # These are overriding the DbBsddbRead's methods of saving metadata # because we now have txn-capable metadata table @catch_db_error def set_default_person_handle(self, handle): """Set the default Person to the passed instance.""" - #we store a byte string! - if isinstance(handle, UNITYPE): - handle = handle.encode('utf-8') if not self.readonly: # Start transaction with BSDDBTxn(self.env, self.metadata) as txn: - txn.put(b'default', handle) + txn.put('default', str(handle)) self.emit('home-person-changed') @catch_db_error @@ -387,7 +386,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): elif (self.metadata) and (not self.readonly): # Start transaction with BSDDBTxn(self.env, self.metadata) as txn: - txn.put(b'default', None) + txn.put('default', None) return None def set_mediapath(self, path): @@ -395,7 +394,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): if self.metadata and not self.readonly: # Start transaction with BSDDBTxn(self.env, self.metadata) as txn: - txn.put(b'mediapath', path) + txn.put('mediapath', path) def __check_bdb_version(self, name): """Older version of Berkeley DB can't read data created by a newer @@ -410,9 +409,6 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): except: # Just assume that the Berkeley DB version is OK. pass - if not env_version: - #empty file, assume it is ok to open - env_version = (0, 0, 0) if (env_version[0] > bdb_version[0]) or \ (env_version[0] == bdb_version[0] and env_version[1] > bdb_version[1]): @@ -423,12 +419,12 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): @catch_db_error def version_supported(self): - dbversion = self.metadata.get(b'version', default=0) + dbversion = self.metadata.get('version', default=0) return ((dbversion <= _DBVERSION) and (dbversion >= _MINVERSION)) @catch_db_error def need_upgrade(self): - dbversion = self.metadata.get(b'version', default=0) + dbversion = self.metadata.get('version', default=0) return not self.readonly and dbversion < _DBVERSION def __check_readonly(self, name): @@ -444,7 +440,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): # See if we lack write access to any files in the directory for base in [FAMILY_TBL, PLACES_TBL, SOURCES_TBL, CITATIONS_TBL, MEDIA_TBL, EVENTS_TBL, PERSON_TBL, REPO_TBL, - NOTE_TBL, REF_MAP, META]: + NOTE_TBL, TAG_TBL, LOCATION_TBL, REF_MAP, META]: path = os.path.join(name, base + DBEXT) if os.path.isfile(path) and not os.access(path, os.W_OK): return True @@ -506,7 +502,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): try: self.env.open(env_name, env_flags) - except Exception as msg: + except Exception, msg: _LOG.warning("Error opening db environment: " + str(msg)) try: self.__close_early() @@ -529,7 +525,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): raise DbVersionError() self.__load_metadata() - gstats = self.metadata.get(b'gender_stats', default=None) + gstats = self.metadata.get('gender_stats', default=None) # Ensure version info in metadata if not self.readonly: @@ -537,12 +533,12 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): with BSDDBTxn(self.env, self.metadata) as txn: if gstats is None: # New database. Set up the current version. - #self.metadata.put(b'version', _DBVERSION, txn=the_txn) - txn.put(b'version', _DBVERSION) - elif b'version' not in self.metadata: + #self.metadata.put('version', _DBVERSION, txn=the_txn) + txn.put('version', _DBVERSION) + elif 'version' not in self.metadata: # Not new database, but the version is missing. # Use 0, but it is likely to fail anyway. - txn.put(b'version', 0) + txn.put('version', 0) self.genderStats = GenderStats(gstats) @@ -558,6 +554,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): ("repository_map", REPO_TBL, db.DB_HASH), ("note_map", NOTE_TBL, db.DB_HASH), ("tag_map", TAG_TBL, db.DB_HASH), + ("location_map", LOCATION_TBL, db.DB_HASH), ("reference_map", REF_MAP, db.DB_BTREE), ] @@ -629,7 +626,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): def __load_metadata(self): # name display formats - self.name_formats = self.metadata.get(b'name_formats', default=[]) + self.name_formats = self.metadata.get('name_formats', default=[]) # upgrade formats if they were saved in the old way for format_ix in range(len(self.name_formats)): format = self.name_formats[format_ix] @@ -639,7 +636,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): # database owner try: - owner_data = self.metadata.get(b'researcher') + owner_data = self.metadata.get('researcher') if owner_data: if len(owner_data[0]) == 7: # Pre-3.3 format owner_data = upgrade_researcher(owner_data) @@ -650,35 +647,35 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): # bookmarks meta = lambda meta: self.metadata.get(meta, default=[]) - self.bookmarks.set(meta(b'bookmarks')) - self.family_bookmarks.set(meta(b'family_bookmarks')) - self.event_bookmarks.set(meta(b'event_bookmarks')) - self.source_bookmarks.set(meta(b'source_bookmarks')) - self.citation_bookmarks.set(meta(b'citation_bookmarks')) - self.repo_bookmarks.set(meta(b'repo_bookmarks')) - self.media_bookmarks.set(meta(b'media_bookmarks')) - self.place_bookmarks.set(meta(b'place_bookmarks')) - self.note_bookmarks.set(meta(b'note_bookmarks')) + self.bookmarks.set(meta('bookmarks')) + self.family_bookmarks.set(meta('family_bookmarks')) + self.event_bookmarks.set(meta('event_bookmarks')) + self.source_bookmarks.set(meta('source_bookmarks')) + self.citation_bookmarks.set(meta('citation_bookmarks')) + self.repo_bookmarks.set(meta('repo_bookmarks')) + self.media_bookmarks.set(meta('media_bookmarks')) + self.place_bookmarks.set(meta('place_bookmarks')) + self.note_bookmarks.set(meta('note_bookmarks')) # Custom type values - self.family_event_names = set(meta(b'fevent_names')) - self.individual_event_names = set(meta(b'pevent_names')) - self.family_attributes = set(meta(b'fattr_names')) - self.individual_attributes = set(meta(b'pattr_names')) - self.marker_names = set(meta(b'marker_names')) - self.child_ref_types = set(meta(b'child_refs')) - self.family_rel_types = set(meta(b'family_rels')) - self.event_role_names = set(meta(b'event_roles')) - self.name_types = set(meta(b'name_types')) - self.origin_types = set(meta(b'origin_types')) - self.repository_types = set(meta(b'repo_types')) - self.note_types = set(meta(b'note_types')) - self.source_media_types = set(meta(b'sm_types')) - self.url_types = set(meta(b'url_types')) - self.media_attributes = set(meta(b'mattr_names')) + self.family_event_names = set(meta('fevent_names')) + self.individual_event_names = set(meta('pevent_names')) + self.family_attributes = set(meta('fattr_names')) + self.individual_attributes = set(meta('pattr_names')) + self.marker_names = set(meta('marker_names')) + self.child_ref_types = set(meta('child_refs')) + self.family_rel_types = set(meta('family_rels')) + self.event_role_names = set(meta('event_roles')) + self.name_types = set(meta('name_types')) + self.origin_types = set(meta('origin_types')) + self.repository_types = set(meta('repo_types')) + self.note_types = set(meta('note_types')) + self.source_media_types = set(meta('sm_types')) + self.url_types = set(meta('url_types')) + self.media_attributes = set(meta('mattr_names')) # surname list - self.surname_list = meta(b'surname_list') + self.surname_list = meta('surname_list') def __connect_secondary(self): """ @@ -706,6 +703,8 @@ 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", LPARENTS, db.DB_HASH, 0), + ("names", LNAMES, db.DB_BTREE, db.DB_DUPSORT), ("reference_map_primary_map", REF_PRI, db.DB_BTREE, 0), ("reference_map_referenced_map", REF_REF, db.DB_BTREE, db.DB_DUPSORT), ] @@ -729,6 +728,8 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): (self.repository_map, self.rid_trans, find_idmap), (self.note_map, self.nid_trans, find_idmap), (self.tag_map, self.tag_trans, find_idmap), + (self.location_map, self.parents, find_parent), + (self.location_map, self.names, find_name), (self.reference_map, self.reference_map_primary_map, find_primary_handle), (self.reference_map, self.reference_map_referenced_map, @@ -770,6 +771,8 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): ( self.nid_trans, NIDTRANS ), ( self.cid_trans, CIDTRANS ), ( self.tag_trans, TAGTRANS ), + ( self.parents, LPARENTS ), + ( self.names, LNAMES ), ( self.reference_map_primary_map, REF_PRI), ( self.reference_map_referenced_map, REF_REF), ] @@ -796,6 +799,60 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): if callback: callback(12) + @catch_db_error + def find_location_child_handles(self, handle): + """ + """ + handle = str(handle) + parent_cur = self.get_location_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.location_map.get(data) + else: + data = pickle.loads(data) + + yield data[0] + ret = parent_cur.next_dup() + + parent_cur.close() + + @catch_db_error + def find_location_from_name(self, name): + """ + """ + name = str(name).upper() + size = len(name) + name_cur = self.get_location_name_cursor() + + try: + ret = name_cur.set_range(name) + ret = name_cur.current() + except: + ret = None + + while (ret is not None): + (key, data) = ret + + if key[:size] != name: + break + + yield data[0] + ret = name_cur.next() + + name_cur.close() + @catch_db_error def find_backlink_handles(self, handle, include_classes=None): """ @@ -814,8 +871,8 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): result_list = list(find_backlink_handles(handle)) """ - if isinstance(handle, UNITYPE): - handle = handle.encode('utf-8') + + handle = str(handle) # Use the secondary index to locate all the reference_map entries # that include a reference to the object we are looking for. referenced_cur = self.get_reference_map_referenced_cursor() @@ -949,34 +1006,25 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): Remove the reference specified by the key, preserving the change in the passed transaction. """ - if isinstance(key, tuple): - #create a string key - key = str(key) - if isinstance(key, UNITYPE): - key = key.encode('utf-8') if not self.readonly: if not transaction.batch: - old_data = self.reference_map.get(key, txn=txn) - transaction.add(REFERENCE_KEY, TXNDEL, key, old_data, None) + old_data = self.reference_map.get(str(key), txn=txn) + transaction.add(REFERENCE_KEY, TXNDEL, str(key), old_data, None) #transaction.reference_del.append(str(key)) - self.reference_map.delete(key, txn=txn) + self.reference_map.delete(str(key), txn=txn) def __add_reference(self, key, data, transaction, txn): """ Add the reference specified by the key and the data, preserving the change in the passed transaction. """ - if isinstance(key, tuple): - #create a string key - key = str(key) - if isinstance(key, UNITYPE): - key = key.encode('utf-8') + if self.readonly or not key: return - self.reference_map.put(key, data, txn=txn) + self.reference_map.put(str(key), data, txn=txn) if not transaction.batch: - transaction.add(REFERENCE_KEY, TXNADD, key, None, data) + transaction.add(REFERENCE_KEY, TXNADD, str(key), None, data) #transaction.reference_add.append((str(key), data)) @catch_db_error @@ -1063,45 +1111,45 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): with BSDDBTxn(self.env, self.metadata) as txn: # name display formats - txn.put(b'name_formats', self.name_formats) + txn.put('name_formats', self.name_formats) # database owner owner_data = self.owner.serialize() - txn.put(b'researcher', owner_data) + txn.put('researcher', owner_data) # bookmarks - txn.put(b'bookmarks', self.bookmarks.get()) - txn.put(b'family_bookmarks', self.family_bookmarks.get()) - txn.put(b'event_bookmarks', self.event_bookmarks.get()) - txn.put(b'source_bookmarks', self.source_bookmarks.get()) - txn.put(b'citation_bookmarks', self.citation_bookmarks.get()) - txn.put(b'place_bookmarks', self.place_bookmarks.get()) - txn.put(b'repo_bookmarks', self.repo_bookmarks.get()) - txn.put(b'media_bookmarks', self.media_bookmarks.get()) - txn.put(b'note_bookmarks', self.note_bookmarks.get()) + txn.put('bookmarks', self.bookmarks.get()) + txn.put('family_bookmarks', self.family_bookmarks.get()) + txn.put('event_bookmarks', self.event_bookmarks.get()) + txn.put('source_bookmarks', self.source_bookmarks.get()) + txn.put('citation_bookmarks', self.citation_bookmarks.get()) + txn.put('place_bookmarks', self.place_bookmarks.get()) + txn.put('repo_bookmarks', self.repo_bookmarks.get()) + txn.put('media_bookmarks', self.media_bookmarks.get()) + txn.put('note_bookmarks', self.note_bookmarks.get()) # gender stats - txn.put(b'gender_stats', self.genderStats.save_stats()) + txn.put('gender_stats', self.genderStats.save_stats()) # Custom type values - txn.put(b'fevent_names', list(self.family_event_names)) - txn.put(b'pevent_names', list(self.individual_event_names)) - txn.put(b'fattr_names', list(self.family_attributes)) - txn.put(b'pattr_names', list(self.individual_attributes)) - txn.put(b'marker_names', list(self.marker_names)) - txn.put(b'child_refs', list(self.child_ref_types)) - txn.put(b'family_rels', list(self.family_rel_types)) - txn.put(b'event_roles', list(self.event_role_names)) - txn.put(b'name_types', list(self.name_types)) - txn.put(b'origin_types', list(self.origin_types)) - txn.put(b'repo_types', list(self.repository_types)) - txn.put(b'note_types', list(self.note_types)) - txn.put(b'sm_types', list(self.source_media_types)) - txn.put(b'url_types', list(self.url_types)) - txn.put(b'mattr_names', list(self.media_attributes)) + txn.put('fevent_names', list(self.family_event_names)) + txn.put('pevent_names', list(self.individual_event_names)) + txn.put('fattr_names', list(self.family_attributes)) + txn.put('pattr_names', list(self.individual_attributes)) + txn.put('marker_names', list(self.marker_names)) + txn.put('child_refs', list(self.child_ref_types)) + txn.put('family_rels', list(self.family_rel_types)) + txn.put('event_roles', list(self.event_role_names)) + txn.put('name_types', list(self.name_types)) + txn.put('origin_types', list(self.origin_types)) + txn.put('repo_types', list(self.repository_types)) + txn.put('note_types', list(self.note_types)) + txn.put('sm_types', list(self.source_media_types)) + txn.put('url_types', list(self.url_types)) + txn.put('mattr_names', list(self.media_attributes)) # name display formats - txn.put(b'surname_list', self.surname_list) + txn.put('surname_list', self.surname_list) self.metadata.close() @@ -1143,6 +1191,8 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.cid_trans.close() self.pid_trans.close() self.tag_trans.close() + self.parents.close() + self.names.close() self.reference_map_primary_map.close() self.reference_map_referenced_map.close() self.reference_map.close() @@ -1160,6 +1210,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.media_map.close() self.event_map.close() self.tag_map.close() + self.location_map.close() self.env.close() self.__close_undodb() @@ -1173,6 +1224,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.media_map = None self.event_map = None self.tag_map = None + self.location_map = None self.surnames = None self.env = None self.metadata = None @@ -1191,6 +1243,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.media_map = None self.event_map = None self.tag_map = None + self.location_map = None self.reference_map_primary_map = None self.reference_map_referenced_map = None self.reference_map = None @@ -1203,14 +1256,9 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): versionpath = os.path.join(self.path, BDBVERSFN) try: with open(versionpath, "w") as version_file: - version = str(db.version()) - if sys.version_info[0] < 3: - if isinstance(version, UNITYPE): - version = version.encode('utf-8') - version_file.write(version) + version_file.write(str(db.version())) except: # Storing the version of Berkeley Db is not really vital. - print ("Error storing berkeley db version") pass try: @@ -1220,7 +1268,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): def create_id(self): return "%08x%08x" % ( int(time.time()*10000), - self.rand.randint(0, maxsize)) + self.rand.randint(0, maxint)) def __add_object(self, obj, transaction, find_next_func, commit_func): if find_next_func and not obj.gramps_id: @@ -1356,12 +1404,18 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): """ return self.__add_object(obj, transaction, None, self.commit_tag) + def add_location(self, obj, transaction): + """ + Add a Location to the database, assigning a handle if it has not already + been defined. + """ + return self.__add_object(obj, transaction, None, self.commit_location) + def __do_remove(self, handle, transaction, data_map, key): if self.readonly or not handle: return - if isinstance(handle, UNITYPE): - handle = handle.encode('utf-8') + handle = str(handle) if transaction.batch: with BSDDBTxn(self.env, data_map) as txn: self.delete_primary_from_reference_map(handle, transaction, @@ -1385,8 +1439,6 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): person = self.get_person_from_handle(handle) self.genderStats.uncount_person (person) self.remove_from_surname_list(person) - if isinstance(handle, UNITYPE): - handle = handle.encode('utf-8') if transaction.batch: with BSDDBTxn(self.env, self.person_map) as txn: self.delete_primary_from_reference_map(handle, transaction, @@ -1395,7 +1447,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): else: self.delete_primary_from_reference_map(handle, transaction, txn=self.txn) - self.person_map.delete(handle, txn=self.txn) + self.person_map.delete(str(handle), txn=self.txn) transaction.add(PERSON_KEY, TXNDEL, handle, person.serialize(), None) def remove_source(self, handle, transaction): @@ -1470,6 +1522,14 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.__do_remove(handle, transaction, self.tag_map, TAG_KEY) + def remove_location(self, handle, transaction): + """ + Remove the Location specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.location_map, + LOCATION_KEY) + @catch_db_error def set_name_group_mapping(self, name, group): if not self.readonly: @@ -1482,7 +1542,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): if group is not None: txn.put(sname, group) if group == None: - grouppar = '' + grouppar = u'' else: grouppar = group self.emit('person-groupname-rebuild', (name, grouppar)) @@ -1508,7 +1568,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): """ if batch_transaction: return - name = conv_to_unicode(find_surname_name(person.handle, + name = unicode(find_surname_name(person.handle, person.get_primary_name().serialize()), 'utf-8') i = bisect.bisect(self.surname_list, name) if 0 < i <= len(self.surname_list): @@ -1528,18 +1588,11 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): """ name = find_surname_name(person.handle, person.get_primary_name().serialize()) - if sys.version_info[0] < 3: - if isinstance(name, unicode): - uname = name - name = str(name) - else: - uname = unicode(name, 'utf-8') + if isinstance(name, unicode): + uname = name + name = str(name) else: - if isinstance(name, str): - uname = name - name = name.encode('utf-8') - else: - uname = str(name) + uname = unicode(name, 'utf-8') try: cursor = self.surnames.cursor(txn=self.txn) cursor_position = cursor.set(name) @@ -1548,7 +1601,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): i = bisect.bisect(self.surname_list, uname) if 0 <= i-1 < len(self.surname_list): del self.surname_list[i-1] - except db.DBError as err: + except db.DBError, err: if str(err) == "(0, 'DB object has been closed')": pass # A batch transaction closes the surnames db table. else: @@ -1566,9 +1619,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): return obj.change = int(change_time or time.time()) - handle = obj.handle - if isinstance(handle, UNITYPE): - handle = handle.encode('utf-8') + handle = str(obj.handle) self.update_reference_map(obj, transaction, self.txn) @@ -1790,11 +1841,17 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.commit_base(tag, self.tag_map, TAG_KEY, transaction, change_time) + def commit_location(self, location, transaction, change_time=None): + """ + Commit the specified Location to the database, storing the changes as + part of the transaction. + """ + self.commit_base(location, self.location_map, LOCATION_KEY, + transaction, change_time) + def get_from_handle(self, handle, class_type, data_map): - if isinstance(handle, UNITYPE): - handle = handle.encode('utf-8') try: - data = data_map.get(handle, txn=self.txn) + data = data_map.get(str(handle), txn=self.txn) except: data = None # under certain circumstances during a database reload, @@ -1879,7 +1936,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.env.log_flush() if not transaction.batch: emit = self.__emit - for obj_type, obj_name in KEY_TO_NAME_MAP.items(): + for obj_type, obj_name in KEY_TO_NAME_MAP.iteritems(): emit(transaction, obj_type, TXNADD, obj_name, '-add') emit(transaction, obj_type, TXNUPD, obj_name, '-update') emit(transaction, obj_type, TXNDEL, obj_name, '-delete') @@ -1926,7 +1983,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): # "while Gtk.events_pending(): Gtk.main_iteration() loop" # (typically used in a progress bar), so emit rebuild signals # to correct that. - object_types = set([x[0] for x in list(transaction.keys())]) + object_types = set([x[0] for x in transaction.keys()]) for object_type in object_types: if object_type == REFERENCE_KEY: continue @@ -1983,11 +2040,11 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): def gramps_upgrade(self, callback=None): UpdateCallback.__init__(self, callback) - version = self.metadata.get(b'version', default=_MINVERSION) + version = self.metadata.get('version', default=_MINVERSION) t = time.time() - from . import upgrade + import upgrade if version < 14: upgrade.gramps_upgrade_14(self) if version < 15: @@ -2007,6 +2064,9 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.__close_undodb() self.db_is_open = False + if version < 17: + self.__connect_secondary() + upgrade.gramps_upgrade_17(self) _LOG.debug("Upgrade time: %d seconds" % int(time.time()-t)) @@ -2060,7 +2120,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.metadata = self.__open_shelf(full_name, META) with BSDDBTxn(self.env, self.metadata) as txn: - txn.put(b'version', _DBVERSION) + txn.put('version', _DBVERSION) self.metadata.close() self.env.close() @@ -2133,14 +2193,13 @@ if __name__ == "__main__": db_name = f.read() if db_name == 'Small Example': break - print("loading", db_path) + print "loading", db_path d.load(db_path, lambda x: x) - print(d.get_default_person()) - out = '' + print d.get_default_person() with d.get_person_cursor() as c: for key, data in c: person = Person(data) - out += key + person.get_primary_name().get_name() + print key, person.get_primary_name().get_name(), - print(out, list(d.surnames.keys())) + print d.surnames.keys() diff --git a/gramps/gen/filters/rules/place/__init__.py b/gramps/gen/filters/rules/place/__init__.py index b893d1b20..648a51d36 100644 --- a/gramps/gen/filters/rules/place/__init__.py +++ b/gramps/gen/filters/rules/place/__init__.py @@ -25,25 +25,25 @@ Package providing filter rules for GRAMPS. """ -from ._allplaces import AllPlaces -from ._hascitation import HasCitation -from ._hasgallery import HasGallery -from ._hasidof import HasIdOf -from ._regexpidof import RegExpIdOf -from ._hasnote import HasNote -from ._hasnoteregexp import HasNoteRegexp -from ._hasnotematchingsubstringof import HasNoteMatchingSubstringOf -from ._hasreferencecountof import HasReferenceCountOf -from ._hassourcecount import HasSourceCount -from ._hassourceof import HasSourceOf -from ._placeprivate import PlacePrivate -from ._matchesfilter import MatchesFilter -from ._hasplace import HasPlace -from ._hasnolatorlon import HasNoLatOrLon -from ._inlatlonneighborhood import InLatLonNeighborhood -from ._matcheseventfilter import MatchesEventFilter -from ._matchessourceconfidence import MatchesSourceConfidence -from ._changedsince import ChangedSince +from _allplaces import AllPlaces +from _hascitation import HasCitation +from _hasgallery import HasGallery +from _hasidof import HasIdOf +from _regexpidof import RegExpIdOf +from _hasnote import HasNote +from _hasnoteregexp import HasNoteRegexp +from _hasnotematchingsubstringof import HasNoteMatchingSubstringOf +from _hasreferencecountof import HasReferenceCountOf +from _hassourcecount import HasSourceCount +from _hassourceof import HasSourceOf +from _placeprivate import PlacePrivate +from _matchesfilter import MatchesFilter +from _haslocation import HasLocation +from _hasnolatorlon import HasNoLatOrLon +from _inlatlonneighborhood import InLatLonNeighborhood +from _matcheseventfilter import MatchesEventFilter +from _matchessourceconfidence import MatchesSourceConfidence +from _changedsince import ChangedSince editor_rule_list = [ AllPlaces, @@ -60,7 +60,7 @@ editor_rule_list = [ PlacePrivate, MatchesFilter, MatchesSourceConfidence, - HasPlace, + HasLocation, HasNoLatOrLon, InLatLonNeighborhood, MatchesEventFilter, diff --git a/gramps/gen/filters/rules/place/_haslocation.py b/gramps/gen/filters/rules/place/_haslocation.py new file mode 100644 index 000000000..cd8859098 --- /dev/null +++ b/gramps/gen/filters/rules/place/_haslocation.py @@ -0,0 +1,63 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2012 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$ + +#------------------------------------------------------------------------- +# +# Standard Python modules +# +#------------------------------------------------------------------------- +from ....ggettext import gettext as _ + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +from .. import Rule + +#------------------------------------------------------------------------- +# +# HasLocation +# +#------------------------------------------------------------------------- +class HasLocation(Rule): + """Rule that checks if a Place is at a specified Location""" + + labels = [ _('Location:') ] + name = _('Place at ') + description = _("Matches places at a specified Location") + category = _('General filters') + + def prepare(self, db): + self.children = [] + to_do = [self.list[0]] + while to_do: + for child in db.find_location_child_handles(to_do.pop()): + to_do.append(child) + self.children.append(child) + + def apply(self, db, obj): + """ + apply the rule on the obj. + return true if the rule passes, false otherwise. + """ + return obj.get_main_location() in self.children diff --git a/gramps/gen/filters/rules/place/_hasnolatorlon.py b/gramps/gen/filters/rules/place/_hasnolatorlon.py index b0c9ee143..fbc17f247 100644 --- a/gramps/gen/filters/rules/place/_hasnolatorlon.py +++ b/gramps/gen/filters/rules/place/_hasnolatorlon.py @@ -50,7 +50,8 @@ class HasNoLatOrLon(Rule): description = _("Matches places with empty latitude or longitude") category = _('Position filters') - def apply(self,db,place): - if place.get_latitude().strip and place.get_longitude().strip() : + def apply(self, db, place): + location = db.get_location_from_handle(place.get_main_location()) + if location.get_latitude().strip and location.get_longitude().strip(): return False return True diff --git a/gramps/gen/filters/rules/place/_inlatlonneighborhood.py b/gramps/gen/filters/rules/place/_inlatlonneighborhood.py index f6aee6e22..880a4ae0d 100644 --- a/gramps/gen/filters/rules/place/_inlatlonneighborhood.py +++ b/gramps/gen/filters/rules/place/_inlatlonneighborhood.py @@ -121,6 +121,10 @@ class InLatLonNeighborhood(Rule): def apply(self,db,place): + location = db.get_location_from_handle(place.get_main_location()) + latitude = location.get_latitude().strip() + longitude = location.get_longitude().strip() + if self.halfheight == -1 and self.halfwidth ==-1 : return False diff --git a/gramps/gen/lib/location.py b/gramps/gen/lib/location.py index 40ca8a009..afa0ad87e 100644 --- a/gramps/gen/lib/location.py +++ b/gramps/gen/lib/location.py @@ -30,16 +30,14 @@ Location class for GRAMPS. # GRAMPS modules # #------------------------------------------------------------------------- -from .secondaryobj import SecondaryObject -from .locationbase import LocationBase -from .const import IDENTICAL, DIFFERENT +from .tableobj import TableObject #------------------------------------------------------------------------- # # Location class for Places # #------------------------------------------------------------------------- -class Location(SecondaryObject, LocationBase): +class Location(TableObject): """ Provide information about a place. @@ -52,17 +50,31 @@ class Location(SecondaryObject, LocationBase): """ Create a Location object, copying from the source object if it exists. """ - LocationBase.__init__(self, source) + TableObject.__init__(self, source) if source: - self.parish = source.parish + self.parent = source.parent + self.name = source.name + self.location_type = source.location_type + self.lat = source.lat + self.long = source.long else: - self.parish = "" + self.parent = None + self.name = '' + self.location_type = 1 # Country + self.lat = '' + self.long = '' def serialize(self): """ Convert the object to a serialized tuple of data. """ - return (LocationBase.serialize(self), self.parish) + return (self.handle, + self.parent, + self.name, + self.location_type, + self.lat, + self.long, + self.change) def to_struct(self): """ @@ -98,9 +110,13 @@ class Location(SecondaryObject, LocationBase): """ Convert a serialized tuple of data to an object. """ - (lbase, self.parish) = data - LocationBase.unserialize(self, lbase) - return self + (self.handle, + self.parent, + self.name, + self.location_type, + self.lat, + self.long, + self.change) = data def get_text_data_list(self): """ @@ -109,42 +125,102 @@ class Location(SecondaryObject, LocationBase): :returns: Returns the list of all textual attributes of the object. :rtype: list """ - return [self.parish] + LocationBase.get_text_data_list(self) - - def is_equivalent(self, other): - """ - Return if this location is equivalent to other. - - :param other: The location to compare this one to. - :rtype other: Location - :returns: Constant inidicating degree of equivalence. - :rtype: int - """ - if self.is_equal(other): - return IDENTICAL - else: - return DIFFERENT - - def merge(self, acquisition): - """ - Merge the content of acquisition into this location. - - Lost: everything of acquisition. - - :param acquisition: The location to merge with the present location. - :rtype acquisition: Location - """ - pass + return [self.name, self.lat, self.long] def is_empty(self): - return not self.street and not self.locality and not self.city and \ - not self.county and not self.state and not self.country and \ - not self.postal and not self.phone - - def set_parish(self, data): - """Set the religious parish name.""" - self.parish = data + """ + Return True if the Location is an empty object (no values set). - def get_parish(self): - """Get the religious parish name.""" - return self.parish + :returns: True if the Location is empty + :rtype: bool + """ + return self.name == '' + + def are_equal(self, other): + """ + Return True if the passed Tag is equivalent to the current Location. + + :param other: Location to compare against + :type other: Location + :returns: True if the Locations are equal + :rtype: bool + """ + if other is None: + other = Location() + + if self.name != other.name or \ + self.parent != other.parent: + return False + return True + + def set_parent(self, parent): + """ + Set the parent of the Location to the passed string. + """ + self.parent = parent + + def get_parent(self): + """ + Return the parent of the Location. + """ + return self.parent + + def set_name(self, name): + """ + Set the name of the Location to the passed string. + """ + self.name = name + + def get_name(self): + """ + Return the name of the Location. + """ + return self.name + + def set_type(self, location_type): + """ + Set the type of the Location to the passed integer. + """ + self.location_type = location_type + + def get_type(self): + """ + Return the type of the Location. + """ + return self.location_type + + def set_latitude(self, latitude): + """ + Set the latitude of the Location object. + + :param latitude: latitude to assign to the Location + :type latitude: str + """ + self.lat = latitude + + def get_latitude(self): + """ + Return the latitude of the Location object. + + :returns: Returns the latitude of the Location + :rtype: str + """ + return self.lat + + def set_longitude(self, longitude): + """ + Set the longitude of the Location object. + + :param longitude: longitude to assign to the Location + :type longitude: str + """ + self.long = longitude + + def get_longitude(self): + """ + Return the longitude of the Location object. + + :returns: Returns the longitude of the Location + :rtype: str + """ + return self.long diff --git a/gramps/gen/lib/place.py b/gramps/gen/lib/place.py index 88b05ba96..9055e8f9d 100644 --- a/gramps/gen/lib/place.py +++ b/gramps/gen/lib/place.py @@ -25,21 +25,18 @@ """ Place object for GRAMPS. """ -from __future__ import unicode_literals #------------------------------------------------------------------------- # # GRAMPS modules # #------------------------------------------------------------------------- -from .primaryobj import PrimaryObject -from .citationbase import CitationBase -from .notebase import NoteBase -from .mediabase import MediaBase -from .urlbase import UrlBase -from .location import Location - -_EMPTY_LOC = Location().serialize() +from primaryobj import PrimaryObject +from citationbase import CitationBase +from notebase import NoteBase +from mediabase import MediaBase +from urlbase import UrlBase +from location import Location #------------------------------------------------------------------------- # @@ -66,14 +63,10 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): MediaBase.__init__(self, source) UrlBase.__init__(self, source) if source: - self.long = source.long - self.lat = source.lat self.title = source.title - self.main_loc = Location(source.main_loc) - self.alt_loc = list(map(Location, source.alt_loc)) + self.main_loc = source.main_loc + self.alt_loc = source.alt_loc else: - self.long = "" - self.lat = "" self.title = "" self.main_loc = None self.alt_loc = [] @@ -96,14 +89,8 @@ 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], + return (self.handle, self.gramps_id, self.title, + self.main_loc, self.alt_loc, UrlBase.serialize(self), MediaBase.serialize(self), CitationBase.serialize(self), @@ -158,15 +145,11 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): Person object :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, + (self.handle, self.gramps_id, self.title, + self.main_loc, self.alt_loc, + urls, media_list, citation_list, note_list, self.change, self.private) = data - if main_loc is None: - self.main_loc = None - else: - self.main_loc = Location().unserialize(main_loc) - self.alt_loc = [Location().unserialize(al) for al in alt_loc] UrlBase.unserialize(self, urls) MediaBase.unserialize(self, media_list) CitationBase.unserialize(self, citation_list) @@ -180,7 +163,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.title, self.gramps_id] def get_text_data_child_list(self): """ @@ -224,6 +207,20 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): """ return self.get_citation_child_list() + def get_referenced_location_handles(self): + """ + Return the list of (classname, handle) tuples for all referenced notes. + + This method should be used to get the :class:`~gen.lib.note.Note` portion of the list + by objects that store note lists. + + :returns: List of (classname, handle) tuples for referenced objects. + :rtype: list + """ + refs = [('Location', self.main_loc)] + refs.extend([('Location', handle) for handle in self.alt_loc]) + return refs + def get_referenced_handles(self): """ Return the list of (classname, handle) tuples for all directly @@ -232,7 +229,8 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): :returns: List of (classname, handle) tuples for referenced objects. :rtype: list """ - return self.get_referenced_note_handles() + \ + return self.get_referenced_location_handles() + \ + self.get_referenced_note_handles() + \ self.get_referenced_citation_handles() def merge(self, acquisition): @@ -266,42 +264,6 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): """ return self.title - def set_longitude(self, longitude): - """ - Set the longitude of the Place object. - - :param longitude: longitude to assign to the Place - :type longitude: str - """ - self.long = longitude - - def get_longitude(self): - """ - Return the longitude of the Place object. - - :returns: Returns the longitude of the Place - :rtype: str - """ - return self.long - - def set_latitude(self, latitude): - """ - Set the latitude of the Place object. - - :param latitude: latitude to assign to the Place - :type latitude: str - """ - self.lat = latitude - - def get_latitude(self): - """ - Return the latitude of the Place object. - - :returns: Returns the latitude of the Place - :rtype: str - """ - return self.lat - def get_main_location(self): """ Return the :class:`~gen.lib.location.Location` object representing the primary information for @@ -313,8 +275,6 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): location information about the Place. :rtype: :class:`~gen.lib.location.Location` """ - if not self.main_loc: - self.main_loc = Location() return self.main_loc def set_main_location(self, location): @@ -382,28 +342,3 @@ class Place(CitationBase, NoteBase, MediaBase, UrlBase, PrimaryObject): 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/gui/dbloader.py b/gramps/gui/dbloader.py index f5aab25b4..b19cb4f92 100644 --- a/gramps/gui/dbloader.py +++ b/gramps/gui/dbloader.py @@ -61,7 +61,6 @@ from gramps.gen.db.exceptions import (DbUpgradeRequiredError, BsddbDowngradeError, DbVersionError, DbEnvironmentError) -from gramps.gen.constfunc import STRTYPE from gramps.gen.utils.file import get_unicode_path_from_file_chooser from .pluginmanager import GuiPluginManager from .dialog import (DBErrorDialog, ErrorDialog, QuestionDialog2, @@ -133,13 +132,11 @@ class DbLoader(CLIDbLoader): pmgr = GuiPluginManager.get_instance() - import_dialog = Gtk.FileChooserDialog(_('Gramps: Import Family Tree'), + import_dialog = Gtk.FileChooserDialog(_('Gramps: Import database'), self.uistate.window, Gtk.FileChooserAction.OPEN, - (Gtk.STOCK_CANCEL, - Gtk.ResponseType.CANCEL, - _('Import'), - Gtk.ResponseType.OK)) + (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, + 'gramps-import', Gtk.ResponseType.OK)) import_dialog.set_local_only(False) # Always add automatic (match all files) filter @@ -208,7 +205,8 @@ class DbLoader(CLIDbLoader): In this process, a warning dialog can pop up. """ - if not isinstance(filename, STRTYPE): + + if not isinstance(filename, basestring): return True filename = os.path.normpath(os.path.abspath(filename)) @@ -251,7 +249,7 @@ class DbLoader(CLIDbLoader): User(callback=self._pulse_progress)) dirname = os.path.dirname(filename) + os.path.sep config.set('paths.recent-import-dir', dirname) - except UnicodeError as msg: + except UnicodeError, msg: ErrorDialog( _("Could not import file: %s") % filename, _("This file incorrectly identifies its character " @@ -268,7 +266,7 @@ class DbLoader(CLIDbLoader): is returned """ if self.import_info is None: - return "" + return u"" return self.import_info.info_text() def read_file(self, filename): @@ -308,12 +306,12 @@ class DbLoader(CLIDbLoader): db.load(filename, self._pulse_progress, mode, upgrade=False) self.dbstate.change_database(db) - except DbUpgradeRequiredError as msg: + except DbUpgradeRequiredError, msg: if QuestionDialog2(_("Need to upgrade database!"), str(msg), _("Upgrade now"), _("Cancel")).run(): - db = gen.db.DbBsddb() + db = DbBsddb() db.disable_signals() db.load(filename, self._pulse_progress, mode, upgrade=True) @@ -321,20 +319,20 @@ class DbLoader(CLIDbLoader): self.dbstate.change_database(db) else: self.dbstate.no_database() - except BsddbDowngradeError as msg: + except BsddbDowngradeError, msg: self.dbstate.no_database() self._errordialog( _("Cannot open database"), str(msg)) - except DbVersionError as msg: + except DbVersionError, msg: self.dbstate.no_database() self._errordialog( _("Cannot open database"), str(msg)) - except DbEnvironmentError as msg: + except DbEnvironmentError, msg: self.dbstate.no_database() self._errordialog( _("Cannot open database"), str(msg)) - except OSError as msg: + except OSError, msg: self.dbstate.no_database() self._errordialog( _("Could not open file: %s") % filename, str(msg)) - except DbError as msg: + except DbError, msg: self.dbstate.no_database() self._dberrordialog(msg) except Exception as newerror: diff --git a/gramps/gui/editors/displaytabs/locationembedlist.py b/gramps/gui/editors/displaytabs/locationembedlist.py index b566d892f..3ed38484a 100644 --- a/gramps/gui/editors/displaytabs/locationembedlist.py +++ b/gramps/gui/editors/displaytabs/locationembedlist.py @@ -36,8 +36,8 @@ from gi.repository import GObject from gramps.gen.lib import Location from gramps.gen.errors import WindowActiveError from ...ddtargets import DdTargets -from .locationmodel import LocationModel -from .embeddedlist import EmbeddedList +from locationmodel import LocationModel +from embeddedlist import EmbeddedList #------------------------------------------------------------------------- # @@ -46,18 +46,13 @@ from .embeddedlist import EmbeddedList #------------------------------------------------------------------------- class LocationEmbedList(EmbeddedList): - _HANDLE_COL = 6 + _HANDLE_COL = 1 _DND_TYPE = DdTargets.LOCATION #index = column in model. Value = # (name, sortcol in model, width, markup/text, weigth_col _column_names = [ - (_('Street'), 0, 150, 0, -1), - (_('Locality'), 1, 100, 0, -1), - (_('City'), 2, 100, 0, -1), - (_('County'), 3, 100, 0, -1), - (_('State'), 4, 100, 0, -1), - (_('Country'), 5, 75, 0, -1), + (_('Location'), 0, 300, 0, -1), ] def __init__(self, dbstate, uistate, track, data): @@ -70,7 +65,7 @@ class LocationEmbedList(EmbeddedList): return self.data def column_order(self): - return ((1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (1, 5)) + return ((1, 0),) def add_button_clicked(self, obj): loc = Location() @@ -81,15 +76,16 @@ class LocationEmbedList(EmbeddedList): except WindowActiveError: pass - def add_callback(self, name): + def add_callback(self, location): data = self.get_data() - data.append(name) + data.append(location.handle) self.rebuild() GObject.idle_add(self.tree.scroll_to_cell, len(data) - 1) def edit_button_clicked(self, obj): - loc = self.get_selected() - if loc: + handle = self.get_selected() + if handle: + loc = self.dbstate.db.get_location_from_handle(handle) try: from .. import EditLocation EditLocation(self.dbstate, self.uistate, self.track, diff --git a/gramps/gui/editors/displaytabs/locationmodel.py b/gramps/gui/editors/displaytabs/locationmodel.py index db687b61b..7295f44bd 100644 --- a/gramps/gui/editors/displaytabs/locationmodel.py +++ b/gramps/gui/editors/displaytabs/locationmodel.py @@ -41,9 +41,19 @@ from gi.repository import Gtk #------------------------------------------------------------------------- class LocationModel(Gtk.ListStore): - def __init__(self, obj_list, db): - Gtk.ListStore.__init__(self, str, str, str, str, str, str, object) + def __init__(self, location_list, db): + Gtk.ListStore.__init__(self, str, object) self.db = db - for obj in obj_list: - self.append(row=[obj.street, obj.locality, obj.city, obj.county, - obj.state, obj.country, obj, ]) + for handle in location_list: + self.__add_location(handle) + + def __add_location(self, handle): + """ + Append a location to the model. + """ + loc = self.db.get_location_from_handle(handle) + lines = [loc.name] + while loc.parent is not None: + loc = self.db.get_location_from_handle(loc.parent) + lines.append(loc.name) + self.append(row=[', '.join(lines), handle]) diff --git a/gramps/gui/editors/editlocation.py b/gramps/gui/editors/editlocation.py index 4c2283b63..075f684d9 100644 --- a/gramps/gui/editors/editlocation.py +++ b/gramps/gui/editors/editlocation.py @@ -27,10 +27,22 @@ # gramps modules # #------------------------------------------------------------------------- -from .editsecondary import EditSecondary +from gramps.gen.db import DbTxn +from editsecondary import EditSecondary from ..glade import Glade -from ..widgets import MonitoredEntry +from gramps.gen.errors import ValidationError +from gramps.gen.utils.place import conv_lat_lon +from ..widgets import MonitoredEntry, LocationEntry from gramps.gen.ggettext import gettext as _ +from ..selectors import SelectorFactory +from ..dialog import ErrorDialog + +SelectLocation = SelectorFactory('Location') + +from gi.repository import Gtk + +LOCATIONTYPES = [_('Country'), _('State'), _('County'), _('City'), + _('Parish'), _('Locality'), _('Street')] #------------------------------------------------------------------------- # @@ -47,72 +59,148 @@ class EditLocation(EditSecondary): self.width_key = 'interface.location-width' self.height_key = 'interface.location-height' self.top = Glade() - self.set_window(self.top.toplevel, None, - _('Location Editor')) + self.set_window(self.top.toplevel, None, _('Location Editor')) def _setup_fields(self): - self.street = MonitoredEntry( - self.top.get_object("street"), - self.obj.set_street, - self.obj.get_street, + self.name = MonitoredEntry(self.top.get_object("entry1"), + self.obj.set_name, + self.obj.get_name, + self.db.readonly) + + self.longitude = MonitoredEntry( + self.top.get_object("lon_entry"), + self.obj.set_longitude, self.obj.get_longitude, self.db.readonly) + self.longitude.connect("validate", self._validate_coordinate, "lon") + #force validation now with initial entry + self.top.get_object("lon_entry").validate(force=True) - self.locality = MonitoredEntry( - self.top.get_object("locality"), - self.obj.set_locality, - self.obj.get_locality, + self.latitude = MonitoredEntry( + self.top.get_object("lat_entry"), + self.obj.set_latitude, self.obj.get_latitude, self.db.readonly) + self.latitude.connect("validate", self._validate_coordinate, "lat") + #force validation now with initial entry + self.top.get_object("lat_entry").validate(force=True) + + self.loc_type = self.top.get_object('combobox1') + active_iter = None + model = Gtk.ListStore(int, str) + for key, value in enumerate(LOCATIONTYPES): + _iter = model.append((key+1, value)) + if key+1 == self.obj.get_type(): + active_iter = _iter + self.loc_type.set_model(model) + cell = Gtk.CellRendererText() + self.loc_type.pack_start(cell, True) + self.loc_type.add_attribute(cell, 'text', 1) + if active_iter is not None: + self.loc_type.set_active_iter(active_iter) - self.city = MonitoredEntry( - self.top.get_object("city"), - self.obj.set_city, - self.obj.get_city, - self.db.readonly) - - self.state = MonitoredEntry( - self.top.get_object("state"), - self.obj.set_state, - self.obj.get_state, - self.db.readonly) - - self.postal = MonitoredEntry( - self.top.get_object("postal"), - self.obj.set_postal_code, - self.obj.get_postal_code, - self.db.readonly) - - self.phone = MonitoredEntry( - self.top.get_object("phone"), - self.obj.set_phone, - self.obj.get_phone, - self.db.readonly) - - self.parish = MonitoredEntry( - self.top.get_object("parish"), - self.obj.set_parish, - self.obj.get_parish, - self.db.readonly) - - self.county = MonitoredEntry( - self.top.get_object("county"), - self.obj.set_county, - self.obj.get_county, - self.db.readonly) - - self.country = MonitoredEntry( - self.top.get_object("country"), - self.obj.set_country, - self.obj.get_country, - self.db.readonly) + self.parent = self.top.get_object('label4') + parent_loc = self.db.get_location_from_handle(self.obj.parent) + if parent_loc: + self.parent.set_text(parent_loc.get_name()) + else: + self.parent.set_text(_('None')) + + button = self.top.get_object('button1') + button.connect('clicked', self.select_parent) + + self.sibling_names = [] + for handle in self.db.find_location_child_handles(self.obj.parent): + location = self.db.get_location_from_handle(handle) + name = location.get_name() + if name != self.obj.get_name(): + self.sibling_names.append(name) + + def _validate_coordinate(self, widget, text, typedeg): + if (typedeg == 'lat') and not conv_lat_lon(text, "0", "ISO-D"): + return ValidationError(_(u"Invalid latitude (syntax: 18\u00b09'") + + _('48.21"S, -18.2412 or -18:9:48.21)')) + elif (typedeg == 'lon') and not conv_lat_lon("0", text, "ISO-D"): + return ValidationError(_(u"Invalid longitude (syntax: 18\u00b09'") + + _('48.21"E, -18.2412 or -18:9:48.21)')) + + def select_parent(self, button): + skip_list = [] + sel = SelectLocation(self.dbstate, self.uistate, self.track) + parent = sel.run() + if parent: + self.parent.set_text(parent.get_name()) + self.obj.parent = parent.get_handle() def _connect_signals(self): + self.ok_button = self.top.get_object('button118') self.define_cancel_button(self.top.get_object('button119')) - self.define_ok_button(self.top.get_object('button118'),self.save) + self.define_ok_button(self.ok_button, self.save) self.define_help_button(self.top.get_object('button128')) def save(self,*obj): + + self.ok_button.set_sensitive(False) + + model = self.loc_type.get_model() + loc_type = model.get_value(self.loc_type.get_active_iter(), 0) + self.obj.set_type(loc_type) + + 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 + + if self.obj.get_name() in self.sibling_names: + msg1 = _("Cannot save location. Name already exists.") + msg2 = _("You have attempted to use a name that is already " + "used at this level in the location hierarchy.'") + ErrorDialog(msg1, msg2) + self.ok_button.set_sensitive(True) + return + + if not self.obj.handle: + with DbTxn(_("Add Location (%s)") % self.obj.get_name(), + self.db) as trans: + self.db.add_location(self.obj, trans) + else: + orig = self.db.get_location_from_handle(self.obj.handle) + if self.obj.parent != orig.parent: + # Location has moved in the tree + for handle in self.db.find_location_child_handles(self.obj.parent): + location = self.db.get_location_from_handle(handle) + name = location.get_name() + if name == self.obj.get_name(): + with DbTxn(_("Merge Location (%s)") % self.obj.get_name(), + self.db) as trans: + self.merge(location, self.obj, trans) + + if cmp(self.obj.serialize(), orig.serialize()): + with DbTxn(_("Edit Location (%s)") % self.obj.get_name(), + self.db) as trans: + self.db.commit_location(self.obj, trans) + if self.callback: self.callback(self.obj) self.close() + def merge(self, location1, location2, trans): + """ + Merge location2 into location1. + """ + children = {} + for handle in self.db.find_location_child_handles(location1.handle): + child = self.db.get_location_from_handle(handle) + children[child.get_name()] = child.handle + for handle in self.db.find_location_child_handles(location2.handle): + child2 = self.db.get_location_from_handle(handle) + if child2.get_name() in children: + handle = children[child2.get_name()] + child1 = self.db.get_location_from_handle(handle) + self.merge(child1, child2, trans) + else: + child2.parent = location1.handle + self.db.commit_location(child2, trans) + + self.db.remove_location(location2.handle, trans) diff --git a/gramps/gui/editors/editplace.py b/gramps/gui/editors/editplace.py index b168c56c4..8898ee04c 100644 --- a/gramps/gui/editors/editplace.py +++ b/gramps/gui/editors/editplace.py @@ -28,8 +28,6 @@ # python modules # #------------------------------------------------------------------------- -from __future__ import unicode_literals - from gramps.gen.ggettext import gettext as _ import logging log = logging.getLogger(".") @@ -48,12 +46,10 @@ 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 editprimary import EditPrimary +from displaytabs import (GrampsTab, LocationEmbedList, CitationEmbedList, GalleryTab, NoteTab, WebEmbedList, PlaceBackRefList) -from ..widgets import MonitoredEntry, PrivacyButton -from gramps.gen.errors import ValidationError -from gramps.gen.utils.place import conv_lat_lon +from ..widgets import MonitoredEntry, PrivacyButton, LocationEntry from ..dialog import ErrorDialog from ..glade import Glade @@ -123,12 +119,12 @@ class EditPlace(EditPrimary): self.set_window(self.top.toplevel, None, self.get_menu_title()) - tblmloc = self.top.get_object('table19') + self.tblmloc = self.top.get_object('loc_table') 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) + _('_Location'), self.tblmloc) self.track_ref_for_deletion("mloc") @@ -154,24 +150,11 @@ 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.gid = MonitoredEntry(self.top.get_object("gid"), self.obj.set_gramps_id, self.obj.get_gramps_id, self.db.readonly) @@ -179,54 +162,6 @@ 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.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.longitude = MonitoredEntry( - self.top.get_object("lon_entry"), - self.obj.set_longitude, self.obj.get_longitude, - self.db.readonly) - self.longitude.connect("validate", self._validate_coordinate, "lon") - #force validation now with initial entry - self.top.get_object("lon_entry").validate(force=True) - - self.latitude = MonitoredEntry( - self.top.get_object("lat_entry"), - self.obj.set_latitude, self.obj.get_latitude, - self.db.readonly) - self.latitude.connect("validate", self._validate_coordinate, "lat") - #force validation now with initial entry - self.top.get_object("lat_entry").validate(force=True) - - def _validate_coordinate(self, widget, text, typedeg): - if (typedeg == 'lat') and not conv_lat_lon(text, "0", "ISO-D"): - return ValidationError(_("Invalid latitude (syntax: 18\u00b09'") + - _('48.21"S, -18.2412 or -18:9:48.21)')) - elif (typedeg == 'lon') and not conv_lat_lon("0", text, "ISO-D"): - return ValidationError(_("Invalid longitude (syntax: 18\u00b09'") + - _('48.21"E, -18.2412 or -18:9:48.21)')) - def build_menu_names(self, place): return (_('Edit Place'), self.get_menu_title()) @@ -240,6 +175,9 @@ class EditPlace(EditPrimary): self._add_tab(notebook, self.mloc) + handle = self.obj.get_main_location() + self.lentry = LocationEntry(self.tblmloc, self.dbstate.db, handle) + self.loc_list = LocationEmbedList(self.dbstate, self.uistate, self.track, @@ -311,6 +249,16 @@ class EditPlace(EditPrimary): self.ok_button.set_sensitive(True) return + handle, new_locations = self.lentry.get_result() + with DbTxn(_('Add location'), self.dbstate.db) as trans: + for loc_type, name in new_locations: + new_location = gen.lib.Location() + new_location.parent = handle + new_location.name = name + new_location.set_type(loc_type) + handle = self.dbstate.db.add_location(new_location, trans) + self.obj.set_main_location(handle) + with DbTxn('', self.db) as trans: if not self.obj.get_handle(): self.db.add_place(self.obj, trans) diff --git a/gramps/gui/editors/filtereditor.py b/gramps/gui/editors/filtereditor.py index 82a3bdf5d..40b9b199b 100644 --- a/gramps/gui/editors/filtereditor.py +++ b/gramps/gui/editors/filtereditor.py @@ -71,7 +71,6 @@ from ..selectors import SelectorFactory from gramps.gen.display.name import displayer as _nd from gramps.gen.utils.db import family_name from gramps.gen.utils.string import confidence -from gramps.gen.constfunc import cuni #------------------------------------------------------------------------- # @@ -107,6 +106,14 @@ _name2typeclass = { _('Surname origin type:'): NameOriginType, } +#------------------------------------------------------------------------- +# +# Sorting function for the filter rules +# +#------------------------------------------------------------------------- +def by_rule_name(f, s): + return cmp(f.name, s.name) + #------------------------------------------------------------------------- # # MyBoolean - check button with standard interface @@ -330,7 +337,7 @@ class MyID(Gtk.Box): self.set_text(val.get_gramps_id()) def get_text(self): - return cuni(self.entry.get_text()) + return unicode(self.entry.get_text()) def name_from_gramps_id(self, gramps_id): if self.namespace == 'Person': @@ -422,6 +429,25 @@ class MyEntry(Gtk.Entry): GObject.GObject.__init__(self) self.show() +#------------------------------------------------------------------------- +# +# MyLocation +# +#------------------------------------------------------------------------- +class MyLocation(Gtk.HBox): + + def __init__(self, dbstate): + Gtk.HBox.__init__(self) + self.location = LocationEntry2(dbstate) + self.pack_start(self.location) + self.show_all() + + def get_text(self): + return self.location.get_handle() + + def set_text(self, handle): + self.location.set_handle(handle) + #------------------------------------------------------------------------- # # EditRule @@ -550,8 +576,10 @@ class EditRule(ManagedWindow): taglist = taglist + [tag.get_name() for tag in dbstate.db.iter_tags()] t = MyList(taglist, taglist) elif v == _('Confidence level:'): - t = MyList(list(map(str, list(range(5)))), + t = MyList(map(str, range(5)), [confidence[i] for i in range(5)]) + elif v == _('Location:'): + t = MyLocation(dbstate) else: t = MyEntry() tlist.append(t) @@ -589,7 +617,7 @@ class EditRule(ManagedWindow): else: self.sel_class = None - keys = sorted(the_map, key=lambda x: x.name, reverse=True) + keys = sorted(the_map, by_rule_name, reverse=True) catlist = sorted(set(class_obj.category for class_obj in keys)) for category in catlist: @@ -614,7 +642,7 @@ class EditRule(ManagedWindow): self.notebook.set_current_page(page) self.display_values(self.active_rule.__class__) (class_obj, vallist, tlist) = self.page[page] - r = list(self.active_rule.values()) + r = self.active_rule.values() for i in range(0, min(len(tlist), len(r))): tlist[i].set_text(r[i]) @@ -701,7 +729,7 @@ class EditRule(ManagedWindow): try: page = self.notebook.get_current_page() (class_obj, vallist, tlist) = self.page[page] - value_list = [cuni(sclass.get_text()) for sclass in tlist] + value_list = [unicode(sclass.get_text()) for sclass in tlist] new_rule = class_obj(value_list) self.update_rule(self.active_rule, new_rule) @@ -783,7 +811,7 @@ class EditFilter(ManagedWindow): self.close() def filter_name_changed(self, obj): - name = cuni(self.fname.get_text()) + name = unicode(self.fname.get_text()) # Make sure that the name is not empty # and not in the list of existing filters (excluding this one) names = [filt.get_name() @@ -806,14 +834,14 @@ class EditFilter(ManagedWindow): self.rlist.add([r.name,r.display_values()],r) def on_ok_clicked(self, obj): - n = cuni(self.fname.get_text()).strip() + n = unicode(self.fname.get_text()).strip() if n == '': return if n != self.filter.get_name(): self.uistate.emit('filter-name-changed', - (self.namespace, cuni(self.filter.get_name()), n)) + (self.namespace,unicode(self.filter.get_name()), n)) self.filter.set_name(n) - self.filter.set_comment(cuni(self.comment.get_text()).strip()) + self.filter.set_comment(unicode(self.comment.get_text()).strip()) for f in self.filterdb.get_filters(self.namespace)[:]: if n == f.get_name(): self.filterdb.get_filters(self.namespace).remove(f) @@ -946,7 +974,7 @@ class ShowResults(ManagedWindow): gid = repo.get_gramps_id() elif self.namespace == 'Note': note = self.db.get_note_from_handle(handle) - name = note.get().replace('\n', ' ') + name = note.get().replace(u'\n', u' ') if len(name) > 80: name = name[:80]+"..." gid = note.get_gramps_id() @@ -1121,7 +1149,7 @@ class FilterEditor(ManagedWindow): # Remove what we found filters = self.filterdb.get_filters(space) - list(map(filters.remove, filter_set)) + map(filters.remove, filter_set) def _find_dependent_filters(self, space, gfilter, filter_set): """ @@ -1137,7 +1165,7 @@ class FilterEditor(ManagedWindow): if the_filter.get_name() == name: continue for rule in the_filter.get_rules(): - values = list(rule.values()) + values = rule.values() if issubclass(rule.__class__, MatchesFilterBase) \ and (name in values): self._find_dependent_filters(space, the_filter, filter_set) @@ -1175,7 +1203,7 @@ class FilterEditor(ManagedWindow): for the_filter in self.filterdb.get_filters(space): for rule in the_filter.get_rules(): - values = list(rule.values()) + values = rule.values() if issubclass(rule.__class__, MatchesFilterBase) \ and (old_name in values): ind = values.index(old_name) @@ -1184,7 +1212,7 @@ class FilterEditor(ManagedWindow): def check_recursive_filters(self, space, name): for the_filter in self.filterdb.get_filters(space): for rule in the_filter.get_rules(): - values = list(rule.values()) + values = rule.values() if issubclass(rule.__class__, MatchesFilterBase) \ and (name in values): return True diff --git a/gramps/gui/filters/sidebar/_placesidebarfilter.py b/gramps/gui/filters/sidebar/_placesidebarfilter.py index f6491edcc..39b885753 100644 --- a/gramps/gui/filters/sidebar/_placesidebarfilter.py +++ b/gramps/gui/filters/sidebar/_placesidebarfilter.py @@ -44,9 +44,8 @@ from gi.repository import Gtk from ... import widgets from .. import build_filter_model from . import SidebarFilter -from gramps.gen.constfunc import cuni from gramps.gen.filters import GenericFilterFactory, rules -from gramps.gen.filters.rules.place import (RegExpIdOf, HasIdOf, HasPlace, +from gramps.gen.filters.rules.place import (RegExpIdOf, HasIdOf, HasLocation, HasNoteRegexp, HasNoteMatchingSubstringOf, MatchesFilter) @@ -63,14 +62,7 @@ class PlaceSidebarFilter(SidebarFilter): self.filter_id = widgets.BasicEntry() self.filter_title = widgets.BasicEntry() - self.filter_street = widgets.BasicEntry() - self.filter_locality = widgets.BasicEntry() - self.filter_city = widgets.BasicEntry() - self.filter_county = widgets.BasicEntry() - self.filter_state = widgets.BasicEntry() - self.filter_country = widgets.BasicEntry() - self.filter_zip = widgets.BasicEntry() - self.filter_parish = widgets.BasicEntry() + self.filter_location = widgets.LocationEntry2(dbstate) self.filter_note = widgets.BasicEntry() self.filter_regex = Gtk.CheckButton(_('Use regular expressions')) @@ -88,49 +80,28 @@ class PlaceSidebarFilter(SidebarFilter): self.add_text_entry(_('ID'), self.filter_id) self.add_text_entry(_('Place Name'), self.filter_title) - self.add_text_entry(_('Street'), self.filter_street) - self.add_text_entry(_('Locality'), self.filter_locality) - self.add_text_entry(_('City'), self.filter_city) - self.add_text_entry(_('County'), self.filter_county) - self.add_text_entry(_('State'), self.filter_state) - self.add_text_entry(_('Country'), self.filter_country) - self.add_text_entry(_('ZIP/Postal code'), self.filter_zip) - self.add_text_entry(_('Church parish'), self.filter_parish) + self.add_text_entry(_('Location'), self.filter_location) self.add_text_entry(_('Note'), self.filter_note) + self.add_filter_entry(_('Custom filter'), self.generic) self.add_regex_entry(self.filter_regex) def clear(self, obj): self.filter_id.set_text('') self.filter_title.set_text('') - self.filter_street.set_text('') - self.filter_locality.set_text('') - self.filter_city.set_text('') - self.filter_county.set_text('') - self.filter_state.set_text('') - self.filter_country.set_text('') - self.filter_zip.set_text('') - self.filter_parish.set_text('') + self.filter_location.set_text('') self.filter_note.set_text('') self.generic.set_active(0) def get_filter(self): - gid = cuni(self.filter_id.get_text()).strip() - title = cuni(self.filter_title.get_text()).strip() - street = cuni(self.filter_street.get_text()).strip() - locality = cuni(self.filter_locality.get_text()).strip() - city = cuni(self.filter_city.get_text()).strip() - county = cuni(self.filter_county.get_text()).strip() - state = cuni(self.filter_state.get_text()).strip() - country = cuni(self.filter_country.get_text()).strip() - zipc = cuni(self.filter_zip.get_text()).strip() - parish = cuni(self.filter_parish.get_text()).strip() - note = cuni(self.filter_note.get_text()).strip() + gid = unicode(self.filter_id.get_text()).strip() + title = unicode(self.filter_title.get_text()).strip() + location = self.filter_location.get_handle() + note = unicode(self.filter_note.get_text()).strip() regex = self.filter_regex.get_active() gen = self.generic.get_active() > 0 - empty = not (gid or title or street or locality or city or county or - state or country or zipc or parish or note or regex or gen) + empty = not (gid or title or location or note or regex or gen) if empty: generic_filter = None else: @@ -141,10 +112,10 @@ class PlaceSidebarFilter(SidebarFilter): else: rule = HasIdOf([gid]) generic_filter.add_rule(rule) - - rule = HasPlace([title, street, locality, city, county, state, - country, zipc, parish], use_regex=regex) - generic_filter.add_rule(rule) + + if location: + rule = HasLocation([location]) + generic_filter.add_rule(rule) if note: if regex: @@ -156,7 +127,7 @@ class PlaceSidebarFilter(SidebarFilter): if self.generic.get_active() != 0: model = self.generic.get_model() node = self.generic.get_active_iter() - obj = cuni(model.get_value(node, 0)) + obj = unicode(model.get_value(node, 0)) rule = MatchesFilter([obj]) generic_filter.add_rule(rule) diff --git a/gramps/gui/glade/editlocation.glade b/gramps/gui/glade/editlocation.glade index 4348058b0..8eb7c60e9 100644 --- a/gramps/gui/glade/editlocation.glade +++ b/gramps/gui/glade/editlocation.glade @@ -17,12 +17,10 @@ gtk-cancel - False True True True True - False True @@ -34,13 +32,11 @@ gtk-ok - False True True True True True - False True @@ -52,12 +48,10 @@ gtk-help - False True True True True - False True @@ -84,7 +78,7 @@ False 12 5 - 4 + 3 12 6 @@ -92,10 +86,9 @@ True False 0 - C_ity: + Parent: True center - city 2 @@ -104,79 +97,28 @@ - - - True - True - The town or city where the place is. - - - - 1 - 2 - 2 - 3 - - - True False 0 - S_treet: + Name: True center - city GTK_FILL - - - True - False - 0 - Ch_urch parish: - True - center - parish - - - 2 - 3 - 1 - 2 - GTK_FILL - - - - - - 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 - Co_unty: + Latitude: True center - county 3 @@ -185,64 +127,14 @@ - - - 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 - Cou_ntry: + Longitude: True center - country 4 @@ -251,106 +143,13 @@ - - - True - True - The country where the place is. - - - - 1 - 2 - 4 - 5 - - - - - - True - False - 0 - _ZIP/Postal code: - True - 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 - True - Lowest level of a place division: eg the street name. - - - - 1 - 4 - - - True False 0 - _Locality: + Type: True - locality 1 @@ -360,17 +159,85 @@ - + True True - A district within, or a settlement near to, a town or city. - + + + + 1 + 3 + GTK_FILL + + + + + True + False + + + 1 + 3 + 1 + 2 + GTK_FILL + + + + + True + False + 0 1 2 - 1 - 2 + 2 + 3 + GTK_FILL + + + + + True + True + + + + 1 + 3 + 3 + 4 + GTK_FILL + + + + + True + True + + + + 1 + 3 + 4 + 5 + GTK_FILL + + + + + ... + True + True + True + + + 2 + 3 + 2 + 3 + diff --git a/gramps/gui/glade/editplace.glade b/gramps/gui/glade/editplace.glade index eb347a985..ed839d499 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,13 +76,10 @@ True False 12 - 3 + 2 4 6 4 - - - True @@ -104,42 +95,6 @@ - - - True - False - 0 - L_atitude: - True - center - lat_entry - - - 1 - 2 - GTK_FILL - - - - - - True - False - 0 - _Longitude: - True - center - lon_entry - - - 2 - 3 - 1 - 2 - GTK_FILL - - - True @@ -163,8 +118,8 @@ gid - 2 - 3 + 1 + 2 GTK_FILL @@ -179,18 +134,16 @@ 1 2 - 2 - 3 + 1 + 2 - False True True True - False none @@ -214,45 +167,14 @@ 2 3 - 2 - 3 + 1 + 2 - - True - True - Latitude (position above the Equator) of the place in decimal or degree notation. -Eg, valid values are 12.0154, 50°52′21.92″N, N50°52′21.92″ or 50:52:21.92 -You can set these values via the Geography View by searching the place, or via a map service in the place view. - - - - 1 - 2 - 1 - 2 - - - - - - True - True - Longitude (position relative to the Prime, or Greenwich, Meridian) of the place in decimal or degree notation. -Eg, valid values are -124.3647, 124°52′21.92″E, E124°52′21.92″ or 124:52:21.92 -You can set these values via the Geography View by searching the place, or via a map service in the place view. - - - - 3 - 4 - 1 - 2 - - + @@ -266,305 +188,17 @@ 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 - - + diff --git a/gramps/gui/selectors/selectlocation.py b/gramps/gui/selectors/selectlocation.py new file mode 100644 index 000000000..73aa06bf9 --- /dev/null +++ b/gramps/gui/selectors/selectlocation.py @@ -0,0 +1,70 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2003-2006 Donald N. Allingham +# Copyright (C) 2009-2010 Gary Burton +# Copyright (C) 2010 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$ + +#------------------------------------------------------------------------- +# +# internationalization +# +#------------------------------------------------------------------------- +from gramps.gen.ggettext import gettext as _ + +#------------------------------------------------------------------------- +# +# gramps modules +# +#------------------------------------------------------------------------- +from ..views.treemodels.locationmodel import LocationTreeModel +from baseselector import BaseSelector + +#------------------------------------------------------------------------- +# +# SelectLocation +# +#------------------------------------------------------------------------- +class SelectLocation(BaseSelector): + + def _local_init(self): + """ + Perform local initialisation for this class + """ + self.width_key = 'interface.place-sel-width' + self.height_key = 'interface.place-sel-height' + + def get_window_title(self): + return _("Select Location") + + def get_model_class(self): + return LocationTreeModel + + def get_column_titles(self): + return [ + (_('Name'), 350, BaseSelector.TEXT, 0), + (_('Type'), 75, BaseSelector.TEXT, 1), + ] + + def get_from_handle_func(self): + return self.db.get_location_from_handle + + def get_handle_column(self): + return LocationTreeModel.HANDLE_COL diff --git a/gramps/gui/selectors/selectorfactory.py b/gramps/gui/selectors/selectorfactory.py index adc92a881..c321af0d6 100644 --- a/gramps/gui/selectors/selectorfactory.py +++ b/gramps/gui/selectors/selectorfactory.py @@ -21,35 +21,38 @@ # $Id$ -from .selectorexceptions import SelectorException +from selectorexceptions import SelectorException def SelectorFactory(classname): if classname == 'Person': - from .selectperson import SelectPerson + from selectperson import SelectPerson cls = SelectPerson elif classname == 'Family': - from .selectfamily import SelectFamily + from selectfamily import SelectFamily cls = SelectFamily elif classname == 'Event': - from .selectevent import SelectEvent + from selectevent import SelectEvent cls = SelectEvent elif classname == 'Place': - from .selectplace import SelectPlace + from selectplace import SelectPlace cls = SelectPlace + elif classname == 'Location': + from selectlocation import SelectLocation + cls = SelectLocation elif classname == 'Source': - from .selectsource import SelectSource + from selectsource import SelectSource cls = SelectSource elif classname == 'Citation': - from .selectcitation import SelectCitation + from selectcitation import SelectCitation cls = SelectCitation elif classname in ['MediaObject', 'Media']: - from .selectobject import SelectObject + from selectobject import SelectObject cls = SelectObject elif classname == 'Repository': - from .selectrepository import SelectRepository + from selectrepository import SelectRepository cls = SelectRepository elif classname == 'Note': - from .selectnote import SelectNote + from selectnote import SelectNote cls = SelectNote else: raise SelectorException("Attempt to create unknown " diff --git a/gramps/gui/selectors/selectplace.py b/gramps/gui/selectors/selectplace.py index 62acf51d7..6b90e4b43 100644 --- a/gramps/gui/selectors/selectplace.py +++ b/gramps/gui/selectors/selectplace.py @@ -35,7 +35,7 @@ from gramps.gen.ggettext import gettext as _ # #------------------------------------------------------------------------- from ..views.treemodels.placemodel import PlaceListModel -from .baseselector import BaseSelector +from baseselector import BaseSelector #------------------------------------------------------------------------- # @@ -59,15 +59,9 @@ class SelectPlace(BaseSelector): 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), + (_('Title'), 350, BaseSelector.TEXT, 0), + (_('ID'), 75, BaseSelector.TEXT, 1), + (_('Location'), 350, BaseSelector.TEXT, 2), ] def get_from_handle_func(self): diff --git a/gramps/gui/views/treemodels/__init__.py b/gramps/gui/views/treemodels/__init__.py index a043d466f..a48986c11 100644 --- a/gramps/gui/views/treemodels/__init__.py +++ b/gramps/gui/views/treemodels/__init__.py @@ -24,14 +24,15 @@ Package init for the treemodels package. """ -from .peoplemodel import PeopleBaseModel, PersonListModel, PersonTreeModel -from .familymodel import FamilyModel -from .eventmodel import EventModel -from .sourcemodel import SourceModel -from .placemodel import PlaceBaseModel, PlaceListModel, PlaceTreeModel -from .mediamodel import MediaModel -from .repomodel import RepositoryModel -from .notemodel import NoteModel -from .citationbasemodel import CitationBaseModel -from .citationlistmodel import CitationListModel -from .citationtreemodel import CitationTreeModel +from peoplemodel import PeopleBaseModel, PersonListModel, PersonTreeModel +from familymodel import FamilyModel +from eventmodel import EventModel +from sourcemodel import SourceModel +from placemodel import PlaceBaseModel, PlaceListModel, PlaceTreeModel +from locationmodel import LocationTreeModel +from mediamodel import MediaModel +from repomodel import RepositoryModel +from notemodel import NoteModel +from citationbasemodel import CitationBaseModel +from citationlistmodel import CitationListModel +from citationtreemodel import CitationTreeModel diff --git a/gramps/gui/views/treemodels/locationmodel.py b/gramps/gui/views/treemodels/locationmodel.py new file mode 100644 index 000000000..42dec59a3 --- /dev/null +++ b/gramps/gui/views/treemodels/locationmodel.py @@ -0,0 +1,252 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2009-2010 Nick Hall +# Copyright (C) 2009 Benny Malengier +# Copyright (C) 2010 Gary Burton +# +# 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 Model. +""" +#------------------------------------------------------------------------- +# +# python modules +# +#------------------------------------------------------------------------- +import cgi +import logging +_LOG = logging.getLogger(".gui.views.treemodels.locationmodel") + +#------------------------------------------------------------------------- +# +# GNOME/GTK modules +# +#------------------------------------------------------------------------- +from gi.repository import Gtk + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +from gramps.gen.datehandler import format_time +from gramps.gen.utils.place import conv_lat_lon +from .flatbasemodel import FlatBaseModel +from .treebasemodel import TreeBaseModel + +#------------------------------------------------------------------------- +# +# internationalization +# +#------------------------------------------------------------------------- +from gramps.gen.ggettext import gettext as _ + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- +LOCATIONTYPES = [_('Country'), _('State'), _('County'), _('City'), + _('Parish'), _('Locality'), _('Street')] + +#------------------------------------------------------------------------- +# +# LocationBaseModel +# +#------------------------------------------------------------------------- +class LocationBaseModel(object): + + HANDLE_COL = 5 + + def __init__(self, db): + self.gen_cursor = db.get_location_cursor + self.map = db.get_raw_location_data + self.fmap = [ + self.column_name, + self.column_type, + self.column_latitude, + self.column_longitude, + self.column_change, + self.column_handle, + ] + self.smap = [ + self.column_name, + self.column_type, + self.sort_latitude, + self.sort_longitude, + self.sort_change, + self.column_handle, + ] + + def destroy(self): + """ + Unset all elements that can prevent garbage collection + """ + self.db = None + self.gen_cursor = None + self.map = None + self.fmap = None + self.smap = None + + def on_get_n_columns(self): + return len(self.fmap)+1 + + def column_handle(self, data): + return unicode(data[0]) + + def column_name(self, data): + return unicode(data[2]) + + def column_type(self, data): + return LOCATIONTYPES[data[3]-1] + + def column_latitude(self, data): + if not data[4]: + return u' ' + value = conv_lat_lon(data[4], '0', format='DEG')[0] + if not value: + return _("Error in format") + return value + + def column_longitude(self, data): + if not data[5]: + return u' ' + value = conv_lat_lon('0', data[5], format='DEG')[1] + if not value: + return _("Error in format") + return value + + def sort_latitude(self, data): + if not data[4]: + return u' ' + value = conv_lat_lon(data[4], '0', format='ISO-DMS') if data[4] else u'' + if not value: + return _("Error in format") + return value + + def sort_longitude(self, data): + if not data[5]: + return u' ' + value = conv_lat_lon('0', data[5], format='ISO-DMS') if data[5] else u'' + if not value: + return _("Error in format") + return value + + def sort_change(self, data): + return "%012x" % data[6] + + def column_change(self, data): + return Utils.format_time(data[6]) + + def column_place_name(self, data): + return unicode(data[2]) + + def sort_place_change(self, data): + return "%012x" % data[9] + + def column_place_change(self, data): + return Utils.format_time(data[9]) + +#------------------------------------------------------------------------- +# +# LocationTreeModel +# +#------------------------------------------------------------------------- +class LocationTreeModel(LocationBaseModel, TreeBaseModel): + """ + Hierarchical place model. + """ + def __init__(self, db, scol=0, order=Gtk.SortType.ASCENDING, search=None, + skip=set(), sort_map=None): + + LocationBaseModel.__init__(self, db) + TreeBaseModel.__init__(self, db, scol=scol, order=order, + tooltip_column=15, + search=search, skip=skip, sort_map=sort_map, + nrgroups = 3, + group_can_have_handle = True, + has_secondary=False) + + def destroy(self): + """ + Unset all elements that can prevent garbage collection + """ + LocationBaseModel.destroy(self) + self.number_items = None + TreeBaseModel.destroy(self) + + def _set_base_data(self): + """See TreeBaseModel, for place, most have been set in init of + PlaceBaseModel + """ + self.number_items = self.db.get_number_of_locations + + self.gen_cursor2 = self.db.get_place_cursor + self.map2 = self.db.get_raw_place_data + self.fmap2 = [ + self.column_place_name, + lambda handle, data: u'', + self.column_place_change, + self.column_handle, + ] + self.smap2 = [ + self.column_place_name, + lambda handle, data: u'', + self.sort_place_change, + self.column_handle, + ] + self.number_items2 = self.db.get_number_of_places + + def get_tree_levels(self): + """ + Return the headings of the levels in the hierarchy. + """ + return [_('Country'), _('State'), _('County'), _('Place')] + + def add_row(self, handle, data): + """ + Add nodes to the node map for a single location. + + handle The handle of the gramps object. + data The object data. + """ + sort_key = self.sort_func(data) + parent = data[1] + + # 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 add_row2(self, handle, data): + """ + Add nodes to the node map for a single place. + + handle The handle of the gramps object. + data The object data. + """ + sort_key = self.sort_func2(data) + parent = data[5] + + if self.get_node(parent): + self.add_node(parent, handle, sort_key, handle, add_parent=False, + secondary=True) diff --git a/gramps/gui/views/treemodels/placemodel.py b/gramps/gui/views/treemodels/placemodel.py index 23167cee1..4a66a9980 100644 --- a/gramps/gui/views/treemodels/placemodel.py +++ b/gramps/gui/views/treemodels/placemodel.py @@ -48,7 +48,6 @@ from gi.repository import Gtk #------------------------------------------------------------------------- from gramps.gen.datehandler import format_time from gramps.gen.utils.place import conv_lat_lon -from gramps.gen.constfunc import cuni from .flatbasemodel import FlatBaseModel from .treebasemodel import TreeBaseModel @@ -76,7 +75,7 @@ COUNTRYLEVELS = { #------------------------------------------------------------------------- class PlaceBaseModel(object): - HANDLE_COL = 14 + HANDLE_COL = 5 def __init__(self, db): self.gen_cursor = db.get_place_cursor @@ -84,16 +83,7 @@ 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_latitude, - self.column_longitude, + self.column_location, self.column_change, self.column_place_name, self.column_handle, @@ -102,16 +92,7 @@ class PlaceBaseModel(object): 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.sort_latitude, - self.sort_longitude, + self.column_location, self.sort_change, self.column_place_name, self.column_handle, @@ -131,102 +112,33 @@ class PlaceBaseModel(object): return len(self.fmap)+1 def column_handle(self, data): - return cuni(data[0]) + return unicode(data[0]) def column_place_name(self, data): - return cuni(data[2]) - - def column_longitude(self, data): - if not data[3]: - return '' - value = conv_lat_lon('0', data[3], format='DEG')[1] - if not value: - return _("Error in format") - return value - - def column_latitude(self, data): - if not data[4]: - return '' - value = conv_lat_lon(data[4], '0', format='DEG')[0] - if not value: - return _("Error in format") - return value - - def sort_longitude(self, data): - if not data[3]: - return '' - value = conv_lat_lon('0', data[3], format='ISO-DMS') if data[3] else '' - if not value: - return _("Error in format") - return value - - def sort_latitude(self, data): - if not data[4]: - return '' - value = conv_lat_lon(data[4], '0', format='ISO-DMS') if data[4] else '' - if not value: - return _("Error in format") - return value + return unicode(data[2]) def column_id(self, data): - return cuni(data[1]) + return unicode(data[1]) - def column_parish(self, data): + def column_location(self, data): try: - return data[5][1] + loc = self.db.get_location_from_handle(data[3]) + lines = [loc.name] + while loc.parent is not None: + loc = self.db.get_location_from_handle(loc.parent) + lines.append(loc.name) + return ', '.join(lines) except: - return '' - - 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 '' + return u'' def sort_change(self, data): - return "%012x" % data[11] + return "%012x" % data[9] def column_change(self, data): - return format_time(data[11]) + return format_time(data[9]) def column_tooltip(self, data): - return cuni('Place tooltip') + return u'Place tooltip' #------------------------------------------------------------------------- # @@ -252,7 +164,7 @@ class PlaceListModel(PlaceBaseModel, FlatBaseModel): FlatBaseModel.destroy(self) def column_name(self, data): - return cuni(data[2]) + return unicode(data[2]) #------------------------------------------------------------------------- # @@ -342,16 +254,16 @@ class PlaceTreeModel(PlaceBaseModel, TreeBaseModel): 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]) + name = unicode(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]) + name = unicode(data[2]) if name: return cgi.escape(name) else: - return "%s" % cgi.escape(_("")) + return u"%s" % cgi.escape(_("")) def column_header(self, node): """ diff --git a/gramps/gui/widgets/__init__.py b/gramps/gui/widgets/__init__.py index 4690aab78..17e418428 100644 --- a/gramps/gui/widgets/__init__.py +++ b/gramps/gui/widgets/__init__.py @@ -23,23 +23,25 @@ """Custom widgets.""" -from .basicentry import * -from .buttons import * -from .expandcollapsearrow import * -from .labels import * -from .linkbox import * -from .photo import * -from .monitoredwidgets import * -from .shortlistcomboentry import * -from .springseparator import * -from .statusbar import Statusbar -from .styledtextbuffer import * -from .styledtexteditor import * -from .toolcomboentry import * -from .undoablebuffer import * -from .undoableentry import * -from .undoablestyledbuffer import * -from .validatedcomboentry import * -from .validatedmaskedentry import * -from .valueaction import * -from .valuetoolitem import * +from basicentry import * +from buttons import * +from expandcollapsearrow import * +from labels import * +from locationentry import * +from locationentry2 import * +from linkbox import * +from photo import * +from monitoredwidgets import * +from shortlistcomboentry import * +from springseparator import * +from statusbar import Statusbar +from styledtextbuffer import * +from styledtexteditor import * +from toolcomboentry import * +from undoablebuffer import * +from undoableentry import * +from undoablestyledbuffer import * +from validatedcomboentry import * +from validatedmaskedentry import * +from valueaction import * +from valuetoolitem import * diff --git a/gramps/gui/widgets/locationentry.py b/gramps/gui/widgets/locationentry.py new file mode 100644 index 000000000..bf8e2012c --- /dev/null +++ b/gramps/gui/widgets/locationentry.py @@ -0,0 +1,196 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2012 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$ +# + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +from gi.repository import Gtk + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from gramps.gen.ggettext import gettext as _ + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- +LOCATIONTYPES = [_('Country'), _('State'), _('County'), _('City'), + _('Parish'), _('Locality'), _('Street')] +MAX_LEVELS = 7 + +#------------------------------------------------------------------------- +# +# LocationEntry class +# +#------------------------------------------------------------------------- +class LocationEntry(object): + """ + Allows the hierarchical entry of a location. + """ + def __init__(self, table, db, handle): + + self.db = db + self.widgets = [] + self.labels = [] + self.types = [-1] * MAX_LEVELS + self.signals = [] + + table.set_col_spacings(10) + + for row in range(MAX_LEVELS): + widget = self.create_widget(table, row) + sig_id = widget.connect('changed', self.changed_cb, row) + self.signals.append(sig_id) + + if handle: + locs = [] + loc = db.get_location_from_handle(handle) + while loc.parent is not None: + locs.append(loc) + loc = db.get_location_from_handle(loc.parent) + locs.append(loc) + locs.reverse() + + for row, loc in enumerate(locs): + self.populate_widget(row, loc.parent, loc.handle) + else: + self.populate_widget(0, None, None) + + def create_widget(self, table, row): + model = Gtk.ListStore(str, str, int) + widget = Gtk.ComboBox.new_with_model_and_entry(model) + widget.set_entry_text_column(1) + widget.set_sensitive(False) + label = Gtk.Label() + label.set_alignment(1, 0.5) + label.show() + table.attach(label, 0, 1, row, row+1, xoptions=Gtk.AttachOptions.FILL, yoptions=0) + self.labels.append(label) + table.attach(widget, 1, 2, row, row+1, yoptions=0) + self.widgets.append(widget) + return widget + + def populate_widget(self, row, parent_handle, default): + widget = self.widgets[row] + model = widget.get_model() + widget.set_model(None) + model.clear() + active_iter = None + children = self.db.find_location_child_handles(str(parent_handle)) + loc_type = None + has_children = False + for handle in children: + child = self.db.get_location_from_handle(handle) + iter_ = model.append((handle, child.name, child.get_type())) + if handle == default: + active_iter = iter_ + loc_type = child.get_type() + has_children = True + model.set_sort_column_id(1, Gtk.SortType.ASCENDING) + widget.set_model(model) + widget.get_child().set_text('') + if active_iter is not None: + widget.set_active_iter(active_iter) + widget.set_sensitive(True) + if has_children: + if loc_type is None: + loc_type = child.get_type() + self.set_label(row, loc_type) + else: + if parent_handle: + parent = self.db.get_location_from_handle(parent_handle) + if parent.get_type() < len(LOCATIONTYPES): + self.set_label(row, parent.get_type() + 1) + else: + self.set_label(row, 1) + + def set_label(self, row, loc_type): + self.types[row] = loc_type + label_text = '%s:' % LOCATIONTYPES[loc_type - 1] + self.labels[row].set_label(label_text) + + def clear_widget(self, row): + widget = self.widgets[row] + widget.get_child().set_text('') + model = widget.get_model() + model.clear() + + def changed_cb(self, widget, row): + self.disable_signals() + if widget.get_active() == -1: + # Text entry + if row+1 < MAX_LEVELS: + self.clear_widget(row+1) + if self.types[row] < len(LOCATIONTYPES): + self.widgets[row+1].set_sensitive(True) + self.set_label(row + 1, self.types[row] + 1) + else: + # New selection + model = widget.get_model() + parent = model.get_value(widget.get_active_iter(), 0) + loc_type = model.get_value(widget.get_active_iter(), 2) + self.set_label(row, loc_type) + if row+1 < MAX_LEVELS: + if self.types[row] < len(LOCATIONTYPES): + self.populate_widget(row+1, parent, None) + else: + self.clear_widget(row+1) + self.widgets[row+1].set_sensitive(False) + self.labels[row+1].set_label('') + + # Clear rows below the active row + for row in range(row+2, MAX_LEVELS): + widget = self.widgets[row] + self.clear_widget(row) + widget.set_sensitive(False) + self.labels[row].set_label('') + + self.enable_signals() + + def enable_signals(self): + for row in range(MAX_LEVELS): + self.widgets[row].handler_unblock(self.signals[row]) + + def disable_signals(self): + for row in range(MAX_LEVELS): + self.widgets[row].handler_block(self.signals[row]) + + def get_result(self): + handle = None + new_locations = [] + for row in range(MAX_LEVELS): + widget = self.widgets[row] + if widget.get_active() == -1: + # New name + new_name = widget.get_child().get_text().strip() + if new_name: + new_locations.append((self.types[row], new_name)) + else: + # Existing location + model = widget.get_model() + handle = model.get_value(widget.get_active_iter(), 0) + return (handle, new_locations) diff --git a/gramps/gui/widgets/locationentry2.py b/gramps/gui/widgets/locationentry2.py new file mode 100644 index 000000000..3082e0eaf --- /dev/null +++ b/gramps/gui/widgets/locationentry2.py @@ -0,0 +1,138 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2012 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$ + +__all__ = ["LocationEntry2"] + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import logging +_LOG = logging.getLogger(".widgets.locationentry2") + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +from gi.repository import Gtk + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- +MIN_CHARACTERS = 3 +MAX_ENTRIES = 20 + +#------------------------------------------------------------------------- +# +# LocationEntry2 class +# +#------------------------------------------------------------------------- +class LocationEntry2(Gtk.Entry): + + def __init__(self, dbstate): + Gtk.Entry.__init__(self) + self.dbstate = dbstate + self.set_width_chars(5) + self.connect('changed', self.changed) + self.connect('focus-out-event', self.lost_focus) + self.show() + + self.handle = None + + self.pwin = Gtk.Window(Gtk.WindowType.POPUP) + self.vbox = Gtk.VBox() + self.pwin.add(self.vbox) + + def get_handle(self): + return self.handle + + def set_handle(handle): + self.set_text(self.get_location_text(handle)) + + def changed(self, widget): + txt = self.get_text() + if len(txt) >= MIN_CHARACTERS: + loc_list = self.get_location_list(txt) + if loc_list: + self.build_menu(loc_list) + else: + self.pwin.hide() + self.handle = None + + def lost_focus(self, widget, event): + self.pwin.hide() + + def build_menu(self, loc_list): + self.pwin.hide() # required to get correct allocation + self.pwin.resize(1, 1) + map(self.vbox.remove, self.vbox.get_children()) + count = 0 + for loc in loc_list: + item = Gtk.Button(loc[1]) + item.set_alignment(0, 0.5) + item.set_relief(Gtk.ReliefStyle.NONE) + item.connect("clicked", self.item_selected, loc[0]) + item.show() + self.vbox.pack_start(item, False, False, 0) + count += 1 + if count >= MAX_ENTRIES: + break + self.pwin.show_all() + + unused, x_pos, y_pos = self.get_window().get_origin() + x_pos += self.get_allocation().x + y_pos += self.get_allocation().y + y_pos += self.get_allocation().height + screen = self.pwin.get_screen() + width = self.pwin.get_allocation().width + height = self.pwin.get_allocation().height + + if x_pos + width > screen.get_width(): + x_pos = screen.get_width() - width + + if y_pos + height > screen.get_height(): + y_pos -= height + self.get_allocation().height + + self.pwin.move(x_pos, y_pos) + + def item_selected(self, menu_item, handle): + self.set_text(menu_item.get_label()) + self.handle = handle + self.pwin.hide() + + def get_location_list(self, txt): + loc_list = [] + for handle in self.dbstate.db.find_location_from_name(txt): + loc_list.append((handle, self.get_location_text(handle))) + return loc_list + + def get_location_text(self, handle): + loc = self.dbstate.db.get_location_from_handle(handle) + lines = [loc.name] + while loc.parent is not None: + loc = self.dbstate.db.get_location_from_handle(loc.parent) + lines.append(loc.name) + return ', '.join(lines) diff --git a/gramps/plugins/gramplet/placedetails.py b/gramps/plugins/gramplet/placedetails.py index 6fc5949c0..d9928f5af 100644 --- a/gramps/plugins/gramplet/placedetails.py +++ b/gramps/plugins/gramplet/placedetails.py @@ -75,7 +75,7 @@ class PlaceDetails(Gramplet): """ Remove all the rows from the table. """ - list(map(self.table.remove, self.table.get_children())) + map(self.table.remove, self.table.get_children()) self.table.resize(1, 2) def db_changed(self): @@ -107,21 +107,27 @@ class PlaceDetails(Gramplet): self.title.set_text(place.get_title()) self.clear_table() - self.display_location(place.get_main_location()) + mloc = place.get_main_location() + self.display_location(mloc) self.display_separator() - lat, lon = conv_lat_lon(place.get_latitude(), - place.get_longitude(), + location = self.dbstate.db.get_location_from_handle(mloc) + lat, lon = conv_lat_lon(location.get_latitude(), + location.get_longitude(), format='DEG') if lat: self.add_row(_('Latitude'), lat) if lon: self.add_row(_('Longitude'), lon) - def display_location(self, location): + def display_location(self, handle): """ Display a location. """ - lines = [line for line in location.get_text_data_list()[:-1] if line] + loc = self.dbstate.db.get_location_from_handle(handle) + lines = [loc.name] + while loc.parent is not None: + loc = self.dbstate.db.get_location_from_handle(loc.parent) + lines.append(loc.name) self.add_row(_('Location'), '\n'.join(lines)) def display_empty(self): diff --git a/gramps/plugins/lib/libmapservice.py b/gramps/plugins/lib/libmapservice.py index 1b0a926b4..d3f2e038c 100644 --- a/gramps/plugins/lib/libmapservice.py +++ b/gramps/plugins/lib/libmapservice.py @@ -83,8 +83,10 @@ class MapService(): """return the lat, lon value of place in the requested format None, None if invalid """ - return conv_lat_lon(place.get_latitude(), - place.get_longitude(), format) + mloc = place.get_main_location() + location = self.database.get_location_from_handle(mloc) + return conv_lat_lon(location.get_latitude(), + location.get_longitude(), format) def calc_url(self): """Base class needs to overwrite this, calculation of the self.path""" diff --git a/gramps/plugins/lib/libplaceview.py b/gramps/plugins/lib/libplaceview.py index db71ad1b6..ea91cc9a1 100644 --- a/gramps/plugins/lib/libplaceview.py +++ b/gramps/plugins/lib/libplaceview.py @@ -31,7 +31,7 @@ Base view for Place Views # Global modules # #------------------------------------------------------------------------- -from __future__ import print_function + #------------------------------------------------------------------------- # @@ -78,44 +78,22 @@ 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_CHAN = 12 + COL_LOCATION = 2 + COL_CHAN = 3 # name of the columns COLUMN_NAMES = [ _('Place Name'), _('ID'), - _('Street'), - _('Locality'), - _('City'), - _('County'), - _('State'), - _('Country'), - _('ZIP/Postal Code'), - _('Church Parish'), - _('Latitude'), - _('Longitude'), + _('Location'), _('Last Changed'), ] # columns that contain markup MARKUP_COLS = [COL_NAME] # 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_CHAN]), - ('columns.size', [250, 75, 150, 150, 150, 150, 100, 100, 100, - 100, 150, 150, 100]) + ('columns.visible', [COL_NAME, COL_ID, COL_LOCATION]), + ('columns.rank', [COL_NAME, COL_ID, COL_LOCATION, COL_CHAN]), + ('columns.size', [250, 75, 350, 100]) ) ADD_MSG = _("Add a new place") EDIT_MSG = _("Edit the selected place") @@ -139,7 +117,7 @@ class PlaceBaseView(ListView): ListView.__init__( self, title, pdata, dbstate, uistate, - self.COLUMN_NAMES, 14, + self.COLUMN_NAMES, 5, model, signal_map, dbstate.db.get_place_bookmarks(), PlaceBookmarks, nav_group, @@ -150,7 +128,7 @@ class PlaceBaseView(ListView): 'J' : self.jump, 'BackSpace' : self.key_delete, }) - self.maptoolbtn = None + self.additional_uis.append(self.additional_ui()) def navigation_type(self): @@ -161,6 +139,11 @@ class PlaceBaseView(ListView): def define_actions(self): ListView.define_actions(self) + self._add_toolmenu_action('MapsList', _('Loading...'), + _("Attempt to see selected locations with a Map " + "Service (OpenstreetMap, Google Maps, ...)"), + self.gotomap, + _('Select a Map Service')) self._add_action('GotoMap', Gtk.STOCK_JUMP_TO, _('_Look up with Map Service'), callback=self.gotomap, @@ -170,13 +153,6 @@ class PlaceBaseView(ListView): callback=self.filter_editor) self._add_action('QuickReport', None, _("Quick View"), None, None, None) - def set_inactive(self): - """called by viewmanager when moving away from the page - Here we need to remove the menutoolbutton from the menu - """ - tb = self.uistate.viewmanager.uimanager.get_widget('/ToolBar') - tb.remove(self.maptoolbtn) - def change_page(self): """ Called by viewmanager at end of realization when arriving on the page @@ -188,18 +164,11 @@ class PlaceBaseView(ListView): 5. store label so it can be changed when selection changes """ ListView.change_page(self) - #menutoolbutton has to be made and added in correct place on toolbar - if not self.maptoolbtn: - self.maptoolbtn = Gtk.MenuToolButton.new_from_stock(Gtk.STOCK_JUMP_TO) - self.maptoolbtn.connect('clicked', self.gotomap) - self.mmenu = self.__create_maps_menu_actions() - self.maptoolbtn.set_menu(self.mmenu) - self.maptoolbtn.show() - tb = self.uistate.viewmanager.uimanager.get_widget('/ToolBar') - ind = tb.get_item_index(self.uistate.viewmanager.uimanager.get_widget( - '/ToolBar/CommonEdit/Merge')) - tb.insert(self.maptoolbtn, ind+1) - widget = self.maptoolbtn + #menutoolbutton actions are stored in PageView class, + # obtain the widgets where we need to add to menu + actionservices = self.action_toolmenu['MapsList'] + widgets = actionservices.get_proxies() + mmenu = self.__create_maps_menu_actions() if not self.mapservicedata: return @@ -207,19 +176,18 @@ class PlaceBaseView(ListView): self.mapslistlabel = [] if not self.mapservice in self.mapservicedata: #stored val no longer exists, use the first key instead - self.set_mapservice(list(self.mapservicedata.keys())[0]) + self.set_mapservice(self.mapservicedata.keys()[0]) - #store all gtk labels to be able to update label on selection change_('Loading...'), - widget.set_menu(self.mmenu) - widget.set_arrow_tooltip_text(_('Select a Map Service')) - widget.set_tooltip_text( - _("Attempt to see selected locations with a Map " - "Service (OpenstreetMap, Google Maps, ...)")) - lbl = Gtk.Label(label=self.mapservice_label()) - lbl.show() - self.mapslistlabel.append(lbl) - widget.set_label_widget(self.mapslistlabel[-1]) - widget.set_stock_id(Gtk.STOCK_JUMP_TO) + #store all gtk labels to be able to update label on selection change + for widget in widgets : + if isinstance(widget, Gtk.MenuToolButton): + widget.set_menu(mmenu) + widget.set_arrow_tooltip_text(actionservices.arrowtooltip) + lbl = Gtk.Label(label=self.mapservice_label()) + lbl.show() + self.mapslistlabel.append(lbl) + widget.set_label_widget(self.mapslistlabel[-1]) + widget.set_stock_id(Gtk.STOCK_JUMP_TO) if self.drag_info(): self.list.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, [], @@ -228,7 +196,7 @@ class PlaceBaseView(ListView): tglist.add(self.drag_info().atom_drag_type, self.drag_info().target_flags, self.drag_info().app_id) - tglist.add_text_targets (0) + tglist.add_text_targets (0L) self.list.drag_source_set_target_list(tglist) def __create_maps_menu_actions(self): @@ -301,7 +269,7 @@ class PlaceBaseView(ListView): servfunc = eval('mod.' + serv.mapservice) servfunc()(self.dbstate.db, places) else: - print('Failed to load map plugin, see Plugin Manager') + print 'Failed to load map plugin, see Plugin Manager' def drag_info(self): return DdTargets.PLACE_LINK @@ -351,6 +319,7 @@ class PlaceBaseView(ListView): + diff --git a/gramps/plugins/view/geoevents.py b/gramps/plugins/view/geoevents.py index d474419f0..c0dac2aee 100644 --- a/gramps/plugins/view/geoevents.py +++ b/gramps/plugins/view/geoevents.py @@ -33,6 +33,7 @@ Geography for events from gramps.gen.ggettext import gettext as _ import os import sys +import urlparse import operator import locale from gi.repository import Gdk @@ -207,8 +208,10 @@ class GeoEvents(GeoGraphyView): place = dbstate.db.get_place_from_handle(place_handle) if place: descr1 = place.get_title() - longitude = place.get_longitude() - latitude = place.get_latitude() + mloc = place.get_main_location() + location = self.dbstate.db.get_location_from_handle(mloc) + longitude = location.get_longitude() + latitude = location.get_latitude() latitude, longitude = conv_lat_lon(latitude, longitude, "D.D8") # place.get_longitude and place.get_latitude return # one string. We have coordinates when the two values diff --git a/gramps/plugins/view/geofamily.py b/gramps/plugins/view/geofamily.py index d63d82c99..f7f91662e 100644 --- a/gramps/plugins/view/geofamily.py +++ b/gramps/plugins/view/geofamily.py @@ -33,6 +33,7 @@ Geography for one family from gramps.gen.ggettext import gettext as _ import os import sys +import urlparse import operator import locale from gi.repository import Gdk @@ -202,8 +203,10 @@ class GeoFamily(GeoGraphyView): if place_handle: place = dbstate.db.get_place_from_handle(place_handle) if place: - longitude = place.get_longitude() - latitude = place.get_latitude() + mloc = place.get_main_location() + location = self.dbstate.db.get_location_from_handle(mloc) + longitude = location.get_longitude() + latitude = location.get_latitude() latitude, longitude = conv_lat_lon(latitude, longitude, "D.D8") descr = place.get_title() diff --git a/gramps/plugins/view/geoperson.py b/gramps/plugins/view/geoperson.py index 60d9fbb9f..dd58c55b1 100644 --- a/gramps/plugins/view/geoperson.py +++ b/gramps/plugins/view/geoperson.py @@ -33,6 +33,7 @@ Geography for one person from gramps.gen.ggettext import gettext as _ import os import sys +import urlparse import operator import locale from gi.repository import Gdk @@ -318,8 +319,10 @@ class GeoPerson(GeoGraphyView): if place_handle: place = dbstate.db.get_place_from_handle(place_handle) if place: - longitude = place.get_longitude() - latitude = place.get_latitude() + mloc = place.get_main_location() + location = self.dbstate.db.get_location_from_handle(mloc) + longitude = location.get_longitude() + latitude = location.get_latitude() latitude, longitude = conv_lat_lon(latitude, longitude, "D.D8") descr = place.get_title() diff --git a/gramps/plugins/view/geoplaces.py b/gramps/plugins/view/geoplaces.py index 74497655d..2557ad2c1 100644 --- a/gramps/plugins/view/geoplaces.py +++ b/gramps/plugins/view/geoplaces.py @@ -34,6 +34,7 @@ from gramps.gen.ggettext import gettext as _ import os import sys import time +import urlparse import operator import locale from gi.repository import Gdk @@ -201,8 +202,10 @@ class GeoPlaces(GeoGraphyView): if self.nbplaces >= self._config.get("geography.max_places"): return descr = place.get_title() - longitude = place.get_longitude() - latitude = place.get_latitude() + mloc = place.get_main_location() + location = self.dbstate.db.get_location_from_handle(mloc) + longitude = location.get_longitude() + latitude = location.get_latitude() latitude, longitude = conv_lat_lon(latitude, longitude, "D.D8") # place.get_longitude and place.get_latitude return # one string. We have coordinates when the two values diff --git a/gramps/plugins/view/locationview.py b/gramps/plugins/view/locationview.py new file mode 100644 index 000000000..ee07a41fa --- /dev/null +++ b/gramps/plugins/view/locationview.py @@ -0,0 +1,281 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2001-2007 Donald N. Allingham +# Copyright (C) 2008 Gary Burton +# Copyright (C) 2011 Tim G L Lyons +# +# 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 location view. +""" + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +from gramps.gen.ggettext import gettext as _ +import logging +_LOG = logging.getLogger(".plugins.locationview") + +#------------------------------------------------------------------------- +# +# gramps modules +# +#------------------------------------------------------------------------- +from gramps.gen.lib import Location +from gramps.gen.db import DbTxn +from gramps.gui.views.listview import ListView +from gramps.gui.views.treemodels import LocationTreeModel +from gramps.gen.errors import WindowActiveError +from gramps.gui.views.bookmarks import PlaceBookmarks +from gramps.gui.ddtargets import DdTargets +from gramps.gui.dialog import ErrorDialog +from gramps.gui.editors import EditLocation +from gramps.gen.plug import CATEGORY_QR_PLACE + +#------------------------------------------------------------------------- +# +# LocationView +# +#------------------------------------------------------------------------- +class LocationView(ListView): + """ + LocationView class, derived from the ListView + """ + # columns in the model used in view + COL_NAME = 0 + COL_TYPE = 1 + COL_LAT = 2 + COL_LON = 3 + COL_CHAN = 4 + # name of the columns + COLUMN_NAMES = [ + _('Name'), + _('Type'), + _('Latitude'), + _('Longitude'), + _('Last Changed'), + ] + # columns that contain markup + #MARKUP_COLS = [COL_DATE] + # default setting with visible columns, order of the col, and their size + CONFIGSETTINGS = ( + ('columns.visible', [COL_NAME, COL_TYPE]), + ('columns.rank', [COL_NAME, COL_TYPE, COL_LAT, COL_LON, COL_CHAN]), + ('columns.size', [400, 100, 150, 150, 100]) + ) + ADD_MSG = _("Add a new location") + EDIT_MSG = _("Edit the selected location") + DEL_MSG = _("Delete the selected location") + MERGE_MSG = _("Merge the selected locations") + FILTER_TYPE = "Place" + QR_CATEGORY = CATEGORY_QR_PLACE + + def __init__(self, pdata, dbstate, uistate, nav_group=0): + """ + Create the Location View + """ + signal_map = { + 'location-add' : self.row_add, + 'location-update' : self.row_update, + 'location-delete' : self.row_delete, + 'location-rebuild' : self.object_build, + } + + ListView.__init__( + self, _('Locations'), pdata, dbstate, uistate, + LocationView.COLUMN_NAMES, len(LocationView.COLUMN_NAMES), + LocationTreeModel, + signal_map, dbstate.db.get_place_bookmarks(), + PlaceBookmarks, nav_group, + multiple=True) + + self.func_list.update({ + 'J' : self.jump, + 'BackSpace' : self.key_delete, + }) + + self.additional_uis.append(self.additional_ui()) + + def navigation_type(self): + return 'Place' + + def get_bookmarks(self): + """ + Return the bookmark object + """ + return self.dbstate.db.get_place_bookmarks() + + def drag_info(self): + """ + Indicate that the drag type is a PLACE_LINK + """ + return DdTargets.PLACE_LINK + + def get_stock(self): + """ + Use the gramps-place stock icon + """ + return 'gramps-place' + + def additional_ui(self): + """ + Defines the UI string for UIManager + """ + return ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ''' + + def define_actions(self): + ListView.define_actions(self) + self._add_action('QuickReport', None, + _("Quick View"), None, None, None) + self._add_action('Dummy', None, + ' ', None, None, self.dummy_report) + + self._add_action('OpenBranch', None, _("Expand this Entire Group"), + callback=self.open_branch) + self._add_action('CloseBranch', None, _("Collapse this Entire Group"), + callback=self.close_branch) + self._add_action('OpenAllNodes', None, _("Expand all Nodes"), + callback=self.open_all_nodes) + self._add_action('CloseAllNodes', None, _("Collapse all Nodes"), + callback=self.close_all_nodes) + + def get_handle_from_gramps_id(self, gid): + obj = self.dbstate.db.get_place_from_gramps_id(gid) + if obj: + return obj.get_handle() + else: + return None + + def add(self, obj): + loc = Location() + selected = self.selected_handles() + if len(selected) == 1: + loc.parent = selected[0] + parent_loc = self.dbstate.db.get_location_from_handle(selected[0]) + parent_type = parent_loc.get_type() + if parent_type < 7: + loc.set_type(parent_type + 1) + else: + loc.set_type(7) + try: + EditLocation(self.dbstate, self.uistate, [], loc, None) + except WindowActiveError: + pass + + def remove(self, obj): + for handle in self.selected_handles(): + place_list = [ + item[1] for item in + self.dbstate.db.find_backlink_handles(handle, ['Place'])] + children = [handle for handle in + self.dbstate.db.find_location_child_handles(handle)] + if place_list or children: + msg = _("Cannot remove location object.") + msg2 = _("The location is in use.") + ErrorDialog(msg, msg2) + else: + location = self.dbstate.db.get_location_from_handle(handle) + with DbTxn(_("Delete Location (%s)") % location.get_name(), + self.dbstate.db) as trans: + self.dbstate.db.remove_location(handle, trans) + + def edit(self, obj): + for handle in self.selected_handles(): + loc = self.dbstate.db.get_location_from_handle(handle) + try: + EditLocation(self.dbstate, self.uistate, [], loc, None) + except WindowActiveError: + pass + + def merge(self, obj): + """ + Merge the selected locations. + """ + msg = _("Not yet implemented.") + ErrorDialog(msg, msg) + + def dummy_report(self, obj): + """ For the xml UI definition of popup to work, the submenu + Quick Report must have an entry in the xml + As this submenu will be dynamically built, we offer a dummy action + """ + pass + + def get_default_gramplets(self): + """ + Define the default gramplets for the sidebar and bottombar. + """ + return ((), ()) diff --git a/gramps/plugins/view/view.gpr.py b/gramps/plugins/view/view.gpr.py index 4dda68d79..bceb85ae2 100644 --- a/gramps/plugins/view/view.gpr.py +++ b/gramps/plugins/view/view.gpr.py @@ -38,7 +38,7 @@ version = '1.0', gramps_target_version = '4.0', status = STABLE, fname = 'eventview.py', -authors = ["The Gramps project"], +authors = [u"The Gramps project"], authors_email = ["http://gramps-project.org"], category = ("Events", _("Events")), viewclass = 'EventView', @@ -53,7 +53,7 @@ version = '1.0', gramps_target_version = '4.0', status = STABLE, fname = 'familyview.py', -authors = ["The Gramps project"], +authors = [u"The Gramps project"], authors_email = ["http://gramps-project.org"], category = ("Families", _("Families")), viewclass = 'FamilyView', @@ -68,7 +68,7 @@ version = '1.0', gramps_target_version = '4.0', status = STABLE, fname = 'grampletview.py', -authors = ["The Gramps project"], +authors = [u"The Gramps project"], authors_email = ["http://gramps-project.org"], category = ("Gramplets", _("Gramplets")), viewclass = 'GrampletView', @@ -83,7 +83,7 @@ version = '1.0', gramps_target_version = '4.0', status = STABLE, fname = 'mediaview.py', -authors = ["The Gramps project"], +authors = [u"The Gramps project"], authors_email = ["http://gramps-project.org"], category = ("Media", _("Media")), viewclass = 'MediaView', @@ -98,7 +98,7 @@ version = '1.0', gramps_target_version = '4.0', status = STABLE, fname = 'noteview.py', -authors = ["The Gramps project"], +authors = [u"The Gramps project"], authors_email = ["http://gramps-project.org"], category = ("Notes", _("Notes")), viewclass = 'NoteView', @@ -113,7 +113,7 @@ version = '1.0', gramps_target_version = '4.0', status = STABLE, fname = 'relview.py', -authors = ["The Gramps project"], +authors = [u"The Gramps project"], authors_email = ["http://gramps-project.org"], category = ("Relationships", _("Relationships")), viewclass = 'RelationshipView', @@ -128,7 +128,7 @@ version = '1.0', gramps_target_version = '4.0', status = STABLE, fname = 'pedigreeview.py', -authors = ["The Gramps project"], +authors = [u"The Gramps project"], authors_email = ["http://gramps-project.org"], category = ("Ancestry", _("Charts")), viewclass = 'PedigreeView', @@ -145,7 +145,7 @@ version = '1.0', gramps_target_version = '4.0', status = STABLE, fname = 'fanchartview.py', -authors = ["Douglas S. Blank", "B. Malengier"], +authors = [u"Douglas S. Blank", u"B. Malengier"], authors_email = ["doug.blank@gmail.com", "benny.malengier@gmail.com"], viewclass = 'FanChartView', stock_icon = 'gramps-fanchart', @@ -160,7 +160,7 @@ version = '1.0', gramps_target_version = '4.0', status = STABLE, fname = 'fanchartdescview.py', -authors = ["B. Malengier"], +authors = [u"B. Malengier"], authors_email = ["benny.malengier@gmail.com"], viewclass = 'FanChartDescView', stock_icon = 'gramps-fanchartdesc', @@ -175,7 +175,7 @@ version = '1.0', gramps_target_version = '4.0', status = STABLE, fname = 'persontreeview.py', -authors = ["The Gramps project"], +authors = [u"The Gramps project"], authors_email = ["http://gramps-project.org"], category = ("People", _("People")), viewclass = 'PersonTreeView', @@ -192,7 +192,7 @@ version = '1.0', gramps_target_version = '4.0', status = STABLE, fname = 'personlistview.py', -authors = ["The Gramps project"], +authors = [u"The Gramps project"], authors_email = ["http://gramps-project.org"], category = ("People", _("People")), viewclass = 'PersonListView', @@ -208,7 +208,7 @@ version = '1.0', gramps_target_version = '4.0', status = STABLE, fname = 'placelistview.py', -authors = ["The Gramps project"], +authors = [u"The Gramps project"], authors_email = ["http://gramps-project.org"], category = ("Places", _("Places")), viewclass = 'PlaceListView', @@ -216,20 +216,20 @@ order = START, stock_icon = 'gramps-tree-list', ) -register(VIEW, -id = 'placetreeview', -name = _("Place Tree"), -description = _("A view displaying places in a tree format."), -version = '1.0', -gramps_target_version = '4.0', -status = STABLE, -fname = 'placetreeview.py', -authors = ["Donald N. Allingham", "Gary Burton", "Nick Hall"], -authors_email = [""], -category = ("Places", _("Places")), -viewclass = 'PlaceTreeView', -stock_icon = 'gramps-tree-group', - ) +#register(VIEW, +#id = 'placetreeview', +#name = _("Place Tree"), +#description = _("A view displaying places in a tree format."), +#version = '1.0', +#gramps_target_version = '4.0', +#status = STABLE, +#fname = 'placetreeview.py', +#authors = [u"Donald N. Allingham", u"Gary Burton", u"Nick Hall"], +#authors_email = [""], +#category = ("Places", _("Places")), +#viewclass = 'PlaceTreeView', +#stock_icon = 'gramps-tree-group', + #) register(VIEW, id = 'repoview', @@ -239,7 +239,7 @@ version = '1.0', gramps_target_version = '4.0', status = STABLE, fname = 'repoview.py', -authors = ["The Gramps project"], +authors = [u"The Gramps project"], authors_email = ["http://gramps-project.org"], category = ("Repositories", _("Repositories")), viewclass = 'RepositoryView', @@ -254,7 +254,7 @@ version = '1.0', gramps_target_version = '4.0', status = STABLE, fname = 'sourceview.py', -authors = ["The Gramps project"], +authors = [u"The Gramps project"], authors_email = ["http://gramps-project.org"], category = ("Sources", _("Sources")), viewclass = 'SourceView', @@ -270,7 +270,7 @@ version = '1.0', gramps_target_version = '4.0', status = STABLE, fname = 'citationlistview.py', -authors = ["The Gramps project"], +authors = [u"The Gramps project"], authors_email = ["http://gramps-project.org"], category = ("Citations", _("Citations")), viewclass = 'CitationListView', @@ -285,9 +285,25 @@ version = '1.0', gramps_target_version = '4.0', status = STABLE, fname = 'citationtreeview.py', -authors = ["Tim G L Lyons", "Nick Hall"], +authors = [u"Tim G L Lyons", u"Nick Hall"], authors_email = [""], category = ("Sources", _("Sources")), viewclass = 'CitationTreeView', stock_icon = 'gramps-tree-select', ) + +register(VIEW, +id = 'locationview', +name = _("Location View"), +description = _("The view showing all locations"), +version = '1.0', +gramps_target_version = '4.0', +status = STABLE, +fname = 'locationview.py', +authors = [u"The Gramps project"], +authors_email = ["http://gramps-project.org"], +category = ("Places", _("Places")), +viewclass = 'LocationView', +order = START, +stock_icon = 'gramps-tree-group', + )