diff --git a/gramps/gen/plug/docgen/basedoc.py b/gramps/gen/plug/docgen/basedoc.py index a1b4fb040..3e967b10c 100644 --- a/gramps/gen/plug/docgen/basedoc.py +++ b/gramps/gen/plug/docgen/basedoc.py @@ -60,7 +60,7 @@ class BaseDoc(metaclass=ABCMeta): such as OpenOffice, AbiWord, and LaTeX are derived from this base class, providing a common interface to all document generators. """ - def __init__(self, styles, paper_style): + def __init__(self, styles, paper_style, track=[]): """ Create a BaseDoc instance, which provides a document generation interface. This class should never be instantiated directly, but @@ -70,9 +70,11 @@ class BaseDoc(metaclass=ABCMeta): :param paper_style: :class:`.PaperStyle` instance containing information about the paper. If set to None, then the document is not a page oriented document (e.g. HTML) + :param track: used in quick reports for GUI window management """ self.paper = paper_style self._style_sheet = styles + self.track = track self._creator = "" self.init_called = False diff --git a/gramps/gui/displaystate.py b/gramps/gui/displaystate.py index ddd8752ce..971ccb072 100644 --- a/gramps/gui/displaystate.py +++ b/gramps/gui/displaystate.py @@ -408,6 +408,7 @@ class DisplayState(Callback): self.disprel_defpers = None self.disprel_active = None self.set_relationship_class() + self.export = False formatter = logging.Formatter('%(levelname)s %(name)s: %(message)s') warnbtn = status.get_warning_button() @@ -527,6 +528,16 @@ class DisplayState(Callback): else: return "" + def set_export_mode(self, value): + self.set_busy_cursor(value) + if value == self.export: + return + else: + self.export = value + + def get_export_mode(self): + return self.export + def set_busy_cursor(self, value): if value == self.busy: return diff --git a/gramps/gui/managedwindow.py b/gramps/gui/managedwindow.py index e0c2676d7..70e9e7654 100644 --- a/gramps/gui/managedwindow.py +++ b/gramps/gui/managedwindow.py @@ -139,6 +139,21 @@ class GrampsWindowManager: # Return None if the ID is not found return self.id2item.get(item_id, None) + def get_item_from_window(self, window): + """ This finds a ManagedWindow from a Gtk top_level object (typicaly + a window). + + For example, to find my managedwindow track within a class of Gtk + widget: + mywindow = self.get_toplevel() # finds top level Gtk object + managed_window = self.uistate.gwm.get_item_from_window(mywindow) + track = managed_window.track + """ + for key, item in self.id2item.items(): + if item.window == window: + return self.id2item[key] + return None + def close_track(self, track): # This is called when item needs to be closed # Closes all its children and then removes the item from the tree. @@ -311,7 +326,7 @@ class ManagedWindow: menu, keeping track of child windows, closing them on close/delete event, and presenting itself when selected or attempted to create again. """ - def __init__(self, uistate, track, obj): + def __init__(self, uistate, track, obj, modal=False): """ Create child windows and add itself to menu, if not there already. @@ -321,18 +336,19 @@ class ManagedWindow: from .managedwindow import ManagedWindow class SomeWindowClass(ManagedWindow): - def __init__(self,uistate,dbstate,track): + def __init__(self, uistate, dbstate, track, modal): window_id = self # Or e.g. window_id = person.handle - submenu_label = None # This window cannot have children - menu_label = 'Menu label for this window' ManagedWindow.__init__(self, uistate, track, window_id, - submenu_label, - menu_label) + modal=False) # Proceed with the class. ... + def build_menu_names(self, obj): + submenu_label = None # This window cannot have children + menu_label = 'Menu label for this window' + return (menu_label, submenu_label) :param uistate: gramps uistate :param track: {list of parent windows, [] if the main GRAMPS window @@ -342,7 +358,17 @@ class ManagedWindow: which works on this obj and creates menu labels for use in the Gramps Window Menu. If self.submenu_label ='' then leaf, otherwise branch + :param modal: True/False, if True, this window is made modal + (always on top, and always has focus). Any child + windows are also automatically made modal by moving + the modal flag to the child. On close of a child + the modal flag is sent back to the parent. + If a modal window is used and has children, its and any child 'track' + parameters must properly be set to the parents 'self.track'. + Only one modal window can be supported by Gtk without potentially + hiding of a modal window while it has focus. So don't use direct + non managed Gtk windows as children and set them modal. """ window_key = self.build_window_key(obj) @@ -354,6 +380,7 @@ class ManagedWindow: self.horiz_position_key = None self.vert_position_key = None self.__refs_for_deletion = [] + self.modal = modal if uistate and uistate.gwm.get_item_from_id(window_key): uistate.gwm.get_item_from_id(window_key).present() @@ -382,10 +409,13 @@ class ManagedWindow: parent_item_track.append(0) # Based on the track, get item and then window object - self.parent_window = self.uistate.gwm.get_item_from_track( - parent_item_track).window + managed_parent = self.uistate.gwm.get_item_from_track( + parent_item_track) + self.parent_window = managed_parent.window + self.parent_modal = managed_parent.modal else: # On the top level: we use gramps top window + self.parent_modal = False if self.uistate: self.parent_window = self.uistate.window else: @@ -412,11 +442,18 @@ class ManagedWindow: self.titlelabel = title if self.isWindow : set_titles(self, title, text, msg) + self.window = self else : set_titles(window, title, text, msg) #closing the Gtk.Window must also close ManagedWindow self.window = window self.window.connect('delete-event', self.close) + if self.modal: + self.window.set_modal(True) + if self.parent_modal: + self.parent_window.set_modal(False) + self.window.set_modal(True) + self.modal = True def get_window(self): """ @@ -513,6 +550,8 @@ class ManagedWindow: self.clean_up() self.uistate.gwm.close_track(self.track) self.opened = False + if self.parent_modal: + self.parent_window.set_modal(True) self.parent_window.present() def present(self): diff --git a/gramps/gui/plug/export/_exportassistant.py b/gramps/gui/plug/export/_exportassistant.py index 99982879a..d201fee37 100644 --- a/gramps/gui/plug/export/_exportassistant.py +++ b/gramps/gui/plug/export/_exportassistant.py @@ -109,12 +109,15 @@ class ExportAssistant(Gtk.Assistant, ManagedWindow) : self.writestarted = False self.confirm = None + # set export mode and busy mode to avoid all other operations + self.uistate.set_export_mode(True) + #set up Assistant Gtk.Assistant.__init__(self) #set up ManagedWindow self.top_title = _("Export Assistant") - ManagedWindow.__init__(self, uistate, [], self.__class__) + ManagedWindow.__init__(self, uistate, [], self.__class__, modal=True) #set_window is present in both parent classes ManagedWindow.set_window(self, self, None, @@ -156,7 +159,7 @@ class ExportAssistant(Gtk.Assistant, ManagedWindow) : def build_menu_names(self, obj): """Override ManagedWindow method.""" - return (self.top_title, None) + return (self.top_title, self.top_title) def create_page_intro(self): """Create the introduction page.""" @@ -272,7 +275,8 @@ class ExportAssistant(Gtk.Assistant, ManagedWindow) : list(map(vbox.remove, vbox.get_children())) # add new content if config_box_class: - self.option_box_instance = config_box_class(self.person, self.dbstate, self.uistate) + self.option_box_instance = config_box_class( + self.person, self.dbstate, self.uistate, track=self.track) box = self.option_box_instance.get_option_box() vbox.add(box) else: @@ -387,6 +391,7 @@ class ExportAssistant(Gtk.Assistant, ManagedWindow) : pass def do_close(self): + self.uistate.set_export_mode(False) if self.writestarted : pass else : @@ -609,11 +614,9 @@ class ExportAssistant(Gtk.Assistant, ManagedWindow) : page.set_child_visible(True) self.show_all() - self.uistate.set_busy_cursor(True) self.set_busy_cursor(1) def post_save(self): - self.uistate.set_busy_cursor(False) self.set_busy_cursor(0) self.progressbar.hide() self.writestarted = False diff --git a/gramps/gui/plug/export/_exportoptions.py b/gramps/gui/plug/export/_exportoptions.py index 0b2ba0b6d..f2a119af3 100644 --- a/gramps/gui/plug/export/_exportoptions.py +++ b/gramps/gui/plug/export/_exportoptions.py @@ -106,10 +106,11 @@ class WriterOptionBox: the options. """ - def __init__(self, person, dbstate, uistate): + def __init__(self, person, dbstate, uistate, track=[]): self.person = person self.dbstate = dbstate self.uistate = uistate + self.track = track self.preview_dbase = None self.preview_button = None self.preview_proxy_button = {} @@ -247,7 +248,8 @@ class WriterOptionBox: run_quick_report_by_name(dbstate, self.uistate, 'filterbyname', - 'all') + 'all', + track=self.track) def preview(self, widget): """ @@ -658,7 +660,7 @@ class WriterOptionBox: else: the_filter = GenericFilterFactory(namespace)() if the_filter: - EditFilter(namespace, self.dbstate, self.uistate, [], + EditFilter(namespace, self.dbstate, self.uistate, self.track, the_filter, filterdb, lambda : self.edit_filter_save(filterdb, namespace)) else: # can't edit this filter diff --git a/gramps/gui/plug/quick/_quickreports.py b/gramps/gui/plug/quick/_quickreports.py index a8e11d61c..4cca1f298 100644 --- a/gramps/gui/plug/quick/_quickreports.py +++ b/gramps/gui/plug/quick/_quickreports.py @@ -179,7 +179,7 @@ def get_quick_report_list(qv_category=None): return names def run_quick_report_by_name(dbstate, uistate, report_name, handle, - container=None, **kwargs): + container=None, track=[], **kwargs): """ Run a QuickView by name. **kwargs provides a way of passing special quick views additional @@ -193,7 +193,8 @@ def run_quick_report_by_name(dbstate, uistate, report_name, handle, break if report: return run_report(dbstate, uistate, report.category, - handle, report, container=container, **kwargs) + handle, report, container=container, + track=track, **kwargs) else: raise AttributeError("No such quick report '%s'" % report_name) @@ -226,7 +227,7 @@ def run_quick_report_by_name_direct(report_name, database, document, handle): raise AttributeError("No such quick report id = '%s'" % report_name) def run_report(dbstate, uistate, category, handle, pdata, container=None, - **kwargs): + track=[], **kwargs): """ Run a Quick Report. Optionally container can be passed, rather than putting the report @@ -241,7 +242,7 @@ def run_report(dbstate, uistate, category, handle, pdata, container=None, return func = eval('mod.' + pdata.runfunc) if handle: - d = TextBufDoc(make_basic_stylesheet(), None) + d = TextBufDoc(make_basic_stylesheet(), None, track=track) d.dbstate = dbstate d.uistate = uistate if isinstance(handle, str): # a handle diff --git a/gramps/gui/plug/quick/_quicktable.py b/gramps/gui/plug/quick/_quicktable.py index 5da6856fc..1070b4628 100644 --- a/gramps/gui/plug/quick/_quicktable.py +++ b/gramps/gui/plug/quick/_quicktable.py @@ -75,6 +75,12 @@ class QuickTable(SimpleTable): self._callback_leftdouble = callback def button_press_event(self, treeview, event): + wid = treeview.get_toplevel() + try: + winmgr = self.simpledoc.doc.uistate.gwm + self.track = winmgr.get_item_from_window(wid).track + except: + self.track = [] index = None button_code = None event_time = None @@ -112,46 +118,50 @@ class QuickTable(SimpleTable): treeview.grab_focus() treeview.set_cursor(path, col, 0) if store and node: - index = store.get_value(node, 0) # index Below, + index = store.get_value(node, 0) # index Below, # you need index, treeview, path, button_code, # func, and event_time if index is not None: + if self._link[index]: + objclass, handle = self._link[index] + else: + return False + if (self.simpledoc.doc.uistate.get_export_mode() and + objclass != 'Filter'): + return False # avoid edition during export self.popup = Gtk.Menu() popup = self.popup menu_item = Gtk.MenuItem(label=_("Copy all")) - menu_item.connect("activate", lambda widget: text_to_clipboard(model_to_text(treeview.get_model()))) + menu_item.connect("activate", lambda widget: text_to_clipboard( + model_to_text(treeview.get_model()))) popup.append(menu_item) menu_item.show() # Now add more items to popup menu, if available - if (index is not None and self._link[index]): # See details (edit, etc): - objclass, handle = self._link[index] - menu_item = Gtk.MenuItem(label=_("the object|See %s details") % glocale.trans_objclass(objclass)) + menu_item = Gtk.MenuItem(label=_("the object|See %s details") % + glocale.trans_objclass(objclass)) + menu_item.connect( + "activate", lambda widget: self.on_table_doubleclick(treeview)) + popup.append(menu_item) + menu_item.show() + # Add other items to menu: + if objclass == 'Person': + menu_item = Gtk.MenuItem(label=_("the object|Make %s active") + % glocale.trans_objclass('Person')) menu_item.connect("activate", - lambda widget: self.on_table_doubleclick(treeview)) + lambda widget: self.on_table_click(treeview)) popup.append(menu_item) menu_item.show() - # Add other items to menu: - if (self._callback_leftclick or - (index is not None and self._link[index])): - objclass, handle = self._link[index] - if objclass == 'Person': - menu_item = Gtk.MenuItem(label=_("the object|Make %s active") % glocale.trans_objclass('Person')) - menu_item.connect("activate", - lambda widget: self.on_table_click(treeview)) - popup.append(menu_item) - menu_item.show() if (self.simpledoc.doc.dbstate.db != - self.simpledoc.doc.dbstate.db.basedb and - (index is not None and self._link[index])): - objclass, handle = self._link[index] + self.simpledoc.doc.dbstate.db.basedb): if (objclass == 'Filter' and handle[0] in ['Person', 'Family', 'Place', 'Event', 'Repository', 'Note', 'Media', 'Citation', 'Source']): menu_item = Gtk.MenuItem(label=_("See data not in Filter")) - menu_item.connect("activate", - lambda widget: self.show_not_in_filter(handle[0])) + menu_item.connect( + "activate", + lambda widget: self.show_not_in_filter(handle[0])) popup.append(menu_item) menu_item.show() # Show the popup menu: @@ -163,7 +173,8 @@ class QuickTable(SimpleTable): run_quick_report_by_name(self.simpledoc.doc.dbstate, self.simpledoc.doc.uistate, 'filterbyname', - 'Inverse %s' % obj_class) + 'Inverse %s' % obj_class, + track=self.track) def on_table_doubleclick(self, obj): """ @@ -269,14 +280,15 @@ class QuickTable(SimpleTable): self.simpledoc.doc.uistate, 'filterbyname', 'list of people', - handles=handle) + handle=handle, + track=self.track) elif objclass == 'Filter': if isinstance(handle, list): handle = handle[0] run_quick_report_by_name(self.simpledoc.doc.dbstate, self.simpledoc.doc.uistate, 'filterbyname', - handle) + handle, track=self.track) return False # didn't handle event def on_table_click(self, obj): diff --git a/gramps/gui/plug/quick/_textbufdoc.py b/gramps/gui/plug/quick/_textbufdoc.py index f2bf0a2b8..cefea83e0 100644 --- a/gramps/gui/plug/quick/_textbufdoc.py +++ b/gramps/gui/plug/quick/_textbufdoc.py @@ -53,14 +53,14 @@ LEFT,RIGHT,CENTER = 'LEFT','RIGHT','CENTER' _WIDTH_IN_CHARS = 72 class DisplayBuf(ManagedWindow): - def __init__(self, title, document): + def __init__(self, title, document, track=[]): self.title = title - ManagedWindow.__init__(self, document.uistate, [], - document) - self.set_window(Gtk.Dialog("", document.uistate.window, - Gtk.DialogFlags.DESTROY_WITH_PARENT, - (_('_Close'), Gtk.ResponseType.CLOSE)), - None, title) + ManagedWindow.__init__(self, document.uistate, track, document) + self.set_window(Gtk.Dialog(title="", + flags=Gtk.DialogFlags.DESTROY_WITH_PARENT, + buttons=(_('_Close'), + Gtk.ResponseType.CLOSE)), + None, title, True) self.window.set_size_request(600,400) scrolled_window = Gtk.ScrolledWindow() scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC,Gtk.PolicyType.AUTOMATIC) @@ -69,7 +69,7 @@ class DisplayBuf(ManagedWindow): self.window.connect('response', self.close) scrolled_window.add(document.text_view) self.window.vbox.pack_start(scrolled_window, True, True, 0) - self.window.show_all() + self.show() # should use ManagedWindow version of show def build_menu_names(self, obj): return ('View', _('Quick View')) @@ -154,7 +154,7 @@ class TextBufDoc(BaseDoc, TextDoc): if container: return DocumentManager(_('Quick View'), self, container) else: - DisplayBuf(_('Quick View'), self) + DisplayBuf(_('Quick View'), self, track=self.track) return #--------------------------------------------------------------------