* src/PedView.py: More work on it.

* src/MapView.py: New View showing locations on a map. Early unfinished version.
* src/gramps_main.py: Enable MapView
* src/land_shallow_topo_2048.jpg:
* src/land_shallow_topo_350.jpg: Two new map images downloaded from NASA so the are assumed to be public domain.


svn: r5050
This commit is contained in:
Martin Hawlisch 2005-08-11 14:57:19 +00:00
parent b39a328f9a
commit 0a245a7f2d
6 changed files with 501 additions and 51 deletions

View File

@ -1,3 +1,11 @@
2005-08-11 Martin Hawlisch <Martin.Hawlisch@gmx.de>
* src/PedView.py: More work on it.
* src/MapView.py: New View showing locations on a map. Early unfinished version.
* src/gramps_main.py: Enable MapView
* src/land_shallow_topo_2048.jpg:
* src/land_shallow_topo_350.jpg: Two new map images downloaded from NASA
so the are assumed to be public domain.
2005-08-10 Don Allingham <don@gramps-project.org>
* src/DbState.py: separate database class from display class
* src/EditPerson.py: separate database class from display class

356
src/MapView.py Normal file
View File

@ -0,0 +1,356 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2001-2005 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 modules
#
#-------------------------------------------------------------------------
from gettext import gettext as _
#-------------------------------------------------------------------------
#
# GTK/Gnome modules
#
#-------------------------------------------------------------------------
import gobject
import gtk
import gtk.gdk
import gc
#-------------------------------------------------------------------------
#
# Gramps Modules
#
#-------------------------------------------------------------------------
import RelLib
import PageView
data = (("_Center", 0,0),
("_North",0,80),
("_South",0,-80),
("_West",-170,0),
("_East",170,0),
("Chicago",-87.75,41.83),
("Berlin",13.42,52.53),
("Honolulu",-157.83,21.32),
("Madrid",-3.72,40.42),
("Moscow",37.70,55.75),
("Vienna",16.37,48.22),
("Sidney",151.17,-33.92),
("Rio de Janeiro",-43.28,-22.88),
("Tokyo",139.75,35.67),
("Cape Town",18.47,-33.93),
("Anchorage",-150.00,61.17))
# Draws a map image and tries to allocate space in the correct aspect ratio
class GuideMap(gtk.DrawingArea):
def __init__(self, map_pixbuf):
gtk.DrawingArea.__init__(self)
self.map_pixbuf = map_pixbuf
self.connect("expose-event", self.expose_cb)
self.connect("size-allocate", self.size_allocate_cb)
#self.connect("size-request", self.size_request_cb)
self.gc = None
self.current_area = None
self.old_size = (-1,-1)
# Set hightlight region
def hightlight_area( self, area):
print "GuideMap.hightlight_area"
print area
self.current_area = area
self.queue_draw()
# Redraw the image
def expose_cb(self,widget,event):
a = widget.get_allocation()
print "GuideMap.expose_cb (%dx%d)" % (a.width,a.height)
if not self.gc:
self.gc = self.window.new_gc()
self.gc.set_foreground( self.get_colormap().alloc_color("red"))
self.gc.set_background( self.get_colormap().alloc_color("blue"))
if self.backbuf and self.gc:
self.window.draw_pixbuf( self.gc, self.backbuf, 0,0, 0,0, -1,-1)
if self.current_area:
r = self.map_to_screen(self.current_area[0],self.current_area[1],self.current_area[2],self.current_area[3])
self.window.draw_rectangle( self.gc, False, r[0],r[1], r[2],r[3])
# Scale backbuffer
def size_allocate_cb(self,widget,allocation):
a = allocation
print "GuideMap.size_allocate_cb (%dx%d)" % (a.width,a.height)
# Always request a height, that is half of the width
w = max( 128,allocation.width)
self.set_size_request(-1,w/2)
# only create new backbuffer if size is different
new_size = (allocation.width,allocation.height)
if new_size is not self.old_size:
self.old_size = new_size
self.backbuf = self.map_pixbuf.scale_simple(self.old_size[0],self.old_size[1],gtk.gdk.INTERP_BILINEAR)
gc.collect()
def map_to_screen( self, x,y,w,h):
px = int((float(x) + 180.0) / 360.0 * self.backbuf.get_width())
py = int((90-float(y)) / 180.0 * self.backbuf.get_height())
pw = int(float(w) / 360.0 * self.backbuf.get_width())
ph = int(float(h) / 180.0 * self.backbuf.get_height())
return (px,py,pw,ph)
# Zoomable map image
class ZoomMap( gtk.DrawingArea):
def __init__(self, map_pixbuf, place_marker_pixbuf, hightlight_marker_pixbuf):
gtk.DrawingArea.__init__(self)
self.map_pixbuf = map_pixbuf
self.place_marker_pixbuf = place_marker_pixbuf
self.hightlight_marker_pixbuf = hightlight_marker_pixbuf
self.connect("expose-event", self.expose_cb)
self.connect("size-allocate", self.size_allocate_cb)
self.gc = None
self.old_size = (-1,-1)
self.zoom_pos = (0,0)
self.current_area = (0,0,0,0)
self.magnifer = 0.5
self.guide = None
# Set the guide map that should follow the zoom area
def set_guide( self, guide):
self.guide = guide
# Redraw the image
def expose_cb(self,widget,event):
a = widget.get_allocation()
print "GuideMap.expose_cb (%dx%d)" % (a.width,a.height)
if not self.gc:
self.gc = self.window.new_gc()
self.gc.set_foreground( self.get_colormap().alloc_color("red"))
self.gc.set_background( self.get_colormap().alloc_color("blue"))
if not self.backbuf:
self.size_allocate_cb( self,self.get_allocation())
if self.backbuf and self.gc:
self.window.draw_pixbuf( self.gc, self.backbuf, 0,0, 0,0, -1,-1)
px = int((float(self.zoom_pos[1]) + 180.0) / 360.0 * self.backbuf.get_width())
py = int((90-float(self.zoom_pos[0])) / 180.0 * self.backbuf.get_height())
self.window.draw_pixbuf( self.gc, self.hightlight_marker_pixbuf, 0,0, px-self.hightlight_marker_pixbuf.get_width()/2,py-self.hightlight_marker_pixbuf.get_height()/2, -1,-1)
self.window.draw_rectangle( self.gc, False, px-3,py-3, 6,6)
# Scale backbuffer
def size_allocate_cb(self,widget,allocation):
a = allocation
print "GuideMap.size_allocate_cb (%dx%d)" % (a.width,a.height)
# only create new backbuffer if size is different
new_size = (allocation.width,allocation.height)
if new_size is not self.old_size or not self.backbuf:
self.old_size = new_size
# Desired midpoint in map
pw = int(self.old_size[0]*self.magnifer)
ph = int(self.old_size[1]*self.magnifer)
px = int((float(self.zoom_pos[1]) + 180.0) / 360.0 * self.map_pixbuf.get_width())
py = int((90-float(self.zoom_pos[0])) / 180.0 * self.map_pixbuf.get_height())
px = max( pw/2, px)
py = max( ph/2, py)
px = min( px, self.map_pixbuf.get_width()-pw/2)
py = min( py, self.map_pixbuf.get_height()-ph/2)
zoomebuf = self.map_pixbuf.subpixbuf( int(px-pw/2), int(py-ph/2), pw,ph)
print ( px-pw/2, py-ph/2, pw,ph)
self.backbuf = zoomebuf.scale_simple(self.old_size[0],self.old_size[1],gtk.gdk.INTERP_BILINEAR)
gc.collect()
if self.guide:
mx = 360.0 / self.map_pixbuf.get_width() * (px-pw/2.0) - 180.0
my = 90.0 - 180.0 / self.map_pixbuf.get_height() * (py-ph/2.0)
mw = 360.0 / self.map_pixbuf.get_width() * pw
mh = 180.0 / self.map_pixbuf.get_height() * ph
self.guide.hightlight_area( (mx,my,mw,mh))
self.current_area = (px-pw/2, py-ph/2, pw,ph)
# Scroll to requested position
def scroll_to( self, lat, lon):
self.zoom_pos = ( min(90,(max(-90,lon))), min(180,(max(-180,lat))))
self.backbuf = None
self.queue_draw()
def zoom_in(self):
self.magnifer = min( 10, self.magnifer * 1.5)
self.backbuf = None
self.queue_draw()
def zoom_out(self):
self.magnifer = max( 0.1, self.magnifer * 0.75)
self.backbuf = None
self.queue_draw()
def zoom_normal(self):
self.magnifer = 1
self.backbuf = None
self.queue_draw()
def zoom_fit(self):
self.magnifer = 2
self.backbuf = None
self.queue_draw()
# Place list widget
class MapPlacesList(gtk.TreeView):
def __init__(self, data):
lstore = gtk.ListStore(
gobject.TYPE_STRING,
gobject.TYPE_FLOAT,
gobject.TYPE_FLOAT)
for item in data:
iter = lstore.append()
lstore.set(iter,
0, item[0],
1, item[1],
2, item[2])
gtk.TreeView.__init__(self, lstore)
self.set_rules_hint(True)
self.set_search_column(0)
column = gtk.TreeViewColumn('Place', gtk.CellRendererText(), text=0)
column.set_sort_column_id(0)
self.append_column(column)
column = gtk.TreeViewColumn('Lat', gtk.CellRendererText(), text=1)
column.set_sort_column_id(1)
self.append_column(column)
column = gtk.TreeViewColumn('Long', gtk.CellRendererText(),text=2)
column.set_sort_column_id(2)
self.append_column(column)
# Map View main class
class MapView(PageView.PageView):
def __init__(self,dbstate,uistate):
PageView.PageView.__init__(self,'Pedigree View',dbstate,uistate)
dbstate.connect('database-changed',self.change_db)
self.current_marker = None
def navigation_type(self):
print "MapView.navigation_type"
return PageView.NAVIGATION_NONE
def define_actions(self):
print "MapView.define_actions"
self.add_action('ZoomIn', gtk.STOCK_ZOOM_IN, "Zoom _In", callback=self.zoom_in_cb)
self.add_action('ZoomOut', gtk.STOCK_ZOOM_OUT, "Zoom _Out", callback=self.zoom_out_cb)
self.add_action('ZoomNormal',gtk.STOCK_ZOOM_100,"_Normal Size", callback=self.zoom_100_cb)
self.add_action('ZoomFit', gtk.STOCK_ZOOM_FIT, "Best _Fit", callback=self.zoom_fit_cb)
def get_stock(self):
"""
Returns the name of the stock icon to use for the display.
This assumes that this icon has already been registered with
GNOME as a stock icon.
"""
return 'gramps-place'
def build_widget(self):
hbox = gtk.HBox( False, 4)
hbox.set_border_width( 4)
# The large zoomable map
self.zoom_map = ZoomMap(
gtk.gdk.pixbuf_new_from_file("land_shallow_topo_2048.jpg"),
gtk.gdk.pixbuf_new_from_file("bad.png"),
gtk.gdk.pixbuf_new_from_file("good.png"))
self.zoom_map.set_size_request(300,300)
hbox.pack_start( self.zoom_map, True, True, 0)
# On the right side
vbox = gtk.VBox( False, 4)
hbox.pack_start( vbox, False, True, 0)
# The small guide map
self.guide_map = GuideMap( gtk.gdk.pixbuf_new_from_file("land_shallow_topo_350.jpg"))
self.guide_map.set_size_request(128,64)
vbox.pack_start( self.guide_map, False, True, 0)
self.zoom_map.set_guide(self.guide_map)
# And the place list
self.place_list_view = MapPlacesList( data)
vbox.pack_start( self.place_list_view,True,True,0)
self.place_list_view.connect("cursor-changed", self.entry_select_cb)
return hbox
def ui_definition(self):
"""
Specifies the UIManager XML code that defines the menus and buttons
associated with the interface.
"""
return '''<ui>
<toolbar name="ToolBar">
<toolitem action="ZoomIn"/>
<toolitem action="ZoomOut"/>
<toolitem action="ZoomNormal"/>
<toolitem action="ZoomFit"/>
</toolbar>
</ui>'''
def change_db(self,db):
print "MapView.change_db"
"""
Callback associated with DbState. Whenenver the database
changes, this task is called. In this case, we rebuild the
columns, and connect signals to the connected database. Tere
is no need to store the database, since we will get the value
from self.state.db
"""
self.db = db
def entry_select_cb(self,treeview):
s = treeview.get_selection()
(model,sel) = s.get_selected_rows()
for path in sel:
iter = model.get_iter(path)
self.zoom_map.scroll_to(model.get_value(iter,1), model.get_value(iter,2))
break
def zoom_in_cb(self,obj):
self.zoom_map.zoom_in()
def zoom_out_cb(self,obj):
self.zoom_map.zoom_out()
def zoom_100_cb(self,obj):
self.zoom_map.zoom_normal()
def zoom_fit_cb(self,obj):
self.zoom_map.zoom_fit()

View File

@ -41,12 +41,10 @@ import gtk.gdk
# Gramps Modules
#
#-------------------------------------------------------------------------
import PageView
import const
import GrampsCfg
import Relationship
import NameDisplay
import RelLib
import PageView
import EditPerson
import NameDisplay
import Utils
import DateHandler
@ -55,9 +53,7 @@ import DateHandler
# Constants
#
#-------------------------------------------------------------------------
_PAD = 3
_CANVASPAD = 3
_PERSON = "p"
_PERSON = "p"
_BORN = _('b.')
_DIED = _('d.')
_BAPT = _('bap.')
@ -74,18 +70,98 @@ _CREM = _('crem.')
class PedView(PageView.PageView):
def __init__(self,dbstate,uistate):
print "PedView.__init__"
PageView.PageView.__init__(self,'Pedigree View',dbstate,uistate)
self.inactive = False
dbstate.connect('database-changed',self.change_db)
dbstate.connect('active-changed',self.goto_active_person)
self.force_size = 0 # Automatic resize
def navigation_type(self):
print "PedView.navigation_type"
return PageView.NAVIGATION_PERSON
def init_parent_signals_cb(self, widget, event):
print "PedView.init_parent_signals_cb"
self.notebook.disconnect(self.bootstrap_handler)
self.notebook.parent.connect("size-allocate", self.size_request_cb)
self.size_request_cb(widget.parent,event)
def add_table_to_notebook( self, table):
frame = gtk.ScrolledWindow(None,None)
frame.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
frame.add_with_viewport(table)
try:
self.notebook.append_page(frame,None)
except:
# for PyGtk < 2.4
self.notebook.append_page(frame,gtk.Label(""))
def define_actions(self):
print "PedView.define_actions"
self.add_action('Forward',gtk.STOCK_GO_FORWARD,"_Forward", callback=self.fwd_clicked)
self.add_action('Back', gtk.STOCK_GO_BACK, "_Back", callback=self.back_clicked)
self.add_action('HomePerson', gtk.STOCK_HOME, "_Home", callback=self.home)
# add the Forward action group to handle the Forward button
self.fwd_action = gtk.ActionGroup(self.title + '/Forward')
self.fwd_action.add_actions([
('Forward',gtk.STOCK_GO_FORWARD,"_Forward", None, None, self.fwd_clicked)
])
# add the Backward action group to handle the Forward button
self.back_action = gtk.ActionGroup(self.title + '/Backward')
self.back_action.add_actions([
('Back',gtk.STOCK_GO_BACK,"_Back", None, None, self.back_clicked)
])
self.add_action_group(self.back_action)
self.add_action_group(self.fwd_action)
def disable_action_group(self):
print "PedView.disable_action_group"
"""
Normally, this would not be overridden from the base class. However,
in this case, we have additional action groups that need to be
handled correctly.
"""
PageView.PageView.disable_action_group(self)
self.fwd_action.set_visible(False)
self.back_action.set_visible(False)
def enable_action_group(self,obj):
print "PedView.enable_action_group"
"""
Normally, this would not be overridden from the base class. However,
in this case, we have additional action groups that need to be
handled correctly.
"""
PageView.PageView.enable_action_group(self,obj)
self.fwd_action.set_visible(True)
self.back_action.set_visible(True)
hobj = self.uistate.phistory
self.fwd_action.set_sensitive(not hobj.at_end())
self.back_action.set_sensitive(not hobj.at_front())
def get_stock(self):
"""
Returns the name of the stock icon to use for the display.
This assumes that this icon has already been registered with
GNOME as a stock icon.
"""
return 'gramps-pedigree'
def build_widget(self):
print "PedView.build_widget"
"""
Builds the interface and returns a gtk.Container type that
contains the interface. This containter will be inserted into
a gtk.Notebook page.
"""
self.notebook = gtk.Notebook()
self.notebook.connect("button-press-event", self.on_show_option_menu_cb)
self.bootstrap_handler = self.notebook.connect("expose-event", self.init_parent_signals_cb)
self.notebook.set_show_border(False)
self.notebook.set_show_tabs(False)
@ -105,75 +181,82 @@ class PedView(PageView.PageView):
self.table_5.connect("button-press-event", self.on_show_option_menu_cb)
self.add_table_to_notebook( self.table_5)
#self.parent_container.connect("size-allocate", self.size_request_cb)
self.rebuild_trees(None)
return self.notebook
def add_table_to_notebook( self, table):
frame = gtk.ScrolledWindow(None,None)
frame.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
frame.add_with_viewport(table)
try:
self.notebook.append_page(frame,None)
except:
# for PyGtk < 2.4
self.notebook.append_page(frame,gtk.Label(""))
def define_actions(self):
#self.add_action('Add', gtk.STOCK_ADD, "_Add", callback=self.add)
#self.add_action('Edit', gtk.STOCK_EDIT, "_Edit", callback=self.edit)
#self.add_action('Remove', gtk.STOCK_REMOVE, "_Remove", callback=self.remove)
#self.add_action('Forward',gtk.STOCK_GO_FORWARD,"_Forward", callback=self.fwd_clicked)
#self.add_action('Back', gtk.STOCK_GO_BACK, "_Back", callback=self.back_clicked)
self.add_action('HomePerson', gtk.STOCK_HOME, "_Home", callback=self.home)
#self.add_toggle_action('Filter', None, '_Filter', callback=self.filter_toggle)
def ui_definition(self):
"""
Specifies the UIManager XML code that defines the menus and buttons
associated with the interface.
"""
return '''<ui>
<menubar name="MenuBar">
<menu action="GoMenu">
<placeholder name="CommonGo">
<menuitem action="Back"/>
<menuitem action="Forward"/>
<separator/>
<menuitem action="HomePerson"/>
<separator/>
</placeholder>
</menu>
</menubar>
<toolbar name="ToolBar">
<placeholder name="CommonNavigation">
<toolitem action="Back"/>
<toolitem action="Forward"/>
<toolitem action="HomePerson"/>
</placeholder>
</toolbar>
</ui>'''
def get_stock(self):
return 'gramps-pedigree'
def change_db(self,db):
# Reconnect signals
print "PedView.change_db"
"""
Callback associated with DbState. Whenenver the database
changes, this task is called. In this case, we rebuild the
columns, and connect signals to the connected database. Tere
is no need to store the database, since we will get the value
from self.state.db
"""
self.db = db
db.connect('person-add', self.person_updated_cb)
db.connect('person-update', self.person_updated_cb)
db.connect('person-delete', self.person_updated_cb)
db.connect('person-rebuild', self.person_rebuild)
self.active_person = None
def person_updated_cb(self,handle_list):
self.rebuild_trees(self.active_person)
def person_rebuild(self):
self.rebuild_trees(self.active_person)
def goto_active_person(self,handle):
self.rebuild_trees(None)
def goto_active_person(self,handle=None):
print "PedView.goto_active_person"
if handle:
self.active_person = self.db.get_person_from_handle(handle)
self.rebuild_trees(self.active_person)
self.rebuild_trees(self.db.get_person_from_handle(handle))
else:
self.rebuild_trees(None)
def fwd_clicked(self,obj,step=1):
pass
def back_clicked(self,obj,step=1):
pass
def person_updated_cb(self,handle_list):
print "PedView.person_updated_cb"
self.rebuild_trees(self.dbstate.active)
def person_rebuild(self):
print "PedView.person_rebuild"
self.rebuild_trees(self.dbstate.active)
def person_edited_cb(self, p1=None, p2=None):
print "PedView.person_edited_cb"
def request_resize(self):
print "PedView.request_resize"
self.size_request_cb(self.notebook.parent,None,None)
def size_request_cb(self, widget, event, data=None):
print "PedView.size_request_cb"
if self.force_size == 0:
v = widget.get_allocation()
page_list = range(0,self.notebook.get_n_pages())
@ -248,13 +331,14 @@ class PedView(PageView.PageView):
self.rebuild( self.table_4, pos_4, person)
self.rebuild( self.table_5, pos_5, person)
gobject.idle_add(self.request_resize)
#gobject.idle_add(self.request_resize)
def rebuild( self, table_widget, positions, active_person):
print "PedView.rebuild"
# Build ancestor tree
lst = [None]*31
self.find_tree(self.active_person,0,1,lst)
self.find_tree(active_person,0,1,lst)
# Purge current table content
for child in table_widget.get_children():
@ -389,9 +473,9 @@ class PedView(PageView.PageView):
person_handle = obj.get_data(_PERSON)
person = self.db.get_person_from_handle(person_handle)
if person:
self.edit_person(person)
EditPerson.EditPerson(self.dbstate, person, self.person_edited_cb)
return True
return 0
return False
def on_show_option_menu_cb(self,obj,data=None):
myMenu = gtk.Menu()
@ -402,16 +486,16 @@ class PedView(PageView.PageView):
def on_show_child_menu(self,obj):
"""User clicked button to move to child of active person"""
if self.active_person:
if self.dbstate.active:
# Build and display the menu attached to the left pointing arrow
# button. The menu consists of the children of the current root
# person of the tree. Attach a child to each menu item.
childlist = find_children(self.db,self.active_person)
childlist = find_children(self.db,self.dbstate.active)
if len(childlist) == 1:
child = self.db.get_person_from_handle(childlist[0])
if child:
self.parent.change_active_person(child)
self.dbstate.change_active_person(child)
elif len(childlist) > 1:
myMenu = gtk.Menu()
for child_handle in childlist:

View File

@ -24,6 +24,7 @@ import gtk
import ViewManager
import PersonView
import PedView
import MapView
import ArgHandler
import DisplayTrace
import GrampsKeys
@ -190,6 +191,7 @@ class Gramps:
a = ViewManager.ViewManager(state)
a.register_view(PersonView.PersonView)
a.register_view(PedView.PedView)
a.register_view(MapView.MapView)
a.init_interface()
if GrampsKeys.get_usetips():

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB