GTK3: converted fanchart, added tooltip over a person.

svn: r20251
This commit is contained in:
Benny Malengier 2012-08-22 15:24:26 +00:00
parent 908d287477
commit e42a90f3b1
2 changed files with 90 additions and 120 deletions

View File

@ -36,6 +36,7 @@ from gi.repository import Pango
from gi.repository import GObject from gi.repository import GObject
from gi.repository import Gdk from gi.repository import Gdk
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import PangoCairo
import cairo import cairo
import math import math
from cgi import escape from cgi import escape
@ -89,18 +90,11 @@ class AttachList(object):
# FanChartWidget # FanChartWidget
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
class FanChartWidget(Gtk.Widget): class FanChartWidget(Gtk.DrawingArea):
""" """
Interactive Fan Chart Widget. Interactive Fan Chart Widget.
""" """
BORDER_WIDTH = 10 BORDER_WIDTH = 10
__gsignals__ = { 'realize': 'override',
##TODO GTK3: no longer expose-event in GTK3, check if this still works
## 'expose-event' : 'override',
'size-allocate': 'override',
##TODO GTK3: no longer size-request in GTK3, check if this still works
## 'size-request': 'override',
}
GENCOLOR = ((229,191,252), GENCOLOR = ((229,191,252),
(191,191,252), (191,191,252),
(191,222,252), (191,222,252),
@ -123,7 +117,6 @@ class FanChartWidget(Gtk.Widget):
self.connect("motion_notify_event", self.on_mouse_move) self.connect("motion_notify_event", self.on_mouse_move)
self.connect("button-press-event", self.on_mouse_down) self.connect("button-press-event", self.on_mouse_down)
self.connect("draw", self.on_draw) self.connect("draw", self.on_draw)
#self.connect("realize", self.realize)
self.context_popup_callback = context_popup_callback self.context_popup_callback = context_popup_callback
self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK |
Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK |
@ -140,6 +133,7 @@ class FanChartWidget(Gtk.Widget):
self.center = 50 # pixel radius of center self.center = 50 # pixel radius of center
self.layout = self.create_pango_layout('cairo') self.layout = self.create_pango_layout('cairo')
self.layout.set_font_description(Pango.FontDescription("sans 8")) self.layout.set_font_description(Pango.FontDescription("sans 8"))
self.set_size_request(120,120)
def reset_generations(self): def reset_generations(self):
""" """
@ -166,61 +160,6 @@ class FanChartWidget(Gtk.Widget):
self.angle[i].append([angle, angle + slice,gender,self.NORMAL]) self.angle[i].append([angle, angle + slice,gender,self.NORMAL])
angle += slice angle += slice
gender = not gender gender = not gender
def do_realize(self, data=None):
"""
Overriden method to handle the realize event.
"""
## TODO GTK3: need to create the window correctly
#if self.get_realized():
# return
#self.set_flags(self.flags() | self.get_realized())
attr = Gdk.WindowAttr()
attr.width = self.allocation.width
attr.height = self.allocation.height
attr.x = 0
attr.y = 0
attr.cursor = Gdk.Cursor.new_for_display(
self.get_display(), Gdk.CursorType.LEFT_PTR)
attr.event_mask = (Gdk.EventMask.ENTER_NOTIFY_MASK |
Gdk.EventMask.LEAVE_NOTIFY_MASK)
attr.wclass = Gdk.WindowWindowClass.INPUT_OUTPUT
attr.window_type = Gdk.WindowType.CHILD
attr.event_mask = (Gdk.EventMask.ENTER_NOTIFY_MASK |
Gdk.EventMask.LEAVE_NOTIFY_MASK)
attr.visual = self.get_visual()
attrmask = (
#Gdk.WindowAttributesType.TITLE |
Gdk.WindowAttributesType.X |
Gdk.WindowAttributesType.Y |
Gdk.WindowAttributesType.CURSOR |
Gdk.WindowAttributesType.VISUAL |
Gdk.WindowAttributesType.NOREDIR
)
self.window = Gdk.Window(self.get_parent_window(),
attr,
attrmask)
self.set_window(self.window)
self.set_realized(True)
### self.window = Gdk.Window(self.get_parent_window(),
### width=self.allocation.width,
### height=self.allocation.height,
### window_type=Gdk.WindowType.CHILD,
### wclass=Gdk.WindowWindowClass.INPUT_OUTPUT,
### event_mask=self.get_events() | Gdk.EventMask.EXPOSURE_MASK)
#if not hasattr(self.window, "cairo_create"):
# self.draw_gc = Gdk.GC(self.window,
# line_width=5,
# line_style=Gdk.SOLID,
# join_style=Gdk.JOIN_ROUND)
#self.window.set_user_data(self)
#self.style.attach(self.window)
#self.style.set_background(self.window, Gtk.StateType.NORMAL)
#self.window.move_resize(*self.allocation)
def do_size_request(self, requisition): def do_size_request(self, requisition):
""" """
@ -246,31 +185,28 @@ class FanChartWidget(Gtk.Widget):
self.do_size_request(req) self.do_size_request(req)
return req.height, req.height return req.height, req.height
def do_size_allocate(self, allocation):
"""
Overridden method to handle size allocation events.
"""
self.allocation = allocation
if self.get_has_window():
if self.get_realized():
self.window.move_resize(allocation.x, allocation.y,
allocation.width, allocation.height)
## def _expose_gdk(self, event):
## x, y, w, h = self.allocation
## self.layout = self.create_pango_layout('no cairo')
## fontw, fonth = self.layout.get_pixel_size()
## self.style.paint_layout(self.window, self.state, False,
## event.area, self, "label",
## (w - fontw) / 2, (h - fonth) / 2,
## self.layout)
def on_draw(self, widget, cr): def on_draw(self, widget, cr):
""" """
The main method to do the drawing. The main method to do the drawing.
""" """
x, y, w, h = self.allocation # first do size request of what we will need
nrgen = None
for generation in range(self.generations - 1, 0, -1):
for p in range(len(self.data[generation])):
(text, person, parents, child) = self.data[generation][p]
if person:
nrgen = generation
break
if nrgen is not None:
break
if nrgen is None:
nrgen = 1
halfdist = self.pixels_per_generation * nrgen + self.center
self.set_size_request(2 * halfdist, 2 * halfdist)
#obtain the allocation
alloc = self.get_allocation()
x, y, w, h = alloc.x, alloc.y, alloc.width, alloc.height
cr.translate(w/2. - self.center_xy[0], h/2. - self.center_xy[1]) cr.translate(w/2. - self.center_xy[0], h/2. - self.center_xy[1])
cr.save() cr.save()
cr.rotate(self.rotate_value * math.pi/180) cr.rotate(self.rotate_value * math.pi/180)
@ -307,8 +243,8 @@ class FanChartWidget(Gtk.Widget):
cr.fill() cr.fill()
fontw, fonth = self.layout.get_pixel_size() fontw, fonth = self.layout.get_pixel_size()
cr.move_to((w - fontw - 4), (h - fonth )) cr.move_to((w - fontw - 4), (h - fonth ))
cr.update_layout(self.layout) self.layout.context_changed()
cr.show_layout(self.layout) PangoCairo.show_layout(cr, self.layout)
def draw_person(self, cr, gender, name, start, stop, generation, def draw_person(self, cr, gender, name, start, stop, generation,
state, parents, child): state, parents, child):
@ -316,7 +252,8 @@ class FanChartWidget(Gtk.Widget):
Display the piece of pie for a given person. start and stop Display the piece of pie for a given person. start and stop
are in degrees. are in degrees.
""" """
x, y, w, h = self.allocation alloc = self.get_allocation()
x, y, w, h = alloc.x, alloc.y, alloc.width, alloc.height
start_rad = start * math.pi/180 start_rad = start * math.pi/180
stop_rad = stop * math.pi/180 stop_rad = stop * math.pi/180
r,g,b = self.GENCOLOR[generation % len(self.GENCOLOR)] r,g,b = self.GENCOLOR[generation % len(self.GENCOLOR)]
@ -382,7 +319,8 @@ class FanChartWidget(Gtk.Widget):
# center text: # center text:
# offset for cairo-font system is 90: # offset for cairo-font system is 90:
pos = start + ((stop - start) - self.text_degrees(text,radius))/2.0 + 90 pos = start + ((stop - start) - self.text_degrees(text,radius))/2.0 + 90
x, y, w, h = self.allocation alloc = self.get_allocation()
x, y, w, h = alloc.x, alloc.y, alloc.width, alloc.height
cr.save() cr.save()
# Create a PangoLayout, set the font and text # Create a PangoLayout, set the font and text
# Draw the layout N_WORDS times in a circle # Draw the layout N_WORDS times in a circle
@ -394,10 +332,10 @@ class FanChartWidget(Gtk.Widget):
cr.set_source_rgb(0, 0, 0) # black cr.set_source_rgb(0, 0, 0) # black
cr.rotate(angle * (math.pi / 180)); cr.rotate(angle * (math.pi / 180));
# Inform Pango to re-layout the text with the new transformation # Inform Pango to re-layout the text with the new transformation
cr.update_layout(layout) layout.context_changed()
width, height = layout.get_size() width, height = layout.get_size()
cr.move_to(- (width / Pango.SCALE) / 2.0, - radius) cr.move_to(- (width / Pango.SCALE) / 2.0, - radius)
cr.show_layout(layout) PangoCairo.show_layout(cr, layout)
cr.restore() cr.restore()
cr.restore() cr.restore()
@ -506,21 +444,55 @@ class FanChartWidget(Gtk.Widget):
self.NORMAL] self.NORMAL]
self.show_parents(generation+1, selected-1, start, slice/2.0) self.show_parents(generation+1, selected-1, start, slice/2.0)
# TODO GTK3: these should be do_ methods now?
def on_mouse_up(self, widget, event): def on_mouse_up(self, widget, event):
# Done with mouse movement # Done with mouse movement
if self.last_x is None or self.last_y is None: return True if self.last_x is None or self.last_y is None:
return True
if self.translating: if self.translating:
self.translating = False self.translating = False
x, y, w, h = self.allocation alloc = self.get_allocation()
x, y, w, h = alloc.x, alloc.y, alloc.width, alloc.height
self.center_xy = w/2 - event.x, h/2 - event.y self.center_xy = w/2 - event.x, h/2 - event.y
self.last_x, self.last_y = None, None self.last_x, self.last_y = None, None
self.queue_draw() self.queue_draw()
return True return True
def on_mouse_move(self, widget, event): def on_mouse_move(self, widget, event):
if self.last_x is None or self.last_y is None: return False if self.last_x is None or self.last_y is None:
x, y, w, h = self.allocation alloc = self.get_allocation()
x, y, w, h = alloc.x, alloc.y, alloc.width, alloc.height
cx = w/2 - self.center_xy[0]
cy = h/2 - self.center_xy[1]
radius = math.sqrt((event.x - cx) ** 2 + (event.y - cy) ** 2)
selected = None
if radius < self.center:
generation = 0
selected = 0
else:
generation = int((radius - self.center) /
self.pixels_per_generation) + 1
rads = math.atan2( (event.y - cy), (event.x - cx) )
if rads < 0: # second half of unit circle
rads = math.pi + (math.pi + rads)
pos = ((rads/(math.pi * 2) - self.rotate_value/360.) * 360.0) % 360
if (0 < generation < self.generations):
for p in range(len(self.angle[generation])):
if self.data[generation][p][1]: # there is a person there
start, stop, male, state = self.angle[generation][p]
if state == self.COLLAPSED: continue
if start <= pos <= stop:
selected = p
break
tooltip = ""
if selected is not None:
text, person, parents, child = self.data[generation][selected]
if person:
tooltip = self.format_helper.format_person(person, 11)
self.set_tooltip_text(tooltip)
return False
alloc = self.get_allocation()
x, y, w, h = alloc.x, alloc.y, alloc.width, alloc.height
if self.translating: if self.translating:
self.center_xy = w/2 - event.x, h/2 - event.y self.center_xy = w/2 - event.x, h/2 - event.y
self.queue_draw() self.queue_draw()
@ -543,7 +515,8 @@ class FanChartWidget(Gtk.Widget):
def on_mouse_down(self, widget, event): def on_mouse_down(self, widget, event):
# compute angle, radius, find out who would be there (rotated) # compute angle, radius, find out who would be there (rotated)
x, y, w, h = self.allocation alloc = self.get_allocation()
x, y, w, h = alloc.x, alloc.y, alloc.width, alloc.height
self.translating = False # keep track of up/down/left/right movement self.translating = False # keep track of up/down/left/right movement
cx = w/2 - self.center_xy[0] cx = w/2 - self.center_xy[0]
cy = h/2 - self.center_xy[1] cy = h/2 - self.center_xy[1]
@ -603,14 +576,6 @@ class FanChartWidget(Gtk.Widget):
self.queue_draw() self.queue_draw()
return True return True
## def set_flags(self, flags):
## ## TODO GTK3: need to set_flags?
## pass
##
## def flags(self):
## ## TODO GTK3: need to get flags?
## return 0
class FanChartView(NavigationView): class FanChartView(NavigationView):
""" """
The Gramplet code that realizes the FanChartWidget. The Gramplet code that realizes the FanChartWidget.
@ -636,7 +601,14 @@ class FanChartView(NavigationView):
def build_widget(self): def build_widget(self):
self.fan = FanChartWidget(self.generations, self.fan = FanChartWidget(self.generations,
context_popup_callback=self.on_popup) context_popup_callback=self.on_popup)
return self.fan self.fan.format_helper = self.format_helper
self.scrolledwindow = Gtk.ScrolledWindow(None, None)
self.scrolledwindow.set_policy(Gtk.PolicyType.AUTOMATIC,
Gtk.PolicyType.AUTOMATIC)
self.fan.show_all()
self.scrolledwindow.add_with_viewport(self.fan)
return self.scrolledwindow
def get_stock(self): def get_stock(self):
""" """
@ -793,13 +765,13 @@ class FanChartView(NavigationView):
return True return True
return False return False
def copy_person_to_clipboard_cb(self, obj,person_handle): def copy_person_to_clipboard_cb(self, obj, person_handle):
"""Renders the person data into some lines of text and puts that into the clipboard""" """Renders the person data into some lines of text and puts that into the clipboard"""
person = self.dbstate.db.get_person_from_handle(person_handle) person = self.dbstate.db.get_person_from_handle(person_handle)
if person: if person:
cb = Gtk.Clipboard.get_for_display(Gdk.Display.get_default(), cb = Gtk.Clipboard.get_for_display(Gdk.Display.get_default(),
Gdk.SELECTION_CLIPBOARD) Gdk.SELECTION_CLIPBOARD)
cb.set_text( self.format_helper.format_person(person,11)) cb.set_text( self.format_helper.format_person(person,11), -1)
return True return True
return False return False
@ -808,8 +780,9 @@ class FanChartView(NavigationView):
Builds the full menu (including Siblings, Spouses, Children, Builds the full menu (including Siblings, Spouses, Children,
and Parents) with navigation. Copied from PedigreeView. and Parents) with navigation. Copied from PedigreeView.
""" """
#store menu for GTK3 to avoid it being destroyed before showing
menu = Gtk.Menu() self.menu = Gtk.Menu()
menu = self.menu
menu.set_title(_('People Menu')) menu.set_title(_('People Menu'))
person = self.dbstate.db.get_person_from_handle(person_handle) person = self.dbstate.db.get_person_from_handle(person_handle)
@ -824,12 +797,12 @@ class FanChartView(NavigationView):
go_item.show() go_item.show()
menu.append(go_item) menu.append(go_item)
edit_item = Gtk.ImageMenuItem(Gtk.STOCK_EDIT) edit_item = Gtk.ImageMenuItem.new_from_stock(stock_id=Gtk.STOCK_EDIT, accel_group=None)
edit_item.connect("activate",self.edit_person_cb,person_handle) edit_item.connect("activate",self.edit_person_cb,person_handle)
edit_item.show() edit_item.show()
menu.append(edit_item) menu.append(edit_item)
clipboard_item = Gtk.ImageMenuItem(Gtk.STOCK_COPY) clipboard_item = Gtk.ImageMenuItem.new_from_stock(stock_id=Gtk.STOCK_COPY, accel_group=None)
clipboard_item.connect("activate",self.copy_person_to_clipboard_cb,person_handle) clipboard_item.connect("activate",self.copy_person_to_clipboard_cb,person_handle)
clipboard_item.show() clipboard_item.show()
menu.append(clipboard_item) menu.append(clipboard_item)
@ -856,7 +829,7 @@ class FanChartView(NavigationView):
item.set_submenu(Gtk.Menu()) item.set_submenu(Gtk.Menu())
sp_menu = item.get_submenu() sp_menu = item.get_submenu()
go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO,Gtk.IconSize.MENU) go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO, Gtk.IconSize.MENU)
go_image.show() go_image.show()
sp_item = Gtk.ImageMenuItem(name_displayer.display(spouse)) sp_item = Gtk.ImageMenuItem(name_displayer.display(spouse))
sp_item.set_image(go_image) sp_item.set_image(go_image)
@ -896,7 +869,7 @@ class FanChartView(NavigationView):
else: else:
label = Gtk.Label(label=escape(name_displayer.display(sib))) label = Gtk.Label(label=escape(name_displayer.display(sib)))
go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO,Gtk.IconSize.MENU) go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO, Gtk.IconSize.MENU)
go_image.show() go_image.show()
sib_item = Gtk.ImageMenuItem(None) sib_item = Gtk.ImageMenuItem(None)
sib_item.set_image(go_image) sib_item.set_image(go_image)
@ -933,7 +906,7 @@ class FanChartView(NavigationView):
else: else:
label = Gtk.Label(label=escape(name_displayer.display(child))) label = Gtk.Label(label=escape(name_displayer.display(child)))
go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO,Gtk.IconSize.MENU) go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO, Gtk.IconSize.MENU)
go_image.show() go_image.show()
child_item = Gtk.ImageMenuItem(None) child_item = Gtk.ImageMenuItem(None)
child_item.set_image(go_image) child_item.set_image(go_image)
@ -970,7 +943,7 @@ class FanChartView(NavigationView):
else: else:
label = Gtk.Label(label=escape(name_displayer.display(par))) label = Gtk.Label(label=escape(name_displayer.display(par)))
go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO,Gtk.IconSize.MENU) go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO, Gtk.IconSize.MENU)
go_image.show() go_image.show()
par_item = Gtk.ImageMenuItem(None) par_item = Gtk.ImageMenuItem(None)
par_item.set_image(go_image) par_item.set_image(go_image)
@ -1006,7 +979,7 @@ class FanChartView(NavigationView):
label = Gtk.Label(label=escape(name_displayer.display(per))) label = Gtk.Label(label=escape(name_displayer.display(per)))
go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO,Gtk.IconSize.MENU) go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO, Gtk.IconSize.MENU)
go_image.show() go_image.show()
per_item = Gtk.ImageMenuItem(None) per_item = Gtk.ImageMenuItem(None)
per_item.set_image(go_image) per_item.set_image(go_image)
@ -1024,4 +997,3 @@ class FanChartView(NavigationView):
menu.append(item) menu.append(item)
menu.popup(None, None, None, None, event.button, event.time) menu.popup(None, None, None, None, event.button, event.time)
return 1 return 1

View File

@ -124,7 +124,6 @@ class _PersonWidgetBase(Gtk.DrawingArea):
self.in_drag = True self.in_drag = True
self.drag_source_set_icon_stock('gramps-person') self.drag_source_set_icon_stock('gramps-person')
def cb_drag_end(self, widget, data): def cb_drag_end(self, widget, data):
"""Set up some inital conditions for drag. Set up icon.""" """Set up some inital conditions for drag. Set up icon."""
self.in_drag = False self.in_drag = False
@ -175,7 +174,6 @@ class _PersonWidgetBase(Gtk.DrawingArea):
rectangle=photo.get_rectangle()) rectangle=photo.get_rectangle())
return image_path return image_path
class PersonBoxWidgetCairo(_PersonWidgetBase): class PersonBoxWidgetCairo(_PersonWidgetBase):
"""Draw person box using cairo library""" """Draw person box using cairo library"""
def __init__(self, view, format_helper, dbstate, person, alive, maxlines, def __init__(self, view, format_helper, dbstate, person, alive, maxlines,