They are pushed to background if background window tooltip appears. Also, The book list display was not popping up above the dialog (no transient parent).
1073 lines
41 KiB
Python
1073 lines
41 KiB
Python
#
|
|
# Gramps - a GTK+/GNOME based genealogy program
|
|
#
|
|
# Copyright (C) 2003-2007 Donald N. Allingham
|
|
# Copyright (C) 2007-2012 Brian G. Matherly
|
|
# Copyright (C) 2010 Jakim Friant
|
|
# Copyright (C) 2012 Nick Hall
|
|
# Copyright (C) 2011-2016 Paul Franklin
|
|
#
|
|
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
#
|
|
|
|
""" GUI dialog for creating and managing books """
|
|
|
|
# Written by Alex Roitman,
|
|
# largely based on the BaseDoc classes by Don Allingham
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Standard Python modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# Set up logging
|
|
#
|
|
#------------------------------------------------------------------------
|
|
import logging
|
|
LOG = logging.getLogger(".Book")
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# GTK/Gnome modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
from gi.repository import Gdk
|
|
from gi.repository import Gtk
|
|
from gi.repository import GObject
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Gramps modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
|
_ = glocale.translation.gettext
|
|
from ...listmodel import ListModel
|
|
from gramps.gen.errors import FilterError, ReportError
|
|
from ...pluginmanager import GuiPluginManager
|
|
from ...dialog import WarningDialog, ErrorDialog, QuestionDialog2
|
|
from gramps.gen.plug.menu import PersonOption, FamilyOption
|
|
from gramps.gen.plug.docgen import StyleSheet
|
|
from ...managedwindow import ManagedWindow, set_titles
|
|
from ...glade import Glade
|
|
from ...utils import is_right_click, open_file_with_default_application
|
|
from ...user import User
|
|
from .. import make_gui_option
|
|
|
|
# Import from specific modules in ReportBase
|
|
from gramps.gen.plug.report import BookList, Book, BookItem, append_styles
|
|
from gramps.gen.plug.report import CATEGORY_BOOK, book_categories
|
|
from gramps.gen.plug.report._options import ReportOptions
|
|
from ._reportdialog import ReportDialog
|
|
from ._docreportdialog import DocReportDialog
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# Private Constants
|
|
#
|
|
#------------------------------------------------------------------------
|
|
_UNSUPPORTED = _("Unsupported")
|
|
|
|
_RETURN = Gdk.keyval_from_name("Return")
|
|
_KP_ENTER = Gdk.keyval_from_name("KP_Enter")
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# Private Functions
|
|
#
|
|
#------------------------------------------------------------------------
|
|
def _initialize_options(options, dbstate, uistate):
|
|
"""
|
|
Validates all options by making sure that their values are consistent with
|
|
the database.
|
|
|
|
menu: The Menu class
|
|
dbase: the database the options will be applied to
|
|
"""
|
|
if not hasattr(options, "menu"):
|
|
return
|
|
dbase = dbstate.get_database()
|
|
if dbase.get_total() == 0:
|
|
return
|
|
menu = options.menu
|
|
|
|
for name in menu.get_all_option_names():
|
|
option = menu.get_option_by_name(name)
|
|
value = option.get_value()
|
|
|
|
if isinstance(option, PersonOption):
|
|
if not dbase.get_person_from_gramps_id(value):
|
|
person_handle = uistate.get_active('Person')
|
|
person = dbase.get_person_from_handle(person_handle)
|
|
option.set_value(person.get_gramps_id())
|
|
|
|
elif isinstance(option, FamilyOption):
|
|
if not dbase.get_family_from_gramps_id(value):
|
|
person_handle = uistate.get_active('Person')
|
|
person = dbase.get_person_from_handle(person_handle)
|
|
if person is None:
|
|
continue
|
|
family_list = person.get_family_handle_list()
|
|
if family_list:
|
|
family_handle = family_list[0]
|
|
else:
|
|
try:
|
|
family_handle = next(dbase.iter_family_handles())
|
|
except StopIteration:
|
|
family_handle = None
|
|
if family_handle:
|
|
family = dbase.get_family_from_handle(family_handle)
|
|
option.set_value(family.get_gramps_id())
|
|
else:
|
|
print("No family specified for ", name)
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# BookListDisplay class
|
|
#
|
|
#------------------------------------------------------------------------
|
|
class BookListDisplay:
|
|
"""
|
|
Interface into a dialog with the list of available books.
|
|
|
|
Allows the user to select and/or delete a book from the list.
|
|
"""
|
|
|
|
def __init__(self, booklist, nodelete=False, dosave=False, parent=None):
|
|
"""
|
|
Create a BookListDisplay object that displays the books in BookList.
|
|
|
|
booklist: books that are displayed -- a :class:`.BookList` instance
|
|
nodelete: if True then the Delete button is hidden
|
|
dosave: if True then the book list is flagged to be saved if needed
|
|
"""
|
|
|
|
self.booklist = booklist
|
|
self.dosave = dosave
|
|
self.xml = Glade('book.glade')
|
|
self.top = self.xml.toplevel
|
|
self.unsaved_changes = False
|
|
|
|
set_titles(self.top, self.xml.get_object('title2'),
|
|
_('Available Books'))
|
|
|
|
if nodelete:
|
|
delete_button = self.xml.get_object("delete_button")
|
|
delete_button.hide()
|
|
self.xml.connect_signals({
|
|
"on_booklist_cancel_clicked" : self.on_booklist_cancel_clicked,
|
|
"on_booklist_ok_clicked" : self.on_booklist_ok_clicked,
|
|
"on_booklist_delete_clicked" : self.on_booklist_delete_clicked,
|
|
"on_book_ok_clicked" : self.do_nothing,
|
|
"destroy_passed_object" : self.do_nothing,
|
|
"on_setup_clicked" : self.do_nothing,
|
|
"on_down_clicked" : self.do_nothing,
|
|
"on_up_clicked" : self.do_nothing,
|
|
"on_remove_clicked" : self.do_nothing,
|
|
"on_add_clicked" : self.do_nothing,
|
|
"on_edit_clicked" : self.do_nothing,
|
|
"on_open_clicked" : self.do_nothing,
|
|
"on_save_clicked" : self.do_nothing,
|
|
"on_clear_clicked" : self.do_nothing
|
|
})
|
|
self.guilistbooks = self.xml.get_object('list')
|
|
self.guilistbooks.connect('button-press-event', self.on_button_press)
|
|
self.guilistbooks.connect('key-press-event', self.on_key_pressed)
|
|
self.blist = ListModel(self.guilistbooks, [('Name', -1, 10)],)
|
|
|
|
self.redraw()
|
|
self.selection = None
|
|
self.top.set_transient_for(parent)
|
|
self.top.run()
|
|
|
|
def redraw(self):
|
|
"""Redraws the list of currently available books"""
|
|
|
|
self.blist.model.clear()
|
|
names = self.booklist.get_book_names()
|
|
if not len(names):
|
|
return
|
|
for name in names:
|
|
the_iter = self.blist.add([name])
|
|
if the_iter:
|
|
self.blist.selection.select_iter(the_iter)
|
|
|
|
def on_booklist_ok_clicked(self, obj):
|
|
"""
|
|
Return selected book.
|
|
Also marks the current list to be saved into the xml file, if needed.
|
|
"""
|
|
store, the_iter = self.blist.get_selected()
|
|
if the_iter:
|
|
data = self.blist.get_data(the_iter, [0])
|
|
self.selection = self.booklist.get_book(str(data[0]))
|
|
if self.dosave and self.unsaved_changes:
|
|
self.booklist.set_needs_saving(True)
|
|
|
|
def on_booklist_delete_clicked(self, obj):
|
|
"""
|
|
Deletes selected book from the list.
|
|
|
|
This change is not final. OK button has to be clicked to save the list.
|
|
"""
|
|
store, the_iter = self.blist.get_selected()
|
|
if not the_iter:
|
|
return
|
|
data = self.blist.get_data(the_iter, [0])
|
|
self.booklist.delete_book(str(data[0]))
|
|
self.blist.remove(the_iter)
|
|
self.unsaved_changes = True
|
|
self.top.run()
|
|
|
|
def on_booklist_cancel_clicked(self, obj):
|
|
""" cancel the booklist dialog """
|
|
if self.unsaved_changes:
|
|
qqq = QuestionDialog2(
|
|
_('Discard Unsaved Changes'),
|
|
_('You have made changes which have not been saved.'),
|
|
_('Proceed'),
|
|
_('Cancel'),
|
|
parent=self.top)
|
|
if not qqq.run():
|
|
self.top.run()
|
|
|
|
def on_button_press(self, obj, event):
|
|
"""
|
|
Checks for a double click event. In the list, we want to
|
|
treat a double click as if it was OK button press.
|
|
"""
|
|
if event.type == Gdk.EventType._2BUTTON_PRESS and event.button == 1:
|
|
store, the_iter = self.blist.get_selected()
|
|
if not the_iter:
|
|
return False
|
|
self.on_booklist_ok_clicked(obj)
|
|
#emit OK response on dialog to close it automatically
|
|
self.top.response(-5)
|
|
return True
|
|
return False
|
|
|
|
def on_key_pressed(self, obj, event):
|
|
"""
|
|
Handles the return key being pressed on list. If the key is pressed,
|
|
the Edit button handler is called
|
|
"""
|
|
if event.type == Gdk.EventType.KEY_PRESS:
|
|
if event.keyval in (_RETURN, _KP_ENTER):
|
|
self.on_booklist_ok_clicked(obj)
|
|
#emit OK response on dialog to close it automatically
|
|
self.top.response(-5)
|
|
return True
|
|
return False
|
|
|
|
def do_nothing(self, obj):
|
|
""" do nothing """
|
|
pass
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# Book Options
|
|
#
|
|
#------------------------------------------------------------------------
|
|
class BookOptions(ReportOptions):
|
|
|
|
"""
|
|
Defines options and provides handling interface.
|
|
"""
|
|
|
|
def __init__(self, name, dbase):
|
|
ReportOptions.__init__(self, name, dbase)
|
|
|
|
# Options specific for this report
|
|
self.options_dict = {
|
|
'bookname' : '',
|
|
}
|
|
# TODO since the CLI code for the "book" generates its own "help" now,
|
|
# the GUI code would be faster if it didn't list all the possible books
|
|
self.options_help = {
|
|
'bookname' : ("=name", _("Name of the book. MANDATORY"),
|
|
BookList('books.xml', dbase).get_book_names(),
|
|
False),
|
|
}
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Book creation dialog
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
class BookSelector(ManagedWindow):
|
|
"""
|
|
Interface into a dialog setting up the book.
|
|
|
|
Allows the user to add/remove/reorder/setup items for the current book
|
|
and to clear/load/save/edit whole books.
|
|
"""
|
|
|
|
def __init__(self, dbstate, uistate):
|
|
self._db = dbstate.db
|
|
self.dbstate = dbstate
|
|
self.uistate = uistate
|
|
self.title = _('Manage Books')
|
|
self.file = "books.xml"
|
|
|
|
ManagedWindow.__init__(self, uistate, [], self.__class__)
|
|
|
|
self.xml = Glade('book.glade', toplevel="top")
|
|
window = self.xml.toplevel
|
|
|
|
title_label = self.xml.get_object('title')
|
|
self.set_window(window, title_label, self.title)
|
|
self.setup_configs('interface.bookselector', 700, 600)
|
|
self.show()
|
|
self.xml.connect_signals({
|
|
"on_add_clicked" : self.on_add_clicked,
|
|
"on_remove_clicked" : self.on_remove_clicked,
|
|
"on_up_clicked" : self.on_up_clicked,
|
|
"on_down_clicked" : self.on_down_clicked,
|
|
"on_setup_clicked" : self.on_setup_clicked,
|
|
"on_clear_clicked" : self.on_clear_clicked,
|
|
"on_save_clicked" : self.on_save_clicked,
|
|
"on_open_clicked" : self.on_open_clicked,
|
|
"on_edit_clicked" : self.on_edit_clicked,
|
|
"on_book_ok_clicked" : self.on_book_ok_clicked,
|
|
"destroy_passed_object" : self.on_close_clicked,
|
|
|
|
# Insert dummy handlers for second top level in the glade file
|
|
"on_booklist_ok_clicked" : lambda _: None,
|
|
"on_booklist_delete_clicked" : lambda _: None,
|
|
"on_booklist_cancel_clicked" : lambda _: None,
|
|
"on_booklist_ok_clicked" : lambda _: None,
|
|
"on_booklist_ok_clicked" : lambda _: None,
|
|
})
|
|
|
|
self.avail_tree = self.xml.get_object("avail_tree")
|
|
self.book_tree = self.xml.get_object("book_tree")
|
|
self.avail_tree.connect('button-press-event', self.avail_button_press)
|
|
self.book_tree.connect('button-press-event', self.book_button_press)
|
|
|
|
self.name_entry = self.xml.get_object("name_entry")
|
|
self.name_entry.set_text(_('New Book'))
|
|
|
|
avail_label = self.xml.get_object('avail_label')
|
|
avail_label.set_text("<b>%s</b>" % _("_Available items"))
|
|
avail_label.set_use_markup(True)
|
|
avail_label.set_use_underline(True)
|
|
book_label = self.xml.get_object('book_label')
|
|
book_label.set_text("<b>%s</b>" % _("Current _book"))
|
|
book_label.set_use_underline(True)
|
|
book_label.set_use_markup(True)
|
|
|
|
avail_titles = [(_('Name'), 0, 230),
|
|
(_('Type'), 1, 80),
|
|
('', -1, 0)]
|
|
|
|
book_titles = [(_('Item name'), -1, 230),
|
|
(_('Type'), -1, 80),
|
|
('', -1, 0),
|
|
(_('Subject'), -1, 50)]
|
|
|
|
self.avail_nr_cols = len(avail_titles)
|
|
self.book_nr_cols = len(book_titles)
|
|
|
|
self.avail_model = ListModel(self.avail_tree, avail_titles)
|
|
self.book_model = ListModel(self.book_tree, book_titles)
|
|
self.draw_avail_list()
|
|
|
|
self.book = Book()
|
|
self.book_list = BookList(self.file, self._db)
|
|
self.book_list.set_needs_saving(False) # just read in: no need to save
|
|
|
|
def build_menu_names(self, obj):
|
|
return (_("Book selection list"), self.title)
|
|
|
|
def draw_avail_list(self):
|
|
"""
|
|
Draw the list with the selections available for the book.
|
|
|
|
The selections are read from the book item registry.
|
|
"""
|
|
pmgr = GuiPluginManager.get_instance()
|
|
regbi = pmgr.get_reg_bookitems()
|
|
if not regbi:
|
|
return
|
|
|
|
available_reports = []
|
|
for pdata in regbi:
|
|
category = _UNSUPPORTED
|
|
if pdata.supported and pdata.category in book_categories:
|
|
category = book_categories[pdata.category]
|
|
available_reports.append([pdata.name, category, pdata.id])
|
|
for data in sorted(available_reports):
|
|
new_iter = self.avail_model.add(data)
|
|
|
|
self.avail_model.connect_model()
|
|
|
|
if new_iter:
|
|
self.avail_model.selection.select_iter(new_iter)
|
|
path = self.avail_model.model.get_path(new_iter)
|
|
col = self.avail_tree.get_column(0)
|
|
self.avail_tree.scroll_to_cell(path, col, 1, 1, 0.0)
|
|
|
|
def open_book(self, book):
|
|
"""
|
|
Open the book: set the current set of selections to this book's items.
|
|
|
|
book: the book object to load.
|
|
"""
|
|
if book.get_paper_name():
|
|
self.book.set_paper_name(book.get_paper_name())
|
|
if book.get_orientation() is not None: # 0 is legal
|
|
self.book.set_orientation(book.get_orientation())
|
|
if book.get_paper_metric() is not None: # 0 is legal
|
|
self.book.set_paper_metric(book.get_paper_metric())
|
|
if book.get_custom_paper_size():
|
|
self.book.set_custom_paper_size(book.get_custom_paper_size())
|
|
if book.get_margins():
|
|
self.book.set_margins(book.get_margins())
|
|
if book.get_format_name():
|
|
self.book.set_format_name(book.get_format_name())
|
|
if book.get_output():
|
|
self.book.set_output(book.get_output())
|
|
if book.get_dbname() != self._db.get_save_path():
|
|
WarningDialog(
|
|
_('Different database'),
|
|
_('This book was created with the references to database '
|
|
'%s.\n\n This makes references to the central person '
|
|
'saved in the book invalid.\n\n'
|
|
'Therefore, the central person for each item is being set '
|
|
'to the active person of the currently opened database.'
|
|
) % book.get_dbname(),
|
|
parent=self.window)
|
|
|
|
self.book.clear()
|
|
self.book_model.clear()
|
|
for saved_item in book.get_item_list():
|
|
name = saved_item.get_name()
|
|
item = BookItem(self._db, name)
|
|
|
|
# The option values were loaded magically by the book parser.
|
|
# But they still need to be applied to the menu options.
|
|
opt_dict = item.option_class.handler.options_dict
|
|
orig_opt_dict = saved_item.option_class.handler.options_dict
|
|
menu = item.option_class.menu
|
|
for optname in opt_dict:
|
|
opt_dict[optname] = orig_opt_dict[optname]
|
|
menu_option = menu.get_option_by_name(optname)
|
|
if menu_option:
|
|
menu_option.set_value(opt_dict[optname])
|
|
|
|
_initialize_options(item.option_class, self.dbstate, self.uistate)
|
|
item.set_style_name(saved_item.get_style_name())
|
|
self.book.append_item(item)
|
|
|
|
data = [item.get_translated_name(),
|
|
item.get_category(), item.get_name()]
|
|
|
|
data[2] = item.option_class.get_subject()
|
|
self.book_model.add(data)
|
|
|
|
def on_add_clicked(self, obj):
|
|
"""
|
|
Add an item to the current selections.
|
|
|
|
Use the selected available item to get the item's name in the registry.
|
|
"""
|
|
store, the_iter = self.avail_model.get_selected()
|
|
if not the_iter:
|
|
return
|
|
data = self.avail_model.get_data(the_iter,
|
|
list(range(self.avail_nr_cols)))
|
|
item = BookItem(self._db, data[2])
|
|
_initialize_options(item.option_class, self.dbstate, self.uistate)
|
|
data[2] = item.option_class.get_subject()
|
|
self.book_model.add(data)
|
|
self.book.append_item(item)
|
|
|
|
def on_remove_clicked(self, obj):
|
|
"""
|
|
Remove the item from the current list of selections.
|
|
"""
|
|
store, the_iter = self.book_model.get_selected()
|
|
if not the_iter:
|
|
return
|
|
row = self.book_model.get_selected_row()
|
|
self.book.pop_item(row)
|
|
self.book_model.remove(the_iter)
|
|
|
|
def on_clear_clicked(self, obj):
|
|
"""
|
|
Clear the whole current book.
|
|
"""
|
|
self.book_model.clear()
|
|
self.book.clear()
|
|
|
|
def on_up_clicked(self, obj):
|
|
"""
|
|
Move the currently selected item one row up in the selection list.
|
|
"""
|
|
row = self.book_model.get_selected_row()
|
|
if not row or row == -1:
|
|
return
|
|
store, the_iter = self.book_model.get_selected()
|
|
data = self.book_model.get_data(the_iter,
|
|
list(range(self.book_nr_cols)))
|
|
self.book_model.remove(the_iter)
|
|
self.book_model.insert(row-1, data, None, 1)
|
|
item = self.book.pop_item(row)
|
|
self.book.insert_item(row-1, item)
|
|
|
|
def on_down_clicked(self, obj):
|
|
"""
|
|
Move the currently selected item one row down in the selection list.
|
|
"""
|
|
row = self.book_model.get_selected_row()
|
|
if row + 1 >= self.book_model.count or row == -1:
|
|
return
|
|
store, the_iter = self.book_model.get_selected()
|
|
data = self.book_model.get_data(the_iter,
|
|
list(range(self.book_nr_cols)))
|
|
self.book_model.remove(the_iter)
|
|
self.book_model.insert(row+1, data, None, 1)
|
|
item = self.book.pop_item(row)
|
|
self.book.insert_item(row+1, item)
|
|
|
|
def on_setup_clicked(self, obj):
|
|
"""
|
|
Configure currently selected item.
|
|
"""
|
|
store, the_iter = self.book_model.get_selected()
|
|
if not the_iter:
|
|
WarningDialog(_('No selected book item'),
|
|
_('Please select a book item to configure.'),
|
|
parent=self.window)
|
|
return
|
|
row = self.book_model.get_selected_row()
|
|
item = self.book.get_item(row)
|
|
option_class = item.option_class
|
|
option_class.handler.set_default_stylesheet_name(item.get_style_name())
|
|
item.is_from_saved_book = bool(self.book.get_name())
|
|
item_dialog = BookItemDialog(self.dbstate, self.uistate,
|
|
item, self.track)
|
|
|
|
while True:
|
|
response = item_dialog.window.run()
|
|
if response == Gtk.ResponseType.OK:
|
|
# dialog will be closed by connect, now continue work while
|
|
# rest of dialog is unresponsive, release when finished
|
|
style = option_class.handler.get_default_stylesheet_name()
|
|
item.set_style_name(style)
|
|
subject = option_class.get_subject()
|
|
self.book_model.model.set_value(the_iter, 2, subject)
|
|
self.book.set_item(row, item)
|
|
item_dialog.close()
|
|
break
|
|
elif response == Gtk.ResponseType.CANCEL:
|
|
item_dialog.close()
|
|
break
|
|
elif response == Gtk.ResponseType.DELETE_EVENT:
|
|
#just stop, in ManagedWindow, delete-event is already coupled to
|
|
#correct action.
|
|
break
|
|
opt_dict = option_class.handler.options_dict
|
|
for optname in opt_dict:
|
|
menu_option = option_class.menu.get_option_by_name(optname)
|
|
if menu_option:
|
|
menu_option.set_value(opt_dict[optname])
|
|
|
|
def book_button_press(self, obj, event):
|
|
"""
|
|
Double-click on the current book selection is the same as setup.
|
|
Right click evokes the context menu.
|
|
"""
|
|
if event.type == Gdk.EventType._2BUTTON_PRESS and event.button == 1:
|
|
self.on_setup_clicked(obj)
|
|
elif is_right_click(event):
|
|
self.build_book_context_menu(event)
|
|
|
|
def avail_button_press(self, obj, event):
|
|
"""
|
|
Double-click on the available selection is the same as add.
|
|
Right click evokes the context menu.
|
|
"""
|
|
if event.type == Gdk.EventType._2BUTTON_PRESS and event.button == 1:
|
|
self.on_add_clicked(obj)
|
|
elif is_right_click(event):
|
|
self.build_avail_context_menu(event)
|
|
|
|
def build_book_context_menu(self, event):
|
|
"""Builds the menu with item-centered and book-centered options."""
|
|
|
|
store, the_iter = self.book_model.get_selected()
|
|
if the_iter:
|
|
sensitivity = 1
|
|
else:
|
|
sensitivity = 0
|
|
entries = [
|
|
(_('_Up'), self.on_up_clicked, sensitivity),
|
|
(_('_Down'), self.on_down_clicked, sensitivity),
|
|
(_("Setup"), self.on_setup_clicked, sensitivity),
|
|
(_('_Remove'), self.on_remove_clicked, sensitivity),
|
|
('', None, 0),
|
|
(_('Clear the book'), self.on_clear_clicked, 1),
|
|
(_('_Save'), self.on_save_clicked, 1),
|
|
(_('_Open'), self.on_open_clicked, 1),
|
|
(_("_Edit"), self.on_edit_clicked, 1),
|
|
]
|
|
|
|
self.menu1 = Gtk.Menu() # TODO could this be just a local "menu ="?
|
|
self.menu1.set_reserve_toggle_size(False)
|
|
for title, callback, sensitivity in entries:
|
|
item = Gtk.MenuItem.new_with_mnemonic(title)
|
|
Gtk.Label.new_with_mnemonic
|
|
if callback:
|
|
item.connect("activate", callback)
|
|
else:
|
|
item = Gtk.SeparatorMenuItem()
|
|
item.set_sensitive(sensitivity)
|
|
item.show()
|
|
self.menu1.append(item)
|
|
self.menu1.popup(None, None, None, None, event.button, event.time)
|
|
|
|
def build_avail_context_menu(self, event):
|
|
"""Builds the menu with the single Add option."""
|
|
|
|
store, the_iter = self.avail_model.get_selected()
|
|
if the_iter:
|
|
sensitivity = 1
|
|
else:
|
|
sensitivity = 0
|
|
entries = [
|
|
(_('_Add'), self.on_add_clicked, sensitivity),
|
|
]
|
|
|
|
self.menu2 = Gtk.Menu() # TODO could this be just a local "menu ="?
|
|
self.menu2.set_reserve_toggle_size(False)
|
|
for title, callback, sensitivity in entries:
|
|
item = Gtk.MenuItem.new_with_mnemonic(title)
|
|
if callback:
|
|
item.connect("activate", callback)
|
|
item.set_sensitive(sensitivity)
|
|
item.show()
|
|
self.menu2.append(item)
|
|
self.menu2.popup(None, None, None, None, event.button, event.time)
|
|
|
|
def on_close_clicked(self, obj):
|
|
"""
|
|
close the BookSelector dialog, saving any changes if needed
|
|
"""
|
|
if self.book_list.get_needs_saving():
|
|
self.book_list.save()
|
|
ManagedWindow.close(self, *obj)
|
|
|
|
def on_book_ok_clicked(self, obj):
|
|
"""
|
|
Run final BookDialog with the current book.
|
|
"""
|
|
if self.book.get_item_list():
|
|
old_paper_name = self.book.get_paper_name() # from books.xml
|
|
old_orientation = self.book.get_orientation()
|
|
old_paper_metric = self.book.get_paper_metric()
|
|
old_custom_paper_size = self.book.get_custom_paper_size()
|
|
old_margins = self.book.get_margins()
|
|
old_format_name = self.book.get_format_name()
|
|
old_output = self.book.get_output()
|
|
BookDialog(self.dbstate, self.uistate, self.book, BookOptions)
|
|
new_paper_name = self.book.get_paper_name()
|
|
new_orientation = self.book.get_orientation()
|
|
new_paper_metric = self.book.get_paper_metric()
|
|
new_custom_paper_size = self.book.get_custom_paper_size()
|
|
new_margins = self.book.get_margins()
|
|
new_format_name = self.book.get_format_name()
|
|
new_output = self.book.get_output()
|
|
# only books in the booklist have a name (not "ad hoc" ones)
|
|
if (self.book.get_name() and
|
|
(old_paper_name != new_paper_name or
|
|
old_orientation != new_orientation or
|
|
old_paper_metric != new_paper_metric or
|
|
old_custom_paper_size != new_custom_paper_size or
|
|
old_margins != new_margins or
|
|
old_format_name != new_format_name or
|
|
old_output != new_output)):
|
|
self.book.set_dbname(self._db.get_save_path())
|
|
self.book_list.set_book(self.book.get_name(), self.book)
|
|
self.book_list.set_needs_saving(True)
|
|
if self.book_list.get_needs_saving():
|
|
self.book_list.save()
|
|
else:
|
|
WarningDialog(_('No items'),
|
|
_('This book has no items.'),
|
|
parent=self.window)
|
|
return
|
|
self.close()
|
|
|
|
def on_save_clicked(self, obj):
|
|
"""
|
|
Save the current book in the xml booklist file.
|
|
"""
|
|
if not self.book.get_item_list():
|
|
WarningDialog(_('No items'),
|
|
_('This book has no items.'),
|
|
parent=self.window)
|
|
return
|
|
name = str(self.name_entry.get_text())
|
|
if not name:
|
|
WarningDialog(
|
|
_('No book name'),
|
|
_('You are about to save away a book with no name.\n\n'
|
|
'Please give it a name before saving it away.'),
|
|
parent=self.window)
|
|
return
|
|
if name in self.book_list.get_book_names():
|
|
qqq = QuestionDialog2(
|
|
_('Book name already exists'),
|
|
_('You are about to save away a '
|
|
'book with a name which already exists.'),
|
|
_('Proceed'),
|
|
_('Cancel'),
|
|
parent=self.window)
|
|
if not qqq.run():
|
|
return
|
|
|
|
# previously, the same book could be added to the booklist
|
|
# under multiple names, which became different books once the
|
|
# booklist was saved into a file so everything was fine, but
|
|
# this created a problem once the paper settings were added
|
|
# to the Book object in the BookDialog, since those settings
|
|
# were retrieved from the Book object in BookList.save, so mutiple
|
|
# books (differentiated by their names) were assigned the
|
|
# same paper values, so the solution is to make each Book be
|
|
# unique in the booklist, so if multiple copies are saved away
|
|
# only the last one will get the paper values assigned to it
|
|
# (although when the earlier books are then eventually run,
|
|
# they'll be assigned paper values also)
|
|
self.book.set_name(name)
|
|
self.book.set_dbname(self._db.get_save_path())
|
|
self.book_list.set_book(name, self.book)
|
|
self.book_list.set_needs_saving(True) # user clicked on save
|
|
self.book = Book(self.book, exact_copy=False) # regenerate old items
|
|
self.book.set_name(name)
|
|
self.book.set_dbname(self._db.get_save_path())
|
|
|
|
def on_open_clicked(self, obj):
|
|
"""
|
|
Run the BookListDisplay dialog to present the choice of books to open.
|
|
"""
|
|
booklistdisplay = BookListDisplay(self.book_list, nodelete=True,
|
|
dosave=False, parent=self.window)
|
|
booklistdisplay.top.destroy()
|
|
book = booklistdisplay.selection
|
|
if book:
|
|
self.open_book(book)
|
|
self.name_entry.set_text(book.get_name())
|
|
self.book.set_name(book.get_name())
|
|
|
|
def on_edit_clicked(self, obj):
|
|
"""
|
|
Run the BookListDisplay dialog to present the choice of books to delete.
|
|
"""
|
|
booklistdisplay = BookListDisplay(self.book_list, nodelete=False,
|
|
dosave=True, parent=self.window)
|
|
booklistdisplay.top.destroy()
|
|
book = booklistdisplay.selection
|
|
if book:
|
|
self.open_book(book)
|
|
self.name_entry.set_text(book.get_name())
|
|
self.book.set_name(book.get_name())
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# Book Item Options dialog
|
|
#
|
|
#------------------------------------------------------------------------
|
|
class BookItemDialog(ReportDialog):
|
|
|
|
"""
|
|
This class overrides the interface methods common for different reports
|
|
in a way specific for this report. This is a book item dialog.
|
|
"""
|
|
|
|
def __init__(self, dbstate, uistate, item, track=[]):
|
|
option_class = item.option_class
|
|
name = item.get_name()
|
|
translated_name = item.get_translated_name()
|
|
self.category = CATEGORY_BOOK
|
|
self.database = dbstate.db
|
|
self.option_class = option_class
|
|
self.is_from_saved_book = item.is_from_saved_book
|
|
ReportDialog.__init__(self, dbstate, uistate,
|
|
option_class, name, translated_name, track)
|
|
|
|
def on_ok_clicked(self, obj):
|
|
"""The user is satisfied with the dialog choices. Parse all options
|
|
and close the window."""
|
|
|
|
# Preparation
|
|
self.parse_style_frame()
|
|
self.parse_user_options()
|
|
|
|
self.options.handler.save_options()
|
|
|
|
def setup_target_frame(self):
|
|
"""Target frame is not used."""
|
|
pass
|
|
|
|
def parse_target_frame(self):
|
|
"""Target frame is not used."""
|
|
return 1
|
|
|
|
def init_options(self, option_class):
|
|
try:
|
|
if issubclass(option_class, object):
|
|
self.options = option_class(self.raw_name, self.database)
|
|
except TypeError:
|
|
self.options = option_class
|
|
if not self.is_from_saved_book:
|
|
self.options.load_previous_values()
|
|
|
|
def add_user_options(self):
|
|
"""
|
|
Generic method to add user options to the gui.
|
|
"""
|
|
if not hasattr(self.options, "menu"):
|
|
return
|
|
menu = self.options.menu
|
|
options_dict = self.options.options_dict
|
|
for category in menu.get_categories():
|
|
for name in menu.get_option_names(category):
|
|
option = menu.get_option(category, name)
|
|
|
|
# override option default with xml-saved value:
|
|
if name in options_dict:
|
|
option.set_value(options_dict[name])
|
|
|
|
widget, label = make_gui_option(option, self.dbstate,
|
|
self.uistate, self.track,
|
|
self.is_from_saved_book)
|
|
if widget is not None:
|
|
if label:
|
|
self.add_frame_option(category,
|
|
option.get_label(),
|
|
widget)
|
|
else:
|
|
self.add_frame_option(category, "", widget)
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# _BookFormatComboBox
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
class _BookFormatComboBox(Gtk.ComboBox):
|
|
"""
|
|
Build a menu of report types that are appropriate for a book
|
|
"""
|
|
|
|
def __init__(self, active):
|
|
|
|
Gtk.ComboBox.__init__(self)
|
|
|
|
pmgr = GuiPluginManager.get_instance()
|
|
self.__bookdoc_plugins = []
|
|
for plugin in pmgr.get_docgen_plugins():
|
|
if plugin.get_text_support() and plugin.get_draw_support():
|
|
self.__bookdoc_plugins.append(plugin)
|
|
|
|
self.store = Gtk.ListStore(GObject.TYPE_STRING)
|
|
self.set_model(self.store)
|
|
cell = Gtk.CellRendererText()
|
|
self.pack_start(cell, True)
|
|
self.add_attribute(cell, 'text', 0)
|
|
|
|
index = 0
|
|
active_index = 0
|
|
for plugin in self.__bookdoc_plugins:
|
|
name = plugin.get_name()
|
|
self.store.append(row=[name])
|
|
if plugin.get_extension() == active:
|
|
active_index = index
|
|
index += 1
|
|
self.set_active(active_index)
|
|
|
|
def get_active_plugin(self):
|
|
"""
|
|
Get the plugin represented by the currently active selection.
|
|
"""
|
|
return self.__bookdoc_plugins[self.get_active()]
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# The final dialog - paper, format, target, etc.
|
|
#
|
|
#------------------------------------------------------------------------
|
|
class BookDialog(DocReportDialog):
|
|
"""
|
|
A usual Report.Dialog subclass.
|
|
|
|
Create a dialog selecting target, format, and paper/HTML options.
|
|
"""
|
|
|
|
def __init__(self, dbstate, uistate, book, options):
|
|
self.format_menu = None
|
|
self.options = options
|
|
self.page_html_added = False
|
|
self.book = book
|
|
self.title = _('Generate Book')
|
|
self.database = dbstate.db
|
|
DocReportDialog.__init__(self, dbstate, uistate, options,
|
|
'book', self.title)
|
|
self.options.options_dict['bookname'] = self.book.get_name()
|
|
|
|
response = self.window.run()
|
|
if response == Gtk.ResponseType.OK:
|
|
handler = self.options.handler
|
|
if self.book.get_paper_name() != handler.get_paper_name():
|
|
self.book.set_paper_name(handler.get_paper_name())
|
|
if self.book.get_orientation() != handler.get_orientation():
|
|
self.book.set_orientation(handler.get_orientation())
|
|
if self.book.get_paper_metric() != handler.get_paper_metric():
|
|
self.book.set_paper_metric(handler.get_paper_metric())
|
|
if (self.book.get_custom_paper_size() !=
|
|
handler.get_custom_paper_size()):
|
|
self.book.set_custom_paper_size(handler.get_custom_paper_size())
|
|
if self.book.get_margins() != handler.get_margins():
|
|
self.book.set_margins(handler.get_margins())
|
|
if self.book.get_format_name() != handler.get_format_name():
|
|
self.book.set_format_name(handler.get_format_name())
|
|
if self.book.get_output() != self.options.get_output():
|
|
self.book.set_output(self.options.get_output())
|
|
try:
|
|
self.make_book()
|
|
except (IOError, OSError) as msg:
|
|
ErrorDialog(str(msg), parent=self.window)
|
|
self.close()
|
|
|
|
def setup_style_frame(self):
|
|
pass
|
|
def setup_other_frames(self):
|
|
pass
|
|
def parse_style_frame(self):
|
|
pass
|
|
|
|
def get_title(self):
|
|
""" get the title """
|
|
return self.title
|
|
|
|
def get_header(self, name):
|
|
""" get the header """
|
|
return _("Gramps Book")
|
|
|
|
def make_doc_menu(self, active=None):
|
|
"""Build a menu of document types that are appropriate for
|
|
this text report. This menu will be generated based upon
|
|
whether the document requires table support, etc."""
|
|
self.format_menu = _BookFormatComboBox(active)
|
|
|
|
def make_document(self):
|
|
"""Create a document of the type requested by the user."""
|
|
user = User(uistate=self.uistate)
|
|
self.rptlist = []
|
|
selected_style = StyleSheet()
|
|
|
|
pstyle = self.paper_frame.get_paper_style()
|
|
self.doc = self.format(None, pstyle)
|
|
|
|
for item in self.book.get_item_list():
|
|
item.option_class.set_document(self.doc)
|
|
report_class = item.get_write_item()
|
|
obj = (write_book_item(self.database, report_class,
|
|
item.option_class, user),
|
|
item.get_translated_name())
|
|
self.rptlist.append(obj)
|
|
append_styles(selected_style, item)
|
|
|
|
self.doc.set_style_sheet(selected_style)
|
|
self.doc.open(self.target_path)
|
|
|
|
def make_book(self):
|
|
"""
|
|
The actual book. Start it out, then go through the item list
|
|
and call each item's write_book_item method (which were loaded
|
|
by the previous make_document method).
|
|
"""
|
|
|
|
try:
|
|
self.doc.init()
|
|
newpage = 0
|
|
for (rpt, name) in self.rptlist:
|
|
if newpage:
|
|
self.doc.page_break()
|
|
newpage = 1
|
|
if rpt:
|
|
rpt.begin_report()
|
|
rpt.write_report()
|
|
self.doc.close()
|
|
except ReportError as msg:
|
|
(msg1, msg2) = msg.messages()
|
|
msg2 += ' (%s)' % name # which report has the error?
|
|
ErrorDialog(msg1, msg2, parent=self.uistate.window)
|
|
return
|
|
except FilterError as msg:
|
|
(msg1, msg2) = msg.messages()
|
|
ErrorDialog(msg1, msg2, parent=self.uistate.window)
|
|
return
|
|
|
|
if self.open_with_app.get_active():
|
|
open_file_with_default_application(self.target_path, self.uistate)
|
|
|
|
def init_options(self, option_class):
|
|
try:
|
|
if issubclass(option_class, object):
|
|
self.options = option_class(self.raw_name, self.database)
|
|
except TypeError:
|
|
self.options = option_class
|
|
self.options.load_previous_values()
|
|
handler = self.options.handler
|
|
if self.book.get_paper_name():
|
|
handler.set_paper_name(self.book.get_paper_name())
|
|
if self.book.get_orientation() is not None: # 0 is legal
|
|
handler.set_orientation(self.book.get_orientation())
|
|
if self.book.get_paper_metric() is not None: # 0 is legal
|
|
handler.set_paper_metric(self.book.get_paper_metric())
|
|
if self.book.get_custom_paper_size():
|
|
handler.set_custom_paper_size(self.book.get_custom_paper_size())
|
|
if self.book.get_margins():
|
|
handler.set_margins(self.book.get_margins())
|
|
if self.book.get_format_name():
|
|
handler.set_format_name(self.book.get_format_name())
|
|
if self.book.get_output():
|
|
self.options.set_output(self.book.get_output())
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# Generic task function for book
|
|
#
|
|
#------------------------------------------------------------------------
|
|
def write_book_item(database, report_class, options, user):
|
|
"""
|
|
Write the report using options set.
|
|
All user dialog has already been handled and the output file opened.
|
|
"""
|
|
try:
|
|
return report_class(database, options, user)
|
|
except ReportError as msg:
|
|
(msg1, msg2) = msg.messages()
|
|
ErrorDialog(msg1, msg2, parent=user.uistate.window)
|
|
except FilterError as msg:
|
|
(msg1, msg2) = msg.messages()
|
|
ErrorDialog(msg1, msg2, parent=user.uistate.window)
|
|
except:
|
|
LOG.error("Failed to write book item.", exc_info=True)
|
|
return None
|