gramps/gramps2/src/DisplayTabs/_EmbeddedList.py
Don Allingham fe546d284a 2006-08-10 Don Allingham <don@gramps-project.org>
* src/DisplayTabs/_EmbeddedList.py: use cPickle instead of pickle
	* src/DisplayTabs/_GalleryTab.py: add dnd functionality
	* src/DdTargets.py: add URI_LIST and APP_ROOT



svn: r7151
2006-08-11 03:22:38 +00:00

356 lines
12 KiB
Python

#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2006 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$
#-------------------------------------------------------------------------
#
# python
#
#-------------------------------------------------------------------------
from gettext import gettext as _
import cPickle as pickle
#-------------------------------------------------------------------------
#
# GTK libraries
#
#-------------------------------------------------------------------------
import gtk
import pango
#-------------------------------------------------------------------------
#
# GRAMPS classes
#
#-------------------------------------------------------------------------
from _ButtonTab import ButtonTab
#-------------------------------------------------------------------------
#
# Classes
#
#-------------------------------------------------------------------------
class EmbeddedList(ButtonTab):
"""
This class provides the base class for all the list tabs. It
maintains a gtk.TreeView, including the selection and button
sensitivity.
"""
_HANDLE_COL = -1
_DND_TYPE = None
_DND_EXTRA = None
def __init__(self, dbstate, uistate, track, name, build_model,
share=False):
"""
Creates a new list, using the passed build_model to
populate the list.
"""
ButtonTab.__init__(self, dbstate, uistate, track, name, share)
self.changed = False
self.build_model = build_model
# handle the selection
self.selection = self.tree.get_selection()
self.selection.connect('changed', self._selection_changed)
# build the columns
self.columns = []
self.build_columns()
if self._DND_TYPE:
self._set_dnd()
# set up right click option
self.tree.connect('button-press-event', self._on_button_press)
# build the initial data
self.rebuild()
self.show_all()
def _on_button_press(self, obj, event):
if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
ref = self.get_selected()
if ref:
self.right_click(obj, event)
def get_popup_menu_items(self):
if self.share_btn:
itemlist = [
(True, gtk.STOCK_ADD, self.add_button_clicked),
(False, _('Share'), self.edit_button_clicked),
(True, gtk.STOCK_EDIT, self.edit_button_clicked),
(True, gtk.STOCK_REMOVE, self.del_button_clicked),
]
else:
itemlist = [
(True, gtk.STOCK_ADD, self.add_button_clicked),
(True, gtk.STOCK_EDIT, self.edit_button_clicked),
(True, gtk.STOCK_REMOVE, self.del_button_clicked),
]
return itemlist
def right_click(self, obj, event):
menu = gtk.Menu()
for (image, title, func) in self.get_popup_menu_items():
if image:
item = gtk.ImageMenuItem(stock_id=title)
else:
item = gtk.MenuItem(title)
item.connect('activate', func)
item.show()
menu.append(item)
menu.popup(None, None, None, event.button, event.time)
def find_index(self, obj):
"""
returns the index of the object within the associated data
"""
return self.get_data().index(obj)
def _set_dnd(self):
"""
Sets up drag-n-drop. The source and destionation are set by calling .target()
on the _DND_TYPE. Obviously, this means that there must be a _DND_TYPE
variable defined that points to an entry in DdTargets.
"""
if self._DND_EXTRA:
dnd_types = [ self._DND_TYPE.target(), self._DND_EXTRA.target() ]
else:
dnd_types = [ self._DND_TYPE.target() ]
self.tree.drag_dest_set(gtk.DEST_DEFAULT_ALL, dnd_types,
gtk.gdk.ACTION_COPY)
self.tree.drag_source_set(gtk.gdk.BUTTON1_MASK,
[self._DND_TYPE.target()],
gtk.gdk.ACTION_COPY)
self.tree.connect('drag_data_get', self.drag_data_get)
self.tree.connect('drag_data_received', self.drag_data_received)
def drag_data_get(self, widget, context, sel_data, info, time):
"""
Provide the drag_data_get function, which passes a tuple consisting of:
1) Drag type defined by the .drag_type field specfied by the value
assigned to _DND_TYPE
2) The id value of this object, used for the purpose of determining
the source of the object. If the source of the object is the same
as the object, we are doing a reorder instead of a normal drag
and drop
3) Pickled data. The pickled version of the selected object
4) Source row. Used for a reorder to determine the original position
of the object
"""
# get the selected object, returning if not is defined
obj = self.get_selected()
if not obj:
return
# pickle the data, and build the tuple to be passed
value = (self._DND_TYPE.drag_type, id(self), obj, self.find_index(obj))
data = pickle.dumps(value)
# pass as a string (8 bits)
sel_data.set(sel_data.target, 8, data)
def drag_data_received(self, widget, context, x, y, sel_data, info, time):
"""
Handle the standard gtk interface for drag_data_received.
If the selection data is define, extract the value from sel_data.data,
and decide if this is a move or a reorder.
"""
if sel_data and sel_data.data:
(mytype, selfid, obj, row_from) = pickle.loads(sel_data.data)
# make sure this is the correct DND type for this object
if mytype == self._DND_TYPE.drag_type:
# determine the destination row
row = self._find_row(x, y)
# if the is same object, we have a move, otherwise,
# it is a standard drag-n-drop
if id(self) == selfid:
self._move(row_from, row, obj)
else:
self._handle_drag(row, obj)
self.rebuild()
elif self._DND_EXTRA and mytype == self._DND_EXTRA.drag_type:
self.handle_extra_type(mytype, obj)
def handle_extra_type(self, objtype, obj):
pass
def _find_row(self, x, y):
row = self.tree.get_path_at_pos(x, y)
if row == None:
return len(self.get_data())
else:
return row[0][0]
def _handle_drag(self, row, obj):
self.get_data().insert(row, obj)
self.changed = True
self.rebuild()
def _move(self, row_from, row_to, obj):
dlist = self.get_data()
if row_from < row_to:
dlist.insert(row_to, obj)
del dlist[row_from]
else:
del dlist[row_from]
dlist.insert(row_to-1, obj)
self.changed = True
self.rebuild()
def get_icon_name(self):
"""
Specifies the basic icon used for a generic list. Typically,
a derived class will override this. The icon chose is the
STOCK_JUSTIFY_FILL icon, which in the default GTK style
looks kind of like a list.
"""
return gtk.STOCK_JUSTIFY_FILL
def del_button_clicked(self, obj):
ref = self.get_selected()
if ref:
ref_list = self.get_data()
ref_list.remove(ref)
self.changed = True
self.rebuild()
def build_interface(self):
"""
Builds the interface, instantiating a gtk.TreeView in a
gtk.ScrolledWindow.
"""
# create the tree, turn on rule hinting and connect the
# button press to the double click function.
self.tree = gtk.TreeView()
self.tree.set_reorderable(True)
self.tree.set_rules_hint(True)
self.tree.connect('button_press_event', self.double_click)
# create the scrolled window, and attach the treeview
scroll = gtk.ScrolledWindow()
scroll.set_shadow_type(gtk.SHADOW_IN)
scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scroll.add(self.tree)
self.pack_start(scroll, True)
def get_selected(self):
"""
returns the value associated with selected row in the model,
based of the _HANDLE_COL value. Each model must define this
to indicate what the returned value should be. If no selection
has been made, None is returned.
"""
(model, node) = self.selection.get_selected()
if node:
return model.get_value(node, self._HANDLE_COL)
return None
def is_empty(self):
"""
Returns True if the get_data returns a length greater than
0. Typically, get_data returns the list of associated data.
"""
return len(self.get_data()) == 0
def get_data(self):
"""
Returns the data associated with the list. This is typically
a list of objects.
This should be overridden in the derrived classes.
"""
return []
def column_order(self):
"""
Specifies the column order for the columns. This should be
in the format of a list of tuples, in the format of (int,int),
where the first in indicates if the column is visible, and the
second column indicates the index into the model.
This should be overridden in the derrived classes.
"""
return []
def build_columns(self):
"""
Builds the columns and inserts them into the TreeView. Any
previous columns exist, they will be in the self.columns array,
and removed.
"""
# remove any existing columns, which would be stored in
# self.columns
for column in self.columns:
self.tree.remove_column(column)
self.columns = []
# loop through the values returned by column_order
for pair in self.column_order():
# if the first value isn't 1, then we skip the values
if not pair[0]:
continue
# extract the name from the _column_names variable, and
# assign it to the column name. The text value is extracted
# from the model column specified in pair[1]
name = self._column_names[pair[1]][0]
renderer = gtk.CellRendererText()
renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
column = gtk.TreeViewColumn(name, renderer, text=pair[1])
# insert the colum into the tree
column.set_resizable(True)
column.set_min_width(self._column_names[pair[1]][2])
column.set_sort_column_id(self._column_names[pair[1]][1])
self.columns.append(column)
self.tree.append_column(column)
def rebuild(self):
"""
Rebuilds the data in the database by creating a new model,
using the build_model function passed at creation time.
"""
self.model = self.build_model(self.get_data(), self.dbstate.db)
self.tree.set_model(self.model)
self._set_label()
self._selection_changed()