Treeviwe Tooltips class
This commit is contained in:
parent
9d827ee3fd
commit
ced499afd0
423
pyfpdb/TreeViewTooltips.py
Normal file
423
pyfpdb/TreeViewTooltips.py
Normal file
|
@ -0,0 +1,423 @@
|
|||
# Copyright (c) 2006, Daniel J. Popowich
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
#
|
||||
# Send bug reports and contributions to:
|
||||
#
|
||||
# dpopowich AT astro dot umass dot edu
|
||||
#
|
||||
|
||||
'''
|
||||
TreeViewTooltips.py
|
||||
|
||||
Provides TreeViewTooltips, a class which presents tooltips for cells,
|
||||
columns and rows in a gtk.TreeView.
|
||||
|
||||
------------------------------------------------------------
|
||||
This file includes a demo. Just execute the file:
|
||||
|
||||
python TreeViewTooltips.py
|
||||
------------------------------------------------------------
|
||||
|
||||
To use, first subclass TreeViewTooltips and implement the get_tooltip()
|
||||
method; see below. Then add any number of gtk.TreeVew widgets to a
|
||||
TreeViewTooltips instance by calling the add_view() method. Overview
|
||||
of the steps:
|
||||
|
||||
# 1. subclass TreeViewTooltips
|
||||
class MyTooltips(TreeViewTooltips):
|
||||
|
||||
# 2. overriding get_tooltip()
|
||||
def get_tooltip(...):
|
||||
...
|
||||
|
||||
# 3. create an instance
|
||||
mytips = MyTooltips()
|
||||
|
||||
# 4. Build up your gtk.TreeView.
|
||||
myview = gtk.TreeView()
|
||||
...# create columns, set the model, etc.
|
||||
|
||||
# 5. Add the view to the tooltips
|
||||
mytips.add_view(myview)
|
||||
|
||||
How it works: the add_view() method connects the TreeView to the
|
||||
"motion-notify" event with the callback set to a private method.
|
||||
Whenever the mouse moves across the TreeView the callback will call
|
||||
get_tooltip() with the following arguments:
|
||||
|
||||
get_tooltip(view, column, path)
|
||||
|
||||
where,
|
||||
|
||||
view: the gtk.TreeView instance.
|
||||
column: the gtk.TreeViewColumn instance that the mouse is
|
||||
currently over.
|
||||
path: the path to the row that the mouse is currently over.
|
||||
|
||||
Based on whether or not column and path are checked for specific
|
||||
values, get_tooltip can return tooltips for a cell, column, row or the
|
||||
whole view:
|
||||
|
||||
Column Checked Path Checked Tooltip For...
|
||||
Y Y cell
|
||||
Y N column
|
||||
N Y row
|
||||
N N view
|
||||
|
||||
get_tooltip() should return None if no tooltip should be displayed.
|
||||
Otherwise the return value will be coerced to a string (with the str()
|
||||
builtin) and stripped; if non-empty, the result will be displayed as
|
||||
the tooltip. By default, the tooltip popup window will be displayed
|
||||
centered and just below the pointer and will remain shown until the
|
||||
pointer leaves the cell (or column, or row, or view, depending on how
|
||||
get_tooltip() is implemented).
|
||||
|
||||
'''
|
||||
|
||||
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
|
||||
import gtk
|
||||
import gtk.gdk
|
||||
import gobject
|
||||
|
||||
if gtk.gtk_version < (2, 8):
|
||||
import warnings
|
||||
|
||||
msg = ('''This module was developed and tested with version 2.8.18 of gtk. You are using version %d.%d.%d. Your milage may vary.'''
|
||||
% gtk.gtk_version)
|
||||
warnings.warn(msg)
|
||||
|
||||
|
||||
# major, minor, patch
|
||||
version = 1, 0, 0
|
||||
|
||||
class TreeViewTooltips:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
'''
|
||||
Initialize the tooltip. After initialization there are two
|
||||
attributes available for advanced control:
|
||||
|
||||
window: the popup window that holds the tooltip text, an
|
||||
instance of gtk.Window.
|
||||
label: a gtk.Label that is packed into the window. The
|
||||
tooltip text is set in the label with the
|
||||
set_label() method, so the text can be plain or
|
||||
markup text.
|
||||
|
||||
Be default, the tooltip is enabled. See the enabled/disabled
|
||||
methods.
|
||||
'''
|
||||
|
||||
# create the window
|
||||
self.window = window = gtk.Window(gtk.WINDOW_POPUP)
|
||||
window.set_name('gtk-tooltips')
|
||||
window.set_resizable(False)
|
||||
window.set_border_width(4)
|
||||
window.set_app_paintable(True)
|
||||
window.connect("expose-event", self.__on_expose_event)
|
||||
|
||||
|
||||
# create the label
|
||||
self.label = label = gtk.Label()
|
||||
label.set_line_wrap(True)
|
||||
label.set_alignment(0.5, 0.5)
|
||||
label.set_use_markup(True)
|
||||
label.show()
|
||||
window.add(label)
|
||||
|
||||
# by default, the tooltip is enabled
|
||||
self.__enabled = True
|
||||
# saves the current cell
|
||||
self.__save = None
|
||||
# the timer id for the next tooltip to be shown
|
||||
self.__next = None
|
||||
# flag on whether the tooltip window is shown
|
||||
self.__shown = False
|
||||
|
||||
def enable(self):
|
||||
'Enable the tooltip'
|
||||
|
||||
self.__enabled = True
|
||||
|
||||
def disable(self):
|
||||
'Disable the tooltip'
|
||||
|
||||
self.__enabled = False
|
||||
|
||||
def __show(self, tooltip, x, y):
|
||||
|
||||
'''show the tooltip popup with the text/markup given by
|
||||
tooltip.
|
||||
|
||||
tooltip: the text/markup for the tooltip.
|
||||
x, y: the coord. (root window based) of the pointer.
|
||||
'''
|
||||
|
||||
window = self.window
|
||||
|
||||
# set label
|
||||
self.label.set_label(tooltip)
|
||||
# resize window
|
||||
w, h = window.size_request()
|
||||
# move the window
|
||||
window.move(*self.location(x,y,w,h))
|
||||
# show it
|
||||
window.show()
|
||||
self.__shown = True
|
||||
|
||||
def __hide(self):
|
||||
'hide the tooltip'
|
||||
|
||||
self.__queue_next()
|
||||
self.window.hide()
|
||||
self.__shown = False
|
||||
|
||||
def __leave_handler(self, view, event):
|
||||
'when the pointer leaves the view, hide the tooltip'
|
||||
|
||||
self.__hide()
|
||||
|
||||
def __motion_handler(self, view, event):
|
||||
'As the pointer moves across the view, show a tooltip.'
|
||||
|
||||
path = view.get_path_at_pos(int(event.x), int(event.y))
|
||||
|
||||
if self.__enabled and path:
|
||||
path, col, x, y = path
|
||||
tooltip = self.get_tooltip(view, col, path)
|
||||
if tooltip is not None:
|
||||
tooltip = str(tooltip).strip()
|
||||
if tooltip:
|
||||
self.__queue_next((path, col), tooltip,
|
||||
int(event.x_root),
|
||||
int(event.y_root))
|
||||
return
|
||||
|
||||
self.__hide()
|
||||
|
||||
def __queue_next(self, *args):
|
||||
|
||||
'queue next request to show a tooltip'
|
||||
|
||||
# if args is non-empty it means a request was made to show a
|
||||
# tooltip. if empty, no request is being made, but any
|
||||
# pending requests should be cancelled anyway.
|
||||
|
||||
cell = None
|
||||
|
||||
# if called with args, break them out
|
||||
if args:
|
||||
cell, tooltip, x, y = args
|
||||
|
||||
# if it's the same cell as previously shown, just return
|
||||
if self.__save == cell:
|
||||
return
|
||||
|
||||
# if we have something queued up, cancel it
|
||||
if self.__next:
|
||||
gobject.source_remove(self.__next)
|
||||
self.__next = None
|
||||
|
||||
# if there was a request...
|
||||
if cell:
|
||||
# if the tooltip is already shown, show the new one
|
||||
# immediately
|
||||
if self.__shown:
|
||||
self.__show(tooltip, x, y)
|
||||
# else queue it up in 1/2 second
|
||||
else:
|
||||
self.__next = gobject.timeout_add(500, self.__show,
|
||||
tooltip, x, y)
|
||||
|
||||
# save this cell
|
||||
self.__save = cell
|
||||
|
||||
|
||||
def __on_expose_event(self, window, event):
|
||||
|
||||
# this magic is required so the window appears with a 1-pixel
|
||||
# black border (default gtk Style). This code is a
|
||||
# transliteration of the C implementation of gtk.Tooltips.
|
||||
w, h = window.size_request()
|
||||
window.style.paint_flat_box(window.window, gtk.STATE_NORMAL,
|
||||
gtk.SHADOW_OUT, None, window,
|
||||
'tooltip', 0, 0, w, h)
|
||||
|
||||
def location(self, x, y, w, h):
|
||||
|
||||
'''Given the x,y coordinates of the pointer and the width and
|
||||
height (w,h) demensions of the tooltip window, return the x, y
|
||||
coordinates of the tooltip window.
|
||||
|
||||
The default location is to center the window on the pointer
|
||||
and 4 pixels below it.
|
||||
'''
|
||||
|
||||
return x - w/2, y + 4
|
||||
|
||||
def add_view(self, view):
|
||||
|
||||
'add a gtk.TreeView to the tooltip'
|
||||
|
||||
assert isinstance(view, gtk.TreeView), \
|
||||
('This handler should only be connected to '
|
||||
'instances of gtk.TreeView')
|
||||
|
||||
view.connect("motion-notify-event", self.__motion_handler)
|
||||
view.connect("leave-notify-event", self.__leave_handler)
|
||||
|
||||
def get_tooltip(self, view, column, path):
|
||||
'See the module doc string for a description of this method'
|
||||
|
||||
raise NotImplemented, 'Subclass must implement get_tooltip()'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
############################################################
|
||||
# DEMO
|
||||
############################################################
|
||||
|
||||
# First, subclass TreeViewTooltips
|
||||
|
||||
class DemoTips(TreeViewTooltips):
|
||||
|
||||
def __init__(self, customer_column):
|
||||
# customer_column is an instance of gtk.TreeViewColumn and
|
||||
# is being used in the gtk.TreeView to show customer names.
|
||||
self.cust_col = customer_column
|
||||
|
||||
# call base class init
|
||||
TreeViewTooltips.__init__(self)
|
||||
|
||||
def get_tooltip(self, view, column, path):
|
||||
|
||||
# we have a two column view: customer, phone; we'll make
|
||||
# tooltips cell-based for the customer column, but generic
|
||||
# column-based for the phone column.
|
||||
|
||||
# customer
|
||||
if column is self.cust_col:
|
||||
|
||||
# By checking both column and path we have a
|
||||
# cell-based tooltip.
|
||||
model = view.get_model()
|
||||
customer = model[path][2]
|
||||
return '<big>%s %s</big>\n<i>%s</i>' % (customer.fname,
|
||||
customer.lname,
|
||||
customer.notes)
|
||||
# phone
|
||||
else:
|
||||
return ('<big><u>Generic Column Tooltip</u></big>\n'
|
||||
'Unless otherwise noted, all\narea codes are 888')
|
||||
|
||||
def XX_location(self, x, y, w, h):
|
||||
# rename me to "location" so I override the base class
|
||||
# method. This will demonstrate being able to change
|
||||
# where the tooltip window popups, relative to the
|
||||
# pointer.
|
||||
|
||||
# this will place the tooltip above and to the right
|
||||
return x + 10, y - (h + 10)
|
||||
|
||||
# Here's our customer
|
||||
class Customer:
|
||||
|
||||
def __init__(self, fname, lname, phone, notes):
|
||||
self.fname = fname
|
||||
self.lname = lname
|
||||
self.phone = phone
|
||||
self.notes = notes
|
||||
|
||||
# create a bunch of customers
|
||||
customers = []
|
||||
for fname, lname, phone, notes in [
|
||||
('Joe', 'Schmoe', '555-1212', 'Likes to Morris dance.'),
|
||||
('Jane', 'Doe', '555-2323',
|
||||
'Wonders what the hell\nMorris dancing is.'),
|
||||
('Phred', 'Phantastic', '900-555-1212', 'Dreams of Betty.'),
|
||||
('Betty', 'Boop', '555-3434', 'Dreams in b&w.'),
|
||||
('Red Sox', 'Fan', '555-4545',
|
||||
"Still livin' 2004!\nEspecially after 2006.")]:
|
||||
customers.append(Customer(fname, lname, phone, notes))
|
||||
|
||||
# Build our model and view
|
||||
model = gtk.ListStore(str, str, object)
|
||||
for c in customers:
|
||||
model.append(['%s %s' % (c.fname, c.lname), c.phone, c])
|
||||
|
||||
view = gtk.TreeView(model)
|
||||
view.get_selection().set_mode(gtk.SELECTION_NONE)
|
||||
|
||||
# two columns, name and phone
|
||||
cell = gtk.CellRendererText()
|
||||
cell.set_property('xpad', 20)
|
||||
namecol = gtk.TreeViewColumn('Customer Name', cell, text=0)
|
||||
namecol.set_min_width(200)
|
||||
view.append_column(namecol)
|
||||
|
||||
cell = gtk.CellRendererText()
|
||||
phonecol = gtk.TreeViewColumn('Phone', cell, text=1)
|
||||
view.append_column(phonecol)
|
||||
|
||||
# finally, connect the tooltip, specifying the name column as the
|
||||
# column we want the tooltip to popup over.
|
||||
tips = DemoTips(namecol)
|
||||
tips.add_view(view)
|
||||
|
||||
# We're going to demonstrate enable/disable. First we need a
|
||||
# callback function to connect to the toggled signal.
|
||||
def toggle(button):
|
||||
if button.get_active():
|
||||
tips.disable()
|
||||
else:
|
||||
tips.enable()
|
||||
|
||||
# create a checkbutton and connect our handler
|
||||
check = gtk.CheckButton('Check to disable view tooltips')
|
||||
check.connect('toggled', toggle)
|
||||
|
||||
# a standard gtk.Tooltips to compare to
|
||||
tt = gtk.Tooltips()
|
||||
tt.set_tip(check, ('This is a standard gtk tooltip.\n'
|
||||
'Compare me to the tooltips above.'))
|
||||
|
||||
# create a VBox to pack the view and checkbutton
|
||||
vbox = gtk.VBox()
|
||||
vbox.pack_start(view)
|
||||
vbox.pack_start(check, False)
|
||||
vbox.show_all()
|
||||
|
||||
# pack the vbox into a simple dialog and run it
|
||||
dialog = gtk.Dialog('TreeViewTooltips Demo')
|
||||
close = dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_NONE)
|
||||
|
||||
# add a tooltip for the close button
|
||||
tt.set_tip(close, 'Click to end the demo.')
|
||||
|
||||
dialog.set_default_size(400,400)
|
||||
dialog.vbox.pack_start(vbox)
|
||||
dialog.run()
|
Loading…
Reference in New Issue
Block a user