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