gramps/gramps/plugins/lib/libplaceview.py

444 lines
16 KiB
Python

# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2001-2006 Donald N. Allingham
# Copyright (C) 2008 Gary Burton
# Copyright (C) 2010 Nick Hall
# Copyright (C) 2011 Tim G L Lyons
#
# 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
#
#-------------------------------------------------------------------------
from __future__ import print_function
#-------------------------------------------------------------------------
#
# GTK/Gnome modules
#
#-------------------------------------------------------------------------
from gi.repository import Gdk
from gi.repository import Gtk
#-------------------------------------------------------------------------
#
# gramps modules
#
#-------------------------------------------------------------------------
from gramps.gen.lib import Place
from gramps.gui.views.listview import ListView
from gramps.gui.widgets.menuitem import add_menuitem
from gramps.gen.errors import WindowActiveError
from gramps.gui.views.bookmarks import PlaceBookmarks
from gramps.gen.config import config
from gramps.gui.dialog import ErrorDialog
from gramps.gui.pluginmanager import GuiPluginManager
from gramps.gui.ddtargets import DdTargets
from gramps.gui.editors import EditPlace, DeletePlaceQuery
from gramps.gui.filters.sidebar import PlaceSidebarFilter
from gramps.gui.merge import MergePlace
from gramps.gen.plug import CATEGORY_QR_PLACE
#-------------------------------------------------------------------------
#
# internationalization
#
#-------------------------------------------------------------------------
from gramps.gen.ggettext import gettext as _
#-------------------------------------------------------------------------
#
# PlaceBaseView
#
#-------------------------------------------------------------------------
class PlaceBaseView(ListView):
""" base view class for place views, be they flat list or tree
"""
COL_NAME = 0
COL_ID = 1
COL_STREET = 2
COL_LOCALITY = 3
COL_CITY = 4
COL_COUNTY = 5
COL_STATE = 6
COL_COUNTRY = 7
COL_ZIP = 8
COL_PARISH = 9
COL_LAT = 10
COL_LON = 11
COL_CHAN = 12
# name of the columns
COLUMN_NAMES = [
_('Place Name'),
_('ID'),
_('Street'),
_('Locality'),
_('City'),
_('County'),
_('State'),
_('Country'),
_('ZIP/Postal Code'),
_('Church Parish'),
_('Latitude'),
_('Longitude'),
_('Last Changed'),
]
# columns that contain markup
MARKUP_COLS = [COL_NAME]
# default setting with visible columns, order of the col, and their size
CONFIGSETTINGS = (
('columns.visible', [COL_NAME, COL_ID, COL_STREET, COL_LOCALITY,
COL_CITY, COL_COUNTY, COL_STATE]),
('columns.rank', [COL_NAME, COL_ID, COL_STREET, COL_LOCALITY, COL_CITY,
COL_COUNTY, COL_STATE, COL_COUNTRY, COL_ZIP,
COL_PARISH, COL_LAT, COL_LON, COL_CHAN]),
('columns.size', [250, 75, 150, 150, 150, 150, 100, 100, 100,
100, 150, 150, 100])
)
ADD_MSG = _("Add a new place")
EDIT_MSG = _("Edit the selected place")
DEL_MSG = _("Delete the selected place")
MERGE_MSG = _("Merge the selected places")
FILTER_TYPE = "Place"
QR_CATEGORY = CATEGORY_QR_PLACE
def __init__(self, pdata, dbstate, uistate, title, model, nav_group,
markup=None):
signal_map = {
'place-add' : self.row_add,
'place-update' : self.row_update,
'place-delete' : self.row_delete,
'place-rebuild' : self.object_build,
}
self.mapservice = config.get('interface.mapservice')
self.mapservicedata = {}
ListView.__init__(
self, title, pdata, dbstate, uistate,
self.COLUMN_NAMES, 14,
model, signal_map,
dbstate.db.get_place_bookmarks(),
PlaceBookmarks, nav_group,
multiple=True,
filter_class=PlaceSidebarFilter, markup=markup)
self.func_list.update({
'<PRIMARY>J' : self.jump,
'<PRIMARY>BackSpace' : self.key_delete,
})
self.maptoolbtn = None
self.additional_uis.append(self.additional_ui())
def navigation_type(self):
return 'Place'
def get_bookmarks(self):
return self.dbstate.db.get_place_bookmarks()
def define_actions(self):
ListView.define_actions(self)
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)
self._add_action('QuickReport', None, _("Quick View"), None, None, None)
def set_inactive(self):
"""called by viewmanager when moving away from the page
Here we need to remove the menutoolbutton from the menu
"""
tb = self.uistate.viewmanager.uimanager.get_widget('/ToolBar')
tb.remove(self.maptoolbtn)
ListView.set_inactive(self)
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 has to be made and added in correct place on toolbar
if not self.maptoolbtn:
self.maptoolbtn = Gtk.MenuToolButton.new_from_stock(Gtk.STOCK_JUMP_TO)
self.maptoolbtn.connect('clicked', self.gotomap)
self.mmenu = self.__create_maps_menu_actions()
self.maptoolbtn.set_menu(self.mmenu)
self.maptoolbtn.show()
tb = self.uistate.viewmanager.uimanager.get_widget('/ToolBar')
ind = tb.get_item_index(self.uistate.viewmanager.uimanager.get_widget(
'/ToolBar/CommonEdit/Merge'))
tb.insert(self.maptoolbtn, ind+1)
widget = self.maptoolbtn
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(list(self.mapservicedata.keys())[0])
#store all gtk labels to be able to update label on selection change_('Loading...'),
widget.set_menu(self.mmenu)
widget.set_arrow_tooltip_text(_('Select a Map Service'))
widget.set_tooltip_text(
_("Attempt to see selected locations with a Map "
"Service (OpenstreetMap, Google Maps, ...)"))
lbl = Gtk.Label(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(Gdk.ModifierType.BUTTON1_MASK,
[],
Gdk.DragAction.COPY)
tglist = Gtk.TargetList.new([])
tglist.add(self.drag_info().atom_drag_type,
self.drag_info().target_flags,
self.drag_info().app_id)
tglist.add_text_targets (0)
self.list.drag_source_set_target_list(tglist)
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 enumerate(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 Manager')
def drag_info(self):
return DdTargets.PLACE_LINK
def get_stock(self):
return 'gramps-place'
def additional_ui(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="GoMenu">
<placeholder name="CommonGo">
<menuitem action="Back"/>
<menuitem action="Forward"/>
<separator/>
</placeholder>
</menu>
<menu action="EditMenu">
<placeholder name="CommonEdit">
<menuitem action="Add"/>
<menuitem action="Edit"/>
<menuitem action="Remove"/>
<menuitem action="Merge"/>
</placeholder>
<menuitem action="FilterEdit"/>
</menu>
</menubar>
<toolbar name="ToolBar">
<placeholder name="CommonNavigation">
<toolitem action="Back"/>
<toolitem action="Forward"/>
</placeholder>
<placeholder name="CommonEdit">
<toolitem action="Add"/>
<toolitem action="Edit"/>
<toolitem action="Remove"/>
<toolitem action="Merge"/>
<separator/>
</placeholder>
</toolbar>
<popup name="Popup">
<menuitem action="Back"/>
<menuitem action="Forward"/>
<separator/>
<menuitem action="Add"/>
<menuitem action="Edit"/>
<menuitem action="Remove"/>
<menuitem action="Merge"/>
<separator/>
<menu name="QuickReport" action="QuickReport"/>
<separator/>
<menuitem action="GotoMap"/>
</popup>
</ui>'''
def add(self, obj):
try:
EditPlace(self.dbstate, self.uistate, [], Place())
except 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 WindowActiveError:
pass
def merge(self, obj):
"""
Merge the selected places.
"""
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:
MergePlace(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 get_default_gramplets(self):
"""
Define the default gramplets for the sidebar and bottombar.
"""
return (("Place Filter",),
("Place Details",
"Place Gallery",
"Place Citations",
"Place Notes",
"Place Backlinks"))
def make_callback(func, val):
return lambda x: func(val)