Expanded my entry in contributors.txt.
[fpdb-dooglus.git] / pyfpdb / TreeViewTooltips.py
blob9b8917088e3c860407798a816385837d3d2c220d
1 # Copyright (c) 2006, Daniel J. Popowich
2 #
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
21 # SOFTWARE.
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
29 '''
30 TreeViewTooltips.py
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
44 of the steps:
46 # 1. subclass TreeViewTooltips
47 class MyTooltips(TreeViewTooltips):
49 # 2. overriding get_tooltip()
50 def get_tooltip(...):
51 ...
53 # 3. create an instance
54 mytips = MyTooltips()
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)
70 where,
72 view: the gtk.TreeView instance.
73 column: the gtk.TreeViewColumn instance that the mouse is
74 currently over.
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
79 whole view:
81 Column Checked Path Checked Tooltip For...
82 Y Y cell
83 Y N column
84 N Y row
85 N N view
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).
95 '''
98 import pygtk
99 pygtk.require('2.0')
101 import gtk
102 import gtk.gdk
103 import gobject
105 if gtk.gtk_version < (2, 8):
106 import warnings
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.''')
109 % gtk.gtk_version)
110 warnings.warn(msg)
113 # major, minor, patch
114 version = 1, 0, 0
116 class TreeViewTooltips:
118 def __init__(self):
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
129 markup text.
131 Be default, the tooltip is enabled. See the enabled/disabled
132 methods.
135 # create the window
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)
144 # create the label
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)
149 label.show()
150 window.add(label)
152 # by default, the tooltip is enabled
153 self.__enabled = True
154 # saves the current cell
155 self.__save = None
156 # the timer id for the next tooltip to be shown
157 self.__next = None
158 # flag on whether the tooltip window is shown
159 self.__shown = False
161 def enable(self):
162 'Enable the tooltip'
164 self.__enabled = True
166 def disable(self):
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
174 tooltip.
176 tooltip: the text/markup for the tooltip.
177 x, y: the coord. (root window based) of the pointer.
180 window = self.window
182 # set label
183 self.label.set_label(tooltip)
184 # resize window
185 w, h = window.size_request()
186 # move the window
187 window.move(*self.location(x,y,w,h))
188 # show it
189 window.show()
190 self.__shown = True
192 def __hide(self):
193 'hide the tooltip'
195 self.__queue_next()
196 self.window.hide()
197 self.__shown = False
199 def __leave_handler(self, view, event):
200 'when the pointer leaves the view, hide the tooltip'
202 self.__hide()
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()
214 if tooltip:
215 self.__queue_next((path, col), tooltip,
216 int(event.x_root),
217 int(event.y_root))
218 return
220 self.__hide()
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.
230 cell = None
232 # if called with args, break them out
233 if args:
234 cell, tooltip, x, y = args
236 # if it's the same cell as previously shown, just return
237 if self.__save == cell:
238 return
240 # if we have something queued up, cancel it
241 if self.__next:
242 gobject.source_remove(self.__next)
243 self.__next = None
245 # if there was a request...
246 if cell:
247 # if the tooltip is already shown, show the new one
248 # immediately
249 if self.__shown:
250 self.__show(tooltip, x, y)
251 # else queue it up in 1/2 second
252 else:
253 self.__next = gobject.timeout_add(500, self.__show,
254 tooltip, x, y)
256 # save this cell
257 self.__save = cell
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 ############################################################
302 # DEMO
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.
323 # customer
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,
331 customer.lname,
332 customer.notes)
333 # phone
334 else:
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
342 # pointer.
344 # this will place the tooltip above and to the right
345 return x + 10, y - (h + 10)
347 # Here's our customer
348 class Customer:
350 def __init__(self, fname, lname, phone, notes):
351 self.fname = fname
352 self.lname = lname
353 self.phone = phone
354 self.notes = notes
356 # create a bunch of customers
357 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&amp;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)
370 for c in customers:
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)
390 tips.add_view(view)
392 # We're going to demonstrate enable/disable. First we need a
393 # callback function to connect to the toggled signal.
394 def toggle(button):
395 if button.get_active():
396 tips.disable()
397 else:
398 tips.enable()
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
405 tt = gtk.Tooltips()
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
410 vbox = gtk.VBox()
411 vbox.pack_start(view)
412 vbox.pack_start(check, False)
413 vbox.show_all()
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)
424 dialog.run()