Add hierarchical place view. Reorganise existing place view and model.

svn: r13601
This commit is contained in:
Nick Hall 2009-11-17 00:13:03 +00:00
parent ef705c09ef
commit d7b4ca2d9c
13 changed files with 999 additions and 500 deletions

View File

@ -11,8 +11,9 @@ pkgdatadir = $(datadir)/@PACKAGE@/gui/views
pkgdata_PYTHON = \
__init__.py \
listview.py \
navigationview.py \
pageview.py
navigationview.py \
pageview.py \
placebaseview.py
pkgpyexecdir = @pkgpyexecdir@/gui/views
pkgpythondir = @pkgpythondir@/gui/views

View File

@ -0,0 +1,381 @@
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2001-2006 Donald N. Allingham
# Copyright (C) 2008 Gary Burton
#
# 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$
"""
Base view for Place Views
"""
#-------------------------------------------------------------------------
#
# Global modules
#
#-------------------------------------------------------------------------
#-------------------------------------------------------------------------
#
# GTK/Gnome modules
#
#-------------------------------------------------------------------------
import gtk
#-------------------------------------------------------------------------
#
# gramps modules
#
#-------------------------------------------------------------------------
import gen.lib
from gui.views.listview import ListView
from gui.utils import add_menuitem
import Errors
import Bookmarks
import config
from QuestionDialog import ErrorDialog
from gui.pluginmanager import GuiPluginManager
from DdTargets import DdTargets
from Editors import EditPlace, DeletePlaceQuery
from Filters.SideBar import PlaceSidebarFilter
#-------------------------------------------------------------------------
#
# internationalization
#
#-------------------------------------------------------------------------
from gettext import gettext as _
#-------------------------------------------------------------------------
#
# PlaceBaseView
#
#-------------------------------------------------------------------------
class PlaceBaseView(ListView):
COLUMN_NAMES = [
_('Place Name'),
_('ID'),
_('Church Parish'),
_('ZIP/Postal Code'),
_('City'),
_('County'),
_('State'),
_('Country'),
_('Latitude'),
_('Longitude'),
_('Last Changed'),
_('Street'),
]
ADD_MSG = _("Add a new place")
EDIT_MSG = _("Edit the selected place")
DEL_MSG = _("Delete the selected place")
FILTER_TYPE = "Place"
def __init__(self, dbstate, uistate, title, model):
signal_map = {
'place-add' : self.row_add,
'place-update' : self.row_update,
'place-delete' : self.row_delete,
'place-rebuild' : self.object_build,
}
self.func_list = {
'<CONTROL>J' : self.jump,
'<CONTROL>BackSpace' : self.key_delete,
}
self.mapservice = config.get('interface.mapservice')
self.mapservicedata = {}
ListView.__init__(
self, title, dbstate, uistate, PlaceBaseView.COLUMN_NAMES,
len(PlaceBaseView.COLUMN_NAMES),
model, signal_map,
dbstate.db.get_place_bookmarks(),
Bookmarks.PlaceBookmarks,
multiple=True,
filter_class=PlaceSidebarFilter)
config.connect("interface.filter",
self.filter_toggle)
def column_ord_setfunc(self, clist):
self.dbstate.db.set_place_column_order(clist)
def get_bookmarks(self):
return self.dbstate.db.get_place_bookmarks()
def define_actions(self):
ListView.define_actions(self)
self._add_action('ColumnEdit', gtk.STOCK_PROPERTIES,
_('_Column Editor'), callback=self._column_editor)
self._add_action('FastMerge', None, _('_Merge...'),
callback=self.fast_merge)
self._add_toolmenu_action('MapsList', _('Loading...'),
_("Attempt to see selected locations with a Map "
"Service (OpenstreetMap, Google Maps, ...)"),
self.gotomap,
_('Select a Map Service'))
self._add_action('GotoMap', gtk.STOCK_JUMP_TO,
_('_Look up with Map Service'),
callback=self.gotomap,
tip=_("Attempt to see this location with a Map "
"Service (OpenstreetMap, Google Maps, ...)"))
self._add_action('FilterEdit', None, _('Place Filter Editor'),
callback=self.filter_editor)
def change_page(self):
"""
Called by viewmanager at end of realization when arriving on the page
At this point the Toolbar is created. We need to:
1. get the menutoolbutton
2. add all possible map services in the drop down menu
3. add the actions that correspond to clicking in this drop down menu
4. set icon and label of the menutoolbutton now that it is realized
5. store label so it can be changed when selection changes
"""
ListView.change_page(self)
#menutoolbutton actions are stored in PageView class,
# obtain the widgets where we need to add to menu
actionservices = self.action_toolmenu['MapsList']
widgets = actionservices.get_proxies()
mmenu = self.__create_maps_menu_actions()
if not self.mapservicedata:
return
self.mapslistlabel = []
if not self.mapservice in self.mapservicedata:
#stored val no longer exists, use the first key instead
self.set_mapservice(self.mapservicedata.keys()[0])
#store all gtk labels to be able to update label on selection change
for widget in widgets :
if isinstance(widget, gtk.MenuToolButton):
widget.set_menu(mmenu)
if gtk.pygtk_version >= (2, 12, 0):
widget.set_arrow_tooltip_text(actionservices.arrowtooltip)
lbl = gtk.Label(self.mapservice_label())
lbl.show()
self.mapslistlabel.append(lbl)
widget.set_label_widget(self.mapslistlabel[-1])
widget.set_stock_id(gtk.STOCK_JUMP_TO)
if self.drag_info():
self.list.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
[('text/plain', 0, 0), self.drag_info().target()],
gtk.gdk.ACTION_COPY)
def __create_maps_menu_actions(self):
"""
Function creating a menu and actions that are used as dropdown menu
from the menutoolbutton
"""
menu = gtk.Menu()
#select the map services to show
self.mapservicedata = {}
servlist = GuiPluginManager.get_instance().get_reg_mapservices()
for i, pdata in zip(range(len(servlist)), servlist):
key = pdata.id.replace(' ', '-')
add_menuitem(menu, pdata.name, None,
make_callback(self.set_mapservice, key))
self.mapservicedata[key] = pdata
return menu
def set_mapservice(self, mapkey):
"""
change the service that runs on click of the menutoolbutton
used as callback menu on menu clicks
"""
self.mapservice = mapkey
for label in self.mapslistlabel:
label.set_label(self.mapservice_label())
label.show()
config.set('interface.mapservice', mapkey)
config.save()
def mapservice_label(self):
"""
return the current label for the menutoolbutton
"""
return self.mapservicedata[self.mapservice].name
def gotomap(self, obj):
"""
Run the map service
"""
#First test if any map service is available
if not len(self.mapservicedata):
msg = _("No map service is available.")
msg2 = _("Check your installation.")
ErrorDialog(msg, msg2)
return
place_handles = self.selected_handles()
try:
place_handle = self.selected_handles()[0]
except IndexError:
msg = _("No place selected.")
msg2 = _("You need to select a place to be able to view it"
" on a map. Some Map Services might support multiple"
" selections.")
ErrorDialog(msg, msg2)
return
#TODO: support for descriptions in some cases. For now, pass None
#TODO: Later this might be 'Birth of William' ....
places = [(x, None) for x in place_handles]
#run the mapservice:
pmgr = GuiPluginManager.get_instance()
serv = self.mapservicedata[self.mapservice]
mod = pmgr.load_plugin(serv)
if mod:
servfunc = eval('mod.' + serv.mapservice)
servfunc()(self.dbstate.db, places)
else:
print 'Failed to load map plugin, see Plugin Status'
def drag_info(self):
return DdTargets.PLACE_LINK
def _column_editor(self, obj):
import ColumnOrder
ColumnOrder.ColumnOrder(
_('Select Place Columns'),
self.uistate,
self.dbstate.db.get_place_column_order(),
PlaceBaseView.COLUMN_NAMES,
self.set_column_order)
def column_order(self):
return self.dbstate.db.get_place_column_order()
def get_stock(self):
return 'gramps-place'
def ui_definition(self):
return '''<ui>
<menubar name="MenuBar">
<menu action="FileMenu">
<placeholder name="LocalExport">
<menuitem action="ExportTab"/>
</placeholder>
</menu>
<menu action="BookMenu">
<placeholder name="AddEditBook">
<menuitem action="AddBook"/>
<menuitem action="EditBook"/>
</placeholder>
</menu>
<menu action="EditMenu">
<placeholder name="CommonEdit">
<menuitem action="Add"/>
<menuitem action="Edit"/>
<menuitem action="Remove"/>
</placeholder>
<menuitem action="ColumnEdit"/>
<menuitem action="FilterEdit"/>
<placeholder name="Merge">
<menuitem action="FastMerge"/>
</placeholder>
</menu>
</menubar>
<toolbar name="ToolBar">
<placeholder name="CommonEdit">
<toolitem action="Add"/>
<toolitem action="Edit"/>
<toolitem action="Remove"/>
<separator/>
<toolitem action="MapsList"/>
</placeholder>
</toolbar>
<popup name="Popup">
<menuitem action="Add"/>
<menuitem action="Edit"/>
<menuitem action="Remove"/>
<menuitem action="GotoMap"/>
</popup>
</ui>'''
def add(self, obj):
try:
EditPlace(self.dbstate, self.uistate, [], gen.lib.Place())
except Errors.WindowActiveError:
pass
def remove(self, obj):
self.remove_selected_objects()
def remove_object_from_handle(self, handle):
person_list = [
item[1] for item in
self.dbstate.db.find_backlink_handles(handle,['Person'])]
family_list = [
item[1] for item in
self.dbstate.db.find_backlink_handles(handle,['Family'])]
event_list = [
item[1] for item in
self.dbstate.db.find_backlink_handles(handle,['Event'])]
object = self.dbstate.db.get_place_from_handle(handle)
query = DeletePlaceQuery(self.dbstate, self.uistate, object,
person_list, family_list, event_list)
is_used = len(person_list) + len(family_list) + len(event_list) > 0
return (query, is_used, object)
def edit(self, obj):
for handle in self.selected_handles():
place = self.dbstate.db.get_place_from_handle(handle)
try:
EditPlace(self.dbstate, self.uistate, [], place)
except Errors.WindowActiveError:
pass
def fast_merge(self, obj):
mlist = self.selected_handles()
if len(mlist) != 2:
msg = _("Cannot merge places.")
msg2 = _("Exactly two places must be selected to perform a merge. "
"A second place can be selected by holding down the "
"control key while clicking on the desired place.")
ErrorDialog(msg, msg2)
else:
import Merge
Merge.MergePlaces(self.dbstate, self.uistate, mlist[0], mlist[1])
def get_handle_from_gramps_id(self, gid):
obj = self.dbstate.db.get_place_from_gramps_id(gid)
if obj:
return obj.get_handle()
else:
return None
def make_callback(func, val):
return lambda x: func(val)

View File

@ -13,7 +13,9 @@ pkgdata_PYTHON = \
mediamodel.py \
notemodel.py \
peoplemodel.py \
placebasemodel.py \
placemodel.py \
placetreemodel.py \
repomodel.py \
sourcemodel.py \
treebasemodel.py

View File

@ -27,7 +27,9 @@ from peoplemodel import PeopleModel
from familymodel import FamilyModel
from eventmodel import EventModel
from sourcemodel import SourceModel
from placebasemodel import PlaceBaseModel
from placemodel import PlaceModel
from placetreemodel import PlaceTreeModel
from mediamodel import MediaModel
from repomodel import RepositoryModel
from notemodel import NoteModel

View File

@ -182,7 +182,7 @@ class PeopleModel(TreeBaseModel):
name_data = data[COLUMN_NAME]
group_name = ngn(self.db, name_data)
sort_key = self.sort_func(data, handle)
sort_key = self.sort_func(data)
#if group_name not in self.group_list:
#self.group_list.append(group_name)
@ -192,12 +192,12 @@ class PeopleModel(TreeBaseModel):
# nodes in the treebasemodel, and will be used as iters
self.add_node(group_name, handle, sort_key, handle)
def sort_name(self, data, node):
def sort_name(self, data):
n = Name()
n.unserialize(data[COLUMN_NAME])
return name_displayer.sort_string(n)
def column_spouse(self, data, node):
def column_spouse(self, data):
spouses_names = u""
handle = data[0]
for family_handle in data[COLUMN_FAMILY]:
@ -214,40 +214,43 @@ class PeopleModel(TreeBaseModel):
spouses_names += name_displayer.display(spouse)
return spouses_names
def column_name(self, data, node):
if node in self.lru_name:
name = self.lru_name[node]
def column_name(self, data):
handle = data[0]
if handle in self.lru_name:
name = self.lru_name[handle]
else:
name = name_displayer.raw_sorted_name(data[COLUMN_NAME])
if not self._in_build:
self.lru_name[node] = name
self.lru_name[handle] = name
return name
def column_id(self, data, node):
def column_id(self, data):
return data[COLUMN_ID]
def column_change(self, data, node):
def column_change(self, data):
return unicode(
time.strftime('%x %X',
time.localtime(data[COLUMN_CHANGE])),
GrampsLocale.codeset)
def column_gender(self, data, node):
def column_gender(self, data):
return PeopleModel._GENDER[data[COLUMN_GENDER]]
def column_birth_day(self, data, node):
if node in self.lru_bdate:
value = self.lru_bdate[node]
def column_birth_day(self, data):
handle = data[0]
if handle in self.lru_bdate:
value = self.lru_bdate[handle]
else:
value = self._get_birth_data(data, node, False)
value = self._get_birth_data(data, False)
if not self._in_build:
self.lru_bdate[node] = value
self.lru_bdate[handle] = value
return value
def sort_birth_day(self, data, node):
return self._get_birth_data(data, node, True)
def sort_birth_day(self, data):
handle = data[0]
return self._get_birth_data(data, True)
def _get_birth_data(self, data, node, sort_mode):
def _get_birth_data(self, data, sort_mode):
index = data[COLUMN_BIRTH]
if index != -1:
try:
@ -288,19 +291,21 @@ class PeopleModel(TreeBaseModel):
return u""
def column_death_day(self, data, node):
if node in self.lru_ddate:
value = self.lru_ddate[node]
def column_death_day(self, data):
handle = data[0]
if handle in self.lru_ddate:
value = self.lru_ddate[handle]
else:
value = self._get_death_data(data, node, False)
value = self._get_death_data(data, False)
if not self._in_build:
self.lru_ddate[node] = value
self.lru_ddate[handle] = value
return value
def sort_death_day(self, data, node):
return self._get_death_data(data, node, True)
def sort_death_day(self, data):
handle = data[0]
return self._get_death_data(data, True)
def _get_death_data(self, data, node, sort_mode):
def _get_death_data(self, data, sort_mode):
index = data[COLUMN_DEATH]
if index != -1:
try:
@ -342,7 +347,7 @@ class PeopleModel(TreeBaseModel):
return retval
return u""
def column_birth_place(self, data, node):
def column_birth_place(self, data):
index = data[COLUMN_BIRTH]
if index != -1:
try:
@ -377,7 +382,7 @@ class PeopleModel(TreeBaseModel):
return u""
def column_death_place(self, data, node):
def column_death_place(self, data):
index = data[COLUMN_DEATH]
if index != -1:
try:
@ -412,12 +417,12 @@ class PeopleModel(TreeBaseModel):
return "<i>" + cgi.escape(place_title) + "</i>"
return u""
def column_marker_text(self, data, node):
def column_marker_text(self, data):
if COLUMN_MARKER < len(data):
return str(data[COLUMN_MARKER])
return ""
def column_marker_color(self, data, node):
def column_marker_color(self, data):
try:
if data[COLUMN_MARKER]:
if data[COLUMN_MARKER][0] == MarkerType.COMPLETE:
@ -430,7 +435,7 @@ class PeopleModel(TreeBaseModel):
pass
return None
def column_tooltip(self, data, node):
def column_tooltip(self, data):
if const.USE_TIPS:
return ToolTips.TipFromFunction(
self.db,
@ -439,8 +444,8 @@ class PeopleModel(TreeBaseModel):
else:
return u''
def column_int_id(self, data, node):
return node
def column_int_id(self, data):
return data[0]
def column_header(self, node):
return node

View File

@ -0,0 +1,208 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2006 Donald N. Allingham
# Copyright (C) 2009 Nick Hall
#
# 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 modules
#
#-------------------------------------------------------------------------
import time
import logging
log = logging.getLogger(".")
#-------------------------------------------------------------------------
#
# GNOME/GTK modules
#
#-------------------------------------------------------------------------
import gtk
#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
import const
import ToolTips
import GrampsLocale
#-------------------------------------------------------------------------
#
# internationalization
#
#-------------------------------------------------------------------------
from gettext import gettext as _
#-------------------------------------------------------------------------
#
# PlaceBaseModel
#
#-------------------------------------------------------------------------
class PlaceBaseModel():
HANDLE_COL = 12
def __init__(self, db):
self.gen_cursor = db.get_place_cursor
self.map = db.get_raw_place_data
self.fmap = [
self.column_name,
self.column_id,
self.column_parish,
self.column_postal_code,
self.column_city,
self.column_county,
self.column_state,
self.column_country,
self.column_latitude,
self.column_longitude,
self.column_change,
self.column_street,
self.column_handle,
self.column_tooltip
]
self.smap = [
self.column_name,
self.column_id,
self.column_parish,
self.column_postal_code,
self.column_city,
self.column_county,
self.column_state,
self.column_country,
self.sort_latitude,
self.sort_longitude,
self.sort_change,
self.column_street,
self.column_handle,
]
def on_get_n_columns(self):
return len(self.fmap)+1
def column_handle(self, data):
return unicode(data[0])
def column_name(self, data):
return unicode(data[2])
def __format_degrees(self, angle, sign_str):
"""
Format a decimal as degrees, minutes and seconds.
If the value is not a decimal leave it unformatted.
"""
try:
angle = float(angle)
except ValueError:
return angle
if angle >= 0:
sign = sign_str[0]
else:
sign = sign_str[1]
seconds = abs(int(angle * 60 * 60))
minutes = seconds / 60
seconds %= 60
degrees = minutes / 60
minutes %= 60
string = unicode(degrees) + u'\u00b0 ' + \
unicode(minutes) + u'\u2032 ' + \
unicode(seconds) + u'\u2033 ' + unicode(sign)
return string
def column_longitude(self, data):
return self.__format_degrees(data[3], _('EW'))
def column_latitude(self, data):
return self.__format_degrees(data[4], _('NS'))
def sort_longitude(self, data):
return unicode(data[3])
def sort_latitude(self, data):
return unicode(data[4])
def column_id(self, data):
return unicode(data[1])
def column_parish(self, data):
try:
return data[5][1]
except:
return u''
def column_street(self, data):
try:
return data[5][0][0]
except:
return u''
def column_city(self, data):
try:
return data[5][0][1]
except:
return u''
def column_county(self, data):
try:
return data[5][0][2]
except:
return u''
def column_state(self, data):
try:
return data[5][0][3]
except:
return u''
def column_country(self, data):
try:
return data[5][0][4]
except:
return u''
def column_postal_code(self, data):
try:
return data[5][0][5]
except:
return u''
def sort_change(self, data):
return "%012x" % data[11]
def column_change(self, data, node):
return unicode(time.strftime('%x %X',time.localtime(data[11])),
GrampsLocale.codeset)
def column_tooltip(self, data):
if const.USE_TIPS:
try:
t = ToolTips.TipFromFunction(
self.db, lambda:
self.db.get_place_from_handle(data[0]))
except:
log.error("Failed to create tooltip.", exc_info=True)
return t
else:
return u''

View File

@ -1,7 +1,7 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2006 Donald N. Allingham
# Copyright (C) 2009 Nick Hall
#
# 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
@ -19,14 +19,16 @@
#
# $Id:_PlaceModel.py 9912 2008-01-22 09:17:46Z acraphae $
"""
Place Model.
"""
#-------------------------------------------------------------------------
#
# python modules
#
#-------------------------------------------------------------------------
import time
import logging
log = logging.getLogger(".")
_LOG = logging.getLogger(".gui.views.treemodels.placemodel")
#-------------------------------------------------------------------------
#
@ -40,9 +42,7 @@ import gtk
# GRAMPS modules
#
#-------------------------------------------------------------------------
import const
import ToolTips
import GrampsLocale
from gui.views.treemodels.placebasemodel import PlaceBaseModel
from gui.views.treemodels.flatbasemodel import FlatBaseModel
#-------------------------------------------------------------------------
@ -50,123 +50,13 @@ from gui.views.treemodels.flatbasemodel import FlatBaseModel
# PlaceModel
#
#-------------------------------------------------------------------------
class PlaceModel(FlatBaseModel):
HANDLE_COL = 12
def __init__(self,db,scol=0, order=gtk.SORT_ASCENDING,search=None,
class PlaceModel(PlaceBaseModel, FlatBaseModel):
"""
Flat place model. (Original code in PlaceBaseModel).
"""
def __init__(self, db, scol=0, order=gtk.SORT_ASCENDING, search=None,
skip=set(), sort_map=None):
self.gen_cursor = db.get_place_cursor
self.map = db.get_raw_place_data
self.fmap = [
self.column_name,
self.column_id,
self.column_parish,
self.column_postal_code,
self.column_city,
self.column_county,
self.column_state,
self.column_country,
self.column_latitude,
self.column_longitude,
self.column_change,
self.column_street,
self.column_handle,
self.column_tooltip
]
self.smap = [
self.column_name,
self.column_id,
self.column_parish,
self.column_postal_code,
self.column_city,
self.column_county,
self.column_state,
self.column_country,
self.column_latitude,
self.column_longitude,
self.column_change,
self.column_street,
self.column_handle,
]
PlaceBaseModel.__init__(self, db)
FlatBaseModel.__init__(self, db, scol, order, tooltip_column=13,
search=search, skip=skip, sort_map=sort_map)
def on_get_n_columns(self):
return len(self.fmap)+1
def column_handle(self,data):
return unicode(data[0])
def column_name(self,data):
return unicode(data[2])
def column_longitude(self,data):
return unicode(data[3])
def column_latitude(self,data):
return unicode(data[4])
def column_id(self,data):
return unicode(data[1])
def column_parish(self,data):
try:
return data[5][1]
except:
return u''
def column_street(self,data):
try:
return data[5][0][0]
except:
return u''
def column_city(self,data):
try:
return data[5][0][1]
except:
return u''
def column_county(self,data):
try:
return data[5][0][2]
except:
return u''
def column_state(self,data):
try:
return data[5][0][3]
except:
return u''
def column_country(self,data):
try:
return data[5][0][4]
except:
return u''
def column_postal_code(self,data):
try:
return data[5][0][5]
except:
return u''
def sort_change(self,data):
return "%012x" % data[11]
def column_change(self,data):
return unicode(time.strftime('%x %X',time.localtime(data[11])),
GrampsLocale.codeset)
def column_tooltip(self,data):
if const.USE_TIPS:
try:
t = ToolTips.TipFromFunction(
self.db, lambda:
self.db.get_place_from_handle(data[0]))
except:
log.error("Failed to create tooltip.", exc_info=True)
return t
else:
return u''

View File

@ -0,0 +1,122 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2009 Nick Hall
#
# 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$
"""
Place tree model.
"""
#-------------------------------------------------------------------------
#
# python modules
#
#-------------------------------------------------------------------------
import logging
_LOG = logging.getLogger(".gui.views.treemodels.placetreemodel")
#-------------------------------------------------------------------------
#
# GNOME/GTK modules
#
#-------------------------------------------------------------------------
import gtk
#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
from gui.views.treemodels.placebasemodel import PlaceBaseModel
from gui.views.treemodels.treebasemodel import TreeBaseModel
#-------------------------------------------------------------------------
#
# Internationalization
#
#-------------------------------------------------------------------------
from gettext import gettext as _
#-------------------------------------------------------------------------
#
# PlaceTreeModel
#
#-------------------------------------------------------------------------
class PlaceTreeModel(PlaceBaseModel, TreeBaseModel):
"""
Hierarchical place model.
"""
def __init__(self, db, scol=0, order=gtk.SORT_ASCENDING, search=None,
skip=set(), sort_map=None):
self.hmap = [
self.column_header,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
]
PlaceBaseModel.__init__(self, db)
TreeBaseModel.__init__(self, db, scol=scol, order=order,
tooltip_column=13,
search=search, skip=skip, sort_map=sort_map,
nrgroups = 3,
group_can_have_handle = True)
def add_row(self, handle, data):
"""
Add nodes to the node map for a single place.
handle The handle of the gramps object.
data The object data.
"""
level1 = data[5][0][4]
if not level1:
level1 = _('Unknown level1')
level2 = data[5][0][3]
if not level2:
level2 = _('Unknown level2')
level3 = data[5][0][2]
if not level3:
level3 = _('Unknown level3')
node1 = (level1, )
node2 = (level2, level1)
node3 = (level3, level2)
sort_key = self.sort_func(data)
self.add_node(None, node1, level1, None, add_parent=False)
self.add_node(node1, node2, level2, None, add_parent=False)
self.add_node(node2, node3, level3, None, add_parent=False)
self.add_node(node3, handle, sort_key, handle, add_parent=False)
def column_header(self, node):
"""
Return a column heading. This is called for nodes with no associated
Gramps handle.
"""
return node[0]

View File

@ -280,7 +280,7 @@ class TreeBaseModel(gtk.GenericTreeModel):
self._in_build = False
self.current_filter = data_filter
_LOG.debug(self.__class__.__name__ + ' rebuild_data ' +
str(time.clock() - cput) + ' sec')
@ -560,7 +560,7 @@ class TreeBaseModel(gtk.GenericTreeModel):
data = self.map(handle)
if not self._in_build:
self.lru_data[handle] = data
return (self.fmap[col](data, handle))
return (self.fmap[col](data))
except:
return None

View File

@ -16,6 +16,8 @@ pkgdata_PYTHON = \
noteview.py \
pedigreeview.py \
personview.py \
placetreeview.gpr.py \
placetreeview.py \
placeview.py \
relview.py \
repoview.py \

View File

@ -0,0 +1,12 @@
register(VIEW,
id = 'placetreeview',
name = _("Place Tree View"),
description = _("A view displaying places in a tree format."),
version = '1.0',
status = UNSTABLE,
fname = 'placetreeview.py',
authors = [u"Donald N. Allingham", u"Gary Burton", u"Nick Hall"],
authors_email = [""],
category = VIEW_PLACE,
viewclass = 'PlaceTreeView',
)

View File

@ -0,0 +1,205 @@
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2009 Nick Hall
#
# 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$
"""
Place Tree View
"""
#-------------------------------------------------------------------------
#
# Gramps modules
#
#-------------------------------------------------------------------------
from gui.views.placebaseview import PlaceBaseView
from gui.views.treemodels import PlaceTreeModel
import gen.lib
import Errors
from Editors import EditPlace
#-------------------------------------------------------------------------
#
# Internationalization
#
#-------------------------------------------------------------------------
from gettext import gettext as _
#-------------------------------------------------------------------------
#
# PlaceTreeView
#
#-------------------------------------------------------------------------
class PlaceTreeView(PlaceBaseView):
"""
A hierarchical view of the top three levels of places.
"""
def __init__(self, dbstate, uistate):
PlaceBaseView.__init__(self, dbstate, uistate,
_('Tree'), PlaceTreeModel)
def get_viewtype_stock(self):
"""
Override the default icon. Set for hierarchical view.
"""
return 'gramps-tree-group'
def define_actions(self):
"""
Define actions for the popup menu specific to the tree view.
"""
PlaceBaseView.define_actions(self)
self._add_action('OpenBranch', None, _("Expand Rows"),
callback=self.open_branch)
self._add_action('CloseBranch', None, _("Collapse Rows"),
callback=self.close_branch)
self._add_action('OpenAllNodes', None, _("Expand all Nodes"),
callback=self.open_all_nodes)
self._add_action('CloseAllNodes', None, _("Collapse all Nodes"),
callback=self.close_all_nodes)
def ui_definition(self):
"""
A user interface definition including tree specific actions.
"""
return '''<ui>
<menubar name="MenuBar">
<menu action="FileMenu">
<placeholder name="LocalExport">
<menuitem action="ExportTab"/>
</placeholder>
</menu>
<menu action="BookMenu">
<placeholder name="AddEditBook">
<menuitem action="AddBook"/>
<menuitem action="EditBook"/>
</placeholder>
</menu>
<menu action="EditMenu">
<placeholder name="CommonEdit">
<menuitem action="Add"/>
<menuitem action="Edit"/>
<menuitem action="Remove"/>
</placeholder>
<menuitem action="ColumnEdit"/>
<menuitem action="FilterEdit"/>
<placeholder name="Merge">
<menuitem action="FastMerge"/>
</placeholder>
</menu>
</menubar>
<toolbar name="ToolBar">
<placeholder name="CommonEdit">
<toolitem action="Add"/>
<toolitem action="Edit"/>
<toolitem action="Remove"/>
<separator/>
<toolitem action="MapsList"/>
</placeholder>
</toolbar>
<popup name="Popup">
<menuitem action="OpenBranch"/>
<menuitem action="CloseBranch"/>
<menuitem action="OpenAllNodes"/>
<menuitem action="CloseAllNodes"/>
<separator/>
<menuitem action="Add"/>
<menuitem action="Edit"/>
<menuitem action="Remove"/>
<separator/>
<menuitem action="GotoMap"/>
</popup>
</ui>'''
def add(self, obj):
"""
Add a new place. Attempt to get the top three levels of hierarchy from
the currently selected row.
"""
place = gen.lib.Place()
model, pathlist = self.selection.get_selected_rows()
level1 = level2 = level3 = u""
if len(pathlist) == 1:
path = pathlist[0]
if len(path) == 1:
level1 = model.on_get_iter(path)[0]
elif len(path) == 2:
level2 = model.on_get_iter(path)[0]
level1 = model.on_get_iter(path)[1]
elif len(path) == 3:
node = model.on_get_iter(path)
level3 = node[0]
level2 = node[1]
level1 = model.on_iter_parent(node)[1]
else:
node = model.on_iter_parent(model.on_get_iter(path))
level3 = node[0]
level2 = node[1]
level1 = model.on_iter_parent(node)[1]
try:
place.get_main_location().set_country(level1)
place.get_main_location().set_state(level2)
place.get_main_location().set_county(level3)
EditPlace(self.dbstate, self.uistate, [], place)
except Errors.WindowActiveError:
pass
def open_branch(self, obj):
"""
Expand the selected branches and all children.
"""
self.uistate.status_text(_("Updating display..."))
self.uistate.set_busy_cursor(True)
selected = self.selection.get_selected_rows()
for path in selected[1]:
self.list.expand_row(path, True)
self.uistate.set_busy_cursor(False)
self.uistate.modify_statusbar(self.dbstate)
def close_branch(self, obj):
"""
Collapse the selected branches.
"""
selected = self.selection.get_selected_rows()
for path in selected[1]:
self.list.collapse_row(path)
def open_all_nodes(self, obj):
"""
Expand the entire tree.
"""
self.uistate.status_text(_("Updating display..."))
self.uistate.set_busy_cursor(True)
self.list.expand_all()
self.uistate.set_busy_cursor(False)
self.uistate.modify_statusbar(self.dbstate)
def close_all_nodes(self, obj):
"""
Collapse the entire tree.
"""
self.list.collapse_all()

View File

@ -1,7 +1,6 @@
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2001-2006 Donald N. Allingham
# Copyright (C) 2008 Gary Burton
# Copyright (C) 2009 Nick Hall
#
# 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
@ -26,357 +25,27 @@ Place View
#-------------------------------------------------------------------------
#
# Global modules
# Gramps modules
#
#-------------------------------------------------------------------------
#-------------------------------------------------------------------------
#
# GTK/Gnome modules
#
#-------------------------------------------------------------------------
import gtk
#-------------------------------------------------------------------------
#
# gramps modules
#
#-------------------------------------------------------------------------
import gen.lib
from gui.views.listview import ListView
from gui.views.placebaseview import PlaceBaseView
from gui.views.treemodels import PlaceModel
from gui.utils import add_menuitem
import Errors
import Bookmarks
import config
from QuestionDialog import ErrorDialog
from gui.pluginmanager import GuiPluginManager
from DdTargets import DdTargets
from Editors import EditPlace, DeletePlaceQuery
from Filters.SideBar import PlaceSidebarFilter
#-------------------------------------------------------------------------
#
# internationalization
# Internationalization
#
#-------------------------------------------------------------------------
from gettext import gettext as _
#-------------------------------------------------------------------------
#
# PlaceView
#
#-------------------------------------------------------------------------
class PlaceView(ListView):
COLUMN_NAMES = [
_('Place Name'),
_('ID'),
_('Church Parish'),
_('ZIP/Postal Code'),
_('City'),
_('County'),
_('State'),
_('Country'),
_('Latitude'),
_('Longitude'),
_('Last Changed'),
_('Street'),
]
ADD_MSG = _("Add a new place")
EDIT_MSG = _("Edit the selected place")
DEL_MSG = _("Delete the selected place")
FILTER_TYPE = "Place"
class PlaceView(PlaceBaseView):
"""
Flat place view. (Original code in PlaceBaseView).
"""
def __init__(self, dbstate, uistate):
signal_map = {
'place-add' : self.row_add,
'place-update' : self.row_update,
'place-delete' : self.row_delete,
'place-rebuild' : self.object_build,
}
self.func_list = {
'<CONTROL>J' : self.jump,
'<CONTROL>BackSpace' : self.key_delete,
}
self.mapservice = config.get('interface.mapservice')
self.mapservicedata = {}
ListView.__init__(
self, _('Places'), dbstate, uistate, PlaceView.COLUMN_NAMES,
len(PlaceView.COLUMN_NAMES),
PlaceModel, signal_map,
dbstate.db.get_place_bookmarks(),
Bookmarks.PlaceBookmarks,
multiple=True,
filter_class=PlaceSidebarFilter)
config.connect("interface.filter",
self.filter_toggle)
def column_ord_setfunc(self, clist):
self.dbstate.db.set_place_column_order(clist)
def get_bookmarks(self):
return self.dbstate.db.get_place_bookmarks()
def define_actions(self):
ListView.define_actions(self)
self._add_action('ColumnEdit', gtk.STOCK_PROPERTIES,
_('_Column Editor'), callback=self._column_editor)
self._add_action('FastMerge', None, _('_Merge...'),
callback=self.fast_merge)
self._add_toolmenu_action('MapsList', _('Loading...'),
_("Attempt to see selected locations with a Map "
"Service (OpenstreetMap, Google Maps, ...)"),
self.gotomap,
_('Select a Map Service'))
self._add_action('GotoMap', gtk.STOCK_JUMP_TO,
_('_Look up with Map Service'),
callback=self.gotomap,
tip=_("Attempt to see this location with a Map "
"Service (OpenstreetMap, Google Maps, ...)"))
self._add_action('FilterEdit', None, _('Place Filter Editor'),
callback=self.filter_editor)
def change_page(self):
"""
Called by viewmanager at end of realization when arriving on the page
At this point the Toolbar is created. We need to:
1. get the menutoolbutton
2. add all possible map services in the drop down menu
3. add the actions that correspond to clicking in this drop down menu
4. set icon and label of the menutoolbutton now that it is realized
5. store label so it can be changed when selection changes
"""
ListView.change_page(self)
#menutoolbutton actions are stored in PageView class,
# obtain the widgets where we need to add to menu
actionservices = self.action_toolmenu['MapsList']
widgets = actionservices.get_proxies()
mmenu = self.__create_maps_menu_actions()
if not self.mapservicedata:
return
self.mapslistlabel = []
if not self.mapservice in self.mapservicedata:
#stored val no longer exists, use the first key instead
self.set_mapservice(self.mapservicedata.keys()[0])
#store all gtk labels to be able to update label on selection change
for widget in widgets :
if isinstance(widget, gtk.MenuToolButton):
widget.set_menu(mmenu)
if gtk.pygtk_version >= (2, 12, 0):
widget.set_arrow_tooltip_text(actionservices.arrowtooltip)
lbl = gtk.Label(self.mapservice_label())
lbl.show()
self.mapslistlabel.append(lbl)
widget.set_label_widget(self.mapslistlabel[-1])
widget.set_stock_id(gtk.STOCK_JUMP_TO)
if self.drag_info():
self.list.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
[('text/plain', 0, 0), self.drag_info().target()],
gtk.gdk.ACTION_COPY)
def __create_maps_menu_actions(self):
"""
Function creating a menu and actions that are used as dropdown menu
from the menutoolbutton
"""
menu = gtk.Menu()
#select the map services to show
self.mapservicedata = {}
servlist = GuiPluginManager.get_instance().get_reg_mapservices()
for i, pdata in zip(range(len(servlist)), servlist):
key = pdata.id.replace(' ', '-')
add_menuitem(menu, pdata.name, None,
make_callback(self.set_mapservice, key))
self.mapservicedata[key] = pdata
return menu
def set_mapservice(self, mapkey):
"""
change the service that runs on click of the menutoolbutton
used as callback menu on menu clicks
"""
self.mapservice = mapkey
for label in self.mapslistlabel:
label.set_label(self.mapservice_label())
label.show()
config.set('interface.mapservice', mapkey)
config.save()
def mapservice_label(self):
"""
return the current label for the menutoolbutton
"""
return self.mapservicedata[self.mapservice].name
def gotomap(self, obj):
"""
Run the map service
"""
#First test if any map service is available
if not len(self.mapservicedata):
msg = _("No map service is available.")
msg2 = _("Check your installation.")
ErrorDialog(msg, msg2)
return
place_handles = self.selected_handles()
try:
place_handle = self.selected_handles()[0]
except IndexError:
msg = _("No place selected.")
msg2 = _("You need to select a place to be able to view it"
" on a map. Some Map Services might support multiple"
" selections.")
ErrorDialog(msg, msg2)
return
#TODO: support for descriptions in some cases. For now, pass None
#TODO: Later this might be 'Birth of William' ....
places = [(x, None) for x in place_handles]
#run the mapservice:
pmgr = GuiPluginManager.get_instance()
serv = self.mapservicedata[self.mapservice]
mod = pmgr.load_plugin(serv)
if mod:
servfunc = eval('mod.' + serv.mapservice)
servfunc()(self.dbstate.db, places)
else:
print 'Failed to load map plugin, see Plugin Status'
def drag_info(self):
return DdTargets.PLACE_LINK
def _column_editor(self, obj):
import ColumnOrder
ColumnOrder.ColumnOrder(
_('Select Place Columns'),
self.uistate,
self.dbstate.db.get_place_column_order(),
PlaceView.COLUMN_NAMES,
self.set_column_order)
def column_order(self):
return self.dbstate.db.get_place_column_order()
def get_stock(self):
return 'gramps-place'
def ui_definition(self):
return '''<ui>
<menubar name="MenuBar">
<menu action="FileMenu">
<placeholder name="LocalExport">
<menuitem action="ExportTab"/>
</placeholder>
</menu>
<menu action="BookMenu">
<placeholder name="AddEditBook">
<menuitem action="AddBook"/>
<menuitem action="EditBook"/>
</placeholder>
</menu>
<menu action="EditMenu">
<placeholder name="CommonEdit">
<menuitem action="Add"/>
<menuitem action="Edit"/>
<menuitem action="Remove"/>
</placeholder>
<menuitem action="ColumnEdit"/>
<menuitem action="FilterEdit"/>
<placeholder name="Merge">
<menuitem action="FastMerge"/>
</placeholder>
</menu>
</menubar>
<toolbar name="ToolBar">
<placeholder name="CommonEdit">
<toolitem action="Add"/>
<toolitem action="Edit"/>
<toolitem action="Remove"/>
<separator/>
<toolitem action="MapsList"/>
</placeholder>
</toolbar>
<popup name="Popup">
<menuitem action="Add"/>
<menuitem action="Edit"/>
<menuitem action="Remove"/>
<menuitem action="GotoMap"/>
</popup>
</ui>'''
def add(self, obj):
try:
EditPlace(self.dbstate, self.uistate, [], gen.lib.Place())
except Errors.WindowActiveError:
pass
def remove(self, obj):
self.remove_selected_objects()
def remove_object_from_handle(self, handle):
person_list = [
item[1] for item in
self.dbstate.db.find_backlink_handles(handle,['Person'])]
family_list = [
item[1] for item in
self.dbstate.db.find_backlink_handles(handle,['Family'])]
event_list = [
item[1] for item in
self.dbstate.db.find_backlink_handles(handle,['Event'])]
object = self.dbstate.db.get_place_from_handle(handle)
query = DeletePlaceQuery(self.dbstate, self.uistate, object,
person_list, family_list, event_list)
is_used = len(person_list) + len(family_list) + len(event_list) > 0
return (query, is_used, object)
def edit(self, obj):
for handle in self.selected_handles():
place = self.dbstate.db.get_place_from_handle(handle)
try:
EditPlace(self.dbstate, self.uistate, [], place)
except Errors.WindowActiveError:
pass
def fast_merge(self, obj):
mlist = self.selected_handles()
if len(mlist) != 2:
msg = _("Cannot merge places.")
msg2 = _("Exactly two places must be selected to perform a merge. "
"A second place can be selected by holding down the "
"control key while clicking on the desired place.")
ErrorDialog(msg, msg2)
else:
import Merge
Merge.MergePlaces(self.dbstate, self.uistate, mlist[0], mlist[1])
def get_handle_from_gramps_id(self, gid):
obj = self.dbstate.db.get_place_from_gramps_id(gid)
if obj:
return obj.get_handle()
else:
return None
def make_callback(func, val):
return lambda x: func(val)
PlaceBaseView.__init__(self, dbstate, uistate, _('Places'), PlaceModel)