GTK3: converted fanchart, added tooltip over a person.
svn: r20251
This commit is contained in:
parent
908d287477
commit
e42a90f3b1
@ -36,6 +36,7 @@ from gi.repository import Pango
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import PangoCairo
|
||||
import cairo
|
||||
import math
|
||||
from cgi import escape
|
||||
@ -89,18 +90,11 @@ class AttachList(object):
|
||||
# FanChartWidget
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class FanChartWidget(Gtk.Widget):
|
||||
class FanChartWidget(Gtk.DrawingArea):
|
||||
"""
|
||||
Interactive Fan Chart Widget.
|
||||
"""
|
||||
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),
|
||||
(191,191,252),
|
||||
(191,222,252),
|
||||
@ -123,7 +117,6 @@ class FanChartWidget(Gtk.Widget):
|
||||
self.connect("motion_notify_event", self.on_mouse_move)
|
||||
self.connect("button-press-event", self.on_mouse_down)
|
||||
self.connect("draw", self.on_draw)
|
||||
#self.connect("realize", self.realize)
|
||||
self.context_popup_callback = context_popup_callback
|
||||
self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK |
|
||||
Gdk.EventMask.BUTTON_RELEASE_MASK |
|
||||
@ -140,6 +133,7 @@ class FanChartWidget(Gtk.Widget):
|
||||
self.center = 50 # pixel radius of center
|
||||
self.layout = self.create_pango_layout('cairo')
|
||||
self.layout.set_font_description(Pango.FontDescription("sans 8"))
|
||||
self.set_size_request(120,120)
|
||||
|
||||
def reset_generations(self):
|
||||
"""
|
||||
@ -166,61 +160,6 @@ class FanChartWidget(Gtk.Widget):
|
||||
self.angle[i].append([angle, angle + slice,gender,self.NORMAL])
|
||||
angle += slice
|
||||
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):
|
||||
"""
|
||||
@ -246,31 +185,28 @@ class FanChartWidget(Gtk.Widget):
|
||||
self.do_size_request(req)
|
||||
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):
|
||||
"""
|
||||
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.save()
|
||||
cr.rotate(self.rotate_value * math.pi/180)
|
||||
@ -307,8 +243,8 @@ class FanChartWidget(Gtk.Widget):
|
||||
cr.fill()
|
||||
fontw, fonth = self.layout.get_pixel_size()
|
||||
cr.move_to((w - fontw - 4), (h - fonth ))
|
||||
cr.update_layout(self.layout)
|
||||
cr.show_layout(self.layout)
|
||||
self.layout.context_changed()
|
||||
PangoCairo.show_layout(cr, self.layout)
|
||||
|
||||
def draw_person(self, cr, gender, name, start, stop, generation,
|
||||
state, parents, child):
|
||||
@ -316,7 +252,8 @@ class FanChartWidget(Gtk.Widget):
|
||||
Display the piece of pie for a given person. start and stop
|
||||
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
|
||||
stop_rad = stop * math.pi/180
|
||||
r,g,b = self.GENCOLOR[generation % len(self.GENCOLOR)]
|
||||
@ -382,7 +319,8 @@ class FanChartWidget(Gtk.Widget):
|
||||
# center text:
|
||||
# offset for cairo-font system is 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()
|
||||
# Create a PangoLayout, set the font and text
|
||||
# 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.rotate(angle * (math.pi / 180));
|
||||
# Inform Pango to re-layout the text with the new transformation
|
||||
cr.update_layout(layout)
|
||||
layout.context_changed()
|
||||
width, height = layout.get_size()
|
||||
cr.move_to(- (width / Pango.SCALE) / 2.0, - radius)
|
||||
cr.show_layout(layout)
|
||||
PangoCairo.show_layout(cr, layout)
|
||||
cr.restore()
|
||||
cr.restore()
|
||||
|
||||
@ -506,21 +444,55 @@ class FanChartWidget(Gtk.Widget):
|
||||
self.NORMAL]
|
||||
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):
|
||||
# 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:
|
||||
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.last_x, self.last_y = None, None
|
||||
self.queue_draw()
|
||||
return True
|
||||
|
||||
def on_mouse_move(self, widget, event):
|
||||
if self.last_x is None or self.last_y is None: return False
|
||||
x, y, w, h = self.allocation
|
||||
if self.last_x is None or self.last_y is None:
|
||||
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:
|
||||
self.center_xy = w/2 - event.x, h/2 - event.y
|
||||
self.queue_draw()
|
||||
@ -543,7 +515,8 @@ class FanChartWidget(Gtk.Widget):
|
||||
|
||||
def on_mouse_down(self, widget, event):
|
||||
# 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
|
||||
cx = w/2 - self.center_xy[0]
|
||||
cy = h/2 - self.center_xy[1]
|
||||
@ -603,14 +576,6 @@ class FanChartWidget(Gtk.Widget):
|
||||
self.queue_draw()
|
||||
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):
|
||||
"""
|
||||
The Gramplet code that realizes the FanChartWidget.
|
||||
@ -636,7 +601,14 @@ class FanChartView(NavigationView):
|
||||
def build_widget(self):
|
||||
self.fan = FanChartWidget(self.generations,
|
||||
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):
|
||||
"""
|
||||
@ -793,13 +765,13 @@ class FanChartView(NavigationView):
|
||||
return True
|
||||
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"""
|
||||
person = self.dbstate.db.get_person_from_handle(person_handle)
|
||||
if person:
|
||||
cb = Gtk.Clipboard.get_for_display(Gdk.Display.get_default(),
|
||||
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 False
|
||||
|
||||
@ -808,8 +780,9 @@ class FanChartView(NavigationView):
|
||||
Builds the full menu (including Siblings, Spouses, Children,
|
||||
and Parents) with navigation. Copied from PedigreeView.
|
||||
"""
|
||||
|
||||
menu = Gtk.Menu()
|
||||
#store menu for GTK3 to avoid it being destroyed before showing
|
||||
self.menu = Gtk.Menu()
|
||||
menu = self.menu
|
||||
menu.set_title(_('People Menu'))
|
||||
|
||||
person = self.dbstate.db.get_person_from_handle(person_handle)
|
||||
@ -824,12 +797,12 @@ class FanChartView(NavigationView):
|
||||
go_item.show()
|
||||
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.show()
|
||||
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.show()
|
||||
menu.append(clipboard_item)
|
||||
@ -856,7 +829,7 @@ class FanChartView(NavigationView):
|
||||
item.set_submenu(Gtk.Menu())
|
||||
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()
|
||||
sp_item = Gtk.ImageMenuItem(name_displayer.display(spouse))
|
||||
sp_item.set_image(go_image)
|
||||
@ -896,7 +869,7 @@ class FanChartView(NavigationView):
|
||||
else:
|
||||
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()
|
||||
sib_item = Gtk.ImageMenuItem(None)
|
||||
sib_item.set_image(go_image)
|
||||
@ -933,7 +906,7 @@ class FanChartView(NavigationView):
|
||||
else:
|
||||
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()
|
||||
child_item = Gtk.ImageMenuItem(None)
|
||||
child_item.set_image(go_image)
|
||||
@ -970,7 +943,7 @@ class FanChartView(NavigationView):
|
||||
else:
|
||||
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()
|
||||
par_item = Gtk.ImageMenuItem(None)
|
||||
par_item.set_image(go_image)
|
||||
@ -1006,7 +979,7 @@ class FanChartView(NavigationView):
|
||||
|
||||
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()
|
||||
per_item = Gtk.ImageMenuItem(None)
|
||||
per_item.set_image(go_image)
|
||||
@ -1024,4 +997,3 @@ class FanChartView(NavigationView):
|
||||
menu.append(item)
|
||||
menu.popup(None, None, None, None, event.button, event.time)
|
||||
return 1
|
||||
|
||||
|
@ -124,7 +124,6 @@ class _PersonWidgetBase(Gtk.DrawingArea):
|
||||
self.in_drag = True
|
||||
self.drag_source_set_icon_stock('gramps-person')
|
||||
|
||||
|
||||
def cb_drag_end(self, widget, data):
|
||||
"""Set up some inital conditions for drag. Set up icon."""
|
||||
self.in_drag = False
|
||||
@ -175,7 +174,6 @@ class _PersonWidgetBase(Gtk.DrawingArea):
|
||||
rectangle=photo.get_rectangle())
|
||||
return image_path
|
||||
|
||||
|
||||
class PersonBoxWidgetCairo(_PersonWidgetBase):
|
||||
"""Draw person box using cairo library"""
|
||||
def __init__(self, view, format_helper, dbstate, person, alive, maxlines,
|
||||
|
Loading…
Reference in New Issue
Block a user