1# ----------------------------------------------------------------------
2# Curses::UI::Menubar
3# Curses::UI::MenuListbox
4#
5# (c) 2001-2002 by Maurice Makaay. All rights reserved.
6# This file is part of Curses::UI. Curses::UI is free software.
7# You can redistribute it and/or modify it under the same terms
8# as perl itself.
9#
10# Currently maintained by Marcus Thiesen
11# e-mail: marcus@cpan.thiesenweb.de
12# ----------------------------------------------------------------------
13
14# TODO: fix dox
15
16# ----------------------------------------------------------------------
17# MenuListbox package
18# ----------------------------------------------------------------------
19
20package Curses::UI::MenuListbox;
21
22use strict;
23use Curses;
24use Curses::UI::Common;
25use Curses::UI::Container;
26use Curses::UI::Window;
27use Curses::UI::Listbox;
28use Curses::UI::Widget;
29
30use vars qw(
31    $VERSION
32    @ISA
33);
34
35$VERSION = '1.10';
36
37@ISA = qw(
38    Curses::UI::Listbox
39    Curses::UI::Common
40    Curses::UI::Window
41);
42
43sub new()
44{
45    my $class = shift;
46
47    my %userargs = @_;
48    keys_to_lowercase(\%userargs);
49
50    my %args = (
51	-menu          => {},     # The menu contents
52        -is_topmenu    => 0,      # First pulldown or not?
53        -menubar       => undef,  # Ref to menubar object
54        -prevobject    => undef,  # Ref to "parent" object (the real parent
55				  # is the rootwindow, but we need to know
56				  # which menulistbox or menubar is parent).
57	-bg            => -1,
58        -fg            => -1,
59	-bbg           => -1,
60        -bfg           => -1,
61
62        %userargs,
63
64        -vscrollbar    => 1,      # Always use a vscrollbar
65        -border        => 1,      # Always show a border
66        -wraparound    => 1,      # Use listbox wraparound
67        -returnaction  => undef,  # Is set by other MenuListboxes
68    );
69
70
71    # First determine the longest label.
72    my $longest = 0;
73    foreach my $item (@{$args{-menu}})
74    {
75        my $l = $item->{-label};
76        $args{-parent}->root->fatalerror(
77		"Missing argument: -label for the MenuListbox"
78        ) unless defined $l;
79        $longest = length($l) if length($l) > $longest;
80    }
81
82    # Increase $longest for some whitespace on the
83    # right side of the labels.
84    $longest++;
85
86    # Now create the values and labels for the listbox.
87    my @values = ();
88    my %labels = ();
89    my $has_submenu = 0;
90    foreach my $item (@{$args{-menu}})
91    {
92        my $l = $item->{-label};
93        if (defined($item->{-submenu}))
94	{
95	    $l = sprintf("%-${longest}s  >>", $l);
96	    $has_submenu++;
97	}
98	push @values, $l;
99    }
100
101    # If there are submenu's, make the $longest variable higher.
102    $longest += 4 if $has_submenu;
103    $args{-values} = \@values;
104
105    # Determine the needed width and hight for the listbox.
106    my $w = width_by_windowscrwidth($longest, %args);
107    my $h = height_by_windowscrheight(@values, %args);
108    $args{-width} = $w;
109    $args{-height} = $h;
110
111    # Check if the menu does fit on the right. If not, try to
112    # shift it to the left as far as needed.
113    if ($args{-x} + $w > $ENV{COLS}) {
114        $args{-x} = $ENV{COLS} - $w;
115        $args{-x} = 0 if $args{-x} < 0;
116    }
117
118    my $this = $class->SUPER::new(%args);
119
120    $this->root->fatalerror(
121	"Missing or illegal argument: -menubar"
122    ) unless defined $args{-menubar} and
123             $args{-menubar}->isa('Curses::UI::Menubar');
124
125    # Clear 'loose-focus' binding, so loosing focus through
126    # the <TAB> key does not work.
127    $this->clear_binding('loose-focus');
128
129    # Create binding routines.
130    $this->set_routine('cursor-left',  \&cursor_left);
131    $this->set_routine('cursor-right', \&cursor_right);
132    $this->set_routine('option-select',\&option_select);
133    $this->set_routine('escape',       \&escape_key);
134
135    # Create bindings.
136    $this->set_binding('escape',       CUI_ESCAPE);
137    $this->set_binding('cursor-left',  KEY_LEFT(), 'h');
138    $this->set_binding('cursor-right', KEY_RIGHT(), 'l');
139
140    if ($Curses::UI::ncurses_mouse) {
141	$this->set_mouse_binding(\&mouse_button1, BUTTON1_CLICKED());
142    }
143
144
145    return $this;
146}
147
148sub escape_key()
149{
150    my $this = shift;
151    $this->{-prevobject}->{-returnaction} = 'COLLAPSE';
152    $this->loose_focus;
153}
154
155sub active_item()
156{
157    my $this = shift;
158    $this->{-menu}->[$this->{-ypos}];
159}
160
161sub cursor_left()
162{
163    my $this = shift;
164    $this->{-prevobject}->{-returnaction} = 'CURSOR_LEFT';
165    $this->loose_focus;
166}
167
168sub cursor_right()
169{
170    my $this = shift;
171
172    # Get the current menu-item.
173    my $item = $this->active_item;
174
175    # This item has a submenu. Open it.
176    if (defined $item->{-submenu})
177    {
178
179        # Compute the (x,y)-position of the new menu.
180        my $x = $this->{-x} + $this->borderwidth;
181        my $y = $this->{-y} + $this->{-ypos};
182
183        # Create the submenu.
184        my $id = "__submenu_$this";
185
186        my $submenu = $this->root->add(
187            $id, 'MenuListbox',
188            -prevobject => $this,
189            -menubar    => $this->{-menubar},
190            -x          => $x,
191            -y          => $y,
192            -menu       => $this->{-menu}->[$this->{-ypos}]->{-submenu},
193            -bg         => $this->{-bg},
194            -fg         => $this->{-fg},
195            -bbg        => $this->{-bbg},
196            -bfg        => $this->{-bfg},
197        );
198
199        # Show the submenu and wait for it to return.
200        $this->{-returnaction} = undef;
201        $submenu->modalfocus;
202        $this->root->delete($id);
203        $this->root->draw;
204
205        # Data set by the previous modal focused menulistbox.
206        my $return = $this->{-returnaction};
207	my $event = $this->{-mouse_event};
208
209        if (defined $return)
210        {
211	    # COLLAPSE:<object>. Collapse further, unless
212	    # $this = <object>.
213	    if ($return =~ /^COLLAPSE\:?(.*)$/)
214	    {
215		if ($this ne $1)
216		{
217		    $this->{-prevobject}->{-returnaction} = $return;
218		    $this->{-prevobject}->{-mouse_event} = $event;
219		    return $this->loose_focus;
220		} else {
221		    $this->focus;
222		    return $this->event_mouse($event);
223		}
224	    }
225	    elsif ($return eq 'COLLAPSE')
226	    {
227                return $this->escape_key;
228	    }
229        }
230
231    # This item has no submenu. Return CURSOR_RIGHT
232    # if this is a topmenu.
233    } elsif ($this->{-is_topmenu}) {
234        $this->{-prevobject}->{-returnaction} = 'CURSOR_RIGHT';
235        $this->loose_focus;
236    }
237
238    return $this;
239}
240
241sub option_select()
242{
243    my $this = shift;
244
245    # Get the current menu-item.
246    my $item = $this->active_item;
247
248    # Submenu selected? Then expand it.
249    if (defined $item->{-submenu}) {
250        return $this->cursor_right;
251    }
252
253    # Let the menubar handle the option that was chosen.
254    my $value = $item->{-value};
255    $this->{-menubar}->menuoption_selected($value);
256
257    # Let the complete menulistbox-hierarchy collapse.
258    $this->{-prevobject}->{-returnaction} = 'COLLAPSE';
259    $this->loose_focus;
260
261    return $this;
262}
263
264sub mouse_button1()
265{
266    my $this = shift;
267    my $event = shift;
268    my $x = shift;
269    my $y = shift;
270
271    # First check if the click is inside the widget (since
272    # this widget has modal focus, all events go to it).
273    my $ev_x = $event->{-x};
274    my $ev_y = $event->{-y};
275    my $tree = $this->root->object_at_xy($this->root, $ev_x, $ev_y);
276
277    # Another object is clicked... Collapse the menu.
278    # If a menu-object was clicked, stay on that object
279    # and rerun the event.
280    if ($this ne $tree->[-1])
281    {
282        $this->{-prevobject}->{-returnaction} = 'COLLAPSE:' . $tree->[-1];
283        $this->{-prevobject}->{-mouse_event} = $event;
284        $this->loose_focus;
285        return $this;
286    }
287
288    # Select listbox entry.
289    $this->{-ypos} = $this->{-active} = $this->{-yscrpos} + $y;
290    $this->layout_content();
291    $this->schedule_draw(1);
292    $this->option_select();
293}
294
295# Let Curses::UI->usemodule() believe that this module
296# was already loaded (usemodule() would else try to
297# require the non-existing file).
298#
299$INC{'Curses/UI/MenuListbox.pm'} = $INC{'Curses/UI/Menubar.pm'};
300
301
302# ----------------------------------------------------------------------
303# Menubar package
304# ----------------------------------------------------------------------
305
306package Curses::UI::Menubar;
307
308use strict;
309use Curses;
310use Curses::UI::Common;
311use Curses::UI::Container;
312use Curses::UI::Window;
313
314use vars qw(
315    $VERSION
316    @ISA
317);
318
319$VERSION = '1.10';
320
321@ISA = qw(
322    Curses::UI::Window
323    Curses::UI::Common
324);
325
326my %routines = (
327    'escape'        => \&escape,
328    'pulldown'      => \&pulldown,
329    'menu-left'     => \&menu_left,
330    'menu-right'    => \&menu_right,
331    'mouse-button1' => \&mouse_button1,
332);
333
334my %bindings = (
335    KEY_DOWN()      => 'pulldown',
336    'j'             => 'pulldown',
337    KEY_ENTER()     => 'pulldown',
338    KEY_LEFT()      => 'menu-left',
339    'h'             => 'menu-left',
340    KEY_RIGHT()     => 'menu-right',
341    'l'             => 'menu-right',
342    CUI_ESCAPE()    => 'escape',
343);
344
345sub new ()
346{
347    my $class = shift;
348
349    my %userargs = @_;
350    keys_to_lowercase(\%userargs);
351
352    my %args = (
353        -parent         => undef,    # the parent window
354        -menu           => [],       # the menu definition
355        -menuhandler    => undef,    # a custom menu handler (optional)
356
357	-bg             => -1,
358        -fg             => -1,
359
360        %userargs,
361
362        -routines       => {%routines},
363        -bindings       => {%bindings},
364
365        -width          => undef,    # Always use the full width
366        -height         => 1,        # Always use height = 1
367        -focus          => 0,
368        -nocursor       => 1,        # This widget does not use a cursor
369        -x              => 0,
370        -y              => 0,
371        -border         => 0,
372        -selected       => 0,
373        -returnaction   => undef,    # is set by MenuListboxes.
374        -menuoption     => undef,    # the value for the chosen option
375                                     # (is also set by MenuListboxes).
376        -is_expanded    => 0,        # let show focused on expand
377
378    );
379
380    my $this = $class->SUPER::new( %args );
381    $this->layout;
382
383    if ($Curses::UI::ncurses_mouse) {
384	$this->set_mouse_binding('mouse-button1', BUTTON1_CLICKED());
385    }
386
387    return $this;
388}
389
390sub escape()
391{
392    my $this = shift;
393    $this->loose_focus;
394}
395
396sub layout()
397{
398    my $this = shift;
399    $this->SUPER::layout or return;
400    return $this;
401}
402
403sub draw()
404{
405    my $this = shift;
406    my $no_doupdate = shift || 0;
407
408    $this->SUPER::draw(1) or return $this;
409
410    # Create full reverse menubar.
411    $this->{-canvasscr}->attron(A_REVERSE);
412
413    # Let there be color
414    if ($Curses::UI::color_support) {
415	my $co = $Curses::UI::color_object;
416	my $pair = $co->get_color_pair(
417			     $this->{-bg},
418			     $this->{-fg});
419
420	$this->{-canvasscr}->attron(COLOR_PAIR($pair));
421    }
422
423    $this->{-canvasscr}->addstr(0, 0, " "x$this->canvaswidth);
424
425    # Create menu-items.
426    my $x = 1;
427    my $idx = 0;
428    foreach my $item (@{$this->{-menu}})
429    {
430        # By default the bar is drawn in reverse.
431        $this->{-canvasscr}->attron(A_REVERSE);
432	# Let there be color
433	if ($Curses::UI::color_support) {
434	    my $co = $Curses::UI::color_object;
435	    my $pair = $co->get_color_pair(
436					   $this->{-bg},
437					   $this->{-fg});
438
439	    $this->{-canvasscr}->attron(COLOR_PAIR($pair));
440	}
441
442
443        # If the bar has focus, the selected item is
444        # show without reverse.
445        if ($this->{-focus} and $idx == $this->{-selected}) {
446            $this->{-canvasscr}->attroff(A_REVERSE);
447        }
448
449        my $label = $item->{-label};
450        $this->{-canvasscr}->addstr(0, $x, " " . $item->{-label} . " ");
451        $x += length($label) + 2;
452
453        $idx++;
454    }
455    $this->{-canvasscr}->attroff(A_REVERSE);
456    $this->{-canvasscr}->move(0,0);
457
458    $this->{-canvasscr}->noutrefresh();
459    doupdate() unless $no_doupdate;
460    return $this;
461}
462
463# This calls the default event_onfocus() routine of
464# the Widget class and it resets the -menuoption
465# data member if the menu is not expanded (this will
466# contain the chosen menuoption at the time the
467# menubar loses focus).
468#
469sub event_onfocus()
470{
471    my $this = shift;
472    unless ($this->{-is_expanded})
473    {
474	$this->{-menuoption} = undef;
475	$this->{-selected}   = 0;
476    }
477    $this->SUPER::event_onfocus;
478}
479
480sub loose_focus()
481{
482    my $this = shift;
483
484    # Draw the menubar like it does not have the focus anymore.
485    $this->{-focus} = 0;
486    $this->draw;
487
488    # Execute callback routine if a menuitem was selected.
489    my $value = $this->{-menuoption};
490    if (defined $value)
491    {
492	# Run the make-your-own-handler handler if defined.
493	if (defined $this->{-menuhandler}) {
494	    $this->{-menuhandler}->($this, $value);
495	}
496
497	# Default handler: If $value has CODE in it, run this code.
498	elsif (defined $value and ref $value eq 'CODE') {
499	    $value->($this);
500	}
501    }
502
503
504    # Focus shifted to another object? Then leave it that way.
505    if ($this->root->focus_path(-1) ne $this) {
506	return $this;
507    }
508    # Else go back to the previous focused window.
509    else
510    {
511	$this->{-focus} = 0;
512	my $prev = $this->root->{-draworder}->[-2];
513	if (defined $prev) {
514	    $this->root->focus($prev);
515	}
516    }
517
518}
519
520# This calls the default event_onblur() routine of the
521# Widget class, but if -is_expanded is set, the widget
522# will still render as a focused widget (this is to
523# let the selected menuoption show focused, even if
524# the focus is set to a menulistbox).
525#
526sub event_onblur()
527{
528    my $this = shift;
529    $this->SUPER::event_onblur;
530
531    if ($this->{-is_expanded}) {
532        $this->{-focus} = 1;
533    }
534
535    return $this;
536}
537
538sub menuoption_selected()
539{
540    my $this = shift;
541    my $value = shift;
542
543    $this->{-menuoption} = $value;
544}
545
546
547sub pulldown()
548{
549    my $this = shift;
550
551    # Find the x position of the selected menu.
552    my $x = 1;
553    my $y = 1;
554
555    # am I in a window
556    if ($this->{-parent}->{-x}) {
557	$x += $this->{-parent}->{-x};
558    }
559
560    # does it have a border
561    if ($this->{-parent}->{-border}) {
562	$x += 1;
563    }
564
565    # find real x value
566    for my $idx (1 .. $this->{-selected})
567    {
568        $x += length($this->{-menu}->[$idx-1]->{-label});
569        $x += 2;
570    }
571
572    # same for y
573    if ($this->{-parent}->{-y}) {
574	$y += $this->{-parent}->{-y};
575    }
576
577    # does it have a border
578    if ($this->{-parent}->{-border}) {
579	$y += 1;
580    }
581
582    # Add the submenu.
583    my $id = "__submenu_$this";
584    my $submenu = $this->root->add(
585        $id, 'MenuListbox',
586        -x          => $x,
587        -y          => $y,
588        -is_topmenu => 1,
589        -menu       => $this->{-menu}->[$this->{-selected}]->{-submenu},
590        -menubar    => $this,
591        -prevobject => $this,
592        -fg         => $this->{-fg},
593	-bg         => $this->{-bg},
594        -bfg        => $this->{-fg},
595        -bbg        => $this->{-bg},
596    );
597
598    # Focus the new window and wait until it returns.
599    $this->{-returnaction} = undef;
600    $this->{-is_expanded}  = 1;
601    $submenu->modalfocus;
602
603    # Remove the submenu.
604    $this->root->delete($id);
605    $this->root->draw;
606
607    $this->{-is_expanded}  = 0;
608
609    # Parameters that are set by the previous modal focused menulistbox.
610    my $return = $this->{-returnaction};
611    my $event = $this->{-mouse_event};
612
613    if (defined $return) {
614	# COLLAPSE:<object>. Collapse further, unless
615	# $this = <object>.
616        if ($return =~ /^COLLAPSE\:?(.*)$/)
617        {
618	    if ($this ne $1)
619	    {
620                $this->{-prevobject}->{-returnaction} = $return;
621                $this->{-prevobject}->{-mouse_event} = $event;
622                return $this->loose_focus;
623	    } else {
624	        $this->focus;
625		return $this->event_mouse($event);
626	    }
627        }
628        elsif ($return eq 'COLLAPSE')
629	{
630	    return $this->loose_focus;
631	}
632        elsif ($return eq 'CURSOR_LEFT')
633        {
634            $this->menu_left;
635            $this->focus;
636            $this->draw;
637            $this->root->feedkey(KEY_DOWN());
638        }
639        elsif ($return eq 'CURSOR_RIGHT')
640        {
641            $this->menu_right;
642            $this->focus;
643            $this->draw;
644            $this->root->feedkey(KEY_DOWN());
645        }
646    }
647
648    return $return;
649}
650
651sub menu_left()
652{
653    my $this = shift;
654    $this->{-selected}--;
655    $this->{-selected} = @{$this->{-menu}}-1
656        if $this->{-selected} < 0;
657    $this->schedule_draw(1);
658    return $this;
659}
660
661sub menu_right()
662{
663    my $this = shift;
664    $this->{-selected}++;
665    $this->{-selected} = 0
666        if $this->{-selected} > (@{$this->{-menu}}-1);
667    $this->schedule_draw(1);
668    return $this;
669}
670
671sub mouse_button1
672{
673    my $this   = shift;
674    my $MEVENT = shift;
675    my $x      = shift;
676    my $y      = shift;
677
678    my $mx = 1;
679    my $idx = 0;
680    foreach my $item (@{$this->{-menu}}) {
681        $mx += length($item->{-label}) + 2;
682	if ($mx > $x) { last }
683        $idx++;
684    }
685    if ($idx > (@{$this->{-menu}}-1)) {$idx--}
686
687    $this->focus();
688    $this->{-selected} = $idx;
689    $this->pulldown();
690    $this->schedule_draw(1);
691
692    return $this;
693}
694
6951;
696
697
698=pod
699
700=head1 NAME
701
702Curses::UI::Menubar - Create and manipulate menubar widgets
703
704=head1 CLASS HIERARCHY
705
706 Curses::UI::Widget
707    |
708    +----Curses::UI::Container
709            |
710            +----Curses::UI::Window
711                    |
712                    +----Curses::UI::Menubar
713
714
715=head1 SYNOPSIS
716
717    use Curses::UI;
718    my $cui = new Curses::UI;
719
720    # define the menu datastructure.
721    my $menu_data = [....];
722
723    my $menu = $cui->add(
724        'menu', 'Menubar',
725        -menu => $menu_data
726    );
727
728    $menu->focus();
729
730
731=head1 DESCRIPTION
732
733This class can be used to add a menubar to Curses::UI. This
734menubar can contain a complete submenu hierarchy. It looks
735(remotely :-) like this:
736
737 -------------------------------------
738 menu1 | menu2 | menu3 | ....
739 -------------------------------------
740       +-------------+
741       |menuitem 1   |
742       |menuitem 2   |+--------------+
743       |menuitem 3 >>||submenuitem 1 |
744       |menuitem 4   ||submenuitem 2 |
745       +-------------+|submenuitem 3 |
746                      |submenuitem 4 |
747                      |submenuitem 5 |
748                      +--------------+
749
750
751See exampes/demo-Curses::UI::Menubar in the distribution
752for a short demo.
753
754
755
756=head1 STANDARD OPTIONS
757
758This class does not use any of the standard options that
759are provided by L<Curses::UI::Widget>.
760
761
762=head1 WIDGET-SPECIFIC OPTIONS
763
764There is only one option: B<-menu>. The value for this
765option is an ARRAYREF. This ARRAYREF behaves exactly
766like the one that is described in
767L<Curses::UI::MenuListbox|Curses::UI::MenuListbox>.
768The difference is that for the top-level menu, you
769will only use -submenu's. Example data structure:
770
771    my $menu1 = [
772        { -label => 'option 1', -value => '1-1' },
773        { -label => 'option 2', -value => '1-2' },
774        { -label => 'option 3', -value => '1-3' },
775    ];
776
777    my $menu2 = [
778        { -label => 'option 1', -value => \&sel1 },
779        { -label => 'option 2', -value => \&sel2 },
780        { -label => 'option 3', -value => \&sel3 },
781    ];
782
783    my $submenu = [
784        { -label => 'suboption 1', -value => '3-3-1' },
785        { -label => 'suboption 2', -callback=> \&do_it },
786    ];
787
788    my $menu3 = [
789        { -label => 'option 1', -value => \&sel2 },
790        { -label => 'option 2', -value => \&sel3 },
791        { -label => 'submenu 1', -submenu => $submenu },
792    ];
793
794    my $menu = [
795        { -label => 'menu 1', -submenu => $menu1 },
796        { -label => 'menu 2', -submenu => $menu2 }
797        { -label => 'menu 3', -submenu => $menu3 }
798    ];
799
800
801
802
803=head1 METHODS
804
805=over 4
806
807=item * B<new> ( OPTIONS )
808
809=item * B<layout> ( )
810
811=item * B<draw> ( BOOLEAN )
812
813=item * B<focus> ( )
814
815These are standard methods. See L<Curses::UI::Widget|Curses::UI::Widget>
816for an explanation of these.
817
818=back
819
820
821
822
823=head1 DEFAULT BINDINGS
824
825=over 4
826
827=item * <B<escape>>
828
829Call the 'escape' routine. This will have the menubar
830loose its focus and return the value 'ESCAPE' to the
831calling routine.
832
833=item * <B<tab>>
834
835Call the 'return' routine. This will have the menubar
836loose its focus and return the value 'LOOSE_FOCUS' to
837the calling routine.
838
839=item * <B<cursor-down>>, <B<j>>, <B<enter>>
840
841Call the 'pulldown' routine. This will open the
842menulistbox for the current menu and give that
843menulistbox the focus. What happens after the
844menulistbox loses its focus, depends upon the
845returnvalue of it:
846
847* the value 'CURSOR_LEFT'
848
849  Call the 'cursor-left' routine and after that
850  call the 'pulldown' routine. So this will open
851  the menulistbox for the previous menu.
852
853* the value 'CURSOR_RIGHT'
854
855  Call the 'cursor-right' routine and after that
856  call the 'pulldown' routine. So this will open
857  the menulistbox for the next menu.
858
859* the value 'LOOSE_FOCUS'
860
861  The menubar will keep the focus, but no
862  menulistbox will be open.
863
864* the value 'ESCAPE'
865
866  The menubar will loose its focus and return the
867  value 'ESCAPE' to the calling routine.
868
869* A CODE reference
870
871  The code will be excuted, the menubar will loose its
872  focus and the returnvalue of the CODE will be
873  returned to the calling routine.
874
875* Any other value
876
877  The menubar will loose its focus and the value will
878  be returned to the calling routine.
879
880=item * <B<cursor-left>>, <B<h>>
881
882Call the 'cursor-left' routine. This will select
883the previous menu. If the first menu is already
884selected, the last menu will be selected.
885
886=item * <B<cursor-right>>, <B<l>>
887
888Call the 'cursor-right' routine. This will select
889the next menu. If the last menu is already selected,
890the first menu will be selected.
891
892=back
893
894
895
896
897
898=head1 SEE ALSO
899
900L<Curses::UI>,
901L<Curses::UI::MenuListbox>,
902L<Curses::UI::Listbox>
903
904
905
906
907=head1 AUTHOR
908
909Copyright (c) 2001-2002 Maurice Makaay. All rights reserved.
910
911Maintained by Marcus Thiesen (marcus@cpan.thiesenweb.de)
912
913
914This package is free software and is provided "as is" without express
915or implied warranty. It may be used, redistributed and/or modified
916under the same terms as perl itself.
917
918