CursesWidgets
view release on metacpan or search on metacpan
Widgets/Menu.pm view on Meta::CPAN
File => {
ITEMORDER => [qw(Save Quit)],
Save => \&Save,
Quit => \&Quit,
},
CURSORPOS => 'File',
BORDER => 1,
});
The new method instantiates a new Menu object. The only mandatory
key/value pairs in the configuration hash are B<X> and B<Y>. All others
have the following defaults:
Key Default Description
============================================================
INPUTFUNC \&scankey Function to use to scan for keystrokes
FOREGROUND undef Default foreground colour
BACKGROUND 'black' Default background colour
FOCUSSWITCH "\t" Characters which signify end of input
MENUS {} Menu structure
CURSORPOS '' Current position of the cursor
BORDER 0 Avoid window borders
The B<MENUS> option is a hash of hashes, with each hash a separate menu, and
the constituent hashes being a Entry/Function pairs. Each hash requires a
special key/value pair that determines the order of the items when displayed.
Each item is separated by two spaces.
=cut
sub _conf {
# Validates and initialises the new Menu object.
#
# Internal use only.
my $self = shift;
my %conf = (
INPUTFUNC => \&scankey,
FOREGROUND => undef,
BACKGROUND => 'black',
FOCUSSWITCH => "\t",
MENUS => {MENUORDER => []},
BORDER => 0,
EXIT => 0,
CURSORPOS => '',
@_
);
my $err = 0;
# Set the default CURSORPOS if undefined
$conf{CURSORPOS} = $conf{MENUS}{MENUORDER}[0] unless
defined $conf{CURSORPOS};
# Make sure no errors are returned by the parent method
$err = 1 unless $self->SUPER::_conf(%conf);
# Get the updated conf hash
%conf = ();
%conf = %{$self->{CONF}};
# Create a listbox as our popup menu
$self->{LISTBOX} = Curses::Widgets::ListBox->new({
X => 0,
Y => 0,
LISTITEMS => [],
FOREGROUND => $conf{FOREGROUND},
BACKGROUND => $conf{BACKGROUND},
LINES => 3,
COLUMNS => 10,
FOCUSSWITCH => "\n\e",
INPUTFUNC => $conf{INPUTFUNC},
}) unless $err;
return $err == 0 ? 1 : 0;
}
=head2 draw
$menu->draw($mwh, 1);
The draw method renders the menu in its current state. This
requires a valid handle to a curses window in which it will render
itself. The optional second argument, if true, will cause the selection
cursor to be rendered as well.
=cut
sub draw {
my $self = shift;
my $mwh = shift;
my $active = shift;
my $conf = $self->{CONF};
my ($y, $x);
# Get the parent window's (max|beg)yx and save the info
$mwh->getmaxyx($y, $x);
$$conf{COLUMNS} = $x;
$mwh->getbegyx($y, $x);
$self->{BEGYX} = [$y, $x];
# Call the parent's draw method
return $self->SUPER::draw($mwh, $active);
}
sub _geometry {
my $self = shift;
my $conf = $self->{CONF};
my @rv = (1, $$conf{COLUMNS}, 0, 0);
if ($$conf{BORDER}) {
$rv[1] -= 2;
@rv[2,3] = (1, 1);
}
return @rv;
}
sub _cgeometry {
my $self = shift;
my $conf = $self->{CONF};
my @rv = (1, $$conf{COLUMNS}, 0, 0);
$rv[1] -= 2 if $$conf{BORDER};
return @rv;
}
sub _border {
# Make sure no one tries to call this on a menu
}
sub _caption {
# Make sure no one tries to call this on a menu
}
sub _content {
my $self = shift;
my $dwh = shift;
my $conf = $self->{CONF};
my $menu = $$conf{MENUS};
my $label;
# Print the labels
$label = join(' ', @{$$menu{MENUORDER}});
carp ref($self), ": Window not wide enough to display all menus!"
if length($label) > $$conf{COLUMNS} - 2 * $$conf{BORDER};
$dwh->addstr(0, 0, $label);
}
sub _cursor {
my $self = shift;
my $dwh = shift;
my $conf = $self->{CONF};
my $menu = $$conf{MENUS};
my $pos = $$conf{CURSORPOS};
my ($x, $label);
# Get the x coordinate of the cursor and display the cursor
$label = join(' ', @{$$menu{MENUORDER}});
if ($label =~ /^(.*\b)\Q$pos\E\b/) {
$x = length($1);
$dwh->chgat(0, $x, length($pos), A_STANDOUT, select_colour(
@$conf{qw(FOREGROUND BACKGROUND)}), 0);
}
$self->_restore($dwh);
}
=head2 popup
$menu->popup;
This method causes the menu to be displayed. Since, theoretically, the menu
should never be seen unless it's being actively used, we will always assume
that we need to draw a cursor on the list as well.
=cut
sub popup {
my $self = shift;
my $conf = $self->{CONF};
my ($x, $y, $border) = (@$conf{qw(X Y)}, 1);
my $lb = $self->{LISTBOX};
my ($pwh, $items, $cp, $in, $rv, $l);
# Calculate the border column/lines
$border *= 2;
# Create the popup window
unless ($pwh = newwin($lb->getField('LINES') + $border,
$lb->getField('COLUMNS') + $border, $y, $x)) {
carp ref($self), ": Popup creation failed, possible geometry problems";
return;
}
$pwh->keypad(1);
# Render the list box
$rv = $lb->execute($pwh);
# Release the window
$pwh->delwin;
# Exit now if $rv is an escape
return undef if $rv =~ /\e/;
# Return the menu selection
($cp, $items) = $lb->getField(qw(CURSORPOS LISTITEMS));
return $$items[$cp] if (defined $cp && scalar @$items);
}
sub input_key {
# Process input a keystroke at a time.
#
# Internal use only.
my $self = shift;
my $in = shift;
my $conf = $self->{CONF};
my $lb = $self->{LISTBOX};
my ($menus, $pos) = @$conf{qw(MENUS CURSORPOS)};
my ($width, $height, $x, $y, $i, $j, $item, $rv, $sub, $l);
return unless @{$$menus{MENUORDER}};
# Get the current menu index
$i = 0;
while ($i < @{$$menus{MENUORDER}} &&
$$menus{MENUORDER}[$i] ne $pos) { $i++ };
$item = $$menus{MENUORDER}[$i];
# Process special keys
if ($in eq KEY_LEFT) {
--$i;
$i = $#{$$menus{MENUORDER}} if $i < 0;
} elsif ($in eq KEY_RIGHT) {
++$i;
$i = 0 if $i > $#{$$menus{MENUORDER}};
# Display the Menu
} elsif ($in eq KEY_DOWN || $in eq "\n") {
# Calculate and set popup geometry
$x = 0;
for (0..$i) {
$x += (length($$menus{MENUORDER}[$i]) + 2) if $_ != $i;
}
$x += 1 if $$conf{BORDER};
$x += $self->{BEGYX}->[1];
$y = $$conf{BORDER} ? 2 : 1;
$y += $self->{BEGYX}->[0];
@$conf{qw(Y X)} = ($y, $x);
$l = 0;
foreach (@{$$menus{$item}{ITEMORDER}}) {
$l = length($_) if $l < length($_) };
$lb->setField(
LISTITEMS => [ @{$$menus{$item}{ITEMORDER}} ],
LINES => scalar @{$$menus{$item}{ITEMORDER}},
COLUMNS => $l,
CURSORPOS => 0,
);
# Display the popup
$rv = $self->popup;
if (defined $rv) {
$$conf{EXIT} = 1;
# Execute the reference
{
no strict 'refs';
$sub = $$menus{$item}{$rv};
if (defined $sub) {
&$sub();
} else {
carp ref($self), ": undefined subroutine ($rv) call attempted";
}
}
}
# Process normal key strokes
} else {
beep();
}
# Save the changes
$pos = $$menus{MENUORDER}[$i];
$$conf{CURSORPOS} = $pos;
}
=head2 execute
$menu->execute;
This method acts like the standard Curses::Widgets method of the same name,
with the exception being that selection of any menu item will also cause it to
exit (having already called the associated item subroutine).
=cut
sub execute {
my $self = shift;
my $mwh = shift;
my $conf = $self->{CONF};
my $menus = $$conf{MENUS};
my $func = $$conf{'INPUTFUNC'} || \&scankey;
my $regex = $$conf{'FOCUSSWITCH'};
my $key;
# Don't execute unless we have menus to interact with
return unless @{$$menus{MENUORDER}};
# Set the initial focused menu to the first in the list
$$conf{CURSORPOS} = $$menus{MENUORDER}[0];
$$conf{EXIT} = 0;
$self->draw($mwh, 1);
# Enter the scan loop
while (1) {
$key = &$func($mwh);
if (defined $key) {
if (defined $regex) {
return $key if ($key =~ /^[$regex]/ || ($regex =~ /\t/ &&
( run in 0.884 second using v1.01-cache-2.11-cpan-364913b4093 )