# # 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: _MyGrampsView.py $ """ MyGrampsView interface """ __author__ = "Doug Blank" __revision__ = "$Revision: $" import gtk import gobject import traceback import time import pango import Errors import const import PageView AVAILABLE_GADGETS = [] debug = False def register_gadget(data_dict): global AVAILABLE_GADGETS AVAILABLE_GADGETS.append(data_dict) def register(**data): if "type" in data: if data["type"].lower() == "gadget": register_gadget(data) def get_gadget_opts(name, opts): for data in AVAILABLE_GADGETS: if data.get("name", None) == name: my_data = data.copy() my_data.update(opts) return my_data return {} def make_requested_gadget(viewpage, name, opts, dbstate, uistate): for data in AVAILABLE_GADGETS: if data.get("name", None) == name: gui = GuiGadget(viewpage, dbstate, uistate, **opts) if opts.get("content", None): opts["content"](gui) return gui return None class LinkTag(gtk.TextTag): 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', "#0000ff") self.set_property('underline', pango.UNDERLINE_SINGLE) tag_table.add(self) class Gadget(object): def __init__(self, gui): self._idle_id = 0 self._generator = None self._need_to_update = False self._tags = [] self.link_cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR) self.standard_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM) self.gui = gui self.dbstate = gui.dbstate self.init() self.dbstate.connect('database-changed', self._db_changed) self.dbstate.connect('active-changed', self.active_changed) self.gui.textview.connect('button-press-event', self.on_button_press) self.gui.textview.connect('motion-notify-event', self.on_motion) def active_changed(self, handle): pass def _db_changed(self, db): if debug: print "%s is _connecting" % self.gui.title self.dbstate.db = db self.gui.dbstate.db = db self.db_changed() self.update() def db_changed(self): if debug: print "%s is connecting" % self.gui.title pass def init(self): # once, constructor pass def main(self): # once per db open pass def update(self, *handles): self.main() if self._idle_id != 0: if debug: print "%s interrupt!" % self.gui.title self.interrupt() if debug: print "%s creating generator" % self.gui.title self._generator = self.background() if debug: print "%s adding to gobject" % self.gui.title self._idle_id = gobject.idle_add(self._updater, priority=gobject.PRIORITY_LOW) def background(self): # return false finishes if debug: print "%s dummy" % self.gui.title yield False def interrupt(self): """ Force the generator to stop running. """ if self._idle_id == 0: if debug: print "%s removing from gobject" % self.gui.title gobject.source_remove(self._idle_id) self._idle_id = 0 def _updater(self): if debug: print "%s _updater" % self.gui.title try: retval = self._generator.next() if retval == False: self._idle_id = 0 return retval except StopIteration: self._idle_id = 0 return False except Exception, e: #self._error = e traceback.print_exc() self._idle_id = 0 return False except: self._idle_id = 0 return False def append_text(self, text): end = self.gui.buffer.get_end_iter() mark = self.gui.buffer.create_mark(None, end, True) self.gui.buffer.insert(end, text) self.gui.textview.scroll_to_mark(mark, 0.0, True, 0, 0) 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) for (tag, person_handle) in self._tags: if iter.has_tag(tag): view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(self.link_cursor) return False # handle event further, if necessary view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(self.standard_cursor) return False # handle event further, if necessary def on_button_press(self, view, event): from Editors import EditPerson 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, person_handle) in self._tags: if iter.has_tag(tag): person = self.dbstate.db.get_person_from_handle(person_handle) if event.button == 1: if event.type == gtk.gdk._2BUTTON_PRESS: try: EditPerson(self.gui.dbstate, self.gui.uistate, [], person) except Errors.WindowActiveError: pass else: self.gui.dbstate.change_active_person(person) return True # handled event return False # did not handle event def link(self, text, data): buffer = self.gui.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() self._tags.append((LinkTag(buffer),data)) buffer.apply_tag(self._tags[-1][0], start, end) def insert_text(self, text): self.gui.buffer.insert_at_cursor(text) def clear_text(self): self.gui.buffer.set_text('') def set_text(self, text): self.gui.buffer.set_text(text) class GuiGadget: """ Class that handles the plugin interfaces for the MyGrampsView. """ TARGET_TYPE_FRAME = 80 LOCAL_DRAG_TYPE = 'GADGET' LOCAL_DRAG_TARGET = (LOCAL_DRAG_TYPE, 0, TARGET_TYPE_FRAME) def __init__(self, viewpage, dbstate, uistate, title, **kwargs): self.viewpage = viewpage self.dbstate = dbstate self.uistate = uistate self.title = title ########## Set defaults self.expand = kwargs.get("expand", False) self.height = kwargs.get("height", 200) self.column = kwargs.get("column", -1) self.row = kwargs.get("row", -1) self.state = kwargs.get("state", "maximized") ########## self.xml = gtk.glade.XML(const.GLADE_FILE, 'gvgadget', "gramps") self.mainframe = self.xml.get_widget('gvgadget') self.textview = self.xml.get_widget('gvtextview') self.buffer = self.textview.get_buffer() self.scrolledwindow = self.xml.get_widget('gvscrolledwindow') self.titlelabel = self.xml.get_widget('gvtitle') self.titlelabel.set_text("%s" % self.title) self.titlelabel.set_use_markup(True) self.xml.get_widget('gvclose').connect('clicked', self.close) self.xml.get_widget('gvstate').connect('clicked', self.change_state) self.xml.get_widget('gvproperties').connect('clicked', self.set_properties) self.xml.get_widget('gvcloseimage').set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) self.xml.get_widget('gvstateimage').set_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) self.xml.get_widget('gvpropertiesimage').set_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU) # source: drag = self.xml.get_widget('gvproperties') drag.drag_source_set(gtk.gdk.BUTTON1_MASK, [GuiGadget.LOCAL_DRAG_TARGET], gtk.gdk.ACTION_COPY) def close(self, obj): del self.viewpage.gadget_map[self.title] del self.viewpage.frame_map[str(self.mainframe)] self.mainframe.destroy() def set_state(self, state): self.state = state if state == "minimized": self.scrolledwindow.hide() self.xml.get_widget('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_widget('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) def change_state(self, obj): if self.state == "maximized": self.set_state("minimized") else: self.set_state("maximized") def set_properties(self, obj): 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): self.buffer.insert_at_cursor(text) def clear_text(self): self.buffer.set_text('') def set_text(self, text): self.buffer.set_text(text) class MyGrampsView(PageView.PageView): """ MyGrampsView interface """ def __init__(self, dbstate, uistate): """ Creates a MyGrampsView, with the current dbstate and uistate """ PageView.PageView.__init__(self, _('My Gramps'), dbstate, uistate) self.column_count = 3 def change_db(self, event): """ """ # FIXME: remove/add widgets from new db ini file pass def build_widget(self): """ Builds the container widget for the interface. Must be overridden by the the base class. Returns a gtk container widget. """ frame = gtk.ScrolledWindow() frame.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) hbox = gtk.HBox(homogeneous=True) # Set up drag and drop frame.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP, [('GADGET', 0, 80)], gtk.gdk.ACTION_COPY) frame.connect('drag_drop', self.drop_widget) frame.add_with_viewport(hbox) # Create the columns: self.columns = [] for i in range(self.column_count): self.columns.append(gtk.VBox()) hbox.pack_start(self.columns[-1],expand=True) # Load the gadgets self.gadget_map = {} # title->gadget self.frame_map = {} # frame->gadget # FIXME # get the user's gadgets from .gramps # and/or from open database # Load the user's gadgets: for (name, opts) in [ ('Stats Gadget', {}), ('Top Surnames Gadget', {}), #('Families Gadget', {}), #('Families Gadget', {"title": "My Peeps"}), ('Hello World Gadget', {}), ('Shell Gadget', {}), ('Python Gadget', {}), ('TODO Gadget', {}), ('Log Gadget', {}), #('Events Gadget', {}), ]: all_opts = get_gadget_opts(name, opts) if "title" not in all_opts: all_opts["title"] = "Untitled Gadget" # uniqify titles: unique = all_opts["title"] cnt = 1 while unique in self.gadget_map: unique = all_opts["title"] + ("-%d" % cnt) cnt += 1 all_opts["title"] = unique if all_opts["title"] not in self.gadget_map: g = make_requested_gadget(self, name, all_opts, self.dbstate, self.uistate) if g: self.gadget_map[all_opts["title"]] = g self.frame_map[str(g.mainframe)] = g else: print "Can't make gadget of type '%s'." % name else: print "Ignoring duplicate named gadget '%s'." % all_opts["title"] # put the gadgets where they go: cnt = 0 for gadget in self.gadget_map.values(): # see if the user wants this in a particular location: # and if there are that many columns if gadget.column >= 0 and gadget.column < len(self.columns): pos = gadget.column else: # else, spread them out: pos = cnt % len(self.columns) # to make as big as possible, set to True: # GTK BUG: can't minimize: #if gadget.state == "minimized": # starts max, change to min it # self.columns[pos].pack_start(gadget.mainframe, expand=False) #else: self.columns[pos].pack_start(gadget.mainframe, expand=gadget.expand) # set height on gadget.scrolledwindow here: gadget.scrolledwindow.set_size_request(-1, gadget.height) # GTK BUG: can't minimize: #if gadget.state == "minimized": # starts max, change to min it #gadget.set_state("minimized") # minimize it cnt += 1 return frame def drop_widget(self, source, context, x, y, timedata): button = context.get_source_widget() hbox = button.get_parent() mainframe = hbox.get_parent() 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() fromcol.remove(mainframe) # now find where to insert in column: stack = [] for gframe in self.columns[col]: rect = gframe.get_allocation() if y < (rect.y + 15): # starts at 0, this allows insert before self.columns[col].remove(gframe) stack.append(gframe) maingadget = self.frame_map[str(mainframe)] if maingadget.state == "maximized": expand = maingadget.expand else: expand = False self.columns[col].pack_start(mainframe, expand=expand) for gframe in stack: gadget = self.frame_map[str(gframe)] if gadget.state == "maximized": expand = gadget.expand else: expand = False self.columns[col].pack_start(gframe, expand=expand) return True def define_actions(self): """ Defines the UIManager actions. Called by the ViewManager to set up the View. The user typically defines self.action_list and self.action_toggle_list in this function. """ return '' def get_stock(self): """ Returns image associated with the view, which is used for the icon for the button. """ return 'gtk-home' def build_tree(self): return