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