comparison urwid_satext/sat_widgets.py @ 110:436076392538

GenericList + List fixes, better 'change' signal handling, added GenericList.contents property (addind or removing an item there trigger the 'change' signal)
author Goffi <goffi@goffi.org>
date Thu, 20 Aug 2015 18:39:40 +0200
parents 13d608824508
children 1cdf4a00b68d
comparison
equal deleted inserted replaced
109:13d608824508 110:436076392538
454 self.__size_cache = size 454 self.__size_cache = size
455 self.__focus_cache = focus 455 self.__focus_cache = focus
456 return super(UnselectableListBox, self).render(size, focus) 456 return super(UnselectableListBox, self).render(size, focus)
457 457
458 458
459 class SimpleListWalkerWithCb(urwid.SimpleListWalker):
460 """a SimpleListWalker which call callbacks on items changes"""
461
462 def __init__(self, contents, on_new=None, on_delete=None):
463 """
464 @param contents: list to copy into this object
465 @param on_new: callback to call when an item is added
466 @param on_delete: callback to call when an item is deleted
467 """
468 # XXX: we can't use modified signal as it doesn't return the modified item
469 super(SimpleListWalkerWithCb, self).__init__(contents)
470 for content in contents:
471 on_new(content)
472 self._on_new = on_new
473 self._on_delete = on_delete
474
475 def __cbSingle(self, item, cb):
476 try:
477 cb(item)
478 except TypeError:
479 pass
480
481 def __cbMulti(self, items, cb):
482 if cb is not None:
483 for item in items:
484 cb(item)
485
486 def __add__(self, new_list):
487 self.__cbMulti(new_list, self._on_new)
488 return super(SimpleListWalkerWithCb, self).__add__(new_list)
489
490 def __delitem__(self, item):
491 self.__cbSingle(item, self._on_delete)
492 return super(SimpleListWalkerWithCb, self).__delitem__(item)
493
494 def __delslice__(self, i,j):
495 items = super(SimpleListWalkerWithCb, self).__getslice__(i,j)
496 self.__cbMulti(items, self._on_delete)
497 return super(SimpleListWalkerWithCb, self).__delslice(i,j)
498
499 def __iadd__(self, y):
500 raise NotImplementedError
501
502 def __imul__(self, y):
503 raise NotImplementedError
504
505 def __mul__(self, n):
506 raise NotImplementedError
507
508 def __rmul__(self, n):
509 raise NotImplementedError
510
511 def __setitem__(self, i, y):
512 parent = super(SimpleListWalkerWithCb, self)
513 self.__cbSingle(y, self._on_new)
514 to_delete = parent.__getitem__(i)
515 self.__cbSingle(to_delete, self._on_delete)
516 return parent.__setitem__(self, i, y)
517
518 def __setslice__(self, i, j, y):
519 parent = super(SimpleListWalkerWithCb, self)
520 items_to_delete = parent.__getslice__(i,j)
521 self.__cbMulti(items_to_delete, self._on_delete)
522 if hasattr(y, '__iter__'):
523 self.__cbMulti(y, self._on_new)
524 else:
525 self.__cbSingle(y, self._on_new)
526 return parent.__setslice__(i, j, y)
527
528 def append(self, obj):
529 self.__cbSingle(obj, self._on_new)
530 return super(SimpleListWalkerWithCb, self).append(obj)
531
532 def extend(self, it):
533 self.__cbMulti(it, self.__on_new)
534 return super(SimpleListWalkerWithCb, self).extend(it)
535
536 def insert(self, idx, obj):
537 self.__cbSingle(obj, self.__on_new)
538 return super(SimpleListWalkerWithCb, self).insert(idx, obj)
539
540 def pop(self, idx=None):
541 if idx is None:
542 idx=len(self)-1
543
544 parent = super(SimpleListWalkerWithCb, self)
545 to_remove = parent.__getitem__(idx)
546 self.__cbSingle(to_remove, self._on_delete)
547 return parent.pop(idx)
548
549 def remove(self, val):
550 ret = super(SimpleListWalkerWithCb, self).remove(val)
551 self.__cbSingle(val, self._on_delete)
552 return ret
553
554
459 class GenericList(urwid.ListBox): 555 class GenericList(urwid.ListBox):
460 signals = ['click','change'] 556 signals = ['click','change']
461 557
462 def __init__(self, options, style=None, align='left', option_type = SelectableText, on_click=None, on_change=None, user_data=None): 558 def __init__(self, options, style=None, align='left', option_type = SelectableText, on_click=None, on_change=None, user_data=None):
463 """Widget managing list of string and their selection 559 """Widget managing list of string and their selection
466 @param style: list of string: 562 @param style: list of string:
467 - 'single' if only one must be selected 563 - 'single' if only one must be selected
468 - 'no_first_select' nothing selected when list is first displayed 564 - 'no_first_select' nothing selected when list is first displayed
469 - 'can_select_none' if we can select nothing 565 - 'can_select_none' if we can select nothing
470 @param align: alignement of text inside the list 566 @param align: alignement of text inside the list
567 @param option_type: callable (usually a class) which will be called with:
568 - option as first argument
569 - align=align as keyword argument
471 @param on_click: method called when click signal is emited 570 @param on_click: method called when click signal is emited
571 @param on_change: method called when change signal is emited
472 @param user_data: data sent to the callback for click signal 572 @param user_data: data sent to the callback for click signal
473 """ 573 """
474 if style is None: 574 if style is None:
475 style = [] 575 style = []
476 self.single = 'single' in style 576 self.single = 'single' in style
484 urwid.connect_signal(self, 'click', on_click, user_data) 584 urwid.connect_signal(self, 'click', on_click, user_data)
485 585
486 if on_change: 586 if on_change:
487 urwid.connect_signal(self, 'change', on_change, user_data) 587 urwid.connect_signal(self, 'change', on_change, user_data)
488 588
489 self.content = urwid.SimpleListWalker([]) 589 self.content = SimpleListWalkerWithCb([], self._addSignals, lambda widget: self._emit('change'))
490 super(GenericList, self).__init__(self.content) 590 super(GenericList, self).__init__(self.content)
491 self.changeValues(options) 591 self.changeValues(options)
492 592
493 def _onStateChange(self, widget, selected): 593 def _addSignals(self, widget):
594 for signal, callback in (('change', self._onStateChange), ('click', self._onClick)):
595 try:
596 urwid.connect_signal(widget, signal, callback)
597 except NameError:
598 pass #the widget given doesn't support the signal
599
600 @property
601 def contents(self):
602 return self.content
603
604 def _onStateChange(self, widget, selected, *args):
494 if self.single: 605 if self.single:
495 if not selected and not self.can_select_none: 606 if not selected and not self.can_select_none:
496 #if in single mode, it's forbidden to unselect a value 607 #if in single mode, it's forbidden to unselect a value
497 widget.setState(True, invisible=True) 608 widget.setState(True, invisible=True)
498 return 609 return
499 if selected: 610 if selected:
500 self.unselectAll(invisible=True) 611 self.unselectAll(invisible=True)
501 widget.setState(True, invisible=True) 612 widget.setState(True, invisible=True)
502 self._emit("change") 613 self._emit("change", widget, selected, *args)
503 614
504 def _onClick(self, widget): 615 def _onClick(self, widget, *args):
505 self._emit("click", widget) 616 if widget not in self.content:
617 urwid.disconnect_signal(widget, "click", self._onClick)
618 return
619 self._emit("click", widget, *args)
506 620
507 def unselectAll(self, invisible=False): 621 def unselectAll(self, invisible=False):
508 for widget in self.content: 622 for widget in self.content:
509 if widget.getState(): 623 if widget.getState():
510 widget.setState(False, invisible) 624 widget.setState(False, invisible)
541 new_values = ListOption.fromOptions(new_values) 655 new_values = ListOption.fromOptions(new_values)
542 if not self.first_display: 656 if not self.first_display:
543 old_selected = self.getSelectedValues() 657 old_selected = self.getSelectedValues()
544 widgets = [] 658 widgets = []
545 for option in new_values: 659 for option in new_values:
546 widget = self.option_type(option, self.align) 660 widget = self.option_type(option, align=self.align)
547 if not self.first_display and option in old_selected: 661 if not self.first_display and option in old_selected:
548 widget.setState(True) 662 widget.setState(True)
549 widgets.append(widget) 663 widgets.append(widget)
550 for signal, callback in (('change', self._onStateChange), ('click', self._onClick)):
551 try:
552 urwid.connect_signal(widget, signal, callback)
553 except NameError:
554 pass #the widget given doesn't support the signal
555 self.content[:] = widgets 664 self.content[:] = widgets
556 if self.first_display and self.single and new_values and not self.no_first_select: 665 if self.first_display and self.single and new_values and not self.no_first_select:
557 self.content[0].setState(True) 666 self.content[0].setState(True)
558 self._emit('change') 667 self._emit('change')
559 self.first_display = False 668 self.first_display = False
606 715
607 def __init__(self, options, style=None, max_height=5, align='left', option_type = SelectableText, on_click=None, on_change=None, user_data=None): 716 def __init__(self, options, style=None, max_height=5, align='left', option_type = SelectableText, on_click=None, on_change=None, user_data=None):
608 if style is None: 717 if style is None:
609 style = [] 718 style = []
610 self.genericList = GenericList(options, style, align, option_type, on_click, on_change, user_data) 719 self.genericList = GenericList(options, style, align, option_type, on_click, on_change, user_data)
611 urwid.connect_signal(self.genericList, 'change', self._onChange) 720 urwid.connect_signal(self.genericList, 'change', lambda *args: self._emit('change'))
612 urwid.connect_signal(self.genericList, 'click', self._onClick)
613 self.max_height = max_height 721 self.max_height = max_height
614 722
615 def _onChange(self, widget): 723 @property
616 self._emit('change') 724 def contents(self):
617 725 return self.genericList.content
618 def _onClick(self, widget):
619 self._emit('click')
620 726
621 def selectable(self): 727 def selectable(self):
622 return True 728 return True
623 729
730 def get_cursor_coords(self, size):
731 return self.genericList.get_cursor_coords((size[0], self._getHeight(size, True)))
732
624 def keypress(self, size, key): 733 def keypress(self, size, key):
625 return self.displayWidget(size,True).keypress(size, key) 734 return self.displayWidget(size,True).keypress(size, key)
626 735
627 def unselectAll(self, invisible=False): 736 def unselectAll(self, invisible=False):
628 return self.genericList.unselectAll(invisible) 737 return self.genericList.unselectAll(invisible)
652 return self.displayWidget(size, focus).render(size, focus) 761 return self.displayWidget(size, focus).render(size, focus)
653 762
654 def rows(self, size, focus=False): 763 def rows(self, size, focus=False):
655 return self.displayWidget(size, focus).rows(size, focus) 764 return self.displayWidget(size, focus).rows(size, focus)
656 765
657 def displayWidget(self, size, focus): 766 def _getHeight(self, size, focus):
658 list_size = sum([wid.rows(size, focus) for wid in self.genericList.content]) 767 list_size = sum([wid.rows(size, focus) for wid in self.genericList.content])
659 height = min(list_size,self.max_height) or 1 768 height = min(list_size,self.max_height) or 1
660 return urwid.BoxAdapter(self.genericList, height) 769 return height
770
771 def displayWidget(self, size, focus):
772 return urwid.BoxAdapter(self.genericList, self._getHeight(size, focus))
661 773
662 774
663 ## MISC ## 775 ## MISC ##
664 776
665 class NotificationBar(urwid.WidgetWrap): 777 class NotificationBar(urwid.WidgetWrap):