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) 2009       Benny Malengier
7# Copyright (C) 2010       Nick Hall
8# Copyright (C) 2011       Tim G L Lyons
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation; either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program; if not, write to the Free Software
22# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23#
24
25#-------------------------------------------------------------------------
26#
27# Python classes
28#
29#-------------------------------------------------------------------------
30import logging
31_LOG = logging.getLogger(".gui.editors.EditNote")
32
33#-------------------------------------------------------------------------
34#
35# GTK libraries
36#
37#-------------------------------------------------------------------------
38from gi.repository import Gtk
39from gi.repository import GObject
40from gi.repository import Pango
41
42#-------------------------------------------------------------------------
43#
44# Gramps modules
45#
46#-------------------------------------------------------------------------
47from gramps.gen.const import GRAMPS_LOCALE as glocale
48_ = glocale.translation.sgettext
49from gramps.gen.config import config
50from .editprimary import EditPrimary
51from .displaytabs import GrampsTab, NoteBackRefList
52from ..widgets import (MonitoredDataType, MonitoredCheckbox,
53                         MonitoredEntry, PrivacyButton, MonitoredTagList)
54from gramps.gen.lib import Note
55from gramps.gen.db import DbTxn
56from ..dialog import ErrorDialog
57from ..glade import Glade
58from gramps.gen.const import URL_MANUAL_SECT2
59
60#-------------------------------------------------------------------------
61#
62# Constants
63#
64#-------------------------------------------------------------------------
65
66WIKI_HELP_PAGE = URL_MANUAL_SECT2
67WIKI_HELP_SEC = _('manual|Editing_information_about_notes')
68
69#-------------------------------------------------------------------------
70#
71# NoteTab
72#
73#-------------------------------------------------------------------------
74
75class NoteTab(GrampsTab):
76    """
77    This class provides the tabpage of the note
78    """
79
80    def __init__(self, dbstate, uistate, track, name, widget):
81        """
82        @param dbstate: The database state. Contains a reference to
83        the database, along with other state information. The GrampsTab
84        uses this to access the database and to pass to and created
85        child windows (such as edit dialogs).
86        @type dbstate: L{DbState.DbState}
87        @param uistate: The UI state. Used primarily to pass to any created
88        subwindows.
89        @type uistate: L{DisplayState.DisplayState}
90        @param track: The window tracking mechanism used to manage windows.
91        This is only used to pass to generted child windows.
92        @type track: list
93        @param name: Notebook label name
94        @type name: str/unicode
95        @param widget: widget to be shown in the tab
96        @type widget: gtk widget
97        """
98        GrampsTab.__init__(self, dbstate, uistate, track, name)
99        eventbox = Gtk.EventBox()
100        eventbox.add(widget)
101        self.pack_start(eventbox, True, True, 0)
102        self._set_label(show_image=False)
103        eventbox.connect('key_press_event', self.key_pressed)
104        self.show_all()
105
106    def is_empty(self):
107        """
108        Override base class
109        """
110        return False
111
112#-------------------------------------------------------------------------
113#
114# EditNote
115#
116#-------------------------------------------------------------------------
117
118class EditNote(EditPrimary):
119    def __init__(self, dbstate, uistate, track, note, callback=None,
120                 callertitle = None, extratype = None):
121        """Create an EditNote window. Associate a note with the window.
122
123        @param callertitle: Text passed by calling object to add to title
124        @type callertitle: str
125        @param extratype: Extra L{NoteType} values to add to the default types.
126        They are removed from the ignorelist of L{NoteType}.
127        @type extratype: list of int
128
129        """
130        self.callertitle = callertitle
131        self.extratype = extratype
132        EditPrimary.__init__(self, dbstate, uistate, track, note,
133                             dbstate.db.get_note_from_handle,
134                             dbstate.db.get_note_from_gramps_id, callback)
135
136    def empty_object(self):
137        """Return an empty Note object for comparison for changes.
138
139        It is used by the base class L{EditPrimary}.
140
141        """
142        empty_note = Note();
143        if self.extratype:
144            empty_note.set_type(self.extratype[0])
145        return empty_note
146
147    def get_menu_title(self):
148        if self.obj.get_handle():
149            if self.callertitle :
150                title = _('Note: %(id)s - %(context)s') % {
151                    'id'      : self.obj.get_gramps_id(),
152                    'context' : self.callertitle
153                }
154            else :
155                title = _('Note: %s') % self.obj.get_gramps_id()
156        else:
157            if self.callertitle :
158                title = _('New Note - %(context)s') % {
159                    'context' : self.callertitle
160                }
161            else :
162                title = _('New Note')
163
164        return title
165
166    def get_custom_notetypes(self):
167        return self.dbstate.db.get_note_types()
168
169    def _local_init(self):
170        """Local initialization function.
171
172        Perform basic initialization, including setting up widgets
173        and the glade interface. It is called by the base class L{EditPrimary},
174        and overridden here.
175
176        """
177        self.top = Glade()
178
179        win = self.top.toplevel
180        self.set_window(win, None, self.get_menu_title())
181        self.setup_configs('interface.note', 700, 500)
182
183        vboxnote = self.top.get_object('vbox131')
184        notebook = self.top.get_object('note_notebook')
185        #recreate start page as GrampsTab
186        notebook.remove_page(0)
187        self.ntab = NoteTab(self.dbstate, self.uistate, self.track,
188                              _('_Note'), vboxnote)
189        self.track_ref_for_deletion("ntab")
190
191        self.build_interface()
192
193    def _setup_fields(self):
194        """Get control widgets and attach them to Note's attributes."""
195        self.type_selector = MonitoredDataType(
196            self.top.get_object('type'),
197            self.obj.set_type,
198            self.obj.get_type,
199            self.db.readonly,
200            custom_values=self.get_custom_notetypes(),
201            ignore_values=self.obj.get_type().get_ignore_list(self.extratype))
202
203        self.check = MonitoredCheckbox(
204            self.obj,
205            self.top.get_object('format'),
206            self.obj.set_format,
207            self.obj.get_format,
208            readonly = self.db.readonly)
209
210        self.gid = MonitoredEntry(
211            self.top.get_object('id'),
212            self.obj.set_gramps_id,
213            self.obj.get_gramps_id,
214            self.db.readonly)
215
216        self.tags = MonitoredTagList(
217            self.top.get_object("tag_label"),
218            self.top.get_object("tag_button"),
219            self.obj.set_tag_list,
220            self.obj.get_tag_list,
221            self.db,
222            self.uistate, self.track,
223            self.db.readonly)
224
225        self.priv = PrivacyButton(
226            self.top.get_object("private"),
227            self.obj, self.db.readonly)
228
229    def _connect_signals(self):
230        """Connects any signals that need to be connected.
231
232        Called by the init routine of the base class L{EditPrimary}.
233
234        """
235        self.define_ok_button(self.top.get_object('ok'), self.save)
236        self.define_cancel_button(self.top.get_object('cancel'))
237        self.define_help_button(self.top.get_object('help'),
238                WIKI_HELP_PAGE, WIKI_HELP_SEC)
239
240    def _connect_db_signals(self):
241        """
242        Connect any signals that need to be connected.
243        Called by the init routine of the base class (_EditPrimary).
244        """
245        self._add_db_signal('note-rebuild', self._do_close)
246        self._add_db_signal('note-delete', self.check_for_close)
247
248    def _create_tabbed_pages(self):
249        """Create the notebook tabs and inserts them into the main window."""
250        notebook = self.top.get_object("note_notebook")
251
252        self._add_tab(notebook, self.ntab)
253
254        handles = self.dbstate.db.find_backlink_handles(self.obj.handle)
255        self.rlist = NoteBackRefList(self.dbstate,
256                                     self.uistate,
257                                     self.track,
258                                     handles)
259        self.backref_tab = self._add_tab(notebook, self.rlist)
260        self.track_ref_for_deletion("rlist")
261        self.track_ref_for_deletion("backref_tab")
262
263        self._setup_notebook_tabs(notebook)
264
265    def build_interface(self):
266        self.texteditor = self.top.get_object('texteditor')
267        self.texteditor.set_editable(not self.dbstate.db.readonly)
268        self.texteditor.set_wrap_mode(Gtk.WrapMode.WORD)
269
270        # create a formatting toolbar
271        if not self.dbstate.db.readonly:
272            vbox = self.top.get_object('container')
273            toolbar, self.action_group = self.texteditor.create_toolbar(
274                self.uistate.uimanager, self.window)
275            vbox.pack_start(toolbar, False, False, 0)
276            self.texteditor.set_transient_parent(self.window)
277
278        # setup initial values for textview and textbuffer
279        if self.obj:
280            self.empty = False
281            with self.texteditor.undo_disabled():
282                self.texteditor.set_text(self.obj.get_styledtext())
283            # Reset the undoable buffer:
284            self.texteditor.reset()
285            _LOG.debug("Initial Note: %s" % str(self.texteditor.get_text()))
286        else:
287            self.empty = True
288
289    def build_menu_names(self, person):
290        """
291        Provide the information needed by the base class to define the
292        window management menu entries.
293        """
294        return (_('Edit Note'), self.get_menu_title())
295
296    def _post_init(self):
297        self.texteditor.grab_focus()
298
299    def update_note(self):
300        """Update the Note object with current value."""
301        if self.obj:
302            text = self.texteditor.get_text()
303            self.obj.set_styledtext(text)
304            _LOG.debug(str(text))
305
306    def close(self, *obj):
307        """Called when cancel button clicked."""
308        self.update_note()
309        super().close()
310
311    def save(self, *obj):
312        """Save the data."""
313        self.ok_button.set_sensitive(False)
314
315        self.update_note()
316
317        if self.object_is_empty():
318            ErrorDialog(_("Cannot save note"),
319                        _("No data exists for this note. Please "
320                          "enter data or cancel the edit."),
321                        parent=self.window)
322            self.ok_button.set_sensitive(True)
323            return
324
325        (uses_dupe_id, id) = self._uses_duplicate_id()
326        if uses_dupe_id:
327            msg1 = _("Cannot save note. ID already exists.")
328            msg2 = _("You have attempted to use the existing Gramps ID with "
329                         "value %(id)s. This value is already used. Please "
330                         "enter a different ID or leave "
331                         "blank to get the next available ID value.") % {
332                         'id' : id }
333            ErrorDialog(msg1, msg2, parent=self.window)
334            self.ok_button.set_sensitive(True)
335            return
336
337        if not self.obj.handle:
338            with DbTxn(_("Add Note"),
339                       self.db) as trans:
340                self.db.add_note(self.obj, trans)
341        else:
342            if self.data_has_changed():
343                with DbTxn(_("Edit Note"),
344                           self.db) as trans:
345                    if not self.obj.get_gramps_id():
346                        self.obj.set_gramps_id(self.db.find_next_note_gramps_id())
347                    self.db.commit_note(self.obj, trans)
348
349        if self.callback:
350            self.callback(self.obj.get_handle())
351        self._do_close()
352
353class DeleteNoteQuery:
354    def __init__(self, dbstate, uistate, note, the_lists):
355        self.note = note
356        self.db = dbstate.db
357        self.uistate = uistate
358        self.the_lists = the_lists
359
360    def query_response(self):
361        with DbTxn(_("Delete Note (%s)") % self.note.get_gramps_id(),
362                   self.db) as trans:
363            self.db.disable_signals()
364
365            (person_list, family_list, event_list, place_list, source_list,
366             citation_list, media_list, repo_list) = self.the_lists
367
368            note_handle = self.note.get_handle()
369
370            for handle in person_list:
371                person = self.db.get_person_from_handle(handle)
372                if person:
373                    person.remove_note(note_handle)
374                    self.db.commit_person(person, trans)
375
376            for handle in family_list:
377                family = self.db.get_family_from_handle(handle)
378                if family:
379                    family.remove_note(note_handle)
380                    self.db.commit_family(family, trans)
381
382            for handle in event_list:
383                event = self.db.get_event_from_handle(handle)
384                if event:
385                    event.remove_note(note_handle)
386                    self.db.commit_event(event, trans)
387
388            for handle in place_list:
389                place = self.db.get_place_from_handle(handle)
390                if place:
391                    place.remove_note(note_handle)
392                    self.db.commit_place(place, trans)
393
394            for handle in source_list:
395                source = self.db.get_source_from_handle(handle)
396                if source:
397                    source.remove_note(note_handle)
398                    self.db.commit_source(source, trans)
399
400            for handle in citation_list:
401                citation = self.db.get_citation_from_handle(handle)
402                if citation:
403                    citation.remove_note(note_handle)
404                    self.db.commit_citation(citation, trans)
405
406            for handle in media_list:
407                media = self.db.get_media_from_handle(handle)
408                if media:
409                    media.remove_note(note_handle)
410                    self.db.commit_media(media, trans)
411
412            for handle in repo_list:
413                repo = self.db.get_repository_from_handle(handle)
414                if repo:
415                    repo.remove_note(note_handle)
416                    self.db.commit_repository(repo, trans)
417
418            self.db.enable_signals()
419            self.db.remove_note(note_handle, trans)
420