diff --git a/src/gui/widgets/grampletpane.py b/src/gui/widgets/grampletpane.py new file mode 100644 index 000000000..4c587eefb --- /dev/null +++ b/src/gui/widgets/grampletpane.py @@ -0,0 +1,1213 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +GrampletView interface. +""" + +#------------------------------------------------------------------------- +# +# Python modules +# +#------------------------------------------------------------------------- +import gtk +import pango +import traceback +import time +import types +import os +from gen.ggettext import gettext as _ + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +import Errors +import const +from gui.editors import EditPerson, EditFamily +import ManagedWindow +import ConfigParser +from gui.utils import add_menuitem +from QuickReports import run_quick_report_by_name +import GrampsDisplay +from glade import Glade +from gui.pluginmanager import GuiPluginManager + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- +WIKI_HELP_PAGE = const.URL_MANUAL_PAGE + '_-_Gramplets' + +#------------------------------------------------------------------------- +# +# Globals +# +#------------------------------------------------------------------------- +PLUGMAN = GuiPluginManager.get_instance() + +GRAMPLET_FILENAME = os.path.join(const.HOME_DIR,"gramplets.ini") +NL = "\n" + +debug = False + +def AVAILABLE_GRAMPLETS(): + return [gplug.id for gplug in PLUGMAN.get_reg_gramplets()] + +def GET_AVAILABLE_GRAMPLETS(name): + for gplug in PLUGMAN.get_reg_gramplets(): + if gplug.id == name: + return { + "name": gplug.id, + "tname": gplug.name, + "version": gplug.version, + "height": gplug.height, + "expand": gplug.expand, + "title": gplug.gramplet_title, + "content": gplug.gramplet, + "detached_width": gplug.detached_width, + "detached_height": gplug.detached_height, + "state": "maximized", + "gramps": "0.0.0", + "column": -1, + "row": -1, + "data": [], + "help_url": gplug.help_url, + } + return None + +def parse_tag_attr(text): + """ + Function used to parse markup. + """ + text = text.strip() + parts = text.split(" ", 1) + attrs = {} + if len(parts) == 2: + attr_values = parts[1].split(" ") # "name=value name=value" + for av in attr_values: + attribute, value = av.split("=", 1) + value = value.strip() + # trim off quotes: + if value[0] == value[-1] and value[0] in ['"', "'"]: + value = value[1:-1] + attrs[attribute.strip().lower()] = value + return [parts[0].upper(), attrs] + +def get_gramplet_opts(name, opts): + """ + Lookup the options for a given gramplet name and update + the options with provided dictionary, opts. + """ + if name in AVAILABLE_GRAMPLETS(): + data = GET_AVAILABLE_GRAMPLETS(name) + my_data = data.copy() + my_data.update(opts) + return my_data + else: + print ("Unknown gramplet name: '%s'" % name) + return {} + +def get_gramplet_options_by_name(name): + """ + Get options by gramplet name. + """ + if debug: print "name:", name + if name in AVAILABLE_GRAMPLETS(): + return GET_AVAILABLE_GRAMPLETS(name).copy() + else: + print ("Unknown gramplet name: '%s'" % name) + return None + +def get_gramplet_options_by_tname(name): + """ + get options by translated name. + """ + if debug: print "name:", name + for key in AVAILABLE_GRAMPLETS(): + if GET_AVAILABLE_GRAMPLETS(key)["tname"] == name: + return GET_AVAILABLE_GRAMPLETS(key).copy() + print ("Unknown gramplet name: '%s'" % name) + return None + +def make_requested_gramplet(pane, name, opts, dbstate, uistate): + """ + Make a GUI gramplet given its name. + """ + if name in AVAILABLE_GRAMPLETS(): + gui = GuiGramplet(pane, dbstate, uistate, **opts) + if opts.get("content", None): + pdata = PLUGMAN.get_plugin(opts["name"]) + module = PLUGMAN.load_plugin(pdata) + if module: + getattr(module, opts["content"])(gui) + else: + print "Error loading gramplet '%s': skipping content" \ + % opts["name"] + # now that we have user code, set the tooltips + msg = gui.tooltip + if msg is None: + msg = _("Drag Properties Button to move and click it for setup") + if msg: + gui.scrolledwindow.set_tooltip_text(msg) + gui.tooltips_text = msg + gui.make_gui_options() + gui.gvoptions.hide() + return gui + return None + +def logical_true(value): + """ + Used for converting text file values to booleans. + """ + return value in ["True", True, 1, "1"] + +class LinkTag(gtk.TextTag): + """ + Class for keeping track of link data. + """ + lid = 0 + def __init__(self, buffer): + LinkTag.lid += 1 + gtk.TextTag.__init__(self, str(LinkTag.lid)) + tag_table = buffer.get_tag_table() + self.set_property('foreground', "blue") + #self.set_property('underline', pango.UNDERLINE_SINGLE) + try: + tag_table.add(self) + except ValueError: # tag is already in tag table + pass + +class GrampletWindow(ManagedWindow.ManagedWindow): + """ + Class for showing a detached gramplet. + """ + def __init__(self, gramplet): + """ + Constructs the window, and loads the GUI gramplet. + """ + self.title = gramplet.title + " " + _("Gramplet") + self.gramplet = gramplet + # Keep track of what state it was in: + self.docked_state = gramplet.state + # Now detach it + self.gramplet.set_state("detached") + ManagedWindow.ManagedWindow.__init__(self, gramplet.uistate, [], self.title) + self.set_window(gtk.Dialog("",gramplet.uistate.window, + gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)), + None, self.title) + self.window.set_size_request(gramplet.detached_width, + gramplet.detached_height) + self.window.add_button(gtk.STOCK_HELP, gtk.RESPONSE_HELP) + # add gramplet: + if self.gramplet.pui: + self.gramplet.pui.active = True + self.gramplet.mainframe.reparent(self.window.vbox) + self.window.connect('response', self.handle_response) + # HACK: must show window to make it work right: + self.show() + # But that shows everything, hide them here: + self.gramplet.gvclose.hide() + self.gramplet.gvstate.hide() + self.gramplet.gvproperties.hide() + if self.gramplet.pui and len(self.gramplet.pui.option_dict) > 0: + self.gramplet.gvoptions.show() + else: + self.gramplet.gvoptions.hide() + + def handle_response(self, object, response): + """ + Callback for taking care of button clicks. + """ + if response in [gtk.RESPONSE_CLOSE, gtk.STOCK_CLOSE]: + self.close() + elif response == gtk.RESPONSE_HELP: + # translated name: + if self.gramplet.help_url: + if self.gramplet.help_url.startswith("http://"): + GrampsDisplay.url(self.gramplet.help_url) + else: + GrampsDisplay.help(self.gramplet.help_url) + else: + GrampsDisplay.help(WIKI_HELP_PAGE, + self.gramplet.tname.replace(" ", "_")) + + def build_menu_names(self, obj): + """ + Part of the GRAMPS window interface. + """ + return (self.title, 'Gramplet') + + def get_title(self): + """ + Returns the window title. + """ + return self.title + + def close(self, *args): + """ + Dock the detached GrampletWindow back in the column from where it came. + """ + self.gramplet.gvoptions.hide() + self.gramplet.pane.detached_gramplets.remove(self.gramplet) + if self.docked_state == "minimized": + self.gramplet.set_state("minimized") + else: + self.gramplet.set_state("maximized") + pane = self.gramplet.pane + col = self.gramplet.column + stack = [] + for gframe in pane.columns[col]: + gramplet = pane.frame_map[str(gframe)] + if gramplet.row > self.gramplet.row: + pane.columns[col].remove(gframe) + stack.append(gframe) + expand = self.gramplet.state == "maximized" and self.gramplet.expand + column = pane.columns[col] + parent = self.gramplet.pane.get_column_frame(self.gramplet.column) + self.gramplet.mainframe.reparent(parent) + if self.gramplet.pui: + self.gramplet.pui.active = self.gramplet.pane.pageview.active + for gframe in stack: + gramplet = pane.frame_map[str(gframe)] + expand = gramplet.state == "maximized" and gramplet.expand + pane.columns[col].pack_start(gframe, expand=expand) + # Now make sure they all have the correct expand: + for gframe in pane.columns[col]: + gramplet = pane.frame_map[str(gframe)] + expand, fill, padding, pack = column.query_child_packing(gramplet.mainframe) + expand = gramplet.state == "maximized" and gramplet.expand + column.set_child_packing(gramplet.mainframe, expand, fill, padding, pack) + self.gramplet.gvclose.show() + self.gramplet.gvstate.show() + self.gramplet.gvproperties.show() + ManagedWindow.ManagedWindow.close(self, *args) + +#------------------------------------------------------------------------ + +class GuiGramplet(object): + """ + Class that handles the plugin interfaces for the GrampletView. + """ + TARGET_TYPE_FRAME = 80 + LOCAL_DRAG_TYPE = 'GRAMPLET' + LOCAL_DRAG_TARGET = (LOCAL_DRAG_TYPE, 0, TARGET_TYPE_FRAME) + def __init__(self, pane, dbstate, uistate, title, **kwargs): + """ + Internal constructor for GUI portion of a gramplet. + """ + self.pane = pane + self.dbstate = dbstate + self.uistate = uistate + self.title = title + self.force_update = False + self._tags = [] + self.link_cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR) + self.standard_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM) + ########## Set defaults + self.name = kwargs.get("name", "Unnamed Gramplet") + self.tname = kwargs.get("tname", "Unnamed Gramplet") + self.version = kwargs.get("version", "0.0.0") + self.gramps = kwargs.get("gramps", "0.0.0") + self.expand = logical_true(kwargs.get("expand", False)) + self.height = int(kwargs.get("height", 200)) + self.column = int(kwargs.get("column", -1)) + self.detached_height = int(kwargs.get("detached_height", 300)) + self.detached_width = int(kwargs.get("detached_width", 400)) + self.row = int(kwargs.get("row", -1)) + self.state = kwargs.get("state", "maximized") + self.data = kwargs.get("data", []) + self.help_url = kwargs.get("help_url", None) + ########## + self.use_markup = False + self.pui = None # user code + self.tooltip = None # text + self.tooltips = None # gtk tooltip widget + self.tooltips_text = None + + self.xml = Glade() + self.gvwin = self.xml.toplevel + self.mainframe = self.xml.get_object('gvgramplet') + self.gvwin.remove(self.mainframe) + + self.gvoptions = self.xml.get_object('gvoptions') + self.textview = self.xml.get_object('gvtextview') + self.buffer = self.textview.get_buffer() + self.scrolledwindow = self.xml.get_object('gvscrolledwindow') + self.vboxtop = self.xml.get_object('vboxtop') + self.titlelabel = self.xml.get_object('gvtitle') + self.titlelabel.set_text("%s" % self.title) + self.titlelabel.set_use_markup(True) + self.gvclose = self.xml.get_object('gvclose') + self.gvclose.connect('clicked', self.close) + self.gvstate = self.xml.get_object('gvstate') + self.gvstate.connect('clicked', self.change_state) + self.gvproperties = self.xml.get_object('gvproperties') + self.gvproperties.connect('clicked', self.set_properties) + self.xml.get_object('gvcloseimage').set_from_stock(gtk.STOCK_CLOSE, + gtk.ICON_SIZE_MENU) + self.xml.get_object('gvstateimage').set_from_stock(gtk.STOCK_REMOVE, + gtk.ICON_SIZE_MENU) + self.xml.get_object('gvpropertiesimage').set_from_stock(gtk.STOCK_PROPERTIES, + gtk.ICON_SIZE_MENU) + + # source: + drag = self.gvproperties + drag.drag_source_set(gtk.gdk.BUTTON1_MASK, + [GuiGramplet.LOCAL_DRAG_TARGET], + gtk.gdk.ACTION_COPY) + + def close(self, *obj): + """ + Remove (delete) the gramplet from view. + """ + if self.state == "detached": + return + self.state = "closed" + self.pane.closed_gramplets.append(self) + self.mainframe.get_parent().remove(self.mainframe) + + def detach(self): + """ + Detach the gramplet from the GrampletView, and open in own window. + """ + # hide buttons: + #self.set_state("detached") + self.pane.detached_gramplets.append(self) + # make a window, and attach it there + self.detached_window = GrampletWindow(self) + + def set_state(self, state): + """ + Set the state of a gramplet. + """ + oldstate = self.state + self.state = state + if state == "minimized": + self.scrolledwindow.hide() + self.xml.get_object('gvstateimage').set_from_stock(gtk.STOCK_ADD, + gtk.ICON_SIZE_MENU) + column = self.mainframe.get_parent() # column + expand,fill,padding,pack = column.query_child_packing(self.mainframe) + column.set_child_packing(self.mainframe,False,fill,padding,pack) + + else: + self.scrolledwindow.show() + self.xml.get_object('gvstateimage').set_from_stock(gtk.STOCK_REMOVE, + gtk.ICON_SIZE_MENU) + column = self.mainframe.get_parent() # column + expand,fill,padding,pack = column.query_child_packing(self.mainframe) + column.set_child_packing(self.mainframe, + self.expand, + fill, + padding, + pack) + if self.pui and self.pui.dirty: + self.pui.update() + + def change_state(self, obj): + """ + Change the state of a gramplet. + """ + if self.state == "detached": + pass # don't change if detached + else: + if self.state == "maximized": + self.set_state("minimized") + else: + self.set_state("maximized") + + def set_properties(self, obj): + """ + Set the properties of a gramplet. + """ + if self.state == "detached": + pass + else: + self.detach() + return + self.expand = not self.expand + if self.state == "maximized": + column = self.mainframe.get_parent() # column + expand,fill,padding,pack = column.query_child_packing(self.mainframe) + column.set_child_packing(self.mainframe,self.expand,fill,padding,pack) + + def append_text(self, text, scroll_to="end"): + enditer = self.buffer.get_end_iter() + start = self.buffer.create_mark(None, enditer, True) + self.buffer.insert(enditer, text) + if scroll_to == "end": + enditer = self.buffer.get_end_iter() + end = self.buffer.create_mark(None, enditer, True) + self.textview.scroll_to_mark(end, 0.0, True, 0, 0) + elif scroll_to == "start": # beginning of this append + self.textview.scroll_to_mark(start, 0.0, True, 0, 0) + elif scroll_to == "begin": # beginning of the buffer + begin_iter = self.buffer.get_start_iter() + begin = self.buffer.create_mark(None, begin_iter, True) + self.textview.scroll_to_mark(begin, 0.0, True, 0, 0) + else: + raise AttributeError, ("no such cursor position: '%s'" % scroll_to) + + def clear_text(self): + self.buffer.set_text('') + + def get_text(self): + start = self.buffer.get_start_iter() + end = self.buffer.get_end_iter() + return self.buffer.get_text(start, end) + + def insert_text(self, text): + self.buffer.insert_at_cursor(text) + + def len_text(self, text): + i = 0 + r = 0 + while i < len(text): + if ord(text[i]) > 126: + t = 0 + while i < len(text) and ord(text[i]) > 126: + i += 1 + t += 1 + r += t/2 + elif text[i] == "\\": + r += 1 + i += 2 + else: + r += 1 + i += 1 + return r + + def render_text(self, text): + markup_pos = {"B": [], "I": [], "U": [], "A": [], "TT": []} + retval = "" + i = 0 + r = 0 + tag = "" + while i < len(text): + if text[i:i+2] == "") + if stop < 0: + retval += text[i] + r += 1 + i += 1 + else: + markup = text[i+2:i+stop].upper() # close tag + markup_pos[markup][-1].append(r) + i += stop + 1 + elif text[i] == "<": + # start of start tag + stop = text[i:].find(">") + if stop < 0: + retval += text[i] + r += 1 + i += 1 + else: + markup, attr = parse_tag_attr(text[i+1:i+stop]) + markup_pos[markup].append([r, attr]) + i += stop + 1 + elif text[i] == "\\": + retval += text[i+1] + r += 1 + i += 2 + elif ord(text[i]) > 126: + while ord(text[i]) > 126: + retval += text[i] + i += 1 + r += 1 + else: + retval += text[i] + r += 1 + i += 1 + offset = self.len_text(self.get_text()) + self.append_text(retval) + for items in markup_pos["TT"]: + if len(items) == 3: + (a,attributes,b) = items + start = self.buffer.get_iter_at_offset(a + offset) + stop = self.buffer.get_iter_at_offset(b + offset) + self.buffer.apply_tag_by_name("fixed", start, stop) + for items in markup_pos["B"]: + if len(items) == 3: + (a,attributes,b) = items + start = self.buffer.get_iter_at_offset(a + offset) + stop = self.buffer.get_iter_at_offset(b + offset) + self.buffer.apply_tag_by_name("bold", start, stop) + for items in markup_pos["I"]: + if len(items) == 3: + (a,attributes,b) = items + start = self.buffer.get_iter_at_offset(a + offset) + stop = self.buffer.get_iter_at_offset(b + offset) + self.buffer.apply_tag_by_name("italic", start, stop) + for items in markup_pos["U"]: + if len(items) == 3: + (a,attributes,b) = items + start = self.buffer.get_iter_at_offset(a + offset) + stop = self.buffer.get_iter_at_offset(b + offset) + self.buffer.apply_tag_by_name("underline", start, stop) + for items in markup_pos["A"]: + if len(items) == 3: + (a,attributes,b) = items + start = self.buffer.get_iter_at_offset(a + offset) + stop = self.buffer.get_iter_at_offset(b + offset) + if "href" in attributes: + url = attributes["href"] + self.link_region(start, stop, "URL", url) # tooltip? + elif "wiki" in attributes: + url = attributes["wiki"] + self.link_region(start, stop, "WIKI", url) # tooltip? + else: + print "warning: no url on link: '%s'" % text[start, stop] + + def link_region(self, start, stop, link_type, url): + link_data = (LinkTag(self.buffer), link_type, url, url) + self._tags.append(link_data) + self.buffer.apply_tag(link_data[0], start, stop) + + def set_use_markup(self, value): + if self.use_markup == value: return + self.use_markup = value + if value: + self.buffer.create_tag("bold",weight=pango.WEIGHT_HEAVY) + self.buffer.create_tag("italic",style=pango.STYLE_ITALIC) + self.buffer.create_tag("underline",underline=pango.UNDERLINE_SINGLE) + self.buffer.create_tag("fixed", font="monospace") + else: + tag_table = self.buffer.get_tag_table() + tag_table.foreach(lambda tag, data: tag_table.remove(tag)) + + def set_text(self, text, scroll_to='start'): + self.buffer.set_text('') + self.append_text(text, scroll_to) + + def get_source_widget(self): + """ + Hack to allow us to send this object to the drop_widget + method as a context. + """ + return self.gvproperties + + def get_container_widget(self): + return self.scrolledwindow + + def make_gui_options(self): + if not self.pui: return + topbox = gtk.VBox() + hbox = gtk.HBox() + labels = gtk.VBox() + options = gtk.VBox() + hbox.pack_start(labels, False) + hbox.pack_start(options, True) + topbox.add(hbox) + self.gvoptions.add(topbox) + for item in self.pui.option_order: + label = gtk.Label(item + ":") + label.set_alignment(1.0, 0.5) + labels.add(label) + options.add(self.pui.option_dict[item][0]) # widget + save_button = gtk.Button(stock=gtk.STOCK_SAVE) + topbox.add(save_button) + topbox.show_all() + save_button.connect('clicked', self.pui.save_update_options) + + def link(self, text, link_type, data, size=None, tooltip=None): + buffer = self.buffer + iter = buffer.get_end_iter() + offset = buffer.get_char_count() + self.append_text(text) + start = buffer.get_iter_at_offset(offset) + end = buffer.get_end_iter() + link_data = (LinkTag(buffer), link_type, data, tooltip) + if size: + link_data[0].set_property("size-points", size) + self._tags.append(link_data) + buffer.apply_tag(link_data[0], start, end) + + def on_motion(self, view, event): + buffer_location = view.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, + int(event.x), + int(event.y)) + iter = view.get_iter_at_location(*buffer_location) + cursor = self.standard_cursor + ttip = None + for (tag, link_type, handle, tooltip) in self._tags: + if iter.has_tag(tag): + tag.set_property('underline', pango.UNDERLINE_SINGLE) + cursor = self.link_cursor + ttip = tooltip + else: + tag.set_property('underline', pango.UNDERLINE_NONE) + view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(cursor) + if ttip: + self.scrolledwindow.set_tooltip_text(ttip) + elif self.tooltips_text: + self.scrolledwindow.set_tooltip_text(self.tooltips_text) + return False # handle event further, if necessary + + def on_button_press(self, view, event): + buffer_location = view.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, + int(event.x), + int(event.y)) + iter = view.get_iter_at_location(*buffer_location) + for (tag, link_type, handle, tooltip) in self._tags: + if iter.has_tag(tag): + if link_type == 'Person': + person = self.dbstate.db.get_person_from_handle(handle) + if person is not None: + if event.button == 1: # left mouse + if event.type == gtk.gdk._2BUTTON_PRESS: # double + try: + EditPerson(self.dbstate, + self.uistate, + [], person) + return True # handled event + except Errors.WindowActiveError: + pass + elif event.type == gtk.gdk.BUTTON_PRESS: # single click + self.uistate.set_active(handle, 'Person') + return True # handled event + elif event.button == 3: # right mouse + #FIXME: add a popup menu with options + try: + EditPerson(self.dbstate, + self.uistate, + [], person) + return True # handled event + except Errors.WindowActiveError: + pass + elif link_type == 'Surname': + if event.button == 1: # left mouse + if event.type == gtk.gdk._2BUTTON_PRESS: # double + run_quick_report_by_name(self.dbstate, + self.uistate, + 'samesurnames', + handle) + return True + elif link_type == 'Given': + if event.button == 1: # left mouse + if event.type == gtk.gdk._2BUTTON_PRESS: # double + run_quick_report_by_name(self.dbstate, + self.uistate, + 'samegivens_misc', + handle) + return True + elif link_type == 'Filter': + if event.button == 1: # left mouse + if event.type == gtk.gdk._2BUTTON_PRESS: # double + run_quick_report_by_name(self.dbstate, + self.uistate, + 'filterbyname', + handle) + return True + elif link_type == 'URL': + if event.button == 1: # left mouse + GrampsDisplay.url(handle) + return True + elif link_type == 'WIKI': + if event.button == 1: # left mouse + GrampsDisplay.help(handle.replace(" ", "_")) + return True + elif link_type == 'Family': + family = self.dbstate.db.get_family_from_handle(handle) + if family is not None: + if event.button == 1: # left mouse + if event.type == gtk.gdk._2BUTTON_PRESS: # double + try: + EditFamily(self.dbstate, + self.uistate, + [], family) + return True # handled event + except Errors.WindowActiveError: + pass + elif event.button == 3: # right mouse + #FIXME: add a popup menu with options + try: + EditFamily(self.dbstate, + self.uistate, + [], family) + return True # handled event + except Errors.WindowActiveError: + pass + elif link_type == 'PersonList': + if event.button == 1: # left mouse + if event.type == gtk.gdk._2BUTTON_PRESS: # double + run_quick_report_by_name(self.dbstate, + self.uistate, + 'filterbyname', + 'list of people', + handles=handle) + return True + elif link_type == 'Attribute': + if event.button == 1: # left mouse + if event.type == gtk.gdk._2BUTTON_PRESS: # double + run_quick_report_by_name(self.dbstate, + self.uistate, + 'attribute_match', + handle) + return True + return False # did not handle event + +class GrampletPane(gtk.ScrolledWindow): + def show_all(self): + # first show them all: + gtk.ScrolledWindow.show_all(self) + # Hack to get around show_all that shows hidden items + # do once, the first time showing + if self.pane: + gramplets = (g for g in self.pane.gramplet_map.itervalues() + if g is not None) + self.pane = None + for gramplet in gramplets: + gramplet.gvoptions.hide() + if gramplet.state == "minimized": + gramplet.set_state("minimized") + + def __init__(self, pageview, dbstate, uistate): + gtk.ScrolledWindow.__init__(self) + self.dbstate = dbstate + self.uistate = uistate + self.pageview = pageview + self.pane = self + self._popup_xy = None + user_gramplets = self.load_gramplets() + # build the GUI: + msg = _("Right click to add gramplets") + self.set_tooltip_text(msg) + self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.hbox = gtk.HBox(homogeneous=True) + # Set up drag and drop + self.drag_dest_set(gtk.DEST_DEFAULT_MOTION | + gtk.DEST_DEFAULT_HIGHLIGHT | + gtk.DEST_DEFAULT_DROP, + [('GRAMPLET', 0, 80)], + gtk.gdk.ACTION_COPY) + self.connect('drag_drop', self.drop_widget) + self.connect('button-press-event', self._button_press) + + self.add_with_viewport(self.hbox) + # Create the columns: + self.columns = [] + for i in range(self.column_count): + self.columns.append(gtk.VBox()) + self.hbox.pack_start(self.columns[-1],expand=True) + # Load the gramplets + self.gramplet_map = {} # title->gramplet + self.frame_map = {} # frame->gramplet + self.detached_gramplets = [] # list of detached gramplets + self.closed_gramplets = [] # list of closed gramplets + self.closed_opts = [] # list of closed options from ini file + # get the user's gramplets from ~/.gramps/gramplets.ini + # Load the user's gramplets: + for (name, opts) in user_gramplets: + all_opts = get_gramplet_opts(name, opts) + if "title" not in all_opts: + all_opts["title"] = "Untitled Gramplet" + if "state" not in all_opts: + all_opts["state"] = "maximized" + # uniqify titles: + unique = all_opts["title"] + cnt = 1 + while unique in self.gramplet_map: + unique = all_opts["title"] + ("-%d" % cnt) + cnt += 1 + all_opts["title"] = unique + if all_opts["state"] == "closed": + self.gramplet_map[all_opts["title"]] = None # save closed name + self.closed_opts.append(all_opts) + continue + g = make_requested_gramplet(self, name, all_opts, + self.dbstate, self.uistate) + if g: + self.gramplet_map[all_opts["title"]] = g + self.frame_map[str(g.mainframe)] = g + else: + print "Can't make gramplet of type '%s'." % name + self.place_gramplets() + + def get_column_frame(self, column_num): + if column_num < len(self.columns): + return self.columns[column_num] + else: + return self.columns[-1] # it was too big, so select largest + + def clear_gramplets(self): + """ + Detach all of the mainframe gramplets from the columns. + """ + gramplets = (g for g in self.gramplet_map.itervalues() + if g is not None) + for gramplet in gramplets: + if (gramplet.state == "detached" or gramplet.state == "closed"): + continue + column = gramplet.mainframe.get_parent() + if column: + column.remove(gramplet.mainframe) + + def place_gramplets(self, recolumn=False): + """ + Place the gramplet mainframes in the columns. + """ + gramplets = [g for g in self.gramplet_map.itervalues() + if g is not None] + # put the gramplets where they go: + # sort by row + gramplets.sort(key=lambda x: x.row) + rows = [0] * max(self.column_count, 1) + for cnt, gramplet in enumerate(gramplets): + # see if the user wants this in a particular location: + # and if there are that many columns + if gramplet.column >= 0 and gramplet.column < self.column_count: + pos = gramplet.column + else: + # else, spread them out: + pos = cnt % self.column_count + gramplet.column = pos + gramplet.row = rows[gramplet.column] + rows[gramplet.column] += 1 + if recolumn and (gramplet.state == "detached" or gramplet.state == "closed"): + continue + if gramplet.state == "minimized": + self.columns[pos].pack_start(gramplet.mainframe, expand=False) + else: + self.columns[pos].pack_start(gramplet.mainframe, expand=gramplet.expand) + # set height on gramplet.scrolledwindow here: + gramplet.scrolledwindow.set_size_request(-1, gramplet.height) + # Can't minimize here, because GRAMPS calls show_all later: + #if gramplet.state == "minimized": # starts max, change to min it + # gramplet.set_state("minimized") # minimize it + # set minimized is called in page subclass hack (above) + if gramplet.state == "detached": + gramplet.detach() + elif gramplet.state == "closed": + gramplet.close() + + def load_gramplets(self): + self.column_count = 2 # default for new user + retval = [] + filename = GRAMPLET_FILENAME + if filename and os.path.exists(filename): + cp = ConfigParser.ConfigParser() + cp.read(filename) + for sec in cp.sections(): + if sec == "Gramplet View Options": + if "column_count" in cp.options(sec): + self.column_count = int(cp.get(sec, "column_count")) + else: + data = {"title": sec} + for opt in cp.options(sec): + if opt.startswith("data["): + temp = data.get("data", []) + temp.append(cp.get(sec, opt).strip()) + data["data"] = temp + else: + data[opt] = cp.get(sec, opt).strip() + if "name" not in data: + data["name"] = "Unnamed Gramplet" + data["tname"]= _("Unnamed Gramplet") + retval.append((data["name"], data)) # name, opts + else: + # give defaults as currently known + for name in ["Top Surnames Gramplet", "Welcome Gramplet"]: + if name in AVAILABLE_GRAMPLETS(): + retval.append((name, GET_AVAILABLE_GRAMPLETS(name))) + return retval + + def save(self): + if debug: print "saving" + if len(self.frame_map) + len(self.detached_gramplets) == 0: + return # something is the matter + filename = GRAMPLET_FILENAME + try: + fp = open(filename, "w") + except: + print "Failed writing '%s'; gramplets not saved" % filename + return + fp.write(";; Gramps gramplets file" + NL) + fp.write((";; Automatically created at %s" % time.strftime("%Y/%m/%d %H:%M:%S")) + NL + NL) + fp.write("[Gramplet View Options]" + NL) + fp.write(("column_count=%d" + NL + NL) % self.column_count) + # showing gramplets: + for col in range(self.column_count): + row = 0 + for gframe in self.columns[col]: + gramplet = self.frame_map[str(gframe)] + opts = get_gramplet_options_by_name(gramplet.name) + if opts is not None: + base_opts = opts.copy() + for key in base_opts: + if key in gramplet.__dict__: + base_opts[key] = gramplet.__dict__[key] + fp.write(("[%s]" + NL) % gramplet.title) + for key in base_opts: + if key == "content": continue + elif key == "title": continue + elif key == "column": continue + elif key == "row": continue + elif key == "version": continue # code, don't save + elif key == "gramps": continue # code, don't save + elif key == "data": + if not isinstance(base_opts["data"], (list, tuple)): + fp.write(("data[0]=%s" + NL) % base_opts["data"]) + else: + cnt = 0 + for item in base_opts["data"]: + fp.write(("data[%d]=%s" + NL) % (cnt, item)) + cnt += 1 + else: + fp.write(("%s=%s" + NL)% (key, base_opts[key])) + fp.write(("column=%d" + NL) % col) + fp.write(("row=%d" + NL) % row) + fp.write(NL) + row += 1 + for gramplet in self.detached_gramplets: + opts = get_gramplet_options_by_name(gramplet.name) + if opts is not None: + base_opts = opts.copy() + for key in base_opts: + if key in gramplet.__dict__: + base_opts[key] = gramplet.__dict__[key] + fp.write(("[%s]" + NL) % gramplet.title) + for key in base_opts: + if key == "content": continue + elif key == "title": continue + elif key == "data": + if not isinstance(base_opts["data"], (list, tuple)): + fp.write(("data[0]=%s" + NL) % base_opts["data"]) + else: + cnt = 0 + for item in base_opts["data"]: + fp.write(("data[%d]=%s" + NL) % (cnt, item)) + cnt += 1 + else: + fp.write(("%s=%s" + NL)% (key, base_opts[key])) + fp.write(NL) + fp.close() + + def drop_widget(self, source, context, x, y, timedata): + """ + This is the destination method for handling drag and drop + of a gramplet onto the main scrolled window. + """ + button = context.get_source_widget() + hbox = button.get_parent() + mframe = hbox.get_parent() + mainframe = mframe.get_parent() # actually a vbox + rect = source.get_allocation() + sx, sy = rect.width, rect.height + # first, find column: + col = 0 + for i in range(len(self.columns)): + if x < (sx/len(self.columns) * (i + 1)): + col = i + break + fromcol = mainframe.get_parent() + if fromcol: + fromcol.remove(mainframe) + # now find where to insert in column: + stack = [] + current_row = 0 + for gframe in self.columns[col]: + gramplet = self.frame_map[str(gframe)] + gramplet.row = current_row + current_row += 1 + rect = gframe.get_allocation() + if y < (rect.y + 15): # starts at 0, this allows insert before + self.columns[col].remove(gframe) + stack.append(gframe) + maingramplet = self.frame_map.get(str(mainframe), None) + maingramplet.column = col + maingramplet.row = current_row + current_row += 1 + expand = maingramplet.state == "maximized" and maingramplet.expand + self.columns[col].pack_start(mainframe, expand=expand) + for gframe in stack: + gramplet = self.frame_map[str(gframe)] + gramplet.row = current_row + current_row += 1 + expand = gramplet.state == "maximized" and gramplet.expand + self.columns[col].pack_start(gframe, expand=expand) + return True + + def set_columns(self, num): + # clear the gramplets: + self.clear_gramplets() + # clear the columns: + for column in self.columns: + frame = column.get_parent() + frame.remove(column) + del column + # create the new ones: + self.column_count = num + self.columns = [] + for i in range(self.column_count): + self.columns.append(gtk.VBox()) + self.columns[-1].show() + self.hbox.pack_start(self.columns[-1],expand=True) + # place the gramplets back in the new columns + self.place_gramplets(recolumn=True) + self.show() + + def restore_gramplet(self, obj): + name = obj.get_child().get_label() + ############### First kind: from current session + for gramplet in self.closed_gramplets: + if gramplet.title == name: + #gramplet.state = "maximized" + self.closed_gramplets.remove(gramplet) + if self._popup_xy is not None: + self.drop_widget(self, gramplet, + self._popup_xy[0], self._popup_xy[1], 0) + else: + self.drop_widget(self, gramplet, 0, 0, 0) + gramplet.set_state("maximized") + return + ################ Second kind: from options + for opts in self.closed_opts: + if opts["title"] == name: + self.closed_opts.remove(opts) + g = make_requested_gramplet(self, opts["name"], opts, + self.dbstate, self.uistate) + if g: + self.gramplet_map[opts["title"]] = g + self.frame_map[str(g.mainframe)] = g + else: + print "Can't make gramplet of type '%s'." % name + if g: + gramplet = g + gramplet.state = "maximized" + if gramplet.column >= 0 and gramplet.column < len(self.columns): + pos = gramplet.column + else: + pos = 0 + self.columns[pos].pack_start(gramplet.mainframe, expand=gramplet.expand) + # set height on gramplet.scrolledwindow here: + gramplet.scrolledwindow.set_size_request(-1, gramplet.height) + ## now drop it in right place + if self._popup_xy is not None: + self.drop_widget(self, gramplet, + self._popup_xy[0], self._popup_xy[1], 0) + else: + self.drop_widget(self, gramplet, 0, 0, 0) + + def add_gramplet(self, obj): + tname = obj.get_child().get_label() + all_opts = get_gramplet_options_by_tname(tname) + name = all_opts["name"] + if all_opts is None: + print "Unknown gramplet type: '%s'; bad gramplets.ini file?" % name + return + if "title" not in all_opts: + all_opts["title"] = "Untitled Gramplet" + # uniqify titles: + unique = all_opts["title"] + cnt = 1 + while unique in self.gramplet_map: + unique = all_opts["title"] + ("-%d" % cnt) + cnt += 1 + all_opts["title"] = unique + if all_opts["title"] not in self.gramplet_map: + g = make_requested_gramplet(self, name, all_opts, + self.dbstate, self.uistate) + if g: + self.gramplet_map[all_opts["title"]] = g + self.frame_map[str(g.mainframe)] = g + gramplet = g + if gramplet.column >= 0 and gramplet.column < len(self.columns): + pos = gramplet.column + else: + pos = 0 + self.columns[pos].pack_start(gramplet.mainframe, expand=gramplet.expand) + # set height on gramplet.scrolledwindow here: + gramplet.scrolledwindow.set_size_request(-1, gramplet.height) + ## now drop it in right place + if self._popup_xy is not None: + self.drop_widget(self, gramplet, + self._popup_xy[0], self._popup_xy[1], 0) + else: + self.drop_widget(self, gramplet, 0, 0, 0) + if gramplet.pui: + gramplet.pui.active = True + gramplet.pui.update() + else: + print "Can't make gramplet of type '%s'." % name + + def _button_press(self, obj, event): + if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3: + self._popup_xy = (event.x, event.y) + menu = self.uistate.uimanager.get_widget('/Popup') + ag_menu = self.uistate.uimanager.get_widget('/Popup/AddGramplet') + if ag_menu: + qr_menu = ag_menu.get_submenu() + qr_menu = gtk.Menu() + names = [GET_AVAILABLE_GRAMPLETS(key)["tname"] for key + in AVAILABLE_GRAMPLETS()] + names.sort() + for name in names: + add_menuitem(qr_menu, name, + None, self.add_gramplet) + self.uistate.uimanager.get_widget('/Popup/AddGramplet').set_submenu(qr_menu) + rg_menu = self.uistate.uimanager.get_widget('/Popup/RestoreGramplet') + if rg_menu: + qr_menu = rg_menu.get_submenu() + if qr_menu is not None: + rg_menu.remove_submenu() + names = [gramplet.title for gramplet in self.closed_gramplets] + names.extend(opts["title"] for opts in self.closed_opts) + names.sort() + if len(names) > 0: + qr_menu = gtk.Menu() + for name in names: + add_menuitem(qr_menu, name, + None, self.restore_gramplet) + self.uistate.uimanager.get_widget('/Popup/RestoreGramplet').set_submenu(qr_menu) + if menu: + menu.popup(None, None, None, event.button, event.time) + return True + return False + + def set_inactive(self): + for title in self.gramplet_map: + if self.gramplet_map[title].pui: + if self.gramplet_map[title].state != "detached": + self.gramplet_map[title].pui.active = False + + def set_active(self): + for title in self.gramplet_map: + if self.gramplet_map[title].pui: + self.gramplet_map[title].pui.active = True + if self.gramplet_map[title].pui.dirty: + if self.gramplet_map[title].state == "maximized": + self.gramplet_map[title].pui.update() + + def on_delete(self): + gramplets = (g for g in self.gramplet_map.itervalues() + if g is not None) + for gramplet in gramplets: + # this is the only place where the gui runs user code directly + if gramplet.pui: + gramplet.pui.on_save() + self.save() + diff --git a/src/plugins/view/grampletview.py b/src/plugins/view/grampletview.py index fde565731..216b1fc14 100644 --- a/src/plugins/view/grampletview.py +++ b/src/plugins/view/grampletview.py @@ -30,1187 +30,15 @@ GrampletView interface. # #------------------------------------------------------------------------- import gtk -import pango -import traceback -import time -import types -import os -from gen.ggettext import gettext as _ #------------------------------------------------------------------------- # # GRAMPS modules # #------------------------------------------------------------------------- -import Errors -import const from gui.views.pageview import PageView -from gui.editors import EditPerson, EditFamily -import ManagedWindow -import ConfigParser -from gui.utils import add_menuitem -from QuickReports import run_quick_report_by_name -import GrampsDisplay -from glade import Glade -from gui.pluginmanager import GuiPluginManager - -#------------------------------------------------------------------------- -# -# Constants -# -#------------------------------------------------------------------------- -WIKI_HELP_PAGE = const.URL_MANUAL_PAGE + '_-_Gramplets' - -#------------------------------------------------------------------------- -# -# Globals -# -#------------------------------------------------------------------------- -PLUGMAN = GuiPluginManager.get_instance() - -GRAMPLET_FILENAME = os.path.join(const.HOME_DIR,"gramplets.ini") -NL = "\n" - -debug = False - -def AVAILABLE_GRAMPLETS(): - return [gplug.id for gplug in PLUGMAN.get_reg_gramplets()] - -def GET_AVAILABLE_GRAMPLETS(name): - for gplug in PLUGMAN.get_reg_gramplets(): - if gplug.id == name: - return { - "name": gplug.id, - "tname": gplug.name, - "version": gplug.version, - "height": gplug.height, - "expand": gplug.expand, - "title": gplug.gramplet_title, - "content": gplug.gramplet, - "detached_width": gplug.detached_width, - "detached_height": gplug.detached_height, - "state": "maximized", - "gramps": "0.0.0", - "column": -1, - "row": -1, - "data": [], - "help_url": gplug.help_url, - } - return None - -def parse_tag_attr(text): - """ - Function used to parse markup. - """ - text = text.strip() - parts = text.split(" ", 1) - attrs = {} - if len(parts) == 2: - attr_values = parts[1].split(" ") # "name=value name=value" - for av in attr_values: - attribute, value = av.split("=", 1) - value = value.strip() - # trim off quotes: - if value[0] == value[-1] and value[0] in ['"', "'"]: - value = value[1:-1] - attrs[attribute.strip().lower()] = value - return [parts[0].upper(), attrs] - -def get_gramplet_opts(name, opts): - """ - Lookup the options for a given gramplet name and update - the options with provided dictionary, opts. - """ - if name in AVAILABLE_GRAMPLETS(): - data = GET_AVAILABLE_GRAMPLETS(name) - my_data = data.copy() - my_data.update(opts) - return my_data - else: - print ("Unknown gramplet name: '%s'" % name) - return {} - -def get_gramplet_options_by_name(name): - """ - Get options by gramplet name. - """ - if debug: print "name:", name - if name in AVAILABLE_GRAMPLETS(): - return GET_AVAILABLE_GRAMPLETS(name).copy() - else: - print ("Unknown gramplet name: '%s'" % name) - return None - -def get_gramplet_options_by_tname(name): - """ - get options by translated name. - """ - if debug: print "name:", name - for key in AVAILABLE_GRAMPLETS(): - if GET_AVAILABLE_GRAMPLETS(key)["tname"] == name: - return GET_AVAILABLE_GRAMPLETS(key).copy() - print ("Unknown gramplet name: '%s'" % name) - return None - -def make_requested_gramplet(pane, name, opts, dbstate, uistate): - """ - Make a GUI gramplet given its name. - """ - if name in AVAILABLE_GRAMPLETS(): - gui = GuiGramplet(pane, dbstate, uistate, **opts) - if opts.get("content", None): - pdata = PLUGMAN.get_plugin(opts["name"]) - module = PLUGMAN.load_plugin(pdata) - if module: - getattr(module, opts["content"])(gui) - else: - print "Error loading gramplet '%s': skipping content" \ - % opts["name"] - # now that we have user code, set the tooltips - msg = gui.tooltip - if msg is None: - msg = _("Drag Properties Button to move and click it for setup") - if msg: - gui.scrolledwindow.set_tooltip_text(msg) - gui.tooltips_text = msg - gui.make_gui_options() - gui.gvoptions.hide() - return gui - return None - -def logical_true(value): - """ - Used for converting text file values to booleans. - """ - return value in ["True", True, 1, "1"] - -class LinkTag(gtk.TextTag): - """ - Class for keeping track of link data. - """ - lid = 0 - def __init__(self, buffer): - LinkTag.lid += 1 - gtk.TextTag.__init__(self, str(LinkTag.lid)) - tag_table = buffer.get_tag_table() - self.set_property('foreground', "blue") - #self.set_property('underline', pango.UNDERLINE_SINGLE) - try: - tag_table.add(self) - except ValueError: # tag is already in tag table - pass - -class GrampletWindow(ManagedWindow.ManagedWindow): - """ - Class for showing a detached gramplet. - """ - def __init__(self, gramplet): - """ - Constructs the window, and loads the GUI gramplet. - """ - self.title = gramplet.title + " " + _("Gramplet") - self.gramplet = gramplet - # Keep track of what state it was in: - self.docked_state = gramplet.state - # Now detach it - self.gramplet.set_state("detached") - ManagedWindow.ManagedWindow.__init__(self, gramplet.uistate, [], self.title) - self.set_window(gtk.Dialog("",gramplet.uistate.window, - gtk.DIALOG_DESTROY_WITH_PARENT, - (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)), - None, self.title) - self.window.set_size_request(gramplet.detached_width, - gramplet.detached_height) - self.window.add_button(gtk.STOCK_HELP, gtk.RESPONSE_HELP) - # add gramplet: - if self.gramplet.pui: - self.gramplet.pui.active = True - self.gramplet.mainframe.reparent(self.window.vbox) - self.window.connect('response', self.handle_response) - # HACK: must show window to make it work right: - self.show() - # But that shows everything, hide them here: - self.gramplet.gvclose.hide() - self.gramplet.gvstate.hide() - self.gramplet.gvproperties.hide() - if self.gramplet.pui and len(self.gramplet.pui.option_dict) > 0: - self.gramplet.gvoptions.show() - else: - self.gramplet.gvoptions.hide() - - def handle_response(self, object, response): - """ - Callback for taking care of button clicks. - """ - if response in [gtk.RESPONSE_CLOSE, gtk.STOCK_CLOSE]: - self.close() - elif response == gtk.RESPONSE_HELP: - # translated name: - if self.gramplet.help_url: - if self.gramplet.help_url.startswith("http://"): - GrampsDisplay.url(self.gramplet.help_url) - else: - GrampsDisplay.help(self.gramplet.help_url) - else: - GrampsDisplay.help(WIKI_HELP_PAGE, - self.gramplet.tname.replace(" ", "_")) - - def build_menu_names(self, obj): - """ - Part of the GRAMPS window interface. - """ - return (self.title, 'Gramplet') - - def get_title(self): - """ - Returns the window title. - """ - return self.title - - def close(self, *args): - """ - Dock the detached GrampletWindow back in the column from where it came. - """ - self.gramplet.gvoptions.hide() - self.gramplet.pane.detached_gramplets.remove(self.gramplet) - if self.docked_state == "minimized": - self.gramplet.set_state("minimized") - else: - self.gramplet.set_state("maximized") - pane = self.gramplet.pane - col = self.gramplet.column - stack = [] - for gframe in pane.columns[col]: - gramplet = pane.frame_map[str(gframe)] - if gramplet.row > self.gramplet.row: - pane.columns[col].remove(gframe) - stack.append(gframe) - expand = self.gramplet.state == "maximized" and self.gramplet.expand - column = pane.columns[col] - parent = self.gramplet.pane.get_column_frame(self.gramplet.column) - self.gramplet.mainframe.reparent(parent) - if self.gramplet.pui: - self.gramplet.pui.active = self.gramplet.pane.pageview.active - for gframe in stack: - gramplet = pane.frame_map[str(gframe)] - expand = gramplet.state == "maximized" and gramplet.expand - pane.columns[col].pack_start(gframe, expand=expand) - # Now make sure they all have the correct expand: - for gframe in pane.columns[col]: - gramplet = pane.frame_map[str(gframe)] - expand, fill, padding, pack = column.query_child_packing(gramplet.mainframe) - expand = gramplet.state == "maximized" and gramplet.expand - column.set_child_packing(gramplet.mainframe, expand, fill, padding, pack) - self.gramplet.gvclose.show() - self.gramplet.gvstate.show() - self.gramplet.gvproperties.show() - ManagedWindow.ManagedWindow.close(self, *args) - -#------------------------------------------------------------------------ - -class GuiGramplet(object): - """ - Class that handles the plugin interfaces for the GrampletView. - """ - TARGET_TYPE_FRAME = 80 - LOCAL_DRAG_TYPE = 'GRAMPLET' - LOCAL_DRAG_TARGET = (LOCAL_DRAG_TYPE, 0, TARGET_TYPE_FRAME) - def __init__(self, pane, dbstate, uistate, title, **kwargs): - """ - Internal constructor for GUI portion of a gramplet. - """ - self.pane = pane - self.dbstate = dbstate - self.uistate = uistate - self.title = title - self.force_update = False - self._tags = [] - self.link_cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR) - self.standard_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM) - ########## Set defaults - self.name = kwargs.get("name", "Unnamed Gramplet") - self.tname = kwargs.get("tname", "Unnamed Gramplet") - self.version = kwargs.get("version", "0.0.0") - self.gramps = kwargs.get("gramps", "0.0.0") - self.expand = logical_true(kwargs.get("expand", False)) - self.height = int(kwargs.get("height", 200)) - self.column = int(kwargs.get("column", -1)) - self.detached_height = int(kwargs.get("detached_height", 300)) - self.detached_width = int(kwargs.get("detached_width", 400)) - self.row = int(kwargs.get("row", -1)) - self.state = kwargs.get("state", "maximized") - self.data = kwargs.get("data", []) - self.help_url = kwargs.get("help_url", None) - ########## - self.use_markup = False - self.pui = None # user code - self.tooltip = None # text - self.tooltips = None # gtk tooltip widget - self.tooltips_text = None - - self.xml = Glade() - self.gvwin = self.xml.toplevel - self.mainframe = self.xml.get_object('gvgramplet') - self.gvwin.remove(self.mainframe) - - self.gvoptions = self.xml.get_object('gvoptions') - self.textview = self.xml.get_object('gvtextview') - self.buffer = self.textview.get_buffer() - self.scrolledwindow = self.xml.get_object('gvscrolledwindow') - self.vboxtop = self.xml.get_object('vboxtop') - self.titlelabel = self.xml.get_object('gvtitle') - self.titlelabel.set_text("%s" % self.title) - self.titlelabel.set_use_markup(True) - self.gvclose = self.xml.get_object('gvclose') - self.gvclose.connect('clicked', self.close) - self.gvstate = self.xml.get_object('gvstate') - self.gvstate.connect('clicked', self.change_state) - self.gvproperties = self.xml.get_object('gvproperties') - self.gvproperties.connect('clicked', self.set_properties) - self.xml.get_object('gvcloseimage').set_from_stock(gtk.STOCK_CLOSE, - gtk.ICON_SIZE_MENU) - self.xml.get_object('gvstateimage').set_from_stock(gtk.STOCK_REMOVE, - gtk.ICON_SIZE_MENU) - self.xml.get_object('gvpropertiesimage').set_from_stock(gtk.STOCK_PROPERTIES, - gtk.ICON_SIZE_MENU) - - # source: - drag = self.gvproperties - drag.drag_source_set(gtk.gdk.BUTTON1_MASK, - [GuiGramplet.LOCAL_DRAG_TARGET], - gtk.gdk.ACTION_COPY) - - def close(self, *obj): - """ - Remove (delete) the gramplet from view. - """ - if self.state == "detached": - return - self.state = "closed" - self.pane.closed_gramplets.append(self) - self.mainframe.get_parent().remove(self.mainframe) - - def detach(self): - """ - Detach the gramplet from the GrampletView, and open in own window. - """ - # hide buttons: - #self.set_state("detached") - self.pane.detached_gramplets.append(self) - # make a window, and attach it there - self.detached_window = GrampletWindow(self) - - def set_state(self, state): - """ - Set the state of a gramplet. - """ - oldstate = self.state - self.state = state - if state == "minimized": - self.scrolledwindow.hide() - self.xml.get_object('gvstateimage').set_from_stock(gtk.STOCK_ADD, - gtk.ICON_SIZE_MENU) - column = self.mainframe.get_parent() # column - expand,fill,padding,pack = column.query_child_packing(self.mainframe) - column.set_child_packing(self.mainframe,False,fill,padding,pack) - - else: - self.scrolledwindow.show() - self.xml.get_object('gvstateimage').set_from_stock(gtk.STOCK_REMOVE, - gtk.ICON_SIZE_MENU) - column = self.mainframe.get_parent() # column - expand,fill,padding,pack = column.query_child_packing(self.mainframe) - column.set_child_packing(self.mainframe, - self.expand, - fill, - padding, - pack) - if self.pui and self.pui.dirty: - self.pui.update() - - def change_state(self, obj): - """ - Change the state of a gramplet. - """ - if self.state == "detached": - pass # don't change if detached - else: - if self.state == "maximized": - self.set_state("minimized") - else: - self.set_state("maximized") - - def set_properties(self, obj): - """ - Set the properties of a gramplet. - """ - if self.state == "detached": - pass - else: - self.detach() - return - self.expand = not self.expand - if self.state == "maximized": - column = self.mainframe.get_parent() # column - expand,fill,padding,pack = column.query_child_packing(self.mainframe) - column.set_child_packing(self.mainframe,self.expand,fill,padding,pack) - - def append_text(self, text, scroll_to="end"): - enditer = self.buffer.get_end_iter() - start = self.buffer.create_mark(None, enditer, True) - self.buffer.insert(enditer, text) - if scroll_to == "end": - enditer = self.buffer.get_end_iter() - end = self.buffer.create_mark(None, enditer, True) - self.textview.scroll_to_mark(end, 0.0, True, 0, 0) - elif scroll_to == "start": # beginning of this append - self.textview.scroll_to_mark(start, 0.0, True, 0, 0) - elif scroll_to == "begin": # beginning of the buffer - begin_iter = self.buffer.get_start_iter() - begin = self.buffer.create_mark(None, begin_iter, True) - self.textview.scroll_to_mark(begin, 0.0, True, 0, 0) - else: - raise AttributeError, ("no such cursor position: '%s'" % scroll_to) - - def clear_text(self): - self.buffer.set_text('') - - def get_text(self): - start = self.buffer.get_start_iter() - end = self.buffer.get_end_iter() - return self.buffer.get_text(start, end) - - def insert_text(self, text): - self.buffer.insert_at_cursor(text) - - def len_text(self, text): - i = 0 - r = 0 - while i < len(text): - if ord(text[i]) > 126: - t = 0 - while i < len(text) and ord(text[i]) > 126: - i += 1 - t += 1 - r += t/2 - elif text[i] == "\\": - r += 1 - i += 2 - else: - r += 1 - i += 1 - return r - - def render_text(self, text): - markup_pos = {"B": [], "I": [], "U": [], "A": [], "TT": []} - retval = "" - i = 0 - r = 0 - tag = "" - while i < len(text): - if text[i:i+2] == "") - if stop < 0: - retval += text[i] - r += 1 - i += 1 - else: - markup = text[i+2:i+stop].upper() # close tag - markup_pos[markup][-1].append(r) - i += stop + 1 - elif text[i] == "<": - # start of start tag - stop = text[i:].find(">") - if stop < 0: - retval += text[i] - r += 1 - i += 1 - else: - markup, attr = parse_tag_attr(text[i+1:i+stop]) - markup_pos[markup].append([r, attr]) - i += stop + 1 - elif text[i] == "\\": - retval += text[i+1] - r += 1 - i += 2 - elif ord(text[i]) > 126: - while ord(text[i]) > 126: - retval += text[i] - i += 1 - r += 1 - else: - retval += text[i] - r += 1 - i += 1 - offset = self.len_text(self.get_text()) - self.append_text(retval) - for items in markup_pos["TT"]: - if len(items) == 3: - (a,attributes,b) = items - start = self.buffer.get_iter_at_offset(a + offset) - stop = self.buffer.get_iter_at_offset(b + offset) - self.buffer.apply_tag_by_name("fixed", start, stop) - for items in markup_pos["B"]: - if len(items) == 3: - (a,attributes,b) = items - start = self.buffer.get_iter_at_offset(a + offset) - stop = self.buffer.get_iter_at_offset(b + offset) - self.buffer.apply_tag_by_name("bold", start, stop) - for items in markup_pos["I"]: - if len(items) == 3: - (a,attributes,b) = items - start = self.buffer.get_iter_at_offset(a + offset) - stop = self.buffer.get_iter_at_offset(b + offset) - self.buffer.apply_tag_by_name("italic", start, stop) - for items in markup_pos["U"]: - if len(items) == 3: - (a,attributes,b) = items - start = self.buffer.get_iter_at_offset(a + offset) - stop = self.buffer.get_iter_at_offset(b + offset) - self.buffer.apply_tag_by_name("underline", start, stop) - for items in markup_pos["A"]: - if len(items) == 3: - (a,attributes,b) = items - start = self.buffer.get_iter_at_offset(a + offset) - stop = self.buffer.get_iter_at_offset(b + offset) - if "href" in attributes: - url = attributes["href"] - self.link_region(start, stop, "URL", url) # tooltip? - elif "wiki" in attributes: - url = attributes["wiki"] - self.link_region(start, stop, "WIKI", url) # tooltip? - else: - print "warning: no url on link: '%s'" % text[start, stop] - - def link_region(self, start, stop, link_type, url): - link_data = (LinkTag(self.buffer), link_type, url, url) - self._tags.append(link_data) - self.buffer.apply_tag(link_data[0], start, stop) - - def set_use_markup(self, value): - if self.use_markup == value: return - self.use_markup = value - if value: - self.buffer.create_tag("bold",weight=pango.WEIGHT_HEAVY) - self.buffer.create_tag("italic",style=pango.STYLE_ITALIC) - self.buffer.create_tag("underline",underline=pango.UNDERLINE_SINGLE) - self.buffer.create_tag("fixed", font="monospace") - else: - tag_table = self.buffer.get_tag_table() - tag_table.foreach(lambda tag, data: tag_table.remove(tag)) - - def set_text(self, text, scroll_to='start'): - self.buffer.set_text('') - self.append_text(text, scroll_to) - - def get_source_widget(self): - """ - Hack to allow us to send this object to the drop_widget - method as a context. - """ - return self.gvproperties - - def get_container_widget(self): - return self.scrolledwindow - - def make_gui_options(self): - if not self.pui: return - topbox = gtk.VBox() - hbox = gtk.HBox() - labels = gtk.VBox() - options = gtk.VBox() - hbox.pack_start(labels, False) - hbox.pack_start(options, True) - topbox.add(hbox) - self.gvoptions.add(topbox) - for item in self.pui.option_order: - label = gtk.Label(item + ":") - label.set_alignment(1.0, 0.5) - labels.add(label) - options.add(self.pui.option_dict[item][0]) # widget - save_button = gtk.Button(stock=gtk.STOCK_SAVE) - topbox.add(save_button) - topbox.show_all() - save_button.connect('clicked', self.pui.save_update_options) - - def link(self, text, link_type, data, size=None, tooltip=None): - buffer = self.buffer - iter = buffer.get_end_iter() - offset = buffer.get_char_count() - self.append_text(text) - start = buffer.get_iter_at_offset(offset) - end = buffer.get_end_iter() - link_data = (LinkTag(buffer), link_type, data, tooltip) - if size: - link_data[0].set_property("size-points", size) - self._tags.append(link_data) - buffer.apply_tag(link_data[0], start, end) - - def on_motion(self, view, event): - buffer_location = view.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, - int(event.x), - int(event.y)) - iter = view.get_iter_at_location(*buffer_location) - cursor = self.standard_cursor - ttip = None - for (tag, link_type, handle, tooltip) in self._tags: - if iter.has_tag(tag): - tag.set_property('underline', pango.UNDERLINE_SINGLE) - cursor = self.link_cursor - ttip = tooltip - else: - tag.set_property('underline', pango.UNDERLINE_NONE) - view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(cursor) - if ttip: - self.scrolledwindow.set_tooltip_text(ttip) - elif self.tooltips_text: - self.scrolledwindow.set_tooltip_text(self.tooltips_text) - return False # handle event further, if necessary - - def on_button_press(self, view, event): - buffer_location = view.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, - int(event.x), - int(event.y)) - iter = view.get_iter_at_location(*buffer_location) - for (tag, link_type, handle, tooltip) in self._tags: - if iter.has_tag(tag): - if link_type == 'Person': - person = self.dbstate.db.get_person_from_handle(handle) - if person is not None: - if event.button == 1: # left mouse - if event.type == gtk.gdk._2BUTTON_PRESS: # double - try: - EditPerson(self.dbstate, - self.uistate, - [], person) - return True # handled event - except Errors.WindowActiveError: - pass - elif event.type == gtk.gdk.BUTTON_PRESS: # single click - self.uistate.set_active(handle, 'Person') - return True # handled event - elif event.button == 3: # right mouse - #FIXME: add a popup menu with options - try: - EditPerson(self.dbstate, - self.uistate, - [], person) - return True # handled event - except Errors.WindowActiveError: - pass - elif link_type == 'Surname': - if event.button == 1: # left mouse - if event.type == gtk.gdk._2BUTTON_PRESS: # double - run_quick_report_by_name(self.dbstate, - self.uistate, - 'samesurnames', - handle) - return True - elif link_type == 'Given': - if event.button == 1: # left mouse - if event.type == gtk.gdk._2BUTTON_PRESS: # double - run_quick_report_by_name(self.dbstate, - self.uistate, - 'samegivens_misc', - handle) - return True - elif link_type == 'Filter': - if event.button == 1: # left mouse - if event.type == gtk.gdk._2BUTTON_PRESS: # double - run_quick_report_by_name(self.dbstate, - self.uistate, - 'filterbyname', - handle) - return True - elif link_type == 'URL': - if event.button == 1: # left mouse - GrampsDisplay.url(handle) - return True - elif link_type == 'WIKI': - if event.button == 1: # left mouse - GrampsDisplay.help(handle.replace(" ", "_")) - return True - elif link_type == 'Family': - family = self.dbstate.db.get_family_from_handle(handle) - if family is not None: - if event.button == 1: # left mouse - if event.type == gtk.gdk._2BUTTON_PRESS: # double - try: - EditFamily(self.dbstate, - self.uistate, - [], family) - return True # handled event - except Errors.WindowActiveError: - pass - elif event.button == 3: # right mouse - #FIXME: add a popup menu with options - try: - EditFamily(self.dbstate, - self.uistate, - [], family) - return True # handled event - except Errors.WindowActiveError: - pass - elif link_type == 'PersonList': - if event.button == 1: # left mouse - if event.type == gtk.gdk._2BUTTON_PRESS: # double - run_quick_report_by_name(self.dbstate, - self.uistate, - 'filterbyname', - 'list of people', - handles=handle) - return True - elif link_type == 'Attribute': - if event.button == 1: # left mouse - if event.type == gtk.gdk._2BUTTON_PRESS: # double - run_quick_report_by_name(self.dbstate, - self.uistate, - 'attribute_match', - handle) - return True - return False # did not handle event - -class GrampletPane(gtk.ScrolledWindow): - def show_all(self): - # first show them all: - gtk.ScrolledWindow.show_all(self) - # Hack to get around show_all that shows hidden items - # do once, the first time showing - if self.pane: - gramplets = (g for g in self.pane.gramplet_map.itervalues() - if g is not None) - self.pane = None - for gramplet in gramplets: - gramplet.gvoptions.hide() - if gramplet.state == "minimized": - gramplet.set_state("minimized") - - def __init__(self, pageview, dbstate, uistate): - gtk.ScrolledWindow.__init__(self) - self.dbstate = dbstate - self.uistate = uistate - self.pageview = pageview - self.pane = self - self._popup_xy = None - user_gramplets = self.load_gramplets() - # build the GUI: - msg = _("Right click to add gramplets") - self.set_tooltip_text(msg) - self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - self.hbox = gtk.HBox(homogeneous=True) - # Set up drag and drop - self.drag_dest_set(gtk.DEST_DEFAULT_MOTION | - gtk.DEST_DEFAULT_HIGHLIGHT | - gtk.DEST_DEFAULT_DROP, - [('GRAMPLET', 0, 80)], - gtk.gdk.ACTION_COPY) - self.connect('drag_drop', self.drop_widget) - self.connect('button-press-event', self._button_press) - - self.add_with_viewport(self.hbox) - # Create the columns: - self.columns = [] - for i in range(self.column_count): - self.columns.append(gtk.VBox()) - self.hbox.pack_start(self.columns[-1],expand=True) - # Load the gramplets - self.gramplet_map = {} # title->gramplet - self.frame_map = {} # frame->gramplet - self.detached_gramplets = [] # list of detached gramplets - self.closed_gramplets = [] # list of closed gramplets - self.closed_opts = [] # list of closed options from ini file - # get the user's gramplets from ~/.gramps/gramplets.ini - # Load the user's gramplets: - for (name, opts) in user_gramplets: - all_opts = get_gramplet_opts(name, opts) - if "title" not in all_opts: - all_opts["title"] = "Untitled Gramplet" - if "state" not in all_opts: - all_opts["state"] = "maximized" - # uniqify titles: - unique = all_opts["title"] - cnt = 1 - while unique in self.gramplet_map: - unique = all_opts["title"] + ("-%d" % cnt) - cnt += 1 - all_opts["title"] = unique - if all_opts["state"] == "closed": - self.gramplet_map[all_opts["title"]] = None # save closed name - self.closed_opts.append(all_opts) - continue - g = make_requested_gramplet(self, name, all_opts, - self.dbstate, self.uistate) - if g: - self.gramplet_map[all_opts["title"]] = g - self.frame_map[str(g.mainframe)] = g - else: - print "Can't make gramplet of type '%s'." % name - self.place_gramplets() - - def get_column_frame(self, column_num): - if column_num < len(self.columns): - return self.columns[column_num] - else: - return self.columns[-1] # it was too big, so select largest - - def clear_gramplets(self): - """ - Detach all of the mainframe gramplets from the columns. - """ - gramplets = (g for g in self.gramplet_map.itervalues() - if g is not None) - for gramplet in gramplets: - if (gramplet.state == "detached" or gramplet.state == "closed"): - continue - column = gramplet.mainframe.get_parent() - if column: - column.remove(gramplet.mainframe) - - def place_gramplets(self, recolumn=False): - """ - Place the gramplet mainframes in the columns. - """ - gramplets = [g for g in self.gramplet_map.itervalues() - if g is not None] - # put the gramplets where they go: - # sort by row - gramplets.sort(key=lambda x: x.row) - rows = [0] * max(self.column_count, 1) - for cnt, gramplet in enumerate(gramplets): - # see if the user wants this in a particular location: - # and if there are that many columns - if gramplet.column >= 0 and gramplet.column < self.column_count: - pos = gramplet.column - else: - # else, spread them out: - pos = cnt % self.column_count - gramplet.column = pos - gramplet.row = rows[gramplet.column] - rows[gramplet.column] += 1 - if recolumn and (gramplet.state == "detached" or gramplet.state == "closed"): - continue - if gramplet.state == "minimized": - self.columns[pos].pack_start(gramplet.mainframe, expand=False) - else: - self.columns[pos].pack_start(gramplet.mainframe, expand=gramplet.expand) - # set height on gramplet.scrolledwindow here: - gramplet.scrolledwindow.set_size_request(-1, gramplet.height) - # Can't minimize here, because GRAMPS calls show_all later: - #if gramplet.state == "minimized": # starts max, change to min it - # gramplet.set_state("minimized") # minimize it - # set minimized is called in page subclass hack (above) - if gramplet.state == "detached": - gramplet.detach() - elif gramplet.state == "closed": - gramplet.close() - - def load_gramplets(self): - self.column_count = 2 # default for new user - retval = [] - filename = GRAMPLET_FILENAME - if filename and os.path.exists(filename): - cp = ConfigParser.ConfigParser() - cp.read(filename) - for sec in cp.sections(): - if sec == "Gramplet View Options": - if "column_count" in cp.options(sec): - self.column_count = int(cp.get(sec, "column_count")) - else: - data = {"title": sec} - for opt in cp.options(sec): - if opt.startswith("data["): - temp = data.get("data", []) - temp.append(cp.get(sec, opt).strip()) - data["data"] = temp - else: - data[opt] = cp.get(sec, opt).strip() - if "name" not in data: - data["name"] = "Unnamed Gramplet" - data["tname"]= _("Unnamed Gramplet") - retval.append((data["name"], data)) # name, opts - else: - # give defaults as currently known - for name in ["Top Surnames Gramplet", "Welcome Gramplet"]: - if name in AVAILABLE_GRAMPLETS(): - retval.append((name, GET_AVAILABLE_GRAMPLETS(name))) - return retval - - def save(self): - if debug: print "saving" - if len(self.frame_map) + len(self.detached_gramplets) == 0: - return # something is the matter - filename = GRAMPLET_FILENAME - try: - fp = open(filename, "w") - except: - print "Failed writing '%s'; gramplets not saved" % filename - return - fp.write(";; Gramps gramplets file" + NL) - fp.write((";; Automatically created at %s" % time.strftime("%Y/%m/%d %H:%M:%S")) + NL + NL) - fp.write("[Gramplet View Options]" + NL) - fp.write(("column_count=%d" + NL + NL) % self.column_count) - # showing gramplets: - for col in range(self.column_count): - row = 0 - for gframe in self.columns[col]: - gramplet = self.frame_map[str(gframe)] - opts = get_gramplet_options_by_name(gramplet.name) - if opts is not None: - base_opts = opts.copy() - for key in base_opts: - if key in gramplet.__dict__: - base_opts[key] = gramplet.__dict__[key] - fp.write(("[%s]" + NL) % gramplet.title) - for key in base_opts: - if key == "content": continue - elif key == "title": continue - elif key == "column": continue - elif key == "row": continue - elif key == "version": continue # code, don't save - elif key == "gramps": continue # code, don't save - elif key == "data": - if not isinstance(base_opts["data"], (list, tuple)): - fp.write(("data[0]=%s" + NL) % base_opts["data"]) - else: - cnt = 0 - for item in base_opts["data"]: - fp.write(("data[%d]=%s" + NL) % (cnt, item)) - cnt += 1 - else: - fp.write(("%s=%s" + NL)% (key, base_opts[key])) - fp.write(("column=%d" + NL) % col) - fp.write(("row=%d" + NL) % row) - fp.write(NL) - row += 1 - for gramplet in self.detached_gramplets: - opts = get_gramplet_options_by_name(gramplet.name) - if opts is not None: - base_opts = opts.copy() - for key in base_opts: - if key in gramplet.__dict__: - base_opts[key] = gramplet.__dict__[key] - fp.write(("[%s]" + NL) % gramplet.title) - for key in base_opts: - if key == "content": continue - elif key == "title": continue - elif key == "data": - if not isinstance(base_opts["data"], (list, tuple)): - fp.write(("data[0]=%s" + NL) % base_opts["data"]) - else: - cnt = 0 - for item in base_opts["data"]: - fp.write(("data[%d]=%s" + NL) % (cnt, item)) - cnt += 1 - else: - fp.write(("%s=%s" + NL)% (key, base_opts[key])) - fp.write(NL) - fp.close() - - def drop_widget(self, source, context, x, y, timedata): - """ - This is the destination method for handling drag and drop - of a gramplet onto the main scrolled window. - """ - button = context.get_source_widget() - hbox = button.get_parent() - mframe = hbox.get_parent() - mainframe = mframe.get_parent() # actually a vbox - rect = source.get_allocation() - sx, sy = rect.width, rect.height - # first, find column: - col = 0 - for i in range(len(self.columns)): - if x < (sx/len(self.columns) * (i + 1)): - col = i - break - fromcol = mainframe.get_parent() - if fromcol: - fromcol.remove(mainframe) - # now find where to insert in column: - stack = [] - current_row = 0 - for gframe in self.columns[col]: - gramplet = self.frame_map[str(gframe)] - gramplet.row = current_row - current_row += 1 - rect = gframe.get_allocation() - if y < (rect.y + 15): # starts at 0, this allows insert before - self.columns[col].remove(gframe) - stack.append(gframe) - maingramplet = self.frame_map.get(str(mainframe), None) - maingramplet.column = col - maingramplet.row = current_row - current_row += 1 - expand = maingramplet.state == "maximized" and maingramplet.expand - self.columns[col].pack_start(mainframe, expand=expand) - for gframe in stack: - gramplet = self.frame_map[str(gframe)] - gramplet.row = current_row - current_row += 1 - expand = gramplet.state == "maximized" and gramplet.expand - self.columns[col].pack_start(gframe, expand=expand) - return True - - def set_columns(self, num): - # clear the gramplets: - self.clear_gramplets() - # clear the columns: - for column in self.columns: - frame = column.get_parent() - frame.remove(column) - del column - # create the new ones: - self.column_count = num - self.columns = [] - for i in range(self.column_count): - self.columns.append(gtk.VBox()) - self.columns[-1].show() - self.hbox.pack_start(self.columns[-1],expand=True) - # place the gramplets back in the new columns - self.place_gramplets(recolumn=True) - self.show() - - def restore_gramplet(self, obj): - name = obj.get_child().get_label() - ############### First kind: from current session - for gramplet in self.closed_gramplets: - if gramplet.title == name: - #gramplet.state = "maximized" - self.closed_gramplets.remove(gramplet) - if self._popup_xy is not None: - self.drop_widget(self, gramplet, - self._popup_xy[0], self._popup_xy[1], 0) - else: - self.drop_widget(self, gramplet, 0, 0, 0) - gramplet.set_state("maximized") - return - ################ Second kind: from options - for opts in self.closed_opts: - if opts["title"] == name: - self.closed_opts.remove(opts) - g = make_requested_gramplet(self, opts["name"], opts, - self.dbstate, self.uistate) - if g: - self.gramplet_map[opts["title"]] = g - self.frame_map[str(g.mainframe)] = g - else: - print "Can't make gramplet of type '%s'." % name - if g: - gramplet = g - gramplet.state = "maximized" - if gramplet.column >= 0 and gramplet.column < len(self.columns): - pos = gramplet.column - else: - pos = 0 - self.columns[pos].pack_start(gramplet.mainframe, expand=gramplet.expand) - # set height on gramplet.scrolledwindow here: - gramplet.scrolledwindow.set_size_request(-1, gramplet.height) - ## now drop it in right place - if self._popup_xy is not None: - self.drop_widget(self, gramplet, - self._popup_xy[0], self._popup_xy[1], 0) - else: - self.drop_widget(self, gramplet, 0, 0, 0) - - def add_gramplet(self, obj): - tname = obj.get_child().get_label() - all_opts = get_gramplet_options_by_tname(tname) - name = all_opts["name"] - if all_opts is None: - print "Unknown gramplet type: '%s'; bad gramplets.ini file?" % name - return - if "title" not in all_opts: - all_opts["title"] = "Untitled Gramplet" - # uniqify titles: - unique = all_opts["title"] - cnt = 1 - while unique in self.gramplet_map: - unique = all_opts["title"] + ("-%d" % cnt) - cnt += 1 - all_opts["title"] = unique - if all_opts["title"] not in self.gramplet_map: - g = make_requested_gramplet(self, name, all_opts, - self.dbstate, self.uistate) - if g: - self.gramplet_map[all_opts["title"]] = g - self.frame_map[str(g.mainframe)] = g - gramplet = g - if gramplet.column >= 0 and gramplet.column < len(self.columns): - pos = gramplet.column - else: - pos = 0 - self.columns[pos].pack_start(gramplet.mainframe, expand=gramplet.expand) - # set height on gramplet.scrolledwindow here: - gramplet.scrolledwindow.set_size_request(-1, gramplet.height) - ## now drop it in right place - if self._popup_xy is not None: - self.drop_widget(self, gramplet, - self._popup_xy[0], self._popup_xy[1], 0) - else: - self.drop_widget(self, gramplet, 0, 0, 0) - if gramplet.pui: - gramplet.pui.active = True - gramplet.pui.update() - else: - print "Can't make gramplet of type '%s'." % name - - def _button_press(self, obj, event): - if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3: - self._popup_xy = (event.x, event.y) - menu = self.uistate.uimanager.get_widget('/Popup') - ag_menu = self.uistate.uimanager.get_widget('/Popup/AddGramplet') - if ag_menu: - qr_menu = ag_menu.get_submenu() - qr_menu = gtk.Menu() - names = [GET_AVAILABLE_GRAMPLETS(key)["tname"] for key - in AVAILABLE_GRAMPLETS()] - names.sort() - for name in names: - add_menuitem(qr_menu, name, - None, self.add_gramplet) - self.uistate.uimanager.get_widget('/Popup/AddGramplet').set_submenu(qr_menu) - rg_menu = self.uistate.uimanager.get_widget('/Popup/RestoreGramplet') - if rg_menu: - qr_menu = rg_menu.get_submenu() - if qr_menu is not None: - rg_menu.remove_submenu() - names = [gramplet.title for gramplet in self.closed_gramplets] - names.extend(opts["title"] for opts in self.closed_opts) - names.sort() - if len(names) > 0: - qr_menu = gtk.Menu() - for name in names: - add_menuitem(qr_menu, name, - None, self.restore_gramplet) - self.uistate.uimanager.get_widget('/Popup/RestoreGramplet').set_submenu(qr_menu) - if menu: - menu.popup(None, None, None, event.button, event.time) - return True - return False - - def set_inactive(self): - for title in self.gramplet_map: - if self.gramplet_map[title].pui: - if self.gramplet_map[title].state != "detached": - self.gramplet_map[title].pui.active = False - - def set_active(self): - for title in self.gramplet_map: - if self.gramplet_map[title].pui: - self.gramplet_map[title].pui.active = True - if self.gramplet_map[title].pui.dirty: - if self.gramplet_map[title].state == "maximized": - self.gramplet_map[title].pui.update() - - def on_delete(self): - gramplets = (g for g in self.gramplet_map.itervalues() - if g is not None) - for gramplet in gramplets: - # this is the only place where the gui runs user code directly - if gramplet.pui: - gramplet.pui.on_save() - self.save() +from gen.ggettext import gettext as _ +from gui.widgets.grampletpane import GrampletPane class GrampletView(PageView): """