Mercurial > urwid-satext
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): |