1 # Copyright (c) 2006, Daniel J. Popowich
3 # Permission is hereby granted, free of charge, to any person
4 # obtaining a copy of this software and associated documentation files
5 # (the "Software"), to deal in the Software without restriction,
6 # including without limitation the rights to use, copy, modify, merge,
7 # publish, distribute, sublicense, and/or sell copies of the Software,
8 # and to permit persons to whom the Software is furnished to do so,
9 # subject to the following conditions:
11 # The above copyright notice and this permission notice shall be
12 # included in all copies or substantial portions of the Software.
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
18 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 # Send bug reports and contributions to:
25 # dpopowich AT astro dot umass dot edu
27 # This version of the file is part of fpdb, contact: fpdb-main@lists.sourceforge.net
32 Provides TreeViewTooltips, a class which presents tooltips for cells,
33 columns and rows in a gtk.TreeView.
35 ------------------------------------------------------------
36 This file includes a demo. Just execute the file:
38 python TreeViewTooltips.py
39 ------------------------------------------------------------
41 To use, first subclass TreeViewTooltips and implement the get_tooltip()
42 method; see below. Then add any number of gtk.TreeVew widgets to a
43 TreeViewTooltips instance by calling the add_view() method. Overview
46 # 1. subclass TreeViewTooltips
47 class MyTooltips(TreeViewTooltips):
49 # 2. overriding get_tooltip()
53 # 3. create an instance
56 # 4. Build up your gtk.TreeView.
57 myview = gtk.TreeView()
58 ...# create columns, set the model, etc.
60 # 5. Add the view to the tooltips
61 mytips.add_view(myview)
63 How it works: the add_view() method connects the TreeView to the
64 "motion-notify" event with the callback set to a private method.
65 Whenever the mouse moves across the TreeView the callback will call
66 get_tooltip() with the following arguments:
68 get_tooltip(view, column, path)
72 view: the gtk.TreeView instance.
73 column: the gtk.TreeViewColumn instance that the mouse is
75 path: the path to the row that the mouse is currently over.
77 Based on whether or not column and path are checked for specific
78 values, get_tooltip can return tooltips for a cell, column, row or the
81 Column Checked Path Checked Tooltip For...
87 get_tooltip() should return None if no tooltip should be displayed.
88 Otherwise the return value will be coerced to a string (with the str()
89 builtin) and stripped; if non-empty, the result will be displayed as
90 the tooltip. By default, the tooltip popup window will be displayed
91 centered and just below the pointer and will remain shown until the
92 pointer leaves the cell (or column, or row, or view, depending on how
93 get_tooltip() is implemented).
105 if gtk
.gtk_version
< (2, 8):
108 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.''')
113 # major, minor, patch
116 class TreeViewTooltips
:
121 Initialize the tooltip. After initialization there are two
122 attributes available for advanced control:
124 window: the popup window that holds the tooltip text, an
125 instance of gtk.Window.
126 label: a gtk.Label that is packed into the window. The
127 tooltip text is set in the label with the
128 set_label() method, so the text can be plain or
131 Be default, the tooltip is enabled. See the enabled/disabled
136 self
.window
= window
= gtk
.Window(gtk
.WINDOW_POPUP
)
137 window
.set_name('gtk-tooltips')
138 window
.set_resizable(False)
139 window
.set_border_width(4)
140 window
.set_app_paintable(True)
141 window
.connect("expose-event", self
.__on
_expose
_event
)
145 self
.label
= label
= gtk
.Label()
146 label
.set_line_wrap(True)
147 label
.set_alignment(0.5, 0.5)
148 label
.set_use_markup(True)
152 # by default, the tooltip is enabled
153 self
.__enabled
= True
154 # saves the current cell
156 # the timer id for the next tooltip to be shown
158 # flag on whether the tooltip window is shown
164 self
.__enabled
= True
167 'Disable the tooltip'
169 self
.__enabled
= False
171 def __show(self
, tooltip
, x
, y
):
173 '''show the tooltip popup with the text/markup given by
176 tooltip: the text/markup for the tooltip.
177 x, y: the coord. (root window based) of the pointer.
183 self
.label
.set_label(tooltip
)
185 w
, h
= window
.size_request()
187 window
.move(*self
.location(x
,y
,w
,h
))
199 def __leave_handler(self
, view
, event
):
200 'when the pointer leaves the view, hide the tooltip'
204 def __motion_handler(self
, view
, event
):
205 'As the pointer moves across the view, show a tooltip.'
207 path
= view
.get_path_at_pos(int(event
.x
), int(event
.y
))
209 if self
.__enabled
and path
:
210 path
, col
, x
, y
= path
211 tooltip
= self
.get_tooltip(view
, col
, path
)
212 if tooltip
is not None:
213 tooltip
= str(tooltip
).strip()
215 self
.__queue
_next
((path
, col
), tooltip
,
222 def __queue_next(self
, *args
):
224 'queue next request to show a tooltip'
226 # if args is non-empty it means a request was made to show a
227 # tooltip. if empty, no request is being made, but any
228 # pending requests should be cancelled anyway.
232 # if called with args, break them out
234 cell
, tooltip
, x
, y
= args
236 # if it's the same cell as previously shown, just return
237 if self
.__save
== cell
:
240 # if we have something queued up, cancel it
242 gobject
.source_remove(self
.__next
)
245 # if there was a request...
247 # if the tooltip is already shown, show the new one
250 self
.__show
(tooltip
, x
, y
)
251 # else queue it up in 1/2 second
253 self
.__next
= gobject
.timeout_add(500, self
.__show
,
260 def __on_expose_event(self
, window
, event
):
262 # this magic is required so the window appears with a 1-pixel
263 # black border (default gtk Style). This code is a
264 # transliteration of the C implementation of gtk.Tooltips.
265 w
, h
= window
.size_request()
266 window
.style
.paint_flat_box(window
.window
, gtk
.STATE_NORMAL
,
267 gtk
.SHADOW_OUT
, None, window
,
268 'tooltip', 0, 0, w
, h
)
270 def location(self
, x
, y
, w
, h
):
272 '''Given the x,y coordinates of the pointer and the width and
273 height (w,h) demensions of the tooltip window, return the x, y
274 coordinates of the tooltip window.
276 The default location is to center the window on the pointer
277 and 4 pixels below it.
280 return x
- w
/2, y
+ 4
282 def add_view(self
, view
):
284 'add a gtk.TreeView to the tooltip'
286 assert isinstance(view
, gtk
.TreeView
), \
287 ('This handler should only be connected to '
288 'instances of gtk.TreeView')
290 view
.connect("motion-notify-event", self
.__motion
_handler
)
291 view
.connect("leave-notify-event", self
.__leave
_handler
)
293 def get_tooltip(self
, view
, column
, path
):
294 'See the module doc string for a description of this method'
296 raise NotImplemented, 'Subclass must implement get_tooltip()'
299 if __name__
== '__main__':
301 ############################################################
303 ############################################################
305 # First, subclass TreeViewTooltips
307 class DemoTips(TreeViewTooltips
):
309 def __init__(self
, customer_column
):
310 # customer_column is an instance of gtk.TreeViewColumn and
311 # is being used in the gtk.TreeView to show customer names.
312 self
.cust_col
= customer_column
314 # call base class init
315 TreeViewTooltips
.__init
__(self
)
317 def get_tooltip(self
, view
, column
, path
):
319 # we have a two column view: customer, phone; we'll make
320 # tooltips cell-based for the customer column, but generic
321 # column-based for the phone column.
324 if column
is self
.cust_col
:
326 # By checking both column and path we have a
327 # cell-based tooltip.
328 model
= view
.get_model()
329 customer
= model
[path
][2]
330 return '<big>%s %s</big>\n<i>%s</i>' % (customer
.fname
,
335 return ('<big><u>Generic Column Tooltip</u></big>\n'
336 'Unless otherwise noted, all\narea codes are 888')
338 def XX_location(self
, x
, y
, w
, h
):
339 # rename me to "location" so I override the base class
340 # method. This will demonstrate being able to change
341 # where the tooltip window popups, relative to the
344 # this will place the tooltip above and to the right
345 return x
+ 10, y
- (h
+ 10)
347 # Here's our customer
350 def __init__(self
, fname
, lname
, phone
, notes
):
356 # create a bunch of customers
358 for fname
, lname
, phone
, notes
in [
359 ('Joe', 'Schmoe', '555-1212', 'Likes to Morris dance.'),
360 ('Jane', 'Doe', '555-2323',
361 'Wonders what the hell\nMorris dancing is.'),
362 ('Phred', 'Phantastic', '900-555-1212', 'Dreams of Betty.'),
363 ('Betty', 'Boop', '555-3434', 'Dreams in b&w.'),
364 ('Red Sox', 'Fan', '555-4545',
365 "Still livin' 2004!\nEspecially after 2006.")]:
366 customers
.append(Customer(fname
, lname
, phone
, notes
))
368 # Build our model and view
369 model
= gtk
.ListStore(str, str, object)
371 model
.append(['%s %s' % (c
.fname
, c
.lname
), c
.phone
, c
])
373 view
= gtk
.TreeView(model
)
374 view
.get_selection().set_mode(gtk
.SELECTION_NONE
)
376 # two columns, name and phone
377 cell
= gtk
.CellRendererText()
378 cell
.set_property('xpad', 20)
379 namecol
= gtk
.TreeViewColumn('Customer Name', cell
, text
=0)
380 namecol
.set_min_width(200)
381 view
.append_column(namecol
)
383 cell
= gtk
.CellRendererText()
384 phonecol
= gtk
.TreeViewColumn('Phone', cell
, text
=1)
385 view
.append_column(phonecol
)
387 # finally, connect the tooltip, specifying the name column as the
388 # column we want the tooltip to popup over.
389 tips
= DemoTips(namecol
)
392 # We're going to demonstrate enable/disable. First we need a
393 # callback function to connect to the toggled signal.
395 if button
.get_active():
400 # create a checkbutton and connect our handler
401 check
= gtk
.CheckButton('Check to disable view tooltips')
402 check
.connect('toggled', toggle
)
404 # a standard gtk.Tooltips to compare to
406 tt
.set_tip(check
, ('This is a standard gtk tooltip.\n'
407 'Compare me to the tooltips above.'))
409 # create a VBox to pack the view and checkbutton
411 vbox
.pack_start(view
)
412 vbox
.pack_start(check
, False)
415 # pack the vbox into a simple dialog and run it
416 dialog
= gtk
.Dialog('TreeViewTooltips Demo')
417 close
= dialog
.add_button(gtk
.STOCK_CLOSE
, gtk
.RESPONSE_NONE
)
419 # add a tooltip for the close button
420 tt
.set_tip(close
, 'Click to end the demo.')
422 dialog
.set_default_size(400,400)
423 dialog
.vbox
.pack_start(vbox
)