comparison frontends/primitivus/custom_widgets.py @ 192:879beacb8e16

Primitivus: major changes in SelectableText, menu can now be used with mouse, TabsContainer show wich tab is selected - SelectableText is a WidgetWrap of urwid.Text, and manage attributes and complexe combination like urwid.Text (list of tuples (attribute,text)) - Menu now use a ColumnsRoller, and can't anymore be displayed on 2 rows, avoiding potential bug when display MenuBox - Mouse can be used in menu. Right click on MenuBox make it disappear - Tab container now display which tab is selected by putting a 'title' attribute on the corresponding button /!\ as SelectableText is heavily used, there can be regressions
author Goffi <goffi@goffi.org>
date Thu, 19 Aug 2010 21:11:22 +0800
parents 33e618d385cf
children 92e4ddd580ae
comparison
equal deleted inserted replaced
191:1438a1337732 192:879beacb8e16
19 along with this program. If not, see <http://www.gnu.org/licenses/>. 19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 """ 20 """
21 21
22 import urwid 22 import urwid
23 from urwid.escape import utf8decode 23 from urwid.escape import utf8decode
24 from logging import debug, info, warning, error
24 25
25 class Password(urwid.Edit): 26 class Password(urwid.Edit):
26 """Edit box which doesn't show what is entered (show '*' or other char instead)""" 27 """Edit box which doesn't show what is entered (show '*' or other char instead)"""
27 28
28 def __init__(self, *args, **kwargs): 29 def __init__(self, *args, **kwargs):
126 (maxcol,) = size 127 (maxcol,) = size
127 middle = (maxcol-len(self.text))/2 128 middle = (maxcol-len(self.text))/2
128 render_text = middle * self.car + self.text + (maxcol - len(self.text) - middle) * self.car 129 render_text = middle * self.car + self.text + (maxcol - len(self.text) - middle) * self.car
129 return urwid.Text(render_text) 130 return urwid.Text(render_text)
130 131
131 class SelectableText(urwid.FlowWidget): 132 class SelectableText(urwid.WidgetWrap):
132 """Text which can be selected with space""" 133 """Text which can be selected with space"""
133 signals = ['change'] 134 signals = ['change']
134 135
135 def __init__(self, text, align='left', header='', select_attr=None, default_attr=None, selected = False, data=None): 136 def __init__(self, text, align='left', header='', focus_attr='default_focus', selected_text=None, selected=False, data=None):
136 self.text=unicode(text) 137 """@param text: same as urwid.Text's text parameter
137 self.header=header 138 @param align: same as urwid.Text's align parameter
138 if data: 139 @select_attr: attrbute to use when selected
139 self.data=data 140 @param selected: is the text selected ?"""
140 if select_attr: 141 self.focus_attr = focus_attr
141 self.selected = select_attr 142 self.__selected = False
142 if default_attr: 143 self.__was_focused = False
143 self.default = default_attr 144 self.header = self.__valid_text(header)
144 self.align = align 145 self.default_txt = self.__valid_text(text)
145 self.__selected=selected 146 urwid.WidgetWrap.__init__(self, urwid.Text("",align=align))
147 self.setSelectedText(selected_text)
148 self.setState(selected)
149
150 def __valid_text(self, text):
151 """Tmp method needed until dbus and urwid are more friends"""
152 if isinstance(text,basestring):
153 return unicode(text)
154 elif isinstance(text,tuple):
155 return (unicode(text[0]),text[1])
156 elif isinstance(text,list):
157 for idx in range(len(text)):
158 elem = text[idx]
159 if isinstance(elem,basestring):
160 text[idx] = unicode(elem)
161 if isinstance(elem,tuple):
162 text[idx] = (unicode(elem[0]),elem[1])
163 else:
164 warning (_('WARNING: unknown text type'))
165 return text
146 166
147 def getValue(self): 167 def getValue(self):
148 return self.text 168 if isinstance(self.default_txt,basestring):
169 return self.default_txt
170 list_attr = self.default_txt if isinstance(self.default_txt, list) else [self.default_txt]
171 txt = ""
172 for attr in list_attr:
173 if isinstance(attr,tuple):
174 txt+=attr[1]
175 else:
176 txt+=attr
177 return txt
149 178
150 def get_text(self): 179 def get_text(self):
151 """for compatibility with urwid.Text""" 180 """for compatibility with urwid.Text"""
152 return self.getValue() 181 return self.getValue()
153 182
154 def set_text(self, text): 183 def set_text(self, text):
155 self.text=unicode(text) 184 """/!\ set_text doesn't change self.selected_txt !"""
156 self._invalidate() 185 self.default_txt = self.__valid_text(text)
157 186 self.setState(self.__selected,invisible=True)
158 def setAttribute(self, name, value): 187
159 """Change attribut used for rendering widget 188 def setSelectedText(self, text=None):
160 @param name: one of 189 """Text to display when selected
161 -default: when not selected 190 @text: text as in urwid.Text or None for default value"""
162 -selected: when selected 191 if text == None:
163 @param value: name of the attribute 192 text = ('selected',self.default_txt)
164 /!\ the attribute name followed by _focus is used when widget has focus""" 193 self.selected_txt = self.__valid_text(text)
165 assert name in ['default', 'selected'] 194 if self.__selected:
166 self.__setattr__(name,value) 195 self.setState(self.__selected)
167 self._invalidate() 196
197
198 def __set_txt(self):
199 txt_list = [self.header]
200 txt = self.selected_txt if self.__selected else self.default_txt
201 if isinstance(txt,list):
202 txt_list.extend(txt)
203 else:
204 txt_list.append(txt)
205 self._w.base_widget.set_text(txt_list)
206
168 207
169 def setState(self, selected, invisible=False): 208 def setState(self, selected, invisible=False):
170 """Change state 209 """Change state
171 @param selected: boolean state value 210 @param selected: boolean state value
172 @param invisible: don't emit change signal if True""" 211 @param invisible: don't emit change signal if True"""
173 assert(type(selected)==bool) 212 assert(type(selected)==bool)
174 self.__selected=selected 213 self.__selected=selected
214 self.__set_txt()
215 self.__was_focused = False
175 self._invalidate() 216 self._invalidate()
176 if not invisible: 217 if not invisible:
177 self._emit("change", self.__selected) 218 self._emit("change", self.__selected)
178 219
179 def getState(self): 220 def getState(self):
193 self.setState(not self.__selected) 234 self.setState(not self.__selected)
194 return True 235 return True
195 236
196 return False 237 return False
197 238
198 def rows(self,size,focus=False):
199 return self.display_widget(size, focus).rows(size, focus)
200
201 def render(self, size, focus=False): 239 def render(self, size, focus=False):
202 return self.display_widget(size, focus).render(size, focus) 240 attr_list = self._w.base_widget._attrib
203 241 if not focus:
204 def display_widget(self, size, focus): 242 if self.__was_focused:
205 try: 243 self.__set_txt()
206 select_attr = self.selected 244 self.__was_focused = False
207 except AttributeError: 245 else:
208 select_attr = 'selected' 246 if not self.__was_focused:
209 try: 247 if not attr_list:
210 default_attr = self.default 248 attr_list.append((self.focus_attr,len(self._w.base_widget.text)))
211 except AttributeError: 249 else:
212 default_attr = 'default' 250 for idx in range(len(attr_list)):
213 attr = select_attr if self.__selected else default_attr 251 attr,attr_len = attr_list[idx]
214 if focus: 252 if attr == None:
215 attr+="_focus" 253 attr = self.focus_attr
216 return urwid.Text((attr,self.header+self.text), align=self.align) 254 attr_list[idx] = (attr,attr_len)
255 else:
256 if not attr.endswith('_focus'):
257 attr+="_focus"
258 attr_list[idx] = (attr,attr_len)
259 self._w.base_widget._invalidate()
260 self.__was_focused = True #bloody ugly hack :)
261 return self._w.render(size, focus)
217 262
218 class ClickableText(SelectableText): 263 class ClickableText(SelectableText):
219 signals = SelectableText.signals + ['click'] 264 signals = SelectableText.signals + ['click']
220 265
221 def setState(self, selected, invisible=False): 266 def setState(self, selected, invisible=False):
222 self._emit('click') 267 super(ClickableText,self).setState(False,True)
268 if not invisible:
269 self._emit('click')
223 270
224 class CustomButton(ClickableText): 271 class CustomButton(ClickableText):
225 272
226 def __init__(self, label, on_press=None, user_data=None, left_border = "[ ", right_border = " ]"): 273 def __init__(self, label, on_press=None, user_data=None, left_border = "[ ", right_border = " ]"):
227 self.label = label 274 self.label = label
228 render_txt = "%s%s%s" % (left_border, label, right_border) 275 self.left_border = left_border
229 self.size = len(render_txt) 276 self.right_border = right_border
230 super(CustomButton, self).__init__(render_txt) 277 super(CustomButton, self).__init__([left_border, label, right_border])
278 self.size = len(self.get_text())
231 if on_press: 279 if on_press:
232 urwid.connect_signal(self, 'click', on_press, user_data) 280 urwid.connect_signal(self, 'click', on_press, user_data)
233 281
234 def getSize(self): 282 def getSize(self):
235 """Return representation size of the button""" 283 """Return representation size of the button"""
236 return self.size 284 return self.size
237 285
238 def get_label(self): 286 def get_label(self):
239 return self.label 287 return self.label[1] if isinstance(self.label,tuple) else self.label
288
289 def set_label(self, label):
290 self.label = label
291 self.set_text([self.left_border, label, self.right_border])
240 292
241 class GenericList(urwid.WidgetWrap): 293 class GenericList(urwid.WidgetWrap):
242 signals = ['click','change'] 294 signals = ['click','change']
243 295
244 def __init__(self, options, style=[], align='left', option_type = SelectableText, on_click=None, on_change=None, user_data=None): 296 def __init__(self, options, style=[], align='left', option_type = SelectableText, on_click=None, on_change=None, user_data=None):
402 """Bar used to show misc information to user""" 454 """Bar used to show misc information to user"""
403 signals = ['change'] 455 signals = ['change']
404 456
405 def __init__(self): 457 def __init__(self):
406 self.waitNotifs = urwid.Text('') 458 self.waitNotifs = urwid.Text('')
407 self.message = ClickableText('', default_attr='notifs') 459 self.message = ClickableText('')
408 urwid.connect_signal(self.message, 'click', lambda wid: self.showNext()) 460 urwid.connect_signal(self.message, 'click', lambda wid: self.showNext())
409 self.progress = ClickableText('', default_attr='notifs') 461 self.progress = ClickableText('')
410 self.columns = urwid.Columns([('fixed',6,self.waitNotifs),self.message,('fixed',4,self.progress)]) 462 self.columns = urwid.Columns([('fixed',6,self.waitNotifs),self.message,('fixed',4,self.progress)])
411 urwid.WidgetWrap.__init__(self, urwid.AttrMap(self.columns,'notifs')) 463 urwid.WidgetWrap.__init__(self, urwid.AttrMap(self.columns,'notifs'))
412 self.notifs = [] 464 self.notifs = []
413 465
414 def __modQueue(self): 466 def __modQueue(self):
415 """must be called each time the notifications queue is changed""" 467 """must be called each time the notifications queue is changed"""
416 self.waitNotifs.set_text("(%i)" % len(self.notifs) if self.notifs else '') 468 self.waitNotifs.set_text(('notifs',"(%i)" % len(self.notifs) if self.notifs else ''))
417 self._emit('change') 469 self._emit('change')
418 470
419 def setProgress(self,percentage): 471 def setProgress(self,percentage):
420 """Define the progression to show on the right side of the bar""" 472 """Define the progression to show on the right side of the bar"""
421 if percentage == None: 473 if percentage == None:
422 self.progress.set_text('') 474 self.progress.set_text('')
423 else: 475 else:
424 self.progress.set_text('%02i%%' % percentage) 476 self.progress.set_text(('notifs','%02i%%' % percentage))
425 self._emit('change') 477 self._emit('change')
426 478
427 def addPopUp(self, pop_up_widget): 479 def addPopUp(self, pop_up_widget):
428 """Add a popup to the waiting queue""" 480 """Add a popup to the waiting queue"""
429 self.notifs.append(('popup',pop_up_widget)) 481 self.notifs.append(('popup',pop_up_widget))
430 self.__modQueue() 482 self.__modQueue()
431 483
432 def addMessage(self, message): 484 def addMessage(self, message):
433 "Add a message to the notificatio bar" 485 "Add a message to the notificatio bar"
434 if not self.message.get_text(): 486 if not self.message.get_text():
435 self.message.set_text(message) 487 self.message.set_text(('notifs',message))
436 self._invalidate() 488 self._invalidate()
437 self._emit('change') 489 self._emit('change')
438 else: 490 else:
439 self.notifs.append(('message',message)) 491 self.notifs.append(('message',message))
440 self.__modQueue() 492 self.__modQueue()
446 if notif[0] == "message": 498 if notif[0] == "message":
447 found = notif 499 found = notif
448 break 500 break
449 if found: 501 if found:
450 self.notifs.remove(found) 502 self.notifs.remove(found)
451 self.message.set_text(found[1]) 503 self.message.set_text(('notifs',found[1]))
452 self.__modQueue() 504 self.__modQueue()
453 else: 505 else:
454 self.message.set_text('') 506 self.message.set_text('')
455 self._emit('change') 507 self._emit('change')
456 508
480 signals = ['click'] 532 signals = ['click']
481 533
482 def __init__(self,parent,items): 534 def __init__(self,parent,items):
483 self.parent = parent 535 self.parent = parent
484 self.selected = None 536 self.selected = None
485 content = urwid.SimpleListWalker([ClickableText(text,default_attr='menuitem') for text in items]) 537 content = urwid.SimpleListWalker([ClickableText(('menuitem',text)) for text in items])
486 for wid in content: 538 for wid in content:
487 urwid.connect_signal(wid, 'click', self.onClick) 539 urwid.connect_signal(wid, 'click', self.onClick)
488 540
489 self.listBox = urwid.ListBox(content) 541 self.listBox = urwid.ListBox(content)
490 menubox = urwid.LineBox(urwid.BoxAdapter(self.listBox,len(items))) 542 menubox = urwid.LineBox(urwid.BoxAdapter(self.listBox,len(items)))
499 self.parent.keypress(size, key) 551 self.parent.keypress(size, key)
500 elif key=='left' or key=='right': 552 elif key=='left' or key=='right':
501 self.parent.keypress(size,'up') 553 self.parent.keypress(size,'up')
502 self.parent.keypress(size,key) 554 self.parent.keypress(size,key)
503 return super(MenuBox,self).keypress(size,key) 555 return super(MenuBox,self).keypress(size,key)
556
557 def mouse_event(self, size, event, button, x, y, focus):
558 if button == 3:
559 self.parent.keypress(size,'up')
560 return True
561 return super(MenuBox,self).mouse_event(size, event, button, x, y, focus)
504 562
505 def onClick(self, wid): 563 def onClick(self, wid):
506 self.selected = wid.getValue() 564 self.selected = wid.getValue()
507 self._emit('click') 565 self._emit('click')
508 566
509 class Menu(urwid.FlowWidget): 567 class Menu(urwid.WidgetWrap):
510 568
511 def __init__(self,loop, x_orig=0): 569 def __init__(self,loop, x_orig=0):
512 """Menu widget 570 """Menu widget
513 @param loop: main loop of urwid 571 @param loop: main loop of urwid
514 @param x_orig: absolute start of the abscissa 572 @param x_orig: absolute start of the abscissa
515 """ 573 """
516 super(Menu, self).__init__()
517 self.loop = loop 574 self.loop = loop
518 self.menu_keys = [] 575 self.menu_keys = []
519 self.menu = {} 576 self.menu = {}
520 self.x_orig = x_orig 577 self.x_orig = x_orig
521 self.shortcuts = {} #keyboard shortcuts 578 self.shortcuts = {} #keyboard shortcuts
522 self.focus_menu = 0
523 self.save_bottom = None 579 self.save_bottom = None
524 580 col_rol = ColumnsRoller()
581 urwid.WidgetWrap.__init__(self, urwid.AttrMap(col_rol,'menubar'))
582
525 def selectable(self): 583 def selectable(self):
526 return True 584 return True
527 585
528 def getMenuSize(self): 586 def getMenuSize(self):
529 """return the current number of categories in this menu""" 587 """return the current number of categories in this menu"""
541 if len(item[0]) > max_len: 599 if len(item[0]) > max_len:
542 max_len = len(item[0]) 600 max_len = len(item[0])
543 601
544 self.save_bottom = self.loop.widget 602 self.save_bottom = self.loop.widget
545 menu_box = MenuBox(self,[item[0] for item in self.menu[menu_key]]) 603 menu_box = MenuBox(self,[item[0] for item in self.menu[menu_key]])
546 urwid.connect_signal(menu_box, 'click', self.onClick) 604 urwid.connect_signal(menu_box, 'click', self.onItemClick)
547 605
548 self.loop.widget = urwid.Overlay(urwid.AttrMap(menu_box,'menubar'),self.save_bottom,('fixed left', columns),max_len+2,('fixed top',1),None) 606 self.loop.widget = urwid.Overlay(urwid.AttrMap(menu_box,'menubar'),self.save_bottom,('fixed left', columns),max_len+2,('fixed top',1),None)
549 607
550 def keypress(self, size, key): 608 def keypress(self, size, key):
551 if key == 'right' and self.focus_menu < len(self.menu)-1: 609 if key == 'down':
552 self.focus_menu += 1 610 key = 'enter'
553 self._invalidate()
554 elif key == 'left' and self.focus_menu > 0:
555 self.focus_menu -= 1
556 self._invalidate()
557 return
558 elif key == 'down':
559 if self.menu_keys and not self.save_bottom:
560 column = sum([len(menu)+4 for menu in self.menu_keys[0:self.focus_menu]],self.focus_menu+self.x_orig)
561 self.__buildOverlay(self.menu_keys[self.focus_menu],column)
562 elif key == 'up': 611 elif key == 'up':
563 if self.save_bottom: 612 if self.save_bottom:
564 self.loop.widget = self.save_bottom 613 self.loop.widget = self.save_bottom
565 self.save_bottom = None 614 self.save_bottom = None
566 615
567 return key 616 return self._w.base_widget.keypress(size, key)
568 617
569 def checkShortcuts(self, key): 618 def checkShortcuts(self, key):
570 for shortcut in self.shortcuts.keys(): 619 for shortcut in self.shortcuts.keys():
571 if key == shortcut: 620 if key == shortcut:
572 category, item, callback = self.shortcuts[shortcut] 621 category, item, callback = self.shortcuts[shortcut]
573 callback((category, item)) 622 callback((category, item))
579 @param item: menu item (e.g. new/close/about) 628 @param item: menu item (e.g. new/close/about)
580 @callback: method to call when item is selected""" 629 @callback: method to call when item is selected"""
581 if not category in self.menu.keys(): 630 if not category in self.menu.keys():
582 self.menu_keys.append(category) 631 self.menu_keys.append(category)
583 self.menu[category] = [] 632 self.menu[category] = []
633 button = CustomButton(('menubar',category), self.onCategoryClick,
634 left_border = ('menubar',"[ "),
635 right_border = ('menubar'," ]"))
636 self._w.base_widget.addWidget(button,button.getSize())
584 self.menu[category].append((item, callback)) 637 self.menu[category].append((item, callback))
585 if shortcut: 638 if shortcut:
586 assert(shortcut not in self.shortcuts.keys()) 639 assert(shortcut not in self.shortcuts.keys())
587 self.shortcuts[shortcut] = (category, item, callback) 640 self.shortcuts[shortcut] = (category, item, callback)
588 641
589 def rows(self,size,focus=False): 642 def onItemClick(self, widget):
590 return self.display_widget(size, focus).rows(size, focus) 643 category = self._w.base_widget.getSelected().get_label()
591
592 def render(self, size, focus=False):
593 return self.display_widget(size, focus).render(size, focus)
594
595 def display_widget(self, size, focus):
596 render_txt = []
597 idx = 0
598 for menu in self.menu_keys:
599 if focus and idx == self.focus_menu:
600 render_txt.append(('selected_menu', '[ %s ]' % menu))
601 render_txt.append(' ')
602 else:
603 render_txt.append('[ %s ] ' % menu)
604 idx += 1
605 return urwid.AttrMap(urwid.Text(render_txt), 'menubar')
606
607 def onClick(self, widget):
608 category = self.menu_keys[self.focus_menu]
609 item = widget.getValue() 644 item = widget.getValue()
645 callback = None
610 for menu_item in self.menu[category]: 646 for menu_item in self.menu[category]:
611 if item == menu_item[0]: 647 if item == menu_item[0]:
612 callback = menu_item[1] 648 callback = menu_item[1]
613 break 649 break
614 if callback: 650 if callback:
615 self.keypress(None,'up') 651 self.keypress(None,'up')
616 callback((category, item)) 652 callback((category, item))
653
654 def onCategoryClick(self, button):
655 self.__buildOverlay(button.get_label(),
656 self.x_orig + self._w.base_widget.getStartCol(button))
657
617 658
618 class MenuRoller(urwid.WidgetWrap): 659 class MenuRoller(urwid.WidgetWrap):
619 660
620 def __init__(self,menus_list): 661 def __init__(self,menus_list):
621 """Create a MenuRoller 662 """Create a MenuRoller
749 def addWidget(self, widget, width): 790 def addWidget(self, widget, width):
750 self.widget_list.append((width,widget)) 791 self.widget_list.append((width,widget))
751 if len(self.widget_list) == 1: 792 if len(self.widget_list) == 1:
752 self.set_focus(0) 793 self.set_focus(0)
753 794
795 def getStartCol(self, widget):
796 """Return the column of the left corner of the widget"""
797 start_col = 0
798 for wid in self.widget_list[self.__start:]:
799 if wid[1] == widget:
800 return start_col
801 start_col+=wid[0]
802 return None
803
754 def selectable(self): 804 def selectable(self):
755 try: 805 try:
756 return self.widget_list[self.focus_column][1].selectable() 806 return self.widget_list[self.focus_column][1].selectable()
757 except IndexError: 807 except IndexError:
758 return False 808 return False
770 return 820 return
771 if self.focus_column<len(self.widget_list): 821 if self.focus_column<len(self.widget_list):
772 return self.widget_list[self.focus_column][1].keypress(size,key) 822 return self.widget_list[self.focus_column][1].keypress(size,key)
773 return key 823 return key
774 824
825 def getSelected(self):
826 """Return selected widget"""
827 return self.widget_list[self.focus_column][1]
828
775 def set_focus(self, idx): 829 def set_focus(self, idx):
776 if idx>len(self.widget_list)-1: 830 if idx>len(self.widget_list)-1:
777 idx = len(self.widget_list)-1 831 idx = len(self.widget_list)-1
778 self.focus_column = idx 832 self.focus_column = idx
779 833
800 _next = True 854 _next = True
801 total_wid-=self.widget_list[end_wid][0] 855 total_wid-=self.widget_list[end_wid][0]
802 end_wid-=1 856 end_wid-=1
803 857
804 cols_left = maxcol - total_wid 858 cols_left = maxcol - total_wid
805 859 self.__start = start_wid #we need to keep it for getStartCol
806 return _prev,_next,start_wid,end_wid,cols_left 860 return _prev,_next,start_wid,end_wid,cols_left
807 861
808 862
809 def mouse_event(self, size, event, button, x, y, focus): 863 def mouse_event(self, size, event, button, x, y, focus):
810 (maxcol,)=size 864 (maxcol,)=size
880 934
881 class TabsContainer(urwid.WidgetWrap): 935 class TabsContainer(urwid.WidgetWrap):
882 signals = ['click'] 936 signals = ['click']
883 937
884 def __init__(self): 938 def __init__(self):
885 #self._current_tab = 0 939 self._current_tab = None
886 self._buttons_cont = ColumnsRoller() 940 self._buttons_cont = ColumnsRoller()
887 self.tabs = [] 941 self.tabs = []
888 self.__frame = FocusFrame(urwid.Filler(urwid.Text('')),urwid.Pile([self._buttons_cont,urwid.Divider(u"─")])) 942 self.__frame = FocusFrame(urwid.Filler(urwid.Text('')),urwid.Pile([self._buttons_cont,urwid.Divider(u"─")]))
889 urwid.WidgetWrap.__init__(self, self.__frame) 943 urwid.WidgetWrap.__init__(self, self.__frame)
890 944
905 break 959 break
906 if tab[0] != tab_name: 960 if tab[0] != tab_name:
907 error(_("INTERNAL ERROR: Tab not found")) 961 error(_("INTERNAL ERROR: Tab not found"))
908 assert(False) 962 assert(False)
909 self.__frame.body = tab[1] 963 self.__frame.body = tab[1]
964 button.set_label(('title',button.get_label()))
965 if self._current_tab:
966 self._current_tab.set_label(self._current_tab.get_label())
967 self._current_tab = button
910 if not invisible: 968 if not invisible:
911 self._emit('click') 969 self._emit('click')
912 970
913 def __appendButton(self, name): 971 def __appendButton(self, name):
914 """Append a button to the frame header, 972 """Append a button to the frame header,