Curses-UI

 view release on metacpan or  search on metacpan

lib/Curses/UI.pm  view on Meta::CPAN


Create a new Curses::UI object:

    my $cui = Curses::UI->new( OPTIONS );

where C<OPTIONS> is one or more of the following.

=head2 -clear_on_exit

If true, Curses::UI will call C<clear> on exit. Defaults to false.

=head2 -color_support

If true, Curses::UI tries to enable color for the
application. Defaults to false.

=head2 -compat

If true, Curses::UI will run in compatibility mode, meaning that only
very simple characters will be used for creating the widgets. Defaults
to false.

=head2 -keydelay

If set to a positive integer, Curses::UI will track elapsed seconds
since the user's last keystroke, preventing timer events from
occurring for the specified number of seconds afterwards. By default
this option is set to '0' (disabled).

=head2 -mouse_support

Curses::UI attempts to auto-discover if mouse support should be
enabled or not. This option allows a hard override. Expects a boolean
value.

=head2 -userdata

Takes a scalar (frequently a hashref) as its argument, and stows that
scalar inside the Curses::UI object where it can be retrieved with the
L<#userdata> method. Handy inside callbacks and the like.

=head2 -default_colors

Directs the underlying Curses library to allow use of default color
pairs on terminals. Is preset to true and you almost certainly don't
want to twiddle it. See C<man use_default_colors> if you think you do.

=cut

sub new {
    my ($class,%userargs) = @_;

    fatalerror("Curses::UI->new can only be called once!")
      if $Curses::UI::initialized;

    &Curses::UI::Common::keys_to_lowercase(\%userargs);

    my %args = (
        -compat        => 0,     # Use compatibility mode?
        -clear_on_exit => 0,     # Clear screen if program exits?
        -cursor_mode   => 0,     # What is the current cursor_mode?
	-debug         => undef, # Turn on debugging mode?
	-keydelay      => 0,     # Track seconds since last keystroke?
	-language      => undef, # Which language to use?
	-mouse_support => 1,     # Do we want mouse support
	-overlapping   => 1,     # Whether overlapping widgets are supported
	-color_support => 0,
	-default_colors=> 1,
        #user data
        -userdata       => undef,    #user internal data
	%userargs,
	-read_timeout   => -1,    # full blocking read by default
	-scheduled_code => [],
	-added_code     => {},
        -lastkey        => 0,     # Last keypress time (set in mainloop)
    );

    $Curses::UI::debug = $args{-debug}
        if defined $args{-debug};

    $Curses::UI::ncurses_mouse = $args{-mouse_support}
        if defined $args{-mouse_support};

    if ($Curses::UI::gpm_mouse && $args{-mouse_support}) {
	$Curses::UI::ncurses_mouse = 1;
	$args{-read_timeout} = 0.25;
    } else {
	$Curses::UI::gpm_mouse = 0;
    }

    my $self = bless { %args }, $class;

    my $lang = new Curses::UI::Language($self->{-language});
    $self->lang($lang);
    print STDERR "DEBUG: Loaded language: $lang->{-lang}\n"
	if $Curses::UI::debug;

    # Color support
    $Curses::UI::color_support = $args{-color_support} if
	defined $args{-color_support};

    $self->layout();

    return $self;
}

DESTROY {
    my $self = shift;
    my $scr = $self->{-canvasscr};
    $scr->delwin() if (defined($scr));
    endwin();
    $Curses::UI::initialized = 0;

    if ($self->{-clear_on_exit})
      {	Curses::erase(); Curses::clear() }
}



=head1 EVENT HANDLING METHODS

lib/Curses/UI.pm  view on Meta::CPAN

    # Curses::UI widget, regarding size, location and
    # drawing area. This will make it possible for
    # child windows / widgets to layout and draw themselves.
    $self->{-width}  = $self->{-w} = $self->{-bw} = $cols;
    $self->{-height} = $self->{-h} = $self->{-bh} = $lines;
    $self->{-x}      = $self->{-y} = 0;
    $self->{-canvasscr} = $root;

    # Walk through all contained objects and let them
    # layout themselves.
    $self->layout_contained_objects;
    $self->draw();

    $Curses::UI::initialized = 1;
    return $self;
}

sub layout_new()
{
    my $self = shift;

    $Curses::UI::screen_too_small = 0;

    # find the terminal size.
    my ($cols,$lines) = GetTerminalSize;
    $ENV{COLS}  = $cols;
    $ENV{LINES} = $lines;

    # Let this object present itself as a standard 
    # Curses::UI widget, regarding size, location and
    # drawing area. This will make it possible for
    # child windows / widgets to layout and draw themselves.
    #
    $self->{-width}  = $self->{-w} = $self->{-bw} = $cols;
    $self->{-height} = $self->{-h} = $self->{-bh} = $lines;
    $self->{-x}      = $self->{-y} = 0;
#    $self->{-canvasscr} = $root;

    # Walk through all contained objects and let them
    # layout themselves.
    $self->layout_contained_objects;

    $Curses::UI::initialized = 1;
    $self->draw();
    return $self;
}


# ----------------------------------------------------------------------
# Event handling
# ----------------------------------------------------------------------


# TODO: document
sub do_one_event(;$)
{
    my $self = shift;
    my $object = shift;
    $object = $self unless defined $object;

    eval {curs_set($self->{-cursor_mode})};

    # gpm mouse?
    if ($Curses::UI::gpm_mouse) {
	$self->handle_gpm_mouse_event($object);
	doupdate();
    }

    # Read a key or use the feeded key.
    my $key = $self->{-feedkey};
    unless (defined $key) {
        $key = $self->get_key($self->{-read_timeout});
    }
    $self->{-feedkey} = undef;

    # If there was a keypress, set -lastkey
    $self->{-lastkey} = time() unless ($key eq '-1');

    # ncurses sends KEY_RESIZE() key on resize. Ignore this key.
    # TODO: Try to redraw and layout everything anew
    # KEY_RESIZE doesn't seem to work right;
    if (Curses->can("KEY_RESIZE")) {
      eval { $key = '-1' if $key eq KEY_RESIZE(); };
    }
    my ($cols,$lines) = GetTerminalSize;
    if ( ($ENV{COLS} != $cols) || ( $ENV{LINES} != $lines )) {
	$self->layout();
	$self->draw;
    }

    # ncurses sends KEY_MOUSE()
    if ($Curses::UI::ncurses_mouse) {
	if ($key eq KEY_MOUSE()) {
	    print STDERR "DEBUG: Got a KEY_MOUSE(), handeling it\n"
		if $Curses::UI::debug;
	    $self->handle_mouse_event($object);
	    doupdate();
	    return $self;
	}
    }

    # If the screen is too small, then <CTRL+C> will exit.
    # Else the next event loop will be started.
    if ($Curses::UI::screen_too_small) {
	exit(1) if $key eq "\cC";
	return $self;
    }

    # Delegate the keypress. This is not done to $self,
    # but to $object, so all events will go to the
    # object that called do_one_event(). This is used to
    # enable modal focusing.
    $object->event_keypress($key) unless $key eq '-1';

    # Execute timer code
    $self->do_timer;

    # Execute one scheduled event;
    if (@{$self->{-scheduled_code}}) {
	my $code = shift @{$self->{-scheduled_code}};
	$code->($self);

lib/Curses/UI.pm  view on Meta::CPAN

    my ($self,$id) = @_;
    if (defined $self->{-timers}->{$id}) {
        $self->{-timers}->{$id}->{-enabled} = 0;
    }
    $self->set_read_timeout;
    return $self;
}

sub enable_timer($;)
{
    my ($self,$id) = @_;
    if (defined $self->{-timers}->{$id}) {
        $self->{-timers}->{$id}->{-enabled} = 1;
    }
    $self->set_read_timeout;
    return $self;
}

sub delete_timer($;)
{
    my ($self,$id) = @_;
    if (defined $self->{-timers}->{$id}) {
        delete $self->{-timers}->{$id};
    }
    $self->set_read_timeout;
    return $self;
}

sub do_timer()
{
    my $self = shift;

    my $now = time();
    my $timers_done = 0;

    # Short-circuit timers if the keydelay hasn't elapsed
    if ($self->{-keydelay}) {
        return $self unless $self->keydelay;
    }

    TIMER: while (my ($id, $config) = each %{$self->{-timers}}) 
    {
        # Skip timer if it is disabled.
        next TIMER unless $config->{-enabled};

        # No -lastrun set? Then do it now.
        unless (defined $config->{-lastrun})
        {
            $config->{-lastrun} = $now; 
            next TIMER;
        }

        if ($config->{-lastrun} <= ($now - $config->{-time})) 
        {
            $config->{-callback}->($self);
            $config->{-lastrun} = $now;
            $timers_done++;
        }
    }

    # Bring the cursor back to the focused object by
    # redrawing it. Due to drawing other objects, it might
    # have moved to another widget or screen location.
    #
    $self->focus_path(-1)->draw if $timers_done;

    return $self;
}

# ----------------------------------------------------------------------
# Mouse events
# ----------------------------------------------------------------------

sub handle_mouse_event()
{
    my $self = shift;
    my $object = shift;
    $object = $self unless defined $object;

    my $MEVENT = 0;
    getmouse($MEVENT);

    # $MEVENT is a struct. From curses.h (note: this might change!):
    #
    # typedef struct
    # {
    #    short id;           /* ID to distinguish multiple devices */
    #	 int x, y, z;        /* event coordinates (character-cell) */
    #	 mmask_t bstate;     /* button state bits */
    # } MEVENT;
    #
    # ---------------
    # s signed short
    # x null byte
    # x null byte
    # ---------------
    # i integer
    # ---------------
    # i integer
    # ---------------
    # i integer
    # ---------------
    # l long
    # ---------------

    my ($id, $x, $y, $z, $bstate) = unpack("sx2i3l", $MEVENT);
    my %MEVENT = (
	-id     => $id,
	-x      => $x,
	-y      => $y,
        -bstate => $bstate,
    );

    # Get the objects at the mouse event position.
    my $tree = $self->object_at_xy($object, $MEVENT{-x}, $MEVENT{-y});

    # Walk through the object tree, top object first.
    foreach my $object (reverse @$tree)
    {
	# Send the mouse-event to the object. 
	# Leave the loop if the object handled the event.

lib/Curses/UI.pm  view on Meta::CPAN

    # if a key is pressed on my Solaris machine. Flushing
    # the input keys solves this. And this is not a bad
    # thing to do during a progress dialog (input is ignored
    # this way).
    $self->flushkeys;

    my $p = $self->getobj("__progress_$self");
    return unless defined $p;
    $p->pos($pos) if defined $pos;
    $p->message($message) if defined $message;
    $p->draw;

    return $self;
}

sub noprogress()
{
    my $self = shift;
    $self->delete("__progress_$self");
    $self->flushkeys;
    $self->draw;
    return $self;
}

sub leave_curses()
{
    my $self = shift;
    def_prog_mode();
    endwin();
}

sub reset_curses()
{
    my $self = shift;
    reset_prog_mode();
    $self->layout(); # In case the terminal has been resized
}

### Color support

sub color() {
    my $self = shift;
    return $Curses::UI::color_object;
}

sub set_color {
    my $self = shift;
    my $co   = shift;

    $Curses::UI::color_object = $co;
}



# ----------------------------------------------------------------------
# Accessor functions
# ----------------------------------------------------------------------

sub compat(;$)        { shift()->accessor('-compat',          shift()) }
sub clear_on_exit(;$) { shift()->accessor('-clear_on_exit',   shift()) }
sub cursor_mode(;$)   { shift()->accessor('-cursor_mode',     shift()) }
sub lang(;$)          { shift()->accessor('-language_object', shift()) }
sub overlapping(;$)   { shift()->accessor('-overlapping',     shift()) }

# TODO: document
sub debug(;$)
{
    my $self  = shift;
    my $value = shift;
    $Curses::UI::debug = $self->accessor('-debug', $value);
}






=head1 CONVENIENCE DIALOG METHODS

=head2 dialog

Use the C<dialog> method to show a dialog window. If you only provide
a single argument, this argument will be used as the message to
show. Example:

    $cui->dialog("Hello, world!");

If you want to have some more control over the dialog window, you will
have to provide more arguments (for an explanation of the arguments
that can be used, see L<Curses::UI::Dialog::Basic>.  Example:

    my $yes = $cui->dialog(
        -message => "Hello, world?",
        -buttons =3D> ['yes','no'],
        -values  => [1,0],
        -title   => 'Question',
    );

    if ($yes) {
        # whatever
    }


=head2 error

The C<error> method will create an error dialog. This is basically a
Curses::UI::Dialog::Basic, but it has an ASCII-art exclamation sign
drawn left to the message. For the rest it's just like
C<dialog>. Example:

    $cui->error("It's the end of the\n"
               ."world as we know it!");

=head2 filebrowser

Creates a file browser dialog. For an explanation of the arguments
that can be used, see L<Curses::UI::Dialog::Filebrowser>.  Example:

    my $file = $cui->filebrowser(
        -path => "/tmp",
        -show_hidden => 1,



( run in 2.983 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )