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