New mapservice plugin structure, as a general solution to #2659

*    configure.in
 *    src/DataViews/PlaceView.py
 *    src/Config/_GrampsConfigKeys.py
 *    src/gen/plug/_manager.py
 *    src/plugins/mapservices
 *    src/plugins/mapservices/googlemap.py
 *    src/plugins/mapservices/openstreetmap.py
 *    src/plugins/lib/Makefile.am
 *    src/plugins/lib/libmapservice.py
 *    src/plugins/Makefile.am
 *    src/widgets/menutoolbuttonaction.py
 *    src/widgets/Makefile.am
 *    src/PageView.py
 *    po/POTFILES.in


svn: r11811
This commit is contained in:
Benny Malengier 2009-02-02 21:55:22 +00:00
parent d14ba6329b
commit 829d78b611
13 changed files with 465 additions and 41 deletions

View File

@ -151,6 +151,7 @@ src/plugins/gramplet/Makefile
src/plugins/graph/Makefile
src/plugins/import/Makefile
src/plugins/lib/Makefile
src/plugins/mapservices/Makefile
src/plugins/quickview/Makefile
src/plugins/rel/Makefile
src/plugins/textreport/Makefile

View File

@ -300,6 +300,11 @@ src/plugins/import/ImportVCard.py
# plugins/lib directory
src/plugins/lib/libholiday.py
src/plugins/lib/libmapservice.py
# plugins/mapservices directory
src/plugins/mapservices/googlemap.py
src/plugins/mapservices/openstreetmap.py
# plugins/quickview directory
src/plugins/quickview/AgeOnDate.py
@ -761,6 +766,7 @@ src/FilterEditor/_ShowResults.py
src/widgets/buttons.py
src/widgets/expandcollapsearrow.py
src/widgets/labels.py
src/widgets/menutoolbuttonaction.py
src/widgets/styledtexteditor.py
src/widgets/validatedmaskedentry.py

View File

@ -150,6 +150,7 @@ LDS_HEIGHT = ('interface', 'lds-height', 1)
LDS_WIDTH = ('interface', 'lds-width', 1)
LOCATION_HEIGHT = ('interface', 'location-height', 1)
LOCATION_WIDTH = ('interface', 'location-width', 1)
MAPSERVICE = ('interface', 'mapservice', 2)
MEDIA_REF_HEIGHT = ('interface', 'media-ref-height', 1)
MEDIA_REF_WIDTH = ('interface', 'media-ref-width', 1)
URL_HEIGHT = ('interface', 'url-height', 1)
@ -281,6 +282,7 @@ default_value = {
LDS_WIDTH : 600,
LOCATION_HEIGHT : 250,
LOCATION_WIDTH : 600,
MAPSERVICE : 'OpenStreetMap',
MEDIA_REF_HEIGHT : 450,
MEDIA_REF_WIDTH : 600,
URL_HEIGHT : 150,

View File

@ -24,6 +24,13 @@
Place View
"""
#-------------------------------------------------------------------------
#
# Global modules
#
#-------------------------------------------------------------------------
#-------------------------------------------------------------------------
#
# GTK/Gnome modules
@ -44,6 +51,7 @@ import Errors
import Bookmarks
import Config
from QuestionDialog import ErrorDialog
from gen.plug import PluginManager
from DdTargets import DdTargets
from Editors import EditPlace, DeletePlaceQuery
from Filters.SideBar import PlaceSidebarFilter
@ -97,6 +105,9 @@ class PlaceView(PageView.ListView):
'<CONTROL>BackSpace' : self.key_delete,
}
self.mapservice = Config.get(Config.MAPSERVICE)
self.mapservicedata = {}
PageView.ListView.__init__(
self, _('Places'), dbstate, uistate, PlaceView.COLUMN_NAMES,
len(PlaceView.COLUMN_NAMES),
@ -118,38 +129,118 @@ class PlaceView(PageView.ListView):
_('_Column Editor'), callback=self._column_editor)
self._add_action('FastMerge', None, _('_Merge...'),
callback=self.fast_merge)
self._add_action('GoogleMaps', gtk.STOCK_JUMP_TO, _('_Google Maps'),
callback=self.google,
tip=_("Attempt to map location on Google Maps"))
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,)
callback=self.filter_editor)
def drag_info(self):
return DdTargets.PLACE_LINK
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
"""
PageView.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()
def google(self, obj):
import GrampsDisplay
from PlaceUtils import conv_lat_lon
if not self.mapservicedata:
return
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
self.mapslistlabel = []
for widget in widgets :
if isinstance(widget, gtk.MenuToolButton):
widget.set_menu(mmenu)
widget.set_arrow_tooltip_text(actionservices.arrowtooltip)
hbox=gtk.HBox()
img = gtk.Image()
img.set_from_stock(gtk.STOCK_JUMP_TO,
gtk.ICON_SIZE_LARGE_TOOLBAR)
hbox.pack_start(img)
self.mapslistlabel.append(gtk.Label(self.mapservice_label()))
hbox.pack_start(self.mapslistlabel[-1])
hbox.show_all()
widget.set_icon_widget(hbox)
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 = PluginManager.get_instance().get_mapservice_list()
for i, service in zip(range(len(servlist)), servlist):
key = service[2].replace(' ', '-')
Utils.add_menuitem(menu, service[1], None,
make_callback(self.set_mapservice, key))
self.mapservicedata[key] = (service[0], service[2], service[3])
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(Config.MAPSERVICE, mapkey)
Config.sync()
def mapservice_label(self):
"""
return the current label for the menutoolbutton
"""
return self.mapservicedata[self.mapservice][1]
def gotomap(self, obj):
"""
Run the map service
"""
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
place = self.dbstate.db.get_place_from_handle(place_handle)
descr = place.get_title()
longitude = place.get_longitude()
latitude = place.get_latitude()
latitude, longitude = conv_lat_lon(latitude, longitude, "D.D8")
city = place.get_main_location().get_city()
country = place.get_main_location().get_country()
if longitude and latitude:
path = "http://maps.google.com/?sll=%s,%s&z=15" % (latitude, longitude)
elif city and country:
path = "http://maps.google.com/maps?q=%s,%s" % (city, country)
else:
path = "http://maps.google.com/maps?q=%s" % '+'.join(descr.split())
GrampsDisplay.url(path)
#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:
self.mapservicedata[self.mapservice][0](self.dbstate.db, places)
def drag_info(self):
return DdTargets.PLACE_LINK
def _column_editor(self, obj):
import ColumnOrder
@ -204,14 +295,14 @@ class PlaceView(PageView.ListView):
<toolitem action="Edit"/>
<toolitem action="Remove"/>
<separator/>
<toolitem action="GoogleMaps"/>
<toolitem action="MapsList"/>
</placeholder>
</toolbar>
<popup name="Popup">
<menuitem action="Add"/>
<menuitem action="Edit"/>
<menuitem action="Remove"/>
<menuitem action="GoogleMaps"/>
<menuitem action="GotoMap"/>
</popup>
</ui>'''
@ -271,3 +362,6 @@ class PlaceView(PageView.ListView):
return obj.get_handle()
else:
return None
def make_callback(func, val):
return lambda x: func(val)

View File

@ -51,6 +51,8 @@ import Errors
from Filters import SearchBar
import Utils
import const
from widgets.menutoolbuttonaction import MenuToolButtonAction
from TransUtils import sgettext as _
from QuestionDialog import QuestionDialog, QuestionDialog2
@ -75,6 +77,8 @@ class PageView:
self.uistate = uistate
self.action_list = []
self.action_toggle_list = []
self.action_toolmenu_list = []
self.action_toolmenu = {} #easy access to toolmenuaction and proxies
self.action_group = None
self.additional_action_groups = []
self.additional_uis = []
@ -206,13 +210,24 @@ class PageView:
self.action_group.add_actions(self.action_list)
if len(self.action_toggle_list) > 0:
self.action_group.add_toggle_actions(self.action_toggle_list)
for action_toolmenu in self.action_toolmenu_list:
self.action_toolmenu[action_toolmenu[0]] = \
MenuToolButtonAction(action_toolmenu[0], #unique name
action_toolmenu[1], #label
action_toolmenu[2], #tooltip
action_toolmenu[3], #callback
action_toolmenu[4] #arrow tooltip
)
self.action_group.add_action(
self.action_toolmenu[action_toolmenu[0]])
def _add_action(self, name, stock_icon, label, accel=None, tip=None,
callback=None):
"""
Add an action to the action list for the current view.
"""
self.action_list.append((name, stock_icon, label, accel, tip, callback))
self.action_list.append((name, stock_icon, label, accel, tip,
callback))
def _add_toggle_action(self, name, stock_icon, label, accel=None,
tip=None, callback=None, value=False):
@ -222,6 +237,11 @@ class PageView:
self.action_toggle_list.append((name, stock_icon, label, accel,
tip, callback, value))
def _add_toolmenu_action(self, name, label, tooltip, callback,
arrowtooltip):
self.action_toolmenu_list.append((name, label, tooltip, callback,
arrowtooltip))
def get_actions(self):
"""
Return the actions that should be used for the view. This includes the

View File

@ -96,6 +96,7 @@ class PluginManager(gen.utils.Callback):
self.__import_plugins = []
self.__export_plugins = []
self.__general_plugins = []
self.__mapservice_list = []
self.__attempt_list = []
self.__loaddir_list = []
self.__textdoc_list = []
@ -240,6 +241,10 @@ class PluginManager(gen.utils.Callback):
""" Return the list of quick report plugins. """
return self.__quick_report_list
def get_mapservice_list(self):
""" Return the list of map services"""
return self.__mapservice_list
def get_book_item_list(self):
""" Return the list of book plugins. """
return self.__bkitems_list
@ -534,6 +539,30 @@ class PluginManager(gen.utils.Callback):
self.__mod2text[run_func.__module__] = description
def register_mapservice(self, name, mapservice, translated_name,
status=_("Unknown"), tooltip=_UNAVAILABLE,
author_name=_("Unknown"),
author_email=_("Unknown"), unsupported=False ):
"""
Register map services for the place view.
A map service is a MapService class. The class can be called with a
list of (place handle, description) values, and manipulate this data
to show on a map.
"""
del_index = -1
for i in range(0, len(self.__mapservice_list)):
val = self.__mapservice_list[i]
if val[2] == name:
del_index = i
if del_index != -1:
del self.__mapservice_list[del_index]
self.__mapservice_list.append( (mapservice, translated_name,
name, tooltip, status,
author_name, author_email, unsupported))
self.__mod2text[mapservice.__module__] = tooltip
def __purge_failed(self):
"""
Purge the failed plugins from the corresponding lists.

View File

@ -11,6 +11,7 @@ SUBDIRS = \
graph \
import \
lib \
mapservices \
quickview \
rel \
textreport \

View File

@ -6,7 +6,8 @@
pkgdatadir = $(datadir)/@PACKAGE@/plugins/lib
pkgdata_PYTHON = \
libholiday.py
libholiday.py\
libmapservices.py
pkgpyexecdir = @pkgpyexecdir@/plugins/lib
pkgpythondir = @pkgpythondir@/plugins/lib

View File

@ -22,6 +22,13 @@
Google Maps map service. Open place in maps.google.com
"""
#------------------------------------------------------------------------
#
# python modules
#
#------------------------------------------------------------------------
from gettext import gettext as _
#-------------------------------------------------------------------------
#
# GRAMPS modules
@ -36,6 +43,10 @@ class MapService():
A service is a singleton, we only need one to exist
Usage is as a callable when used
"""
def __init__(self):
self.database = None
self.items = None
self.url = ''
def __call__(self, database, items):
"""Callable class, usable as a function. This guarantees the class is
@ -45,11 +56,11 @@ class MapService():
items: list of tuples (place_handle, description), where description
is None or a string to use for marker (eg 'birth John Doe')
"""
self.db = database
self.database = database
self.items = items
self.url = ''
#An instance is called, we display the result
self._calc_url()
self.calc_url()
self.__display()
self._free()
@ -57,23 +68,24 @@ class MapService():
"""Obtain the first place object"""
place_handle = self.items[0][0]
return self.db.get_place_from_handle(place_handle), self.items[0][1]
return self.database.get_place_from_handle(place_handle), \
self.items[0][1]
def _all_places(self):
"""Obtain a list generator of all place objects
Usage: for place, descr in mapservice.all_places()
"""
for handle, descr in db.get_handles():
yield self.db.get_place_from_handle(handle), descr
for handle, descr in self.database.get_handles():
yield self.database.get_place_from_handle(handle), descr
def _lat_lon(self, place, format="D.D8"):
"""return the lat, lon value of place in the requested format
None, None if invalid
"""
return PlaceUtils.conv_lat_lon(place.get_latitude(),
return conv_lat_lon(place.get_latitude(),
place.get_longitude(), format)
def _calc_url(self):
def calc_url(self):
"""Base class needs to overwrite this, calculation of the self.path"""
raise NotImplementedError

View File

@ -0,0 +1,84 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2009 Benny Malengier
#
# 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
#
"""
Google Maps map service plugin. Open place in maps.google.com
"""
#------------------------------------------------------------------------
#
# python modules
#
#------------------------------------------------------------------------
from gettext import gettext as _
#------------------------------------------------------------------------
#
# GRAMPS modules
#
#------------------------------------------------------------------------
from gen.plug import PluginManager
from libmapservice import MapService
class GoogleMapService(MapService):
"""Map service using http://maps.google.com"""
def __init__(self):
MapService.__init__(self)
def calc_url(self):
""" Determine the url to use on maps.google.com
Logic: use lat lon if present
otherwise use city and country if present
otherwise use description of the place
"""
place = self._get_first_place()[0]
latitude, longitude = self._lat_lon(place)
if longitude and latitude:
self.url = "http://maps.google.com/?sll=%s,%s&z=15" % (latitude,
longitude)
return
city = place.get_main_location().get_city()
country = place.get_main_location().get_country()
if city and country:
self.url = "http://maps.google.com/maps?q=%s,%s" % (city, country)
return
titledescr = place.get_title()
self.url = "http://maps.google.com/maps?q=%s" % \
'+'.join(titledescr.split())
#------------------------------------------------------------------------
#
# Register map service
#
#------------------------------------------------------------------------
PluginManager.get_instance().register_mapservice(
name = 'GoogleMaps',
mapservice = GoogleMapService(),
translated_name = _("GoogleMaps"),
status = _("Stable"),
tooltip= _("Open on maps.google.com"),
author_name="Benny Malengier",
author_email="benny.malengier@gramps-project.org"
)

View File

@ -0,0 +1,88 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2009 Benny Malengier
#
# 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
#
"""
OpenStreetMap map service plugin. Open place in openstreetmap.org
"""
#------------------------------------------------------------------------
#
# python modules
#
#------------------------------------------------------------------------
from gettext import gettext as _
#------------------------------------------------------------------------
#
# GRAMPS modules
#
#------------------------------------------------------------------------
from gen.plug import PluginManager
from libmapservice import MapService
class OpensStreetMapService(MapService):
"""Map service using http://openstreetmap.org
Resource: http://wiki.openstreetmap.org/index.php/Name_finder
"""
def __init__(self):
MapService.__init__(self)
def calc_url(self):
""" Determine the url to use
Logic: use lat lon if present
otherwise use city and country if present
otherwise use description of the place
"""
place = self._get_first_place()[0]
latitude, longitude = self._lat_lon(place)
if longitude and latitude:
self.url = "http://www.openstreetmap.org/" \
"?lat=%s&lon=%s&zoom=15" % (latitude, longitude)
return
city = place.get_main_location().get_city()
country = place.get_main_location().get_country()
if city and country:
self.url = "http://gazetteer.openstreetmap.org/namefinder/"\
"?find=%s%%2C%s" % (city, country)
return
titledescr = place.get_title()
self.url = "http://gazetteer.openstreetmap.org/namefinder/"\
"?find=%s" % '+'.join(titledescr.split())
#------------------------------------------------------------------------
#
# Register map service
#
#------------------------------------------------------------------------
PluginManager.get_instance().register_mapservice(
name = 'OpenStreetMap',
mapservice = OpensStreetMapService(),
translated_name = _("OpenStreetMap"),
status = _("Stable"),
tooltip= _("Open on openstreetmap.org"),
author_name="Benny Malengier",
author_email="benny.malengier@gramps-project.org"
)

View File

@ -11,6 +11,7 @@ pkgdata_PYTHON = \
expandcollapsearrow.py \
labels.py \
linkbox.py \
menutoolbuttonaction.py \
monitoredwidgets.py \
shortlistcomboentry.py \
springseparator.py \

View File

@ -0,0 +1,85 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2009 Benny Malengier
#
# 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: menutoolbuttonaction 10763 2008-05-27 19:53:25Z zfoldvar $
"MenuToolButtonAction class."
__all__ = ["MenuToolButtonAction"]
#-------------------------------------------------------------------------
#
# Python modules
#
#-------------------------------------------------------------------------
import logging
_LOG = logging.getLogger(".widgets.menutoolbuttonaction")
#-------------------------------------------------------------------------
#
# GTK modules
#
#-------------------------------------------------------------------------
import gobject
import gtk
#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
#-------------------------------------------------------------------------
#
# ValueAction class
#
#-------------------------------------------------------------------------
class MenuToolButtonAction(gtk.Action):
"""MenuToolButton action class.
(A MenuToolButtonAction with menu item doesn't make any sense,
use for toolbar.)
"""
__gtype_name__ = "MenuToolButtonAction"
__gsignals__ = {
'changed': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, #return value
()), # arguments
}
def __init__(self, name, label, tooltip, callback, arrowtooltip):
"""Create a new MenuToolButtonAction instance.
@param name: the name of the action
@type name: str
@param tooltip: tooltip string
@type tooltip: str
"""
gtk.Action.__init__(self, name, label, tooltip, None)
self.set_tool_item_type(gtk.MenuToolButton)
if callback:
self.connect('activate', callback)
self.arrowtooltip = arrowtooltip