1# 2# Gramps - a GTK+/GNOME based genealogy program 3# 4# Copyright (C) 2000-2006 Donald N. Allingham 5# Copyright (C) 2009 Gary Burton 6# Copyright (C) 2010,2015 Nick Hall 7# Copyright (C) 2011 Tim G L lyons 8# 9# This program is free software; you can redistribute it and/or modify 10# it under the terms of the GNU General Public License as published by 11# the Free Software Foundation; either version 2 of the License, or 12# (at your option) any later version. 13# 14# This program is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17# GNU General Public License for more details. 18# 19# You should have received a copy of the GNU General Public License 20# along with this program; if not, write to the Free Software 21# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22# 23 24#------------------------------------------------------------------------- 25# 26# python modules 27# 28#------------------------------------------------------------------------- 29import logging 30log = logging.getLogger(".") 31 32#------------------------------------------------------------------------- 33# 34# GTK/Gnome modules 35# 36#------------------------------------------------------------------------- 37from gi.repository import Gtk 38 39#------------------------------------------------------------------------- 40# 41# gramps modules 42# 43#------------------------------------------------------------------------- 44from gramps.gen.const import GRAMPS_LOCALE as glocale 45_ = glocale.translation.sgettext 46from gramps.gen.lib import NoteType, Place 47from gramps.gen.db import DbTxn 48from .editprimary import EditPrimary 49from .displaytabs import (PlaceRefEmbedList, PlaceNameEmbedList, 50 LocationEmbedList, CitationEmbedList, 51 GalleryTab, NoteTab, WebEmbedList, PlaceBackRefList) 52from ..widgets import (MonitoredEntry, PrivacyButton, MonitoredTagList, 53 MonitoredDataType) 54from gramps.gen.errors import ValidationError, WindowActiveError 55from gramps.gen.utils.place import conv_lat_lon 56from gramps.gen.display.place import displayer as place_displayer 57from gramps.gen.config import config 58from ..dialog import ErrorDialog 59from ..glade import Glade 60from gramps.gen.const import URL_MANUAL_SECT2 61 62#------------------------------------------------------------------------- 63# 64# Constants 65# 66#------------------------------------------------------------------------- 67 68WIKI_HELP_PAGE = URL_MANUAL_SECT2 69WIKI_HELP_SEC = _('manual|Place_Editor_dialog') 70 71#------------------------------------------------------------------------- 72# 73# EditPlace 74# 75#------------------------------------------------------------------------- 76class EditPlace(EditPrimary): 77 78 def __init__(self, dbstate, uistate, track, place, callback=None): 79 EditPrimary.__init__(self, dbstate, uistate, track, place, 80 dbstate.db.get_place_from_handle, 81 dbstate.db.get_place_from_gramps_id, callback) 82 83 def empty_object(self): 84 return Place() 85 86 def _local_init(self): 87 self.top = Glade() 88 self.set_window(self.top.toplevel, None, self.get_menu_title()) 89 self.setup_configs('interface.place', 650, 450) 90 self.place_name_label = self.top.get_object('place_name_label') 91 self.place_name_label.set_text(_('place|Name:')) 92 93 def get_menu_title(self): 94 if self.obj and self.obj.get_handle(): 95 title = place_displayer.display(self.db, self.obj) 96 dialog_title = _('Place: %s') % title 97 else: 98 dialog_title = _('New Place') 99 return dialog_title 100 101 def _connect_signals(self): 102 self.define_ok_button(self.top.get_object('ok'), self.save) 103 self.define_cancel_button(self.top.get_object('cancel')) 104 self.define_help_button(self.top.get_object('help'), 105 WIKI_HELP_PAGE, WIKI_HELP_SEC) 106 107 def _connect_db_signals(self): 108 """ 109 Connect any signals that need to be connected. 110 Called by the init routine of the base class (_EditPrimary). 111 """ 112 self._add_db_signal('place-rebuild', self._do_close) 113 self._add_db_signal('place-delete', self.check_for_close) 114 115 def _setup_fields(self): 116 117 if not config.get('preferences.place-auto'): 118 self.top.get_object("place_title").show() 119 self.top.get_object("place_title_label").show() 120 self.title = MonitoredEntry(self.top.get_object("place_title"), 121 self.obj.set_title, self.obj.get_title, 122 self.db.readonly) 123 124 self.name = MonitoredEntry(self.top.get_object("name_entry"), 125 self.obj.get_name().set_value, 126 self.obj.get_name().get_value, 127 self.db.readonly, 128 changed=self.name_changed) 129 130 edit_button = self.top.get_object("name_button") 131 edit_button.connect('clicked', self.edit_place_name) 132 133 self.gid = MonitoredEntry(self.top.get_object("gid"), 134 self.obj.set_gramps_id, 135 self.obj.get_gramps_id, self.db.readonly) 136 137 self.tags = MonitoredTagList(self.top.get_object("tag_label"), 138 self.top.get_object("tag_button"), 139 self.obj.set_tag_list, 140 self.obj.get_tag_list, 141 self.db, 142 self.uistate, self.track, 143 self.db.readonly) 144 145 self.privacy = PrivacyButton(self.top.get_object("private"), self.obj, 146 self.db.readonly) 147 148 custom_place_types = sorted(self.db.get_place_types(), 149 key=lambda s: s.lower()) 150 self.place_type = MonitoredDataType(self.top.get_object("place_type"), 151 self.obj.set_type, 152 self.obj.get_type, 153 self.db.readonly, 154 custom_place_types) 155 156 self.code = MonitoredEntry( 157 self.top.get_object("code_entry"), 158 self.obj.set_code, self.obj.get_code, 159 self.db.readonly) 160 161 entry = self.top.get_object("lon_entry") 162 entry.set_ltr_mode() 163 self.longitude = MonitoredEntry( 164 entry, 165 self.obj.set_longitude, self.obj.get_longitude, 166 self.db.readonly) 167 self.longitude.connect("validate", self._validate_coordinate, "lon") 168 #force validation now with initial entry 169 entry.validate(force=True) 170 171 entry = self.top.get_object("lat_entry") 172 entry.set_ltr_mode() 173 self.latitude = MonitoredEntry( 174 entry, 175 self.obj.set_latitude, self.obj.get_latitude, 176 self.db.readonly) 177 self.latitude.connect("validate", self._validate_coordinate, "lat") 178 #force validation now with initial entry 179 entry.validate(force=True) 180 181 entry = self.top.get_object("latlon_entry") 182 entry.set_ltr_mode() 183 self.latlon = MonitoredEntry( 184 entry, 185 self.set_latlongitude, self.get_latlongitude, 186 self.db.readonly) 187 188 def set_latlongitude(self, value): 189 try: 190 # Bug 12349 191 parts = value.split(', ') 192 if len(parts) == 2: 193 longitude = parts[0].strip().replace(',', '.') 194 latitude = parts[1].strip().replace(',', '.') 195 else: 196 longitude, latitude = value.split(',') 197 198 self.longitude.set_text(longitude) 199 self.latitude.set_text(latitude) 200 self.top.get_object("lat_entry").validate(force=True) 201 self.top.get_object("lon_entry").validate(force=True) 202 self.obj.set_latitude(self.latitude.get_value()) 203 self.obj.set_longitude(self.longitude.get_value()) 204 except: 205 pass 206 207 def get_latlongitude(self): 208 return "" 209 210 def _validate_coordinate(self, widget, text, typedeg): 211 if (typedeg == 'lat') and not conv_lat_lon(text, "0", "ISO-D"): 212 return ValidationError( 213 # translators: translate the "S" too (and the "or" of course) 214 _('Invalid latitude\n(syntax: ' 215 '18\u00b09\'48.21"S, -18.2412 or -18:9:48.21)')) 216 elif (typedeg == 'lon') and not conv_lat_lon("0", text, "ISO-D"): 217 return ValidationError( 218 # translators: translate the "E" too (and the "or" of course) 219 _('Invalid longitude\n(syntax: ' 220 '18\u00b09\'48.21"E, -18.2412 or -18:9:48.21)')) 221 222 def update_title(self): 223 new_title = place_displayer.display(self.db, self.obj) 224 self.top.get_object("preview_title").set_text(new_title) 225 226 def name_changed(self, obj): 227 self.update_title() 228 229 def build_menu_names(self, place): 230 return (_('Edit Place'), self.get_menu_title()) 231 232 def _create_tabbed_pages(self): 233 """ 234 Create the notebook tabs and inserts them into the main 235 window. 236 237 """ 238 notebook = self.top.get_object('notebook3') 239 240 self.placeref_list = PlaceRefEmbedList(self.dbstate, 241 self.uistate, 242 self.track, 243 self.obj.get_placeref_list(), 244 self.obj.handle, 245 self.update_title) 246 self._add_tab(notebook, self.placeref_list) 247 self.track_ref_for_deletion("placeref_list") 248 249 self.alt_name_list = PlaceNameEmbedList(self.dbstate, 250 self.uistate, 251 self.track, 252 self.obj.alt_names) 253 self._add_tab(notebook, self.alt_name_list) 254 self.track_ref_for_deletion("alt_name_list") 255 256 if len(self.obj.alt_loc) > 0: 257 self.loc_list = LocationEmbedList(self.dbstate, 258 self.uistate, 259 self.track, 260 self.obj.alt_loc) 261 self._add_tab(notebook, self.loc_list) 262 self.track_ref_for_deletion("loc_list") 263 264 self.citation_list = CitationEmbedList(self.dbstate, 265 self.uistate, 266 self.track, 267 self.obj.get_citation_list(), 268 self.get_menu_title()) 269 self._add_tab(notebook, self.citation_list) 270 self.track_ref_for_deletion("citation_list") 271 272 self.note_tab = NoteTab(self.dbstate, 273 self.uistate, 274 self.track, 275 self.obj.get_note_list(), 276 self.get_menu_title(), 277 notetype=NoteType.PLACE) 278 self._add_tab(notebook, self.note_tab) 279 self.track_ref_for_deletion("note_tab") 280 281 self.gallery_tab = GalleryTab(self.dbstate, 282 self.uistate, 283 self.track, 284 self.obj.get_media_list()) 285 self._add_tab(notebook, self.gallery_tab) 286 self.track_ref_for_deletion("gallery_tab") 287 288 self.web_list = WebEmbedList(self.dbstate, 289 self.uistate, 290 self.track, 291 self.obj.get_url_list()) 292 self._add_tab(notebook, self.web_list) 293 self.track_ref_for_deletion("web_list") 294 295 self.backref_list = PlaceBackRefList(self.dbstate, 296 self.uistate, 297 self.track, 298 self.db.find_backlink_handles(self.obj.handle)) 299 self.backref_tab = self._add_tab(notebook, self.backref_list) 300 self.track_ref_for_deletion("backref_list") 301 self.track_ref_for_deletion("backref_tab") 302 303 self._setup_notebook_tabs(notebook) 304 305 def edit_place_name(self, obj): 306 try: 307 from . import EditPlaceName 308 EditPlaceName(self.dbstate, self.uistate, self.track, 309 self.obj.get_name(), self.edit_callback) 310 except WindowActiveError: 311 return 312 313 def edit_callback(self, obj): 314 value = self.obj.get_name().get_value() 315 self.top.get_object("name_entry").set_text(value) 316 317 def save(self, *obj): 318 self.ok_button.set_sensitive(False) 319 if self.obj.get_name().get_value().strip() == '': 320 msg1 = _("Cannot save place. Name not entered.") 321 msg2 = _("You must enter a name before saving.") 322 ErrorDialog(msg1, msg2, parent=self.window) 323 self.ok_button.set_sensitive(True) 324 return 325 326 (uses_dupe_id, id) = self._uses_duplicate_id() 327 if uses_dupe_id: 328 prim_object = self.get_from_gramps_id(id) 329 name = place_displayer.display(self.db, prim_object) 330 msg1 = _("Cannot save place. ID already exists.") 331 msg2 = _("You have attempted to use the existing Gramps ID with " 332 "value %(id)s. This value is already used by '" 333 "%(prim_object)s'. Please enter a different ID or leave " 334 "blank to get the next available ID value.") % { 335 'id' : id, 'prim_object' : name } 336 ErrorDialog(msg1, msg2, parent=self.window) 337 self.ok_button.set_sensitive(True) 338 return 339 340 place_title = place_displayer.display(self.db, self.obj) 341 if not self.obj.handle: 342 with DbTxn(_("Add Place (%s)") % place_title, 343 self.db) as trans: 344 self.db.add_place(self.obj, trans) 345 else: 346 if self.data_has_changed(): 347 with DbTxn(_("Edit Place (%s)") % place_title, 348 self.db) as trans: 349 if not self.obj.get_gramps_id(): 350 self.obj.set_gramps_id(self.db.find_next_place_gramps_id()) 351 self.db.commit_place(self.obj, trans) 352 353 self._do_close() 354 if self.callback: 355 self.callback(self.obj) 356 357#------------------------------------------------------------------------- 358# 359# DeletePlaceQuery 360# 361#------------------------------------------------------------------------- 362class DeletePlaceQuery: 363 364 def __init__(self, dbstate, uistate, place, person_list, family_list, 365 event_list): 366 self.db = dbstate.db 367 self.uistate = uistate 368 self.obj = place 369 self.person_list = person_list 370 self.family_list = family_list 371 self.event_list = event_list 372 373 def query_response(self): 374 place_title = place_displayer.display(self.db, self.obj) 375 with DbTxn(_("Delete Place (%s)") % place_title, self.db) as trans: 376 self.db.disable_signals() 377 378 place_handle = self.obj.get_handle() 379 380 for handle in self.person_list: 381 person = self.db.get_person_from_handle(handle) 382 person.remove_handle_references('Place', place_handle) 383 self.db.commit_person(person, trans) 384 385 for handle in self.family_list: 386 family = self.db.get_family_from_handle(handle) 387 family.remove_handle_references('Place', place_handle) 388 self.db.commit_family(family, trans) 389 390 for handle in self.event_list: 391 event = self.db.get_event_from_handle(handle) 392 event.remove_handle_references('Place', place_handle) 393 self.db.commit_event(event, trans) 394 395 self.db.enable_signals() 396 self.db.remove_place(place_handle, trans) 397