~sylvain-pineau/checkbox/show_reports

« back to all changes in this revision

Viewing changes to checkbox_urwid/urwid_interface.py

  • Committer: Sylvain Pineau
  • Date: 2012-02-20 14:39:24 UTC
  • Revision ID: sylvain.pineau@canonical.com-20120220143924-ceg7lrnyzzco2dxu
Provide a test report screen for cli/urwid/gtk ui and a bug report for the gtk ui (to be used in conjunction with checkbox-oem).

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
import urwid
20
20
 
21
21
import re, string
 
22
from operator import itemgetter
22
23
from gettext import gettext as _
23
24
 
24
25
from checkbox.user_interface import (UserInterface, NEXT, PREV,
37
38
               ('button focused', 'white', 'dark blue'),
38
39
               ('highlight', 'black', 'dark cyan'),
39
40
               ('highlight focused', 'white', 'dark blue'),
 
41
               ('fail', 'light red', 'dark cyan'),
 
42
               ('pass', 'light green', 'dark cyan'),
 
43
               ('result', 'light gray', 'dark cyan'),
40
44
               )
 
45
    PALETTE_MEMBERS = set(color_spec[0]
 
46
                          for color_spec in PALETTE)
41
47
    header = None
42
48
    footer = None
43
49
 
368
374
        """
369
375
        Create a tree node and all its children
370
376
        """
371
 
        widget = TreeNodeWidget(name, parent)
 
377
        widget = SelectableTreeNodeWidget(name, parent)
372
378
        urwid.signals.connect_signal(widget, 'change',
373
379
                                     widget.changed_cb, self.walker)
374
380
 
375
381
        if isinstance(data, dict):
376
 
            items = sorted(data.iteritems(), key=lambda item: item[0])
 
382
            items = sorted(data.iteritems(), key=itemgetter(0))
377
383
            for children_name, children_data in items:
378
384
                child_widget = self.create_tree(children_name, children_data, widget)
379
385
                widget.append(child_widget)
402
408
 
403
409
        # Show tree
404
410
        self.option_widgets = []
405
 
        items = sorted(self.options.iteritems(), key=lambda item: item[0])
 
411
        items = sorted(self.options.iteritems(),
 
412
                       key=itemgetter(0))
406
413
        for name, data in items:
407
414
            widget = self.create_tree(name, data)
408
415
            self.option_widgets.append(widget)
409
416
            self.walker.append(widget)
410
417
 
411
418
        self._set_default([node for node in self.walker
412
 
                           if isinstance(node, TreeNodeWidget)],
 
419
                           if isinstance(node, SelectableTreeNodeWidget)],
413
420
                          self.default)
414
421
 
415
422
        # Show buttons
422
429
        self.walker.append(buttons_box)
423
430
 
424
431
 
 
432
class ReportDialog(ChoiceDialog):
 
433
    """
 
434
    Display test results dialog
 
435
    """
 
436
    footer = urwid.AttrMap(urwid.Columns((urwid.Text('Arrow keys/Page Up/Page Down: Move'),
 
437
                                          urwid.Text(''),
 
438
                                          urwid.Text('+/-/Enter/Space: Expand/Collapse'))),
 
439
                           'footer')
 
440
 
 
441
    def __init__(self, text, results):
 
442
        Dialog.__init__(self, text)
 
443
        self.results = results
 
444
 
 
445
 
 
446
    def _get_tree_node(self, node):
 
447
        """
 
448
        Get tree node even if a column is wrapping it
 
449
        """
 
450
        if issubclass(type(node), TreeNodeWidget):
 
451
            return node
 
452
        elif issubclass(type(node), urwid.Columns):
 
453
            for widget in child.widget_list:
 
454
                if issubclass(type(widget), TreeNodeWidget):
 
455
                    return widget
 
456
        return node
 
457
 
 
458
 
 
459
    def expand_all_clicked_cb(self, button):
 
460
        """
 
461
        Expand all elements in the tree to see results
 
462
        """
 
463
        for tree_node in [self._get_tree_node(node)
 
464
                          for node in self.root_nodes]:
 
465
            tree_node.expand(expand_all=True)
 
466
 
 
467
 
 
468
    def collapse_all_clicked_cb(self, button):
 
469
        """
 
470
        Collapse all elements in the tree to see results
 
471
        """
 
472
        for tree_node in [self._get_tree_node(node)
 
473
                          for node in self.root_nodes]:
 
474
            tree_node.collapse(collapse_all=True)
 
475
 
 
476
 
 
477
    def next_button_clicked_cb(self, button):
 
478
        """
 
479
        Set direction, response and exit
 
480
        """
 
481
        self.direction = NEXT
 
482
        raise urwid.ExitMainLoop
 
483
 
 
484
 
 
485
    def previous_button_clicked_cb(self, button):
 
486
        """
 
487
        Set direction, response and exit
 
488
        """
 
489
        self.direction = PREV
 
490
        raise urwid.ExitMainLoop
 
491
 
 
492
 
 
493
    def create_tree(self, name, data, parent=None):
 
494
        """
 
495
        Create a tree node and all its children
 
496
        """
 
497
        widget = TreeNodeWidget(name, parent)
 
498
        urwid.signals.connect_signal(widget, 'change',
 
499
                                     widget.changed_cb, self.walker)
 
500
 
 
501
        items = sorted(data.iteritems(), key=itemgetter(0))
 
502
        for child_name, child_data in items:
 
503
            is_suite = all(issubclass(type(value), dict)
 
504
                           for value in child_data.itervalues())
 
505
 
 
506
            if is_suite:
 
507
                child_widget = self.create_tree(child_name,
 
508
                                                child_data,
 
509
                                                widget)
 
510
            else:
 
511
                result=child_data['status']
 
512
 
 
513
                # Use color specification for result
 
514
                # if found or default one
 
515
                attr = (result
 
516
                        if result in self.PALETTE_MEMBERS
 
517
                        else 'result')
 
518
 
 
519
                child_widget = urwid.Columns(
 
520
                    (TreeNodeWidget(child_name, widget),
 
521
                     urwid.AttrMap(urwid.Text((attr, result)),
 
522
                                   'highlight', 'highlight focused')))
 
523
            widget.append(child_widget)
 
524
 
 
525
        return widget
 
526
 
 
527
 
 
528
    def show(self):
 
529
        """
 
530
        Display dialog text, options tree and buttons
 
531
        """
 
532
        # Show text
 
533
        Dialog.show(self)
 
534
 
 
535
        # Show tree
 
536
        items = sorted(self.results.iteritems(),
 
537
                       key=itemgetter(0))
 
538
        for name, data in items:
 
539
            widget = self.create_tree(name, data)
 
540
            self.walker.append(widget)
 
541
 
 
542
        self.root_nodes = [node for node in self.walker]
 
543
 
 
544
        # Show buttons
 
545
        labels = ((_('Expand All'), self.expand_all_clicked_cb),
 
546
                  (_('Collapse All'), self.collapse_all_clicked_cb),
 
547
                  (_('Previous'), self.previous_button_clicked_cb),
 
548
                  (_('Next'), self.next_button_clicked_cb))
 
549
        buttons_box = self.create_buttons(labels)
 
550
        self.walker.append(urwid.Divider())
 
551
        self.walker.append(buttons_box)
 
552
 
 
553
 
425
554
class TreeNodeWidget(urwid.WidgetWrap):
426
555
    """
427
 
    Implementation of a node in a tree that can be selected/deselected
 
556
    Implementation of a node in a tree that can be expanded/unexpanded
428
557
    """
429
558
    signals = ['change']
430
559
 
436
565
 
437
566
        self.expanded = False
438
567
 
439
 
        # Use a checkbox as internal representation of the widget
440
 
        self.checkbox = urwid.CheckBox(self._get_label())
441
 
        w = urwid.AttrMap(self.checkbox, 'highlight', 'highlight focused')
 
568
        w = self._get_widget()
442
569
        super(TreeNodeWidget, self).__init__(w)
443
570
 
444
571
 
 
572
    def _get_widget(self):
 
573
        """
 
574
        Create widget that is wrapped by this class
 
575
        """
 
576
        self.widget = urwid.Text(self._get_label())
 
577
        w = urwid.AttrMap(self.widget, 'highlight', 'highlight focused')
 
578
        return w
 
579
 
 
580
 
 
581
    def _update_label(self):
 
582
        """
 
583
        Update text label
 
584
        """
 
585
        self.widget.set_text(self._get_label())
 
586
 
 
587
 
 
588
    def _get_node(self, child):
 
589
        """
 
590
        Get TreeNode directly without traversing Columns
 
591
        """
 
592
        if issubclass(type(child), TreeNodeWidget):
 
593
            return child
 
594
        elif issubclass(type(child), urwid.Columns):
 
595
            for widget in child.widget_list:
 
596
                if issubclass(type(widget), TreeNodeWidget):
 
597
                    return widget
 
598
        return child
 
599
 
 
600
 
445
601
    def __iter__(self):
446
602
        """
447
603
        Iterate over children nodes
448
604
        """
449
 
        return iter(self.children)
 
605
        return iter([self._get_node(child)
 
606
                     for child in self.children])
450
607
 
451
608
 
452
609
    def __len__(self):
476
633
        return True
477
634
 
478
635
 
479
 
    @property
480
 
    def state(self):
481
 
        """
482
 
        Get state from checkbox widget
483
 
        """
484
 
        return self.checkbox.get_state()
485
 
 
486
 
 
487
 
    @state.setter
488
 
    def state(self, value):
489
 
        """
490
 
        Set state to checkbox widget
491
 
        """
492
 
        self.checkbox.set_state(value)
493
 
 
494
 
 
495
 
    def set_ancestors_state(self, new_state):
496
 
        """
497
 
        Set the state of all ancestors consistently
498
 
        """
499
 
        # If child is set, then all ancestors must be set
500
 
        if new_state:
501
 
            parent = self.parent
502
 
            while parent:
503
 
                parent.state = new_state
504
 
                parent = parent.parent
505
 
        # If child is not set, then all ancestors mustn't be set
506
 
        # unless another child of the ancestor is set
507
 
        else:
508
 
            parent = self.parent
509
 
            while parent:
510
 
                if any((child.state
511
 
                        for child in parent)):
512
 
                    break
513
 
                parent.state = new_state
514
 
                parent = parent.parent
515
 
 
516
 
 
517
 
    def set_children_state(self, new_state):
518
 
        """
519
 
        Set the state of all children recursively
520
 
        """
521
 
        self.state = new_state
522
 
        for child in self:
523
 
            child.set_children_state(new_state)
524
 
 
525
 
 
526
636
    def keypress(self, size, key):
527
637
        """
528
 
        Use key events to select checkbox and expand tree hierarchy
 
638
        Use key events to expand/collapse tree hierarchy
529
639
        """
530
 
 
531
 
        if key == ' ':
532
 
            new_state = not self.state
533
 
            self.state = new_state
534
 
            self.set_children_state(new_state)
535
 
            self.set_ancestors_state(new_state)
536
 
            return None
537
 
        elif self.children:
538
 
            if key in ('+', 'enter') and self.expanded == False:
 
640
        if self.children:
 
641
            if (key in ('+', 'enter', ' ')
 
642
                and self.expanded == False):
539
643
                urwid.signals.emit_signal(self, 'change')
540
644
                return None
541
 
            elif key in ('-', 'enter') and self.expanded == True:
 
645
            elif (key in ('-', 'enter', ' ')
 
646
                  and self.expanded == True):
542
647
                urwid.signals.emit_signal(self, 'change')
543
648
                return None
544
649
 
547
652
 
548
653
    def mouse_event(self, size, event, button, col, row, focus):
549
654
        """
550
 
        Use mouse events to select checkbox and expand tree hierarchy
 
655
        Use mouse events to expand/collapse tree hierarchy
551
656
        """
552
 
        # Left click event
553
 
        if button == 1:
554
 
            new_state = not self.state
555
 
            self.state = new_state
556
 
            self.set_children_state(new_state)
557
 
            self.set_ancestors_state(new_state)
558
657
        # Ignore button release event
559
 
        elif button == 0:
 
658
        if button == 0:
560
659
            pass
561
660
        else:
562
661
            urwid.signals.emit_signal(self, 'change')
581
680
        return label
582
681
 
583
682
 
584
 
    def _update_label(self):
585
 
        """
586
 
        Update text label
587
 
        """
588
 
        self.checkbox.set_label(self._get_label())
 
683
    def collapse(self, collapse_all=False):
 
684
        """
 
685
        Collapse node
 
686
        """
 
687
        if self.expanded == True:
 
688
            urwid.signals.emit_signal(self, 'change')
 
689
 
 
690
        if collapse_all:
 
691
            self._collapse_children()
589
692
 
590
693
 
591
694
    def _collapse_children(self):
593
696
        Collapse all children
594
697
        """
595
698
        for child in self:
 
699
            child._collapse_children()
596
700
            if child.expanded:
597
 
                child._collapse_children()
598
701
                child.expanded = False
599
702
                child._update_label()
600
703
 
601
704
 
 
705
    def expand(self, expand_all=False):
 
706
        """
 
707
        Expand node
 
708
        """
 
709
        if self.expanded == False:
 
710
            urwid.signals.emit_signal(self, 'change')
 
711
 
 
712
            if expand_all:
 
713
                for child in self:
 
714
                    child.expand(expand_all)
 
715
 
 
716
 
602
717
    def changed_cb(self, walker):
603
718
        """
604
719
        Handle node expansion in the tree
610
725
            del_end_position = (del_start_position +
611
726
                                len(self) - 1)
612
727
            del walker[del_start_position:del_end_position]
613
 
            self._collapse_children()
 
728
            #self._collapse_children()
614
729
            self.expanded = False
615
730
        else:
616
731
            insert_position = position + 1
617
732
 
618
733
            # Append widgets to the list
619
 
            walker[insert_position:insert_position] = self.children
 
734
            subtree = list(self._get_subtree())
 
735
            walker[insert_position:insert_position] = subtree
620
736
            self.expanded = True
621
737
 
622
738
        self._update_label()
623
739
 
624
740
 
 
741
    def _get_subtree(self):
 
742
        """
 
743
        Return subtree with expanded children
 
744
        """
 
745
        for child in self.children:
 
746
            yield child
 
747
 
 
748
            child_node = self._get_node(child)
 
749
            if child_node.expanded:
 
750
                for descendant in child._get_subtree():
 
751
                    yield descendant
 
752
 
 
753
 
 
754
class SelectableTreeNodeWidget(TreeNodeWidget):
 
755
    """
 
756
    Implementation of a node in a tree that can be selected/deselected
 
757
    """
 
758
    def __init__(self, name, parent=None):
 
759
        super(SelectableTreeNodeWidget, self).__init__(name, parent)
 
760
 
 
761
 
 
762
    def _get_widget(self):
 
763
        """
 
764
        Create widget that is wrapped by this class
 
765
        """
 
766
        # Use a checkbox to preserve widget selection stat
 
767
        self.widget = urwid.CheckBox(self._get_label())
 
768
        w = urwid.AttrMap(self.widget, 'highlight', 'highlight focused')
 
769
        return w
 
770
 
 
771
 
 
772
    def _update_label(self):
 
773
        """
 
774
        Update text label
 
775
        """
 
776
        self.widget.set_label(self._get_label())
 
777
 
 
778
 
 
779
    @property
 
780
    def state(self):
 
781
        """
 
782
        Get state from checkbox widget
 
783
        """
 
784
        return self.widget.get_state()
 
785
 
 
786
 
 
787
    @state.setter
 
788
    def state(self, value):
 
789
        """
 
790
        Set state to checkbox widget
 
791
        """
 
792
        self.widget.set_state(value)
 
793
 
 
794
 
 
795
    def set_ancestors_state(self, new_state):
 
796
        """
 
797
        Set the state of all ancestors consistently
 
798
        """
 
799
        # If child is set, then all ancestors must be set
 
800
        if new_state:
 
801
            parent = self.parent
 
802
            while parent:
 
803
                parent.state = new_state
 
804
                parent = parent.parent
 
805
        # If child is not set, then all ancestors mustn't be set
 
806
        # unless another child of the ancestor is set
 
807
        else:
 
808
            parent = self.parent
 
809
            while parent:
 
810
                if any((child.state
 
811
                        for child in parent)):
 
812
                    break
 
813
                parent.state = new_state
 
814
                parent = parent.parent
 
815
 
 
816
 
 
817
    def set_children_state(self, new_state):
 
818
        """
 
819
        Set the state of all children recursively
 
820
        """
 
821
        self.state = new_state
 
822
        for child in self:
 
823
            child.set_children_state(new_state)
 
824
 
 
825
 
 
826
    def keypress(self, size, key):
 
827
        """
 
828
        Use key events to select checkbox and expand tree hierarchy
 
829
        """
 
830
 
 
831
        if key == ' ':
 
832
            new_state = not self.state
 
833
            self.state = new_state
 
834
            self.set_children_state(new_state)
 
835
            self.set_ancestors_state(new_state)
 
836
            return None
 
837
 
 
838
        return super(SelectableTreeNodeWidget, self).keypress(size, key)
 
839
 
 
840
 
 
841
    def mouse_event(self, size, event, button, col, row, focus):
 
842
        """
 
843
        Use mouse events to select checkbox and expand tree hierarchy
 
844
        """
 
845
        # Left click event
 
846
        if button == 1:
 
847
            new_state = not self.state
 
848
            self.state = new_state
 
849
            self.set_children_state(new_state)
 
850
            self.set_ancestors_state(new_state)
 
851
            return True
 
852
 
 
853
        return (super(SelectableTreeNodeWidget, self)
 
854
                .mouse_event(size, event, button, col, row, focus))
 
855
 
 
856
 
625
857
class ProgressDialog(Dialog):
626
858
    """
627
859
    Show progress through a bar
753
985
        return dialog.response
754
986
 
755
987
 
 
988
    def show_report(self, text, results):
 
989
        """
 
990
        Show test case results in a tree hierarchy
 
991
        """
 
992
        dialog = ReportDialog(text, results).run()
 
993
        self.direction = dialog.direction
 
994
 
 
995
 
756
996
    def show_test(self, test, runner):
757
997
        """
758
998
        Show test description, radio buttons to set result
792
1032
        test['data'] = dialog.input
793
1033
        test['status'] = ANSWER_TO_STATUS[answer]
794
1034
        self.direction = dialog.direction
795
 
        return self.response
 
1035
        return dialog.response
796
1036
 
797
1037
 
798
1038
    def show_info(self, text, options=[], default=None):