1 """General-purpose UI components.
2
3 Copyright 2007-2014 Research Foundation State University of New York
4 This file is part of QUB Express.
5
6 QUB Express is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 QUB Express is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License,
17 named LICENSE.txt, in the QUB Express program directory. If not, see
18 <http://www.gnu.org/licenses/>.
19
20 """
21
22 import pygtk
23 pygtk.require('2.0')
24 import gtk
25 from gtk import gdk
26 from gtk import keysyms
27 import gobject
28 import pango
29 import threading
30 import traceback
31 import re
32 import sys
33 import datetime
34 import thread
35 from math import *
36 from qubx.util_types import *
37 from qubx.accept import *
38
39
41 """
42 Shows a message, some buttons, and a checkbox "[x] don't show this message again."
43 """
44 - def __init__(self, title='', parent=None, message='', buttons=('OK', gtk.RESPONSE_ACCEPT),
45 default_response=gtk.RESPONSE_ACCEPT, dont_show_again=True,
46 dont_show_caption="Don't show this message again"):
47 """
48 @param title: title of the dialog
49 @param parent: parent gtk.Window
50 @param message: some text
51 @param buttons: list of (string_label, response)
52 @param default_response: see gtk.Dialog.set_default_response
53 @param dont_show_again: initial state of check box
54 """
55 gtk.Dialog.__init__(self, title, parent or get_active_window(), gtk.DIALOG_MODAL, buttons=None)
56 pack_label(message, self.get_content_area(), expand=True)
57 action_v = pack_item(gtk.VBox(), self.get_content_area(), expand=True)
58 vh = pack_item(gtk.HBox(), action_v)
59 for id, resp in reversed(buttons):
60 pack_button(id, vh, bind(self.response, resp), at_end=True)
61 self.set_default_response(default_response)
62
63 vh = pack_item(gtk.HBox(), action_v)
64 self.chkDontShow = pack_check(dont_show_caption, vh, active=dont_show_again)
66 response = gtk.Dialog.run(self)
67 self.hide()
68 return response, self.chkDontShow.get_active()
69
96
97
98
99 -class NumEntryDialog(gtk.Dialog):
100 """
101 Prompts the user to write or edit a string.
102 """
103 - def __init__(self, title='', parent=None, message='', value='', accept=None, format=str,
104 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
105 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT) ):
106 """
107 @param title: title of the dialog
108 @param parent: parent gtk.Window, for centering
109 @param message: label to the left of the string
110 @param value: initial value
111 @param accept: f(string) returns value or raises Exception
112 see qubx.accept for stock examples
113 @param format: either a %-style format string
114 or f(value) -> string
115 @param buttons: same as the gtk.Dialog constructor argument
116 """
117 gtk.Dialog.__init__(self, title, parent or get_active_window(), gtk.DIALOG_MODAL, buttons=buttons)
118 self.__ref = Reffer()
119 self.set_default_response(gtk.RESPONSE_ACCEPT)
120 hbox = pack_item(gtk.HBox(), self.vbox, expand=True)
121 lbl = pack_label(message, hbox, expand=True)
122 self.entry = pack_item(NumEntry(value, accept, format), hbox, expand=True)
123 self.entry.OnChange += self.__ref(self.__onChange)
124
125 - def __onChange(self, entry, val):
126 self.response(gtk.RESPONSE_ACCEPT)
127 - def __onReset(self, entry, val):
128 self.response(gtk.RESPONSE_REJECT)
130 response = gtk.Dialog.run(self)
131 self.value = self.entry.value
132 return response
133
134
135
137 """
138 Prompts the user to write or edit some values.
139
140 @ivar values: dict[key] -> value, after run()
141 """
142 - def __init__(self, title='', items=[], parent=None,
143 buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT, gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT) ):
144 """
145 @param title: title of the dialog
146 @param parent: parent gtk.Window, for centering
147 @param items: list of tuple (key, caption, value, accept, format)
148 @param buttons: same as the gtk.Dialog constructor argument
149 """
150 gtk.Dialog.__init__(self, title, parent or get_active_window(), gtk.DIALOG_MODAL, buttons=buttons)
151 self.__ref = Reffer()
152 self.set_default_response(gtk.RESPONSE_ACCEPT)
153 self.entries = {}
154 for item in items:
155 self.add_item(*item)
156 - def add_item(self, key, caption, value, accept=acceptFloat, format='%.5g'):
157 if (value is None) and (accept is None) and (format is None):
158 pack_label(caption, self.vbox, expand=True)
159 self.entries[key] = Anon(value=None)
160 else:
161 hbox = pack_item(gtk.HBox(True), self.vbox)
162 lbl = pack_label(caption, hbox, expand=True)
163 entry = pack_item(NumEntry(value, accept, format), hbox, expand=True)
164 entry.OnChange += self.__ref(bind_with_args_before(self.__onChange, key))
165 self.entries[key] = entry
167 entry.get_toplevel().child_focus(gtk.DIR_TAB_FORWARD)
174
175
176
177 MAX_EVENTS_PER_WAIT = 20
178
180 """
181 Modal dialog with progress bar and stop button; supervises a long-running computation.
182 """
185 """
186 Inside your long-task callback:
187
188 >>> func( update )
189
190 You should the update function regularly to give progress and find out if stopped:
191
192 >>> update( fraction ) -> bool should_continue
193
194 @param title: text for the title bar
195 @param func: long-running computation func(update)
196 should periodically call update( fraction )
197 and stop immediately if update returns False
198 @param finish_func called on successful completion (no args)
199 @param stopCoords: screen coordinates of the Stop button, for repositioning
200 @param parent: parent gtk.Window
201 @param maxEventsPerUpdate: process up to this many gui events each time you call update
202 """
203 gtk.Dialog.__init__(self, title, parent or get_active_window(), gtk.DIALOG_MODAL)
204 self.prog = pack_item(gtk.ProgressBar(), self.vbox, expand=True)
205 self.btnStop = pack_button('Stop', self.action_area, self.onStop)
206 self.func = func
207 self.finish_func = finish_func
208 self.maxEvents = maxEventsPerUpdate
209 self._stopped = False
210 self.stopCoords = stopCoords
211 self.realize()
213 """Shows the dialog and runs func."""
214 self.show()
215 gobject.idle_add(self.start)
216 result = gtk.Dialog.run(self)
217 if self.exc:
218 raise self.exc[0], self.exc[1], self.exc[2]
219 if result == gtk.RESPONSE_OK:
220 self.finish_func()
221 return result
223 if not (self.stopCoords is None):
224 x, y = self.stopCoords
225 xb, yb = self.btnStop.translate_coordinates(self, 0, 0)
226 self.move(x-xb, y-yb)
227
228 try:
229 self.exc = None
230 self.func(self.update)
231 except:
232 self.exc = sys.exc_info()
233 if self._stopped:
234 self.response(gtk.RESPONSE_CANCEL)
235 else:
236 self.response(gtk.RESPONSE_OK)
240 self.prog.set_fraction(max(0.0, min(1.0, fraction)))
241 for i in xrange(self.maxEvents):
242 if gtk.events_pending():
243 gtk.main_iteration(False)
244 else:
245 break
246 return not self._stopped
247
248 HARMLESS_KEYS = [keysyms.Left, keysyms.Right, keysyms.Up, keysyms.Down, keysyms.Home, keysyms.End, keysyms.Insert]
249
250 -class NumEntry(gtk.Entry):
251 """
252 Originally intended for numbers, this gtk.Entry can parse, validate and display any suitable data type.
253
254 @ivar accept: as above
255 @ivar value: the last accepted value
256 @ivar text: the last accepted string
257 @ivar cGood: gdk.Color of accepted text
258 @ivar cDiff: gdk.Color of not-yet-accepted text
259 @ivar good: whether the text is accepted (what color to use)
260
261 @ivar OnEdit: L{WeakEvent}(NumEntry, txt) user has typed, text is now txt
262 @ivar OnChange: L{WeakEvent}(NumEntry, val) text accepted; new value is val
263 @ivar OnExit: L{WeakEvent}(NumEntry) focus-out
264 @ivar OnReset: L{WeakEvent}(NumEntry, val)
265 @ivar OnReject: L{WeakEvent}(NumEntry, txt, exc) txt not accepted due to exception exc
266 """
267
268 __explore_featured = ['accept', 'format', 'value', 'text', 'cGood', 'cDiff', 'good',
269 'OnEdit', 'OnChange', 'OnExit', 'OnReset', 'OnReject',
270 'onParse']
271
272 - def __init__(self, value, accept=None, format='%s', width_chars=None):
273 """
274 @param value: initial value
275 @param accept: f(string) returns value or raises Exception
276 see qubx.accept for stock examples
277 default is str
278 @param format: either a %-style format string
279 or f(value) -> string
280 @param width_chars: shortcut to self.set_width_chars(width_chars)
281 """
282 gtk.Entry.__init__(self)
283
284 self.format = acceptFormat(format)
285 self.connect("key_press_event", self.onKey)
286 self.connect("focus_out_event", self.onExit)
287 self.OnEdit = WeakEvent()
288 self.OnChange = WeakEvent()
289 self.OnExit = WeakEvent()
290 self.OnReject = WeakEvent()
291 self.OnReset = WeakEvent()
292 self._cGood = gdk.color_parse("#000000")
293 self._cDiff = gdk.color_parse("#ff0000")
294 self._good = True
295 self._accept = accept or (isinstance(value, str) and str) or (lambda s: type(value)(eval(s)))
296 self.value = value
297 if width_chars:
298 self.set_width_chars(width_chars)
299 - def setAccept(self, accept):
300 self._accept = accept
301 self.onParse()
302 accept = property(lambda s: s._accept, setAccept)
303 - def setValue(self, value, repr=None, good=True, parse=False):
304 self._value = value
305 if repr is None:
306 self._str = self.format(value)
307 else:
308 self._str = repr
309 self.good = good
310 self.set_text(self._str)
311 if parse:
312 self.onParse()
313 value = property(lambda s: s._value, setValue)
314 text = property(lambda s: s._str)
315 - def setGoodColor(self, c):
316 self._cGood = c
317 self._good = not self._good
318 self.good = not self.good
319 cGood = property(lambda s: s._cGood, setGoodColor)
320 - def setDiffColor(self, c):
321 self._cDiff = c
322 self._good = not self._good
323 self.good = not self.good
324 cDiff = property(lambda s: s._cDiff, setDiffColor)
325 - def setGood(self, good):
326 if self._good != good:
327 self._good = good
328 self.modify_text(gtk.STATE_NORMAL, self._good and self._cGood or self._cDiff)
329 self.modify_text(gtk.STATE_SELECTED, self._good and self._cGood or self._cDiff)
330 good = property(lambda s: s._good, setGood)
331 - def onKey(self, txt, event):
332 if event.keyval == keysyms.Escape:
333 self.set_text(self._str)
334 self.select_region(0, -1)
335 self.good = True
336 self.OnReset(self, self.value)
337 elif event.keyval == keysyms.Return:
338 if self.get_text() == self._str:
339 self.OnChange(self, self.value)
340 self.good = True
341 else:
342 self.onParse()
343 elif (not (event.keyval in HARMLESS_KEYS)):
344 self.OnEdit(self, self.get_text())
345 self.good = False
346 - def onExit(self, txt, event):
347 self.onParse()
348 self.OnExit(self)
349 - def onParse(self, force=False):
350 s = self.get_text()
351 if (not force) and (s == self._str):
352 self.good = True
353 self.OnReset(self, self.value)
354 else:
355 try:
356 self.setValue(self.accept(s), s)
357 self.OnChange(self, self.value)
358 self.good = True
359 self.select_region(0, -1)
360 except Exception, e:
361 self.good = False
362 self.OnReject(self, s, e)
363
364
366 - def __init__(self, label, active):
368
369 -class CheckListEntry(gtk.Entry):
370 """
371 Text widget showing state of a list of boolean items; clicking shows a popup with checkable items.
372
373 @ivar items: list of L{CheckListEntryItem}
374 @ivar menu_items: list of additional (caption, func(item)) for the popup e.g. ("Custom...", on_custom)
375 @ivar OnToggle: L{WeakEvent}(CheckListEntry, index, active)
376 """
377
378 __explore_featured = ['items', 'menu_items', 'OnToggle', 'append', 'insert', 'remove', 'clear', 'get_active', 'set_active', 'update']
379
380 - def __init__(self):
381 gtk.Entry.__init__(self)
382 self.OnToggle = WeakEvent()
383 self.set_editable(False)
384 self.connect('button_press_event', self.__onMouseDown)
385 self.items = []
386 self.menu_items = []
387 - def append(self, label, active):
388 self.insert(len(self.items), label, active)
389 - def insert(self, index, label, active):
392 - def remove(self, index):
393 del self.items[index]
394 self.update()
396 self.items[:] = []
397 self.update()
398 - def get_active(self, index):
399 return self.items[index].active
400 - def set_active(self, index, active):
405 - def __onMouseDown(self, selfy, event):
406 menu = gtk.Menu()
407 for index, item in enumerate(self.items):
408 mi = build_menuitem(item.label, sensitive=True, item_class=gtk.CheckMenuItem)
409 mi.set_active(item.active)
410 mi.connect('toggled', self.__onToggleItem, index)
411 menu.append(mi)
412 for caption, on_click in self.menu_items:
413 menu.append(build_menuitem(caption, on_click))
414 menu.popup(None, None, None, 0, event.time)
415 - def __onToggleItem(self, mi, index):
416 active = not self.items[index].active
417 self.set_active(index, active)
418 self.OnToggle(self, index, active)
419
420 -class CustomCheckListEntry(CheckListEntry):
421 """
422 Text widget showing state of a list of boolean items; clicking shows a popup with checkable items;
423 "Custom..." bypasses the checkboxes.
424
425 @ivar OnChange: L{WeakEvent}(CustomCheckListEntry, value_str) -- checks and custom too
426 """
427
428 __explore_featured = ['OnChange', 'edit_custom', 'custom', 'custom_val']
429
430 - def __init__(self, edit_custom=lambda x: x, menu_caption='Custom...'):
431 """@param edit_custom: func(expr) -> expr; return None for no-op e.g. on click cancel"""
432 CheckListEntry.__init__(self)
433 self.__ref = Reffer()
434 self.OnChange = WeakEvent()
435 self.edit_custom = edit_custom
436 self.menu_items.append((menu_caption, self.__onClickCustom))
437 self.__custom = False
438 self.__custom_val = ""
439 custom = property(lambda self: self.__custom, doc="if it's True, ignore 'active' and use custom_val")
440 custom_val = property(lambda self: self.get_text(), lambda self, x: self.set_custom_val(x))
441 - def __onClickCustom(self, item):
442 new_val = self.edit_custom(self.get_text())
443 if not (new_val is None):
444 self.custom_val = new_val
445 - def set_custom_val(self, x):
446 self.__custom = True
447 self.__custom_val = x
448 self.set_text(x)
449 self.OnChange(self, x)
450 - def set_active(self, index, active):
451 self.__custom = False
452 CheckListEntry.set_active(self, index, active)
453 self.OnChange(self, self.get_text())
455 if not self.__custom:
456 self.set_text('; '.join(item.label for item in self.items if item.active))
457
458
460 """
461 Widget with two logarithmic sliders: coarse and fine.
462
463 @ivar octaves: coarse ranges from 1/(2^octaves) to 2^octaves
464 @ivar divs: number of sub-divisions per octave
465 @ivar zoom:
466
467 @ivar onScale: L{WeakEvent}(V2Scale, factor) dragging slider, now at factor
468 @ivar onSnap: L{WeakEvent}(V2Scale) released slider, it pops back to center
469 """
470
471 __explore_featured = ['octaves', 'divs', 'zoom', 'onScale', 'onSnap', 'maj', 'majs', 'min', 'mins', 'snapState', 'sliding', 'reverse']
472
473 - def __init__(self, octaves=4, divs=8, minor_zoom=8):
474 """
475 @param octaves: coarse ranges from 1/(2^octaves) to 2^octaves
476 @param divs: number of sub-divisions per octave
477 @param minor_zoom: fine range is 1/minor_zoom of coarse range
478 """
479 gtk.Frame.__init__(self)
480 self.set_shadow_type(gtk.SHADOW_ETCHED_OUT)
481 self.set_size_request(-1, 50)
482 self.octs = octaves
483 self.divs = divs
484 self.zoom = minor_zoom
485 self.onScale = WeakEvent()
486 self.onSnap = WeakEvent()
487
488 maxval = octaves * divs
489 h = gtk.HBox(True, 0)
490 self.maj = gtk.Adjustment( value=0, lower=-maxval, upper=maxval, step_incr=1, page_incr=divs, page_size=1 )
491 self.maj.connect("value_changed", self.onSlide, 1)
492 self.majs = gtk.VScale(self.maj)
493 self.majs.connect("button_release_event", self.onEndSlide, self.maj)
494 self.majs.set_draw_value(False)
495 h.pack_start(self.majs, True, True)
496 self.majs.show()
497 self.min = gtk.Adjustment( value=0, lower=-maxval, upper=maxval, step_incr=1, page_incr=divs, page_size=1 )
498 self.min.connect("value_changed", self.onSlide, minor_zoom)
499 self.mins = gtk.VScale(self.min)
500 self.mins.connect("button_release_event", self.onEndSlide, self.min)
501 self.mins.set_draw_value(False)
502 h.pack_start(self.mins, True, True)
503 self.mins.show()
504 self.snapState = 0
505 self.sliding = False
506 self.add(h)
507 h.show()
508
509
510
511
512
513
514
516 if self.snapState == 0:
517 self.sliding = True
518 self.onScale(self, pow(2.0, - adj.value / (self.divs * zoom)))
519 elif self.snapState == 1:
520 self.snapState = 2
521 elif self.snapState == 2:
522 adj.value = 0
523 self.snapState = 3
524 else:
525 self.onSnap(self)
526 self.snapState = 0
527 self.sliding = False
532 """If factor is modifying a negative number, you want up to mean abs(smaller)."""
533 if factor == 0.0: return 0.0
534 power = log(factor) / log(2.0)
535 return pow(2.0, - power)
536
538 """
539 Widget containing V2Scale and NumEntry, for kbd and log-slider editing of a number.
540
541 @ivar txt: the NumEntry
542 @ivar slide: the V2Scale
543 @ivar center: value corresponding to the central slider position;
544 center == txt.value except when dragging a slider
545 @ivar OnChange: L{WeakEvent}(val)
546
547 """
548
549 __explore_featured = ['slide', 'center', 'txt', 'OnChange', 'set_value']
550
552 """@param format: see L{NumEntry}"""
553 gtk.VBox.__init__(self, False, 0)
554 self.slide = V2Scale()
555 self.hOnScale = self.onScale
556 self.slide.onScale += self.hOnScale,"onScale"
557 self.hOnSnap = self.onSnap
558 self.slide.onSnap += self.hOnSnap,"onSnap"
559 self.pack_start(self.slide, True, True)
560 self.slide.show()
561 self.center = 0.0
562 self.txt = NumEntry(self.center, format=format)
563 self.txt.set_width_chars(7)
564 self.hOnNumChange = self.onNumChange
565 self.txt.OnChange += self.hOnNumChange,"onNumChange"
566 self.pack_start(self.txt, False, True)
567 self.txt.show()
568 self.OnChange = WeakEvent()
570 if not self.slide.sliding:
571 self.txt.value = value
572 self.center = value
573
575 if self.center == 0.0:
576 self.txt.value = factor - 1.0
577 elif self.center < 0:
578 self.txt.value = scale.reverse(factor) * self.center
579 else:
580 self.txt.value = factor * self.center
581 self.OnChange(self.txt.value)
583 self.center = self.txt.value
587
588
590 """
591 Horizontal logarithmish slider with steps at [s * top * 10**(p - powers) for s in steps, for p in range(powers)] + [top].
592
593 For example, steps=[1, 5], powers=3, top=10:
594 - values = [.01, .05, .1, .5, 1, 5, 10]
595
596 @ivar steps: list of stops to use at each power of 10
597 @ivar powers: number of powers of 10, in [..., -3, -2, -1] to list
598 @ivar top: top of the scale; on the order of the largest level listed; automatically rounded up to power of 10
599 @ivar values: list of numbers: all the slider stops
600 @ivar value: numerical value, which need not be in the list of values
601 @ivar OnChange: L{WeakEvent}(Pow10Slider, value) when the slider is moved
602 """
603
604 __explore_featured = ['steps', 'powers', 'top', 'values', 'value', 'OnChange']
605
606 - def __init__(self, steps=[1.0, 2.0, 3.0, 5.0, 8.0], powers=4, top=1.0):
607 n = len(steps) * powers + 1
608 self.__adj = gtk.Adjustment(value=n-1, lower=0, upper=n, step_incr=1, page_incr=len(steps), page_size=1)
609 self.__adj.connect('value_changed', self.__onSlide)
610 gtk.HScale.__init__(self, self.__adj)
611 self.set_draw_value(False)
612 self.__steps = steps
613 self.__powers = powers
614 self.__top = 10**int(ceil(log10(top)))
615 self.__value = top
616 self.__approximating = False
617 self.__setup_values()
618 self.OnChange = WeakEvent()
619 steps = property(lambda self: self.__steps, lambda self, x: self.set_steps(x))
620 powers = property(lambda self: self.__powers, lambda self, x: self.set_powers(x))
621 top = property(lambda self: self.__top, lambda self, x: self.set_top(x))
622 value = property(lambda self: self.__value, lambda self, x: self.set_value(x))
627 self.__powers = x
628 self.__setup()
640 nstep = len(self.__steps)
641 self.values = [self.__top * self.__steps[i % nstep] * 10**(i/nstep - self.__powers) for i in xrange(nstep*self.__powers+1)]
643 n = len(self.__steps) * self.__powers + 1
644 self.__adj.upper = n
645 self.__adj.page_incr=len(self.__steps)
646 self.__adj.emit('changed')
647 self.__setup_values()
648 self.__update_slider()
650 self.__approximating = True
651 self.__adj.set_value(min(((self.__value-x)**2, i) for i,x in enumerate(self.values))[1])
652 self.__approximating = False
658
660 """
661 Widget for entering/editing legitimate datetime.datetime values.
662
663 @ivar date: datetime.datetime
664 @ivar OnChange: L{WeakEvent}(DateBox, date)
665
666 """
667
668 __explore_featured = ['day', 'month', 'year', 'date', 'OnChange']
669
671 gtk.HBox.__init__(self, *args, **kw)
672 self.day = NumEntry(1, self._acceptDay, "%d")
673 self.hOnChangeDay = self._onChangeDay
674 self.day.OnChange += self.hOnChangeDay
675 self.day.set_width_chars(2)
676 self.pack_start(self.day, False, False)
677 self.day.show()
678 self.month = gtk.combo_box_new_text()
679 for m in xrange(1, 13):
680 self.month.append_text(datetime.datetime(1999, m, 1).strftime("%b"))
681 self.month.connect('changed', self._onChangeMonth)
682 self.pack_start(self.month, False, False)
683 self.month.show()
684 self.year = NumEntry(1999, int, "%d")
685 self.hOnChangeYear = self._onChangeYear
686 self.year.OnChange += self.hOnChangeYear
687 self.year.set_width_chars(4)
688 self.pack_start(self.year, False, False)
689 self.year.show()
690 self.OnChange = WeakEvent()
691 self.set_date(datetime.datetime.now())
699 date = property(lambda s: s._date, set_date)
701 self.date.replace(day=int(s))
702 return int(s)
708 if self._updating: return
709 while True:
710 try:
711 self.date = self.date.replace(month=mnu.get_active()+1)
712 self.OnChange(self, self.date)
713 return
714 except:
715 self._updating = True
716 self.date = self.date.replace(day=self.date.day-1)
717 self.day.setValue(self.date.day)
718 self._updating = False
723
724
726 """
727 A menu whose choices never change.
728
729 @ivar active_i: index of the chosen item
730 @ivar active_text: text of the chosen item
731
732 @ivar OnChange: L{WeakEvent}(StaticComboList, active_text) when a different choice is made
733 """
734
735 __explore_featured = ['active_i', 'active_text', 'OnChange', 'choices', 'indexOf', 'add_choice', 'remove_choice', 'choose']
736
738 gtk.EventBox.__init__(self)
739 self.choices = []
740 self._box = gtk.combo_box_new_text()
741 self.add(self._box)
742 self._box.show()
743 self.indexOf = {}
744 self._count = 0
745 for i, choice in enumerate(choices):
746 self.add_choice(choice)
747 if not self._count:
748 self._active_text = ''
749 self._active_i = -1
750 self._changed_handler = self._box.connect('changed', self.combo_changed)
751 self.OnChange = WeakEvent()
753 self.choices.append(choice)
754 self._box.append_text(choice)
755 self.indexOf[choice] = self._count
756 self._count += 1
757 if self._count == 1:
758 self._box.set_active(0)
759 self._active_text = choice
760 self._active_i = 0
762 i = self.choices.index(choice)
763 if self._active_i == i:
764 if (i+1) < self._count:
765 self.active_i = i+1
766 else:
767 self._active_i = i-1
768 self.OnChange(self._box, self._active_text)
769 self.choices.remove(choice)
770 self._count -= 1
771 for j in xrange(i, self._count):
772 self.indexOf[self.choices[j]] = j
773 self._box.remove_text(i)
774 active_i = property(lambda s: s._active_i, lambda s, x: s.set_active_i(x))
775 active_text = property(lambda s: s._active_text, lambda s, x: s.choose(x))
781 if 0 <= x < len(self.indexOf):
782 self._box.handler_block(self._changed_handler)
783 self._box.set_active(x)
784 self._active_i = x
785 self._active_text = self.choices[x]
786 self._box.handler_unblock(self._changed_handler)
787 else:
788 self._active_i = -1
789 self._active_text = ""
798
800 """
801 A menu which is re-built each time it drops down.
802
803 @ivar active_i: index of the chosen item
804 @ivar active_text: text of the chosen item
805
806 @ivar OnPopulate: L{WeakEvent}(add) when dropping down; call add(item_text) for each desired item
807 @ivar OnChanged: L{WeakEvent}(DynamicComboBox, active_text) when a different choice is made
808 """
809
810 __explore_featured = ['active_i', 'active_text', 'OnPopulate', 'OnChanged', 'indexOf', 'choose', 'repopulate']
811
813 gtk.EventBox.__init__(self)
814 self._box = gtk.combo_box_new_text()
815 self.add(self._box)
816 self._box.show()
817 self._changed_handler = self._box.connect('changed', self.combo_changed)
818 self.OnPopulate = WeakEvent()
819 self.OnChanged = WeakEvent()
820 self._count = 0
821 self._active_text = ''
822 self._active_i = -1
823 self.indexOf = {}
824 self.connect('enter-notify-event', self.enter_notify_event)
825 active_i = property(lambda s: s._active_i)
826 active_text = property(lambda s: s._active_text, lambda s, x: s.choose(x))
827 - def _add(self, text):
828 self._box.append_text(text)
829 self._count += 1
830 if text == self._active_text:
831 self._box.set_active(self._count - 1)
832 self.indexOf[text] = self._count - 1
834 """Selects the item with txt, adding it if necessary."""
835 self._box.handler_block(self._changed_handler)
836 if not self.indexOf.has_key(txt):
837 self._add(txt)
838 self._active_i = self.indexOf[txt]
839 self._active_text = txt
840 self._box.set_active(self._active_i)
841 self._box.handler_unblock(self._changed_handler)
845 self._active_text = self._box.get_active_text()
846 self._box.handler_block(self._changed_handler)
847 for i in xrange(self._count):
848 self._box.remove_text(0)
849 self._count = 0
850 self.indexOf = {}
851 self.OnPopulate(self._add)
852 self._box.handler_unblock(self._changed_handler)
854 active_i = combo.get_active()
855 if active_i < 0:
856 combo.set_active(self._active_i)
857 else:
858 self._active_i = active_i
859 self._active_text = combo.get_active_text()
860 self.OnChanged(combo, self._active_text)
861
863 """
864 Like L{NumEntry}, with drop-down list of suggestions.
865
866 Properties:
867 @ivar accept: as above
868 @ivar value: the last accepted value
869 @ivar text: the last accepted string
870 @ivar cGood: gdk.Color of accepted text
871 @ivar cDiff: gdk.Color of not-yet-accepted text
872 @ivar good: whether the text is accepted (what color to use)
873
874 @ivar OnEdit: L{WeakEvent}(combo, txt) user has typed, text is now txt
875 @ivar OnChange: L{WeakEvent}(combo, val) text accepted; new value is val
876 @ivar OnExit: L{WeakEvent}(combo) focus-out
877 @ivar OnReject: L{WeakEvent}(combo, txt, exc) txt not accepted due to exception exc
878 @ivar OnPopup: L{WeakEvent}(combo, suggest) showing menu, suggest('menu item')
879 @ivar OnSuggest: L{WeakEvent}(combo, item_text)
880 """
881
882 __explore_featured = ['accept', 'format', 'value', 'text', 'cGood', 'cDiff', 'good',
883 'OnEdit', 'OnChange', 'OnExit', 'OnReject', 'OnPopup', 'OnSuggest',
884 'repopulate', 'onParse']
885
886 - def __init__(self, value, accept=None, format='%s'):
887 """
888 @param value: initial value
889 @param accept: f(string) returns value or raises Exception;
890 see qubx.accept for stock examples;
891 default is str
892 @param format: either a %-style format string
893 or f(value) -> string
894 """
895 gtk.EventBox.__init__(self)
896 self._box = gtk.combo_box_entry_new_text()
897 self.add(self._box)
898 self._box.show()
899 self._changed_handler = self._box.connect('changed', self.combo_changed)
900 self._box.connect('notify::popup-shown', self._onPopup)
901 self._box.set_property('button-sensitivity', gtk.SENSITIVITY_ON)
902 self._entry = self._box.child
903 self.format = acceptFormat(format)
904 self._entry.connect("key_press_event", self.onKey)
905 self._entry.connect("focus_out_event", self.onExit)
906 self.OnEdit = WeakEvent()
907 self.OnChange = WeakEvent()
908 self.OnExit = WeakEvent()
909 self.OnReject = WeakEvent()
910 self.OnReset = WeakEvent()
911 self.OnPopup = WeakEvent()
912 self.OnSuggest = WeakEvent()
913 self._cGood = gdk.color_parse("#000000")
914 self._cDiff = gdk.color_parse("#ff0000")
915 self._good = True
916 self._accept = accept or (isinstance(value, str) and str) or (lambda s: type(value)(eval(s)))
917 self.value = value
918 - def _add(self, text):
919 self._box.append_text(text)
921 self._prePopText = self._entry.get_text()
922 if self._box.get_property("popup-shown"):
923 self.repopulate()
925 self._box.handler_block(self._changed_handler)
926 for i in xrange(len(self._box.get_model())):
927 self._box.remove_text(0)
928 self.OnPopup(self, self._add)
929 self._box.handler_unblock(self._changed_handler)
944 accept = property(lambda s: s._accept, setAccept)
945 - def setValue(self, value, repr=None, good=True):
953 value = property(lambda s: s._value, setValue)
954 text = property(lambda s: s._str)
956 self._cGood = c
957 self._good = not self._good
958 self.good = not self.good
959 cGood = property(lambda s: s._cGood, setGoodColor)
961 self._cDiff = c
962 self._good = not self._good
963 self.good = not self.good
964 cDiff = property(lambda s: s._cDiff, setDiffColor)
966 if self._good != good:
967 self._good = good
968 self._entry.modify_text(gtk.STATE_NORMAL, self._good and self._cGood or self._cDiff)
969 self._entry.modify_text(gtk.STATE_SELECTED, self._good and self._cGood or self._cDiff)
970 good = property(lambda s: s._good, setGood)
971 - def onKey(self, txt, event):
986 - def onExit(self, txt, event):
987 self.onParse()
988 self.OnExit(self)
990 s = self._entry.get_text()
991 if s == self._str:
992 self.good = True
993 self.OnReset(self, self.value)
994 else:
995 try:
996 self.setValue(self.accept(s), s)
997 self.OnChange(self, self.value)
998 self.good = True
999 self._entry.select_region(0, -1)
1000 except Exception, e:
1001 self.OnReject(self, s, e)
1002
1003
1004
1006 """
1007 A gtk.TreeView specialized as a single-select single-column list.
1008
1009 @ivar popup: a gtk.Menu if they right-click, default None
1010 @ivar model: gtk.ListStore
1011 @ivar index: (unsorted) index of selected item
1012 @ivar active: text of the selected item, or None
1013
1014 @ivar OnSelect: L{WeakEvent}(SimpleList, ix) selected item graphically changed to ix'th
1015 @ivar OnDblClick: L{WeakEvent}(SimpleList, ix) ix'th item was double-clicked (or <enter> key)
1016 """
1017
1018 __explore_featured = ['popup', 'model', 'index', 'active', 'OnSelect', 'OnDblClick',
1019 'get_selection', 'set_model', '_model', '_smodel', '_popup', 'indexOf',
1020 'clear', 'append', 'insert', 'edit', 'remove', 'select']
1021
1022 - def __init__(self, header=None, sortable=True):
1023 """
1024 @param header: caption for the top row
1025 @param sortable: default True: the user can click header to sort
1026 """
1027 gtk.TreeView.__init__(self)
1028 self.set_headers_visible(bool(header))
1029 self.get_selection().set_mode(gtk.SELECTION_SINGLE)
1030 self._index = -1
1031 self.OnSelect = WeakEvent()
1032 self.OnDblClick = WeakEvent()
1033 self.connect("cursor_changed", self._onCursorChanged)
1034 self.connect("row_activated", self._onRowActivated)
1035 self.connect('button_press_event', self._onButtonPress)
1036 self._popup = None
1037 self._model = gtk.ListStore(str)
1038 self._smodel = gtk.TreeModelSort(self._model)
1039 self.set_model(self._smodel)
1040 renderer = gtk.CellRendererText()
1041 column = gtk.TreeViewColumn(header or "", renderer, text=0)
1042 if sortable:
1043 column.set_sort_column_id(0)
1044 self._smodel.set_sort_column_id(0, gtk.SORT_ASCENDING)
1045 self.append_column(column)
1046 popup = property(lambda s: s._popup, lambda s,x: s._setPopup(x))
1049 model = property(lambda s: s._model)
1050 index = property(lambda s: s._index, lambda s,x: s._setIndex(x))
1052 try:
1053 if ix >= 0:
1054 self.set_cursor(self._smodel.convert_child_path_to_path((ix,)))
1055 self._index = ix
1056
1057 if ix >= 0:
1058 self.get_selection().select_path(self._smodel.convert_child_path_to_path((ix,)))
1059 else:
1060 self.get_selection().unselect_all()
1061 except:
1062 traceback.print_exc()
1063 active = property(lambda s: s.getActive(), lambda s,x: s.setActive(x))
1065 if self._index >= 0:
1066 return self._model.get(self._model.get_iter((self._index,)), 0)[0]
1067 else:
1068 return None
1072 """Returns the index of the first item with text x"""
1073 iter = self._model.get_iter_first()
1074 i = 0
1075 while iter:
1076 if self._model.get(iter, 0)[0] == x:
1077 return i
1078 iter = self._model.iter_next(iter)
1079 i += 1
1080 return -1
1082 """Removes all items from the list."""
1083 self._model.clear()
1084 self.index = -1
1085 self.OnSelect(self, self.index)
1087 """Adds x at the end of the list."""
1088 self._model.append((x,))
1091 - def edit(self, index, newtext):
1092 """Replaces the text at index with newtext."""
1093 self._model.set(self._model.get_iter((index,)), 0, newtext)
1102 - def select(self, i, s=True):
1103 """Selects the item at index i, or unselects if s==False."""
1104 path = self._smodel.convert_child_path_to_path((i,))
1105 if s:
1106 self.get_selection().select_path(path)
1107 else:
1108 self.get_selection().unselect_path(path)
1110 sel = self.get_selection()
1111 smodel, iter = sel.get_selected()
1112 if iter:
1113 ix = smodel.convert_path_to_child_path(smodel.get_path(iter))[0]
1114 if ix != self._index:
1115 self._index = ix
1116 self.OnSelect(self, self._index)
1117 else:
1118 if self._index >= 0:
1119 self._index = -1
1120 self.OnSelect(self, -1)
1122 path = self._smodel.convert_path_to_child_path(spath)
1123 if self._index != path[0]:
1124 self._index = path[0]
1125 self.OnSelect(self, self._index)
1126 self.OnDblClick(self, self._index)
1142
1144 """
1145 A gtk.TreeView specialized as a two-column display: checkbox and label
1146
1147 @ivar model: gtk.ListStore
1148 @ivar OnClick: L{WeakEvent}(index)
1149 @ivar OnToggle: L{WeakEvent}(index, active)
1150
1151 """
1152
1153 __explore_featured = ['model', 'OnClick', 'OnToggle', '_index',
1154 'clear', 'append', 'insert', 'edit', 'remove', 'get_active', 'set_active']
1155
1157 """
1158 @param headers: list of captions for the top row
1159 """
1160 gtk.TreeView.__init__(self)
1161 self.set_headers_visible(bool(headers))
1162 self.get_selection().set_mode(gtk.SELECTION_SINGLE)
1163 self._index = -1
1164 self._model = gtk.ListStore(bool, str)
1165 self.set_model(self._model)
1166 self.connect("button_press_event", self.__onButtonPress)
1167 renderer = gtk.CellRendererToggle()
1168 renderer.set_property('activatable', True)
1169 renderer.connect( 'toggled', self.__toggled)
1170 column = gtk.TreeViewColumn(headers and headers[0] or "", renderer)
1171 column.add_attribute( renderer, "active", 0)
1172 self.append_column(column)
1173 self.colCheck = column
1174 renderer = gtk.CellRendererText()
1175 column = gtk.TreeViewColumn((len(headers)>1) and headers[1] or "", renderer, text=1)
1176 self.append_column(column)
1177 self.colNames = column
1178 self.OnToggle = WeakEvent()
1179 self.OnClick = WeakEvent()
1180 model = property(lambda s: s._model, lambda s, x: s._set_model(x))
1185 """Removes all items from the list."""
1186 self._model.clear()
1188 """Adds x at the end of the list."""
1189 self._model.append(item)
1192 - def edit(self, index, newtext):
1193 """Replaces the text at index with newtext."""
1194 self._model.set(self._model.get_iter((index,)), 1, newtext)
1196 """Removes the item at index."""
1197 self._model.remove(self._model.get_iter((index,)))
1199 return self._model[index][0]
1201 self._model[index][0] = x
1219
1220
1222 """
1223 A gtk.TreeView specialized as a two-column display: checkbox and label, plus columns with up and down arrows to modify order
1224
1225 @ivar model: gtk.ListStore
1226 @ivar OnClick: L{WeakEvent}(index)
1227 @ivar OnToggle: L{WeakEvent}(index, active)
1228 @ivar OnSwap: L{WeakEvent}(index0, index1)
1229
1230 """
1231
1232 __explore_featured = ['model', 'OnClick', 'OnToggle', 'OnSwap', '_index',
1233 'clear', 'append', 'insert', 'edit', 'remove', 'get_active', 'set_active']
1234
1236 """
1237 @param headers: list of captions for the top row
1238 """
1239 gtk.TreeView.__init__(self)
1240 self.set_headers_visible(bool(headers))
1241 self.get_selection().set_mode(gtk.SELECTION_SINGLE)
1242 self._index = -1
1243 self._model = gtk.ListStore(bool, str)
1244 self.set_model(self._model)
1245 self.connect("button_press_event", self.__onButtonPress)
1246 renderer = gtk.CellRendererToggle()
1247 renderer.set_property('activatable', True)
1248 renderer.connect( 'toggled', self.__toggled)
1249 column = gtk.TreeViewColumn(headers and headers[0] or "", renderer)
1250 column.add_attribute( renderer, "active", 0)
1251 self.append_column(column)
1252 self.colCheck = column
1253 renderer = gtk.CellRendererText()
1254 column = gtk.TreeViewColumn((len(headers)>1) and headers[1] or "", renderer, text=1)
1255 column.set_expand(True)
1256 self.append_column(column)
1257 self.colNames = column
1258 renderer = gtk.CellRendererPixbuf()
1259 renderer.set_property('stock-id', gtk.STOCK_GO_UP)
1260 column = gtk.TreeViewColumn("", renderer)
1261 column.set_max_width(32)
1262 self.append_column(column)
1263 self.colUp = column
1264 renderer = gtk.CellRendererPixbuf()
1265 renderer.set_property('stock-id', gtk.STOCK_GO_DOWN)
1266 column = gtk.TreeViewColumn("", renderer)
1267 column.set_max_width(32)
1268 self.append_column(column)
1269 self.colDown = column
1270 self.append_column(column)
1271 self.OnToggle = WeakEvent()
1272 self.OnClick = WeakEvent()
1273 self.OnSwap = WeakEvent()
1274 model = property(lambda s: s._model, lambda s, x: s._set_model(x))
1279 """Removes all items from the list."""
1280 self._model.clear()
1282 """Adds x at the end of the list."""
1283 self._model.append(item)
1286 - def edit(self, index, newtext):
1287 """Replaces the text at index with newtext."""
1288 self._model.set(self._model.get_iter((index,)), 1, newtext)
1290 """Removes the item at index."""
1291 self._model.remove(self._model.get_iter((index,)))
1293 return self._model[index][0]
1295 self._model[index][0] = x
1325
1326
1328 """
1329 A gtk.TreeView specialized as a two-column display: count and label
1330
1331 @ivar model: gtk.ListStore
1332
1333 """
1334
1335 __explore_featured = ['model', 'colNames', 'OnToggle', 'OnClick',
1336 'clear', 'append', 'insert', 'edit', 'remove', 'get_count', 'set_count', 'get_entries']
1337
1339 """
1340 @param headers: list of captions for the top row
1341 """
1342 gtk.TreeView.__init__(self)
1343 self.__ref = Reffer()
1344 self.set_headers_visible(bool(headers))
1345 self.get_selection().set_mode(gtk.SELECTION_SINGLE)
1346 self._model = gtk.ListStore(int, str)
1347 self.set_model(self._model)
1348 renderer = gtk.CellRendererText()
1349 renderer.set_property('editable', True)
1350 renderer.connect('edited', self._onEdit)
1351 column = gtk.TreeViewColumn(headers and headers[0] or "", renderer, text=0)
1352 self.append_column(column)
1353 self.colCount = column
1354 renderer = gtk.CellRendererText()
1355 column = gtk.TreeViewColumn((len(headers)>1) and headers[1] or "", renderer, text=1)
1356 self.append_column(column)
1357 self.colNames = column
1358 self.OnToggle = WeakEvent()
1359 self.OnClick = WeakEvent()
1360 model = property(lambda s: s._model)
1362 """Removes all items from the list."""
1363 self._model.clear()
1364 - def append(self, count, lbl):
1365 """Adds x at the end of the list."""
1366 self._model.append((count, lbl))
1367 - def insert(self, i, count, lbl):
1369 - def edit(self, index, newtext):
1370 """Replaces the text at index with newtext."""
1371 self._model.set(self._model.get_iter((index,)), 1, newtext)
1373 """Removes the item at index."""
1374 self._model.remove(self._model.get_iter((index,)))
1376 return self._model[index][0]
1378 self._model[index][0] = x
1379 - def _onEdit(self, cell, path_string, new_text):
1380 i = int(path_string)
1381 try:
1382 self._model[i][0] = acceptIntGreaterThanOrEqualTo(0)(new_text)
1383 except:
1384 pass
1386 """Returns list of (count, label)."""
1387 return [(self._model[i][0], self._model[i][1]) for i in xrange(len(self._model))]
1388
1390 """
1391 A gtk.TreeView specialized as a multi-select, single-column list.
1392
1393 @ivar model: gtk.ListStore
1394 @ivar count: len(model)
1395 """
1396
1397 __explore_featured = ['model', 'count', '_model', '_smodel', 'sortedToUn', 'unToSorted', 'indexOf',
1398 'clear', 'append', 'selected', 'select']
1399
1400 - def __init__(self, header=None, sortable=True):
1401 """
1402 @param header: caption for the top row
1403 @param sortable: default True: the user can click header to sort
1404 """
1405 gtk.TreeView.__init__(self)
1406 self.set_rubber_banding(True)
1407 self.set_headers_visible(bool(header))
1408 self.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
1409 self._count = 0
1410 self._model = gtk.ListStore(str)
1411 self._smodel = gtk.TreeModelSort(self._model)
1412 self.set_model(self._smodel)
1413 renderer = gtk.CellRendererText()
1414 column = gtk.TreeViewColumn(header or "", renderer, text=0)
1415 if sortable:
1416 column.set_sort_column_id(0)
1417 self._smodel.set_sort_column_id(0, gtk.SORT_ASCENDING)
1418 self.append_column(column)
1419 model = property(lambda s: s._model)
1420 count = property(lambda s: s._count)
1422 """Returns the unsorted index of the sorted ix'th item."""
1423 return self._smodel.convert_path_to_child_path((ix,))[0]
1425 """Returns the sorted index of the unsorted ix'th item."""
1426 return self._smodel.convert_child_path_to_path((ix,))[0]
1428 self._index = ix
1429
1430 if ix >= 0:
1431 self.get_selection().select_path((self.unToSorted(ix),))
1432 else:
1433 self.get_selection().unselect_all()
1435 """Returns the unsorted index of the first item with text x."""
1436 iter = self._model.get_iter_first()
1437 i = 0
1438 while iter:
1439 if self._model.get(iter, 0)[0] == x:
1440 return i
1441 iter = self._model.iter_next(iter)
1442 i += 1
1443 return -1
1445 """Removes all items from the list."""
1446 self._model.clear()
1447 self._count = 0
1448 - def append(self, x, s=True):
1449 """Adds item x to the end of the list; selects it if s == True."""
1450 self._model.append((x,))
1451 self._count += 1
1452 self.select(self._count-1, s)
1454 """Returns True if unsorted item i if selected."""
1455 return self.get_selection().path_is_selected((self.unToSorted(i),))
1456 - def select(self, i, s=True):
1457 """Selects unsorted item i; or unselects if s == False."""
1458 if s:
1459 self.get_selection().select_path((self.unToSorted(i),))
1460 else:
1461 self.get_selection().unselect_path((self.unToSorted(i),))
1462
1464 """Returns a gtk.TreeModel sort_func which sorts first by column i, then column j, ..."""
1465 def sort_cmp(model, a, b):
1466 for c in cols:
1467 ac, bc = [model.get(iter, c)[0] for iter in [a, b]]
1468 if ac < bc:
1469 return -1
1470 elif ac > bc:
1471 return 1
1472 return 0
1473 return sort_cmp
1474
1476 """
1477 Specialization of a flat gtk.TreeView that can dynamically add columns for text or number.
1478
1479 @ivar popup: gtk.Menu for right-click, default None
1480 @ivar model: gtk.ListStore
1481 @ivar index: unsorted index of selected row
1482 @ivar count: len(model)
1483
1484 @ivar OnDblClick: L{WeakEvent}(DictTable, ix)
1485 """
1486
1487 __explore_featured = ['popup', 'model', 'index', 'count', '_model', '_smodel', 'columns',
1488 '_col_of_name', '_name_of_col', '_type_of_col', '_used_col', '_spare_str', '_spare_float',
1489 'sortedToUn', 'unToSorted', 'clear', 'append', 'remove', 'selected', 'select',
1490 'select_all', 'unselect_all', 'copy']
1491
1492 - def __init__(self, fixed, string_cap=10, float_cap=256):
1493 """
1494 @param fixed: list of (name, type) of initially visible columns
1495 @param string_cap: max number of dynamically added string columns
1496 @param float_cap: max number of dynamically added number columns
1497 """
1498
1499 gtk.TreeView.__init__(self)
1500 self.set_rubber_banding(True)
1501 self.set_headers_visible(True)
1502 self.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
1503 self._count = 0
1504 self._index = -1
1505 self.OnDblClick = WeakEvent()
1506 self.connect("row_activated", self._onRowActivated)
1507 self.connect('button_press_event', self._onButtonPress)
1508 self.connect('key_press_event', self._onKeyPress)
1509 self._popup = None
1510 self._col_of_name = {}
1511 self._name_of_col = {}
1512 self._type_of_col = {}
1513 self._used_col = []
1514 self._spare_str = [x for x in xrange(len(fixed), len(fixed)+string_cap)]
1515 self._spare_str.reverse()
1516 self._spare_float = [x for x in xrange(len(fixed)+string_cap, len(fixed)+string_cap+float_cap)]
1517 self._spare_float.reverse()
1518 types = [typ for nm, typ in fixed] + string_cap*[str] + float_cap*[float]
1519 self._model = gtk.ListStore(*types)
1520 self._smodel = gtk.TreeModelSort(self._model)
1521 self.set_model(self._smodel)
1522 self.columns = []
1523 for i, fix in enumerate(fixed):
1524 nm, typ = fix
1525 renderer = gtk.CellRendererText()
1526 column = gtk.TreeViewColumn(nm, renderer, text=i)
1527 column.set_sort_column_id(i)
1528 self.append_column(column)
1529 self._col_of_name[nm] = i
1530 self._name_of_col[i] = nm
1531 self._type_of_col[i] = typ
1532 self._used_col.append(i)
1533 self.columns.append(column)
1534 self._smodel.set_sort_column_id(0, gtk.SORT_ASCENDING)
1535 popup = property(lambda s: s._popup, lambda s,x: s._setPopup(x))
1536 model = property(lambda s: s._model)
1537 index = property(lambda s: s._getIndex(), lambda s,x: s._setIndex(x))
1538 count = property(lambda s: s._count)
1540 """Returns the unsorted index of the ix'th sorted row."""
1541 return self._smodel.convert_path_to_child_path((ix,))[0]
1543 """Returns the sorted index of the ix'th unsorted row."""
1544 return self._smodel.convert_child_path_to_path((ix,))[0]
1548 if self._index >= 0 and self.selected(self._index):
1549 return self._index
1550 for i in xrange(self._count):
1551 if self.selected(i):
1552 self._index = i
1553 return i
1554 self._index = -1
1555 return -1
1557 self._index = ix
1558
1559 if ix >= 0:
1560 self.get_selection().select_path(self._smodel.convert_child_path_to_path((ix,)))
1561 else:
1562 self.get_selection().unselect_all()
1564 """Removes all rows."""
1565 self._model.clear()
1566 self._count = 0
1568 """Adds a row described by dictionary d, where
1569 d[column_name] = column_value."""
1570 entry = [None] * (len(self._used_col) + len(self._spare_str) + len(self._spare_float))
1571 for i in self._used_col:
1572 entry[i] = self._type_of_col[i]()
1573 for i in self._spare_str:
1574 entry[i] = ""
1575 for i in self._spare_float:
1576 entry[i] = 0.0
1577 for nm in sorted(d.iterkeys()):
1578 val = d[nm]
1579 try:
1580 entry[self._col_of_name[nm]] = val
1581 except:
1582 if isinstance(val, str):
1583 typ = str
1584 ix = self._spare_str.pop()
1585 else:
1586 typ = float
1587 ix = self._spare_float.pop()
1588 renderer = gtk.CellRendererText()
1589 column = gtk.TreeViewColumn(nm, renderer, text=ix)
1590 column.set_sort_column_id(ix)
1591 self.append_column(column)
1592 self._col_of_name[nm] = ix
1593 self._name_of_col[ix] = nm
1594 self._type_of_col[ix] = typ
1595 self._used_col.append(ix)
1596 self.columns.append(column)
1597 entry[ix] = val
1598 self._model.append(entry)
1599 self._count += 1
1601 """Removes the item with unsorted index i."""
1602 self._model.remove(self._model.get_iter((i,)))
1603 self._count -= 1
1605 """Returns True if the i'th unsorted row is selected."""
1606 return self.get_selection().path_is_selected(self._smodel.convert_child_path_to_path((i,)))
1607 - def select(self, i, s=True):
1608 """Selects the i'th unsorted row, or unselect if s == False."""
1609 path = self._smodel.convert_child_path_to_path((i,))
1610 if s:
1611 self.get_selection().select_path(path)
1612 else:
1613 self.get_selection().unselect_path(path)
1615 """Selects all rows."""
1616 self.get_selection().select_all()
1618 """Unselects all rows."""
1619 self.get_selection().unselect_all()
1621 """Copies all non-empty columns of all selected rows as tab-selected text, to clipboard."""
1622 sel = [i for i in xrange(self._count) if self.selected(i)]
1623
1624
1625 col = [i for i in self._used_col]
1626
1627 lines = []
1628 toks = []
1629 for c in col:
1630 toks.append(self._name_of_col[c])
1631 lines.append(toks)
1632 for i in xrange(self._count):
1633 r = self.sortedToUn(i)
1634 if r in sel:
1635 toks = []
1636 for c in col:
1637 toks.append(str(self._model.get(self._model.get_iter((r)), c)[0]))
1638 lines.append(toks)
1639 lines.append([])
1640 text = '\n'.join(['\t'.join(toks) for toks in lines])
1641 clipboard = gtk.clipboard_get(gdk.SELECTION_CLIPBOARD)
1642 clipboard.set_text(text)
1644 path = self._smodel.convert_path_to_child_path(spath)
1645 if self._index != path[0]:
1646 self._index = path[0]
1647 self.OnDblClick(self, self._index)
1664
1665 if event.state & gdk.CONTROL_MASK:
1666 if event.keyval == ord('c') or event.keyval == ord('C'):
1667 self.copy()
1668
1669
1671 r = c = 1
1672 w_one, h_one = w, h
1673 while (r*c) < n:
1674 if aspect < (w_one*1.0/h_one):
1675 c += 1
1676 w_one = max(1, w/c)
1677 else:
1678 r += 1
1679 h_one = max(1, h/r)
1680 while (r > 1) and ((r-1)*c >= n):
1681 r -= 1
1682 return r, c
1683
1685 __explore_featured = ['aspect', 'resize_aspect', 'pack_aspect', 'remove']
1687 gtk.Table.__init__(self, 1, 1, homogeneous=True)
1688 self.connect('size_allocate', self.__on_size_allocate)
1689 self.aspect = aspect
1690 self.__children = []
1691 self.__size = (1, 1)
1692 self.__dim = (1, 1)
1696 w = width or self.allocation.width or 1
1697 h = height or self.allocation.height or 1
1698 n = nchild or len(self.__children)
1699 r, c = aspect_dimensions(w, h, n, self.aspect)
1700 if (r, c) != self.__dim:
1701 self.__dim = (r, c)
1702 for child in self.__children:
1703 gtk.Table.remove(self, child)
1704 self.resize(r, c)
1705 i = j = 0
1706 for child in self.__children:
1707 self.attach(child, j, j+1, i, i+1)
1708 j += 1
1709 if j == c:
1710 j = 0
1711 i += 1
1713 self.resize_aspect(nchild=len(self.__children)+1)
1714 ix = len(self.__children)
1715 if not (index is None):
1716 ix = index
1717 i = ix / self.__dim[1]
1718 j = ix % self.__dim[1]
1719 self.__children.insert(ix, child)
1720 self.attach(child, j, j+1, i, i+1)
1721 - def remove(self, child, temporary=False):
1728
1729
1731 """
1732 Widget combining H scrollbar with zoom in/out buttons.
1733
1734 @ivar range: tuple (lo, hi) the portion of (0, 100) that should be shown
1735 @ivar zoom: percent magnification, where 100% means all-on-screen
1736 @ivar adjustment: lower = range[0], upper = range[1]; connect to 'changed' and 'value_changed'
1737 """
1738
1739 __explore_featured = ['range', 'zoom', 'adjustment', 'txtPct', 'btnMinus', 'btnPlus', 'scroll']
1740
1742 """
1743 @param zoom_min: smallest allowed zoom value
1744 """
1745 gtk.HBox.__init__(self)
1746 self.zoom_min = zoom_min
1747 self._range = (0.0, 100.0)
1748 self._zoom = 100.0
1749 self.r = Reffer()
1750 dist = self._range[1] - self._range[0]
1751 self.adjustment = gtk.Adjustment( value=0, lower=self._range[0], upper=self._range[1],
1752 step_incr=dist/1000, page_incr=dist, page_size=dist )
1753 self.txtPct = pack_item(NumEntry(100.0, self._acceptPct, "%.0f", width_chars=4), self)
1754 self.txtPct.OnChange += self.r(self._onChangePct)
1755 self.btnMinus = pack_button('-', self, self._onPressMinus)
1756 self.btnPlus = pack_button('+', self, self._onPressPlus)
1757 self.scroll = pack_item(gtk.HScrollbar(self.adjustment), self, expand=True)
1758 self._setting = False
1760 self._range = r
1761 self._recalc()
1763 self._zoom = self._acceptPct(z)
1764 self._setting = True
1765 self.txtPct.setValue(self._zoom)
1766 self._setting = False
1767 self._recalc()
1768 range = property(lambda s: s._range, set_range)
1769 zoom = property(lambda s: s._zoom, set_zoom)
1771 lo, hi = self._range
1772 d = hi - lo
1773 z = self._zoom
1774 a = self.adjustment
1775 a.lower, a.upper = lo, hi
1776 a.step_increment = d/(10*z)
1777 a.page_increment = d*100/z
1778 a.page_size = d*100/z
1779 a.emit('changed')
1780 a.set_value(min(a.value, a.upper-a.page_size))
1782 pct = float(pctStr)
1783 if pct < self.zoom_min:
1784 raise ValueError('zoom percent must be >= %f' % self._zoom_min)
1785 return pct
1787 if self._setting: return
1788 self.zoom = pct
1790 z = self.zoom / 2
1791 if z >= self.zoom_min:
1792 self.zoom = z
1795
1796
1798 cr.save()
1799 rad = max(1e-1, min(w,h)/2.0)
1800
1801 side = 2*rad * sin(pi/3)
1802 corner_y = side/2
1803
1804 corner_theta = pi - asin(corner_y/rad)
1805 corner_x = cos(corner_theta) * rad
1806 cr.translate(w/2.0, h/2.0)
1807 cr.rotate(- angle)
1808 if (not (goal is None)) and (goal == angle):
1809 cr.set_source_rgb(*color)
1810 else:
1811 cr.set_source_rgba(*(list(color)+[.7]))
1812 cr.move_to(rad, 0)
1813 cr.line_to(corner_x, corner_y)
1814 cr.line_to(corner_x, -corner_y)
1815 cr.fill_preserve()
1816 cr.set_source_rgb(0,0,0)
1817 cr.set_line_width(0.5)
1818 cr.stroke()
1819 cr.restore()
1820
1822 rad = min(w, h)/2.0
1823 cr.save()
1824 cr.translate(w/2.0, h/2.0)
1825 cr.rotate(- point_theta)
1826 if draw_circle:
1827 cr.arc(0, 0, rad, 0, 2*pi)
1828 cr.stroke()
1829 ys = shaft_w_rads * rad / 2
1830 rad -= ys
1831 cr.move_to(rad, 0)
1832 xb = rad*cos(back_dtheta)
1833 yb = rad*sin(back_dtheta)
1834 cr.line_to(xb, yb)
1835 cr.line_to(xb, -yb)
1836 cr.line_to(rad, 0)
1837 cr.fill()
1838 cr.rectangle(-rad, -ys, rad+xb, 2*ys)
1839 cr.fill()
1840 cr.restore()
1841
1842
1844 """
1845 Shows a triangle; animates when you change angle.
1846
1847 @ivar angle: in radians
1848 @ivar color: (r, g, b) in [0..1]
1849 """
1850
1851 __explore_featured = ['angle', 'color', 'invalidate', 'animate']
1852
1853 - def __init__(self, angle=-pi/2, color=(0.2, 1.0, 0.2)):
1854 gtk.DrawingArea.__init__(self)
1855 self.set_size_request(12, 12)
1856 self.connect('expose_event', self._expose)
1857 self._angle = angle
1858 self._color = color
1859 self._goal = angle
1861 self._goal = angle
1862 if self.window:
1863 self.animate()
1864 else:
1865 self._angle = self._goal
1866 angle = property(lambda s: s._angle, set_angle)
1870 color = property(lambda s: s._color, set_color)
1872 """Forces redraw."""
1873 if not self.window: return
1874 alloc = self.get_allocation()
1875 rect = gdk.Rectangle(0, 0, alloc.width, alloc.height)
1876 self.window.invalidate_rect(rect, True)
1878 if self._angle != self._goal:
1879 sign = (self._goal > self._angle) and 1.0 or -1.0
1880
1881 self._angle += 0.2 * (self._goal - self._angle)
1882 self.invalidate()
1883
1884 if abs(self._angle - self._goal) < 1e-1:
1885 self._angle = self._goal
1886 else:
1887 gobject.timeout_add(20, self.animate)
1888 - def _expose(self, widget, event):
1892
1906
1908 __explore_featured = ['expanded']
1915 expanded = property(lambda self: self._expanded, lambda self, x: self.set_expanded(x))
1917 if self._expanded == x: return
1918 self._expanded = x
1919 self.triangle.angle = x and (-pi/2) or 0
1920 self.OnExpanded(self, x)
1923
1924
1925
1926 -class TextViewAppender(object):
1927 """File-like object which appends to a gtk.TextView, optionally formatted, and scrolls to the end."""
1928 __explore_featured = ['tv', 'OnWrite', 'write', 'write_bold']
1929 - def __init__(self, tv):
1930 self.tv = tv
1931 tv.get_buffer().create_tag("bold", weight=pango.WEIGHT_BOLD)
1932 self.OnWrite = WeakEvent()
1933 - def write(self, msg, scroll=True):
1934 buf = self.tv.get_buffer()
1935 self.OnWrite(msg)
1936 buf.insert(buf.get_end_iter(), msg)
1937 if scroll:
1938 self.tv.scroll_to_mark(buf.get_insert(), 0, True)
1939 - def write_bold(self, msg, scroll=True):
1940 buf = self.tv.get_buffer()
1941 self.OnWrite(msg)
1942 buf.insert_with_tags_by_name(buf.get_end_iter(), msg, "bold")
1943 if scroll:
1944 self.tv.scroll_to_mark(buf.get_insert(), 0)
1945 - def __call__(self, msg):
1947
1949 """Changes the font of a gtk.TextView to Monospace 10."""
1950 pangoFont = pango.FontDescription('monospace %i' % points)
1951 textView.modify_font(pangoFont)
1952
1954 """Puts custom data on the specified clipboard, identified by target string."""
1955 targets = [ (target, 0, 0) ]
1956 def get_func(clipboard, selectiondata, info, data):
1957 selectiondata.set_text(data)
1958 return
1959 def clear_func(clipboard, data):
1960 del data
1961 return
1962 clipboard.set_with_data(targets, get_func, clear_func, bytes)
1963
1964
1966 """A customizable copy image dialog"""
1967 - def __init__(self, title='Copy', parent=None):
1968 gtk.Dialog.__init__(self, title, parent or get_active_window(), gtk.DIALOG_MODAL,
1969 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
1970 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
1971 self.set_default_response(gtk.RESPONSE_ACCEPT)
1972 self.__ref = Reffer()
1973 self.basic_controls = pack_item(gtk.VBox(), self.vbox)
1974 frame = pack_item(gtk.Frame('Size'), self.basic_controls)
1975 fbox = gtk.VBox()
1976 fbox.show()
1977 frame.add(fbox)
1978 self.chkAuto = pack_check('auto', fbox, True, self.__onToggleAuto)
1979 line = pack_item(gtk.HBox(True), fbox)
1980 pack_label('Width:', line, expand=True)
1981 self.txtWidth = pack_item(NumEntry(1, acceptIntGreaterThan(0), width_chars=11), line, expand=True)
1982 self.txtWidth.OnChange += self.__ref(self.__onChangeSize)
1983 line = pack_item(gtk.HBox(True), fbox)
1984 pack_label('Height:', line, expand=True)
1985 self.txtHeight = pack_item(NumEntry(1, acceptIntGreaterThan(0), width_chars=11), line, expand=True)
1986 self.txtHeight.OnChange += self.__ref(self.__onChangeSize)
1994 - def run(self, view):
1995 self.view = view
1996 if self.chkAuto.get_active():
1997 w, h = view.window.get_size()
1998 self.txtWidth.setValue(w)
1999 self.txtHeight.setValue(h)
2000 response = gtk.Dialog.run(self)
2001 self.hide()
2002 self.view = None
2003 self.copy_width = self.txtWidth.value
2004 self.copy_height = self.txtHeight.value
2005 return response
2006
2007
2008
2009
2011 return gdk.Color(*[int(round(x*65535.0)) for x in tup[:3]])
2014
2015
2016 -def ShowMessage(message, buttons=gtk.BUTTONS_OK, title="", parent=None):
2017 mdlg = gtk.MessageDialog(parent or get_active_window(), buttons=buttons, flags=gtk.DIALOG_MODAL, message_format=message)
2018 response = mdlg.run()
2019 mdlg.destroy()
2020 return response
2021
2022 -def PromptChoices(message, choices=['Cancel', 'OK'], title="", parent=None):
2023 """Returns index of choice; 0 is also window-close."""
2024 dlg = gtk.Dialog(title, parent or get_active_window(), gtk.DIALOG_MODAL)
2025 pack_label(message, dlg.vbox, fill=False)
2026 for i, choice in enumerate(choices):
2027 dlg.add_button(choice, i)
2028 response = dlg.run()
2029 if response == gtk.RESPONSE_REJECT:
2030 response = 0
2031 dlg.destroy()
2032 return response
2033
2034 -def PromptEntry(message, value, accept=None, format='%s', title="", parent=None):
2035 """Returns modified value, or None if canceled."""
2036 dlg = NumEntryDialog(title=title, parent=parent or get_active_window(), message=message, value=value, accept=accept, format=format)
2037 response = dlg.run()
2038 dlg.destroy()
2039 if response == gtk.RESPONSE_ACCEPT:
2040 return dlg.value
2041 else:
2042 return None
2043
2057
2058
2060 """Defines a resource which is calculated when needed and cached until invalidated;
2061 it may take a long time to calculate, so request is asynchronous --
2062 you provide a receiver callback, which is called by gobject some time later.
2063 """
2065 """
2066
2067 @param get: lambda receiver: gobject.idle_add(receiver, answer_part_1, answer_part_2, ...) -- computation function to replace self.get (or you can inherit this class and override self.get)
2068 """
2069 self.serial = 0
2070 self.__get = get or self.get
2071 self.__last_serial = -1
2072 self.__last_val = None
2073 self.OnInvalidate = WeakEvent()
2075 """Dumps any cached value; next request will recompute."""
2076 self.serial += 1
2077 self.__last_val = None
2078 self.OnInvalidate(self)
2080 """Requests that the value be calculated if needed, then receiver(answer_part_1, answer_part_2, ...)
2081 is called by gobject."""
2082 if self.serial != self.__last_serial:
2083 self.__receiver = receiver
2084 self.__recv_serial = self.serial
2085 def receive(*args):
2086 self.__last_serial = self.__recv_serial
2087 self.__last_val = args
2088 receiver(*args)
2089 gobject.idle_add(self.__get, receive)
2090 else:
2091 gobject.idle_add(receiver, *self.__last_val)
2092 - def get(self, receiver):
2093 """Override this method to compute the answer(s) and pass them via gobject; e.g.:
2094
2095 >>> val = 2+2
2096 >>> val2 = 2*2
2097 >>> gobject.idle_add(receiver, val, val2) # it's up to you how many args are passed
2098
2099 Alternatively, pass get=lambda receiver: ... to the constructor.
2100 """
2101 raise Exception('abstract')
2102
2103
2105 dlg = gtk.FileChooserDialog(caption, parent or get_active_window(), gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
2106 buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
2107 dlg.set_default_response(gtk.RESPONSE_OK)
2108 dlg.set_current_folder(path)
2109 response = dlg.run()
2110 path = dlg.get_filename()
2111 dlg.destroy()
2112 if response == gtk.RESPONSE_OK:
2113 return path
2114 else:
2115 return None
2116
2117
2118 -def Open(caption, path, do_open, filters=[('Text files', '.txt')], parent=None, allow_all_files=True, do_on_cancel=lambda: None):
2119 dlg = gtk.FileChooserDialog(caption, parent or get_active_window(), gtk.FILE_CHOOSER_ACTION_OPEN,
2120 buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
2121 dlg.set_default_response(gtk.RESPONSE_OK)
2122 for lbl, ext in filters:
2123 filter = gtk.FileFilter()
2124 filter.set_name(lbl)
2125 filter.add_pattern('*'+ext)
2126 dlg.add_filter(filter)
2127 if allow_all_files:
2128 filter = gtk.FileFilter()
2129 filter.set_name('All files')
2130 filter.add_pattern('*.*')
2131 dlg.add_filter(filter)
2132 dlg.set_filter(filter)
2133 dlg.set_current_folder(path)
2134 response = dlg.run()
2135 newpath = dlg.get_current_folder()
2136 newname = dlg.get_filename()
2137 dlg.destroy()
2138 if response != gtk.RESPONSE_OK:
2139 do_on_cancel()
2140 return ""
2141 fname = newname
2142 path = newpath
2143 if fname:
2144 try:
2145 do_open(fname)
2146 return fname
2147 except:
2148 mdlg = gtk.MessageDialog(None, buttons=gtk.BUTTONS_OK, flags=gtk.DIALOG_MODAL,
2149 message_format= "Error opening %s:\n%s" % (fname, traceback.format_exc()))
2150 mdlg.run()
2151 mdlg.destroy()
2152 fname = ""
2153 do_on_cancel()
2154 return fname
2155
2156 -def OpenMulti(caption, path, do_open, filters=[('Text files', '.txt')], parent=None, allow_all_files=True):
2157 dlg = gtk.FileChooserDialog(caption, parent or get_active_window(), gtk.FILE_CHOOSER_ACTION_OPEN,
2158 buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
2159 dlg.set_select_multiple(True)
2160 dlg.set_default_response(gtk.RESPONSE_OK)
2161 for lbl, ext in filters:
2162 filter = gtk.FileFilter()
2163 filter.set_name(lbl)
2164 filter.add_pattern('*'+ext)
2165 dlg.add_filter(filter)
2166 if allow_all_files:
2167 filter = gtk.FileFilter()
2168 filter.set_name('All files')
2169 filter.add_pattern('*.*')
2170 dlg.add_filter(filter)
2171 dlg.set_current_folder(path)
2172 response = dlg.run()
2173 newpath = dlg.get_current_folder()
2174 names = dlg.get_filenames()
2175 dlg.destroy()
2176 if response != gtk.RESPONSE_OK:
2177 return []
2178 path = newpath
2179 if names:
2180 try:
2181 do_open(names)
2182 return names
2183 except:
2184 mdlg = gtk.MessageDialog(None, buttons=gtk.BUTTONS_OK, flags=gtk.DIALOG_MODAL,
2185 message_format= "Error opening list:\n%s" % traceback.format_exc())
2186 mdlg.run()
2187 mdlg.destroy()
2188 names = []
2189 return names
2190
2191
2192 -def SaveAs(caption, path, name, do_save, filters=[('Text files', '.txt')], caption_if_exists='Overwrite', parent=None,
2193 allow_all_files=False):
2194 fname = name
2195 while True:
2196 dlg = gtk.FileChooserDialog(caption, parent or get_active_window(), gtk.FILE_CHOOSER_ACTION_SAVE,
2197 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK))
2198 dlg.set_default_response(gtk.RESPONSE_OK)
2199 filter_objs = []
2200 for lbl, ext in filters:
2201 filter = gtk.FileFilter()
2202 filter.set_name(lbl)
2203 filter.add_pattern('*'+ext)
2204 dlg.add_filter(filter)
2205 filter_objs.append(filter)
2206 if allow_all_files:
2207 filter = gtk.FileFilter()
2208 filter.set_name('All files')
2209 filter.add_pattern('*.*')
2210 dlg.add_filter(filter)
2211 if filter_objs:
2212 dlg.set_filter(filter_objs[0])
2213 dlg.set_current_folder(path)
2214 dlg.set_current_name(fname)
2215 response = dlg.run()
2216 if response == gtk.RESPONSE_OK:
2217 newpath = dlg.get_current_folder()
2218 newname = dlg.get_filename()
2219 fname = newname
2220 path = newpath
2221 if True:
2222 chosen_filter = dlg.get_filter()
2223 for i, fi in enumerate(filter_objs):
2224 if fi == chosen_filter:
2225 fname = os.path.splitext(fname)[0] + filters[i][1]
2226 break
2227 dlg.destroy()
2228 if response != gtk.RESPONSE_OK:
2229 return ""
2230 if os.path.exists(fname):
2231 mdlg = gtk.MessageDialog(parent or get_active_window(), buttons=gtk.BUTTONS_YES_NO,
2232 flags=gtk.DIALOG_MODAL,
2233 message_format= "The file exists. %s it?" % caption_if_exists)
2234 response = mdlg.run()
2235 mdlg.destroy()
2236 if response == gtk.RESPONSE_NO:
2237 fname = ""
2238 if fname:
2239 try:
2240 do_save(fname)
2241 return fname
2242 except:
2243 mdlg = gtk.MessageDialog(None, buttons=gtk.BUTTONS_OK, flags=gtk.DIALOG_MODAL,
2244 message_format= "Error saving %s:\n%s" % (fname, traceback.format_exc()))
2245 mdlg.run()
2246 mdlg.destroy()
2247 fname = ""
2248
2249
2250 -def pack_item(item, container, expand=False, fill=True, show=True, at_end=False):
2251 if show:
2252 item.show()
2253 if not (container is None):
2254 (at_end and container.pack_end or container.pack_start)(item, expand, fill)
2255 return item
2256
2257 -def pack_space(container, x=-1, y=-1, expand=False, fill=True, show=True, at_end=False):
2258 sp = gtk.EventBox()
2259 sp.set_size_request(x, y)
2260 sp.show()
2261 return pack_item(sp, container, expand, fill, show, at_end)
2262
2263 -def pack_hsep(pix, container, x=-1, expand=False, fill=True, show=True, at_end=False):
2264 sep = gtk.HSeparator()
2265 sep.set_size_request(x, pix)
2266 return pack_item(sep, container, expand, fill, show, at_end)
2267
2268 -def pack_vsep(pix, container, y=-1, expand=False, fill=True, show=True, at_end=False):
2269 sep = gtk.VSeparator()
2270 sep.set_size_request(pix, y)
2271 return pack_item(sep, container, expand, fill, show, at_end)
2272
2273 -def pack_label(text, container, expand=False, fill=True, show=True, at_end=False):
2275
2281
2282 -def pack_check(text, container, active=False, on_toggle=None, expand=False, fill=True, show=True, at_end=False):
2283 chk = gtk.CheckButton(text)
2284 if active:
2285 chk.set_active(True)
2286 if on_toggle:
2287 chk.evt_toggled = chk.connect('toggled', on_toggle)
2288 return pack_item(chk, container, expand, fill, show, at_end)
2289
2290 -def pack_radio(text, container, group=None, active=False, on_toggle=None, expand=False, fill=True, show=True, at_end=False):
2291 rad = gtk.RadioButton(group, text)
2292 if active:
2293 rad.set_active(True)
2294 if on_toggle:
2295 rad.evt_toggled = rad.connect('toggled', on_toggle)
2296 return pack_item(rad, container, expand, fill, show, at_end)
2297
2308
2310 weak_activate = None if (on_activate is None) else weakref.ref(on_activate)
2311 def do_weak_activate(item):
2312 act = weak_activate and weak_activate()
2313 if act:
2314 act(item)
2315 if ((caption is None) or (caption is '-')) and (on_activate is None) and (submenu is None):
2316 item = gtk.SeparatorMenuItem()
2317 else:
2318 item = item_class(caption)
2319 if active:
2320 item.set_active(True)
2321 if not (on_activate is None):
2322 item.connect('activate', do_weak_activate)
2323 if not (submenu is None):
2324 item.set_submenu(submenu)
2325 if (not (sensitive is None)) and (not (on_activate or submenu)) and (not sensitive):
2326 item.set_sensitive(False)
2327 if show:
2328 item.show()
2329 if menu:
2330 menu.append(item)
2331 return item
2332
2333
2335 for window in gtk.window_list_toplevels():
2336 if window.is_active():
2337 return True
2338 return False
2339
2340
2342 Settings=gtk.settings_get_for_screen(gdk.screen_get_default())
2343 gtk.rc_parse_string("""
2344 style "general-font"
2345 {
2346 font_name = "Sans %s"
2347 }
2348 widget_class "*" style "general-font" """ % fs)
2349 gtk.rc_reset_styles(Settings)
2350
2351
2353
2354 return s.replace('_', '__')
2355
2356
2358 for w in gtk.window_list_toplevels():
2359 if w.is_active():
2360 return w
2361 return None
2362
2363
2364 -class HyperTextView(gtk.TextView):
2365 __explore_featured = ['get_buffer', 'tag', 'links', 'write', 'write_link']
2366 - def __init__(self, *args, **kw):
2367 gtk.TextView.__init__(self, *args, **kw)
2368 self.set_wrap_mode(gtk.WRAP_WORD)
2369 tb = self.get_buffer()
2370 self.tag = tb.create_tag("blue", foreground='blue', underline=pango.UNDERLINE_SINGLE)
2371 self.tag.connect("event", self.__on_tag_event)
2372 self.links = collections.defaultdict(WeakCall)
2373 - def __on_tag_event(self, tag, widget, event, iter):
2374 if event.type == gtk.gdk.BUTTON_RELEASE:
2375 txt = read_tagged(self.get_buffer(), iter, self.tag)
2376 self.links[txt](txt)
2377 - def write(self, s):
2378 """Appends str(s) to the end of the buffer."""
2379 tb = self.get_buffer()
2380 tb.insert(tb.get_end_iter(), str(s))
2381 - def write_link(self, s, on_click):
2382 """Appends str(s) to the end of the buffer as a hyperlink.
2383
2384 @param s: a string -- this is the key to look up the appropriate on_click handler, so it should be unique
2385 @param on_click: function(s) called with event.button on gtk.gdk.BUTTON_RELEASE -- MAKE SURE TO KEEP A REFERENCE
2386 """
2387 txt = str(s)
2388 if txt in self.links:
2389 print "Warning: overwriting on_click handler from last %s link" % txt
2390 self.links[txt].assign(on_click)
2391 tb = self.get_buffer()
2392 tb.insert_with_tags(tb.get_end_iter(), txt, self.tag)
2393
2394
2400
2401 -class HyperTextView2(gtk.TextView):
2402 __explore_featured = ['get_buffer', 'tag', 'links', 'write', 'write_link']
2403 - def __init__(self, *args, **kw):
2404 gtk.TextView.__init__(self, *args, **kw)
2405 self.set_editable(False)
2406 self.set_wrap_mode(gtk.WRAP_WORD)
2407 tb = self.get_buffer()
2408 self.tag = tb.create_tag("blue", foreground='blue', underline=pango.UNDERLINE_SINGLE)
2409 self.tag.connect("event", self.__on_tag_event)
2410 self.links = collections.defaultdict(WeakCall)
2412 self.get_buffer().set_text("")
2413 self.links = collections.defaultdict(WeakCall)
2414 - def __on_tag_event(self, tag, widget, event, iter):
2415 if event.type == gtk.gdk.BUTTON_RELEASE:
2416 txt = read_tagged(self.get_buffer(), iter, self.tag)
2417 iter.backward_to_tag_toggle(tag)
2418 self.links[iter.get_offset()]()
2419 - def write(self, *args):
2420 """Appends string(s) to the end of the buffer.
2421 If an argument is callable, it is used as the on_click handler for the following (string) argument which becomes a link.
2422 """
2423 tb = self.get_buffer()
2424 on_click = None
2425 for arg in args:
2426 if (on_click is None) and callable(arg):
2427 on_click = arg
2428 elif not (on_click is None):
2429 self.write_link(arg, on_click)
2430 on_click = None
2431 else:
2432 tb.insert(tb.get_end_iter(), str(arg))
2433 - def write_link(self, s, on_click):
2434 """Appends str(s) to the end of the buffer as a hyperlink.
2435
2436 @param s: a string -- this is the key to look up the appropriate on_click handler, so it should be unique
2437 @param on_click: function(s) called with event.button on gtk.gdk.BUTTON_RELEASE -- MAKE SURE TO KEEP A REFERENCE
2438 """
2439 tb = self.get_buffer()
2440 end = tb.get_end_iter()
2441 self.links[end.get_offset()].assign(on_click)
2442 tb.insert_with_tags(end, str(s), self.tag)
2443
2444
2445
2446 if __name__ == '__main__':
2447 from qubx.accept import *
2448 from math import *
2449 from itertools import *
2450 from random import *
2451
2452
2453
2454
2455
2456
2457
2458