Curses-UI-Grid
view release on metacpan or search on metacpan
lib/Curses/UI/Grid.pm view on Meta::CPAN
my $cui = new Curses::UI;
my $win = $cui->add('window_id', 'Window');
my $grid =$win->add(
'mygrid', 'Grid'
-rows => 3,
-columns => 5,
);
# set header desc
$grid->set_label("cell$_", "Head $_")
for (1 .. 5);
# add some data
$grid->set_cell_value("row1", "cell$_", "value $_")
for 1 .. 5;
my $val = $grid->get_value("row1", "cell2");
=head1 DESCRIPTION
Curses::UI::Grid is a widget that can be used to
browsing or manipulate data in grid model
See exampes/grid-demo.pl in the distribution for a short demo.
=head1 STANDARD OPTIONS
-parent, -x, -y, -width, -height, -pad, -padleft,
-padright, -padtop, -padbottom, -ipad, -ipadleft,
-ipadright, -ipadtop, -ipadbottom, -title,
-titlefull-width, -titlereverse, -onfocus, -onblur,
-fg,-bg,-bfg,-bbg
=head1 WIDGET-SPECIFIC OPTIONS
=over
=item * B<-basebindings> < HASHREF >
Basebindings is assigned to bindings with editbindings
if editable option is set.
Hash key is a keystroke and the value is a routines that will be bound. In the event key is empty,
the corresponding routine will become the default routine for all not mapped keys.
B<process_bindings> applies to unmatched keystrokes it receives.
By default, the following mappings are used for basebindings:
KEY ROUTINE
------------------ ----------
CUI_TAB next_cell
KEY_ENTER() next_cell
KEY_BTAB() prev-cell
KEY_UP() prev_row
KEY_DOWN() next_row
KEY_RIGHT() cursor_right
KEY_LEFT() cursor_left
KEY_HOME() cursor_home
KEY_END() cursor_end
KEY_PPAGE() grid_pageup
KEY_NPAGE() grid_pagedown
=cut
my %basebindings = (
CUI_TAB() => 'next-cell',
KEY_ENTER() => 'next-cell',
KEY_BTAB() => 'prev-cell',
KEY_UP() => 'prev-row',
KEY_DOWN() => 'next-row',
KEY_RIGHT() => 'cursor-right',
KEY_LEFT() => 'cursor-left',
KEY_HOME() => 'cursor-home',
KEY_END() => 'cursor-end',
KEY_PPAGE() => 'grid-pageup',
KEY_NPAGE() => 'grid-pagedown',
);
=item * B<-editindings> < HASHREF >
By default, the following mappings are used for basebindings:
KEY ROUTINE
------------------ ----------
any add_string
KEY_DC() delete_character
KEY_BACKSPACE() backspace
KEY_IC() insert_row
KEY_SDC() delete_row
=cut
my %editbindings = (
'' => 'add-string',
KEY_IC() => 'insert-row',
KEY_SDC() => 'delete-row',
KEY_DC() => 'delete-character',
KEY_BACKSPACE() => 'backspace',
);
=item * B<-routines> < HASHREF >
ROUTINE ACTION
---------- -------------------------
loose_focus loose grid focus
first_row make first row active
last_row make last row active
grid-pageup trigger event -onnextpage
grid-pagedown trigger event -onprevpage
next_row make next row active
prev_row make prev row active
next_cell make next cell active
prev_cell make prev cell active
first_cell make first row active
last_cell make last row active
cursor_home move cursor into home pos in focused cell
cursor_end move cursor into end pos in focused cell
cursor_righ move cursor right in focused cell
cursor_left move cursor left in focused cell
add_string add string to focused cell
delete_row delete active row from grid, shift rows upstairs
insert_row insert row in current position
delete_character delete_character from focused cell
backspace delete_character from focused cell
=cut
my %routines = (
'loose-focus' => \&loose_focus,
'cursor-right' => \&cursor_right,
'cursor-left' => \&cursor_left,
'add-string' => \&add_string,
'grid-pageup' => \&grid_pageup,
'grid-pagedown' => \&grid_pagedown,
'cursor-home' => \&cursor_to_home,
'cursor-end' => \&cursor_to_end,
'next-cell' => \&next_cell,
'prev-cell' => \&prev_cell,
'next-row' => \&next_row,
'prev-row' => \&prev_row,
'insert-row' => \&insert_row,
'delete-row' => \&delete_row,
'delete-character' => \&delete_character,
'backspace' => \&backspace,
'mouse-button1' => \&mouse_button1,
);
=item * B<-editable> < BOOLEAN >
The grid widget will be created as a editable grid for the truth value,
otherwise it will be in read only mode (data viewer)
Default value is true.
=item * B<-columns> < COLUMNS >
This option control how many cell objects should be created for the grid widget.
Default value is 0. If this value is set to non FALSE, construtor creates empty cells.
=item * B<-rows> < ROWS >
This option control how many row objects should be created for the grid widget.
Default value is 0. If this value is set to non FALSE, construtor creates empty rows.
=item * B<-count> < COUNT >
This option store logical number of all rows.
It can be used for calculating vertical scroll.
=item * B<-page> < NUMBER >
This option store logical number of current page.
It can be used for calculating vertical scroll.
=back
=head2 GRID EVENTS
=over
=item * B<-onnextpage> < CODEREF >
This sets the onnextpage event handler for the widget.
If the widget trigger event nextpage, the code in CODEREF will
be executed.
=item * B<-onprevpage> < CODEREF >
This sets the onnextpage event handler for the widget.
If the widget trigger event previouspage, the code in CODEREF will
be executed.
lib/Curses/UI/Grid.pm view on Meta::CPAN
warn 'DEBUG: ' .
($msg ?
"$msg in $caller" :
"$caller() called by " . ((caller(2))[3] || 'main')
) .
"().\n";
}
=item new( OPTIONS )
Constructs a new grid object.
Takes list of options as parameters.
=cut
sub new {
my $class = shift;
my %userargs = @_;
keys_to_lowercase(\%userargs);
# support only arguments listed in @valid_args;
my @valid_args = (
'x', 'y', 'width', 'height',
'pad', 'padleft', 'padright', 'padtop', 'padbottom',
'ipad', 'ipadleft', 'ipadright', 'ipadtop', 'ipadbottom',
'border','bg', 'fg' ,'bfg' ,'bbg','titlereverse',
'intellidraw',
'onrowchange',
'onfocus','onblur','onnextpage','onprevpage',
'onrowdraw','onrowfocus','onrowblur','onrowchange',
'onbeforerowinsert','onrowinsert','onrowdelete','onafterrowdelete',
'oncelldraw','oncellfocus','oncellblur','oncellchange','oncelllayout','oncellkeypress',
'routines', 'basebindings','editbindings',
'parent',
'rows','columns','editable',
'test_more',
'sh','sw',
'canvasscr',
);
foreach my $arg (keys %userargs) {
unless (grep($arg eq "-$_", @valid_args)) {
debug_msg (" deleting invalid arg '$arg'");
delete $userargs{$arg};
}
}
my %args = (
# Parent info
-parent => undef, # the parent object
# Position and size
-x => 0, # horizontal position (rel. to -window)
-y => 0, # vertical position (rel. to -window)
-width => undef, # horizontal editsize, undef = stretch
-height => undef, # vertical editsize, undef = stretch
# Initial state
-xpos => 0, # cursor position
-ypos => 0, # cursor position
# General options
-border => undef, # use border?
-frozen_with => 0,
-x_offsetbar => 0, # show vertical scrollbar
-hscrollbar => 0, # show horizontal scrollbar
-x_offset => 0, # vertical offset
-hscroll => 0, # horizontal offset
-editable => 1, # 0 - only used as viewer
-focus => 1,
# Events
# grid event
-onfocus => undef,
-onblur => undef,
-onnextpage => undef,
-onprevpage => undef,
# row event
-onrowblur => undef,
-onrowfocus => undef,
-onrowchange => undef,
-onrowdraw => undef,
-onbeforerowinsert => undef,
-onrowinsert => undef,
-onrowdelete => undef,
-onafterrowdelete => undef,
# cell event
-oncellblur => undef,
-oncellfocus => undef,
-oncellchange => undef,
-oncelldraw => undef,
-oncellkeypress => undef,
-oncelllayout => undef,
# Grid model
-columns => 0, # number of coluns
-rows => 0, # number of rows
-page_size => 0, # max number rows in grid = canvasheight - 2
-row_idx_prev => 0, # previous row idx
-row_idx => 0, # current index idx
-cell_idx_prev => 0, # current cell idx
-cell_idx => 0, # current cell idx
-count => 0, # numbers of all rows from data source
-page => 0, # current page
_cells => [], # collection of cells id
_rows => [], # collection of rows id
-rowid2idx => {},
-focusable => 1,
-test_more => undef,
%userargs,
-routines => {%routines}, # binding routines
# Init values
-focus => 0,
);
#overwrite base bindings
%basebindings = (%{$args{-basebindings}}) if exists($args{-basebindings});
#overwrite base editbindings
%editbindings = (%{$args{-editbindings}}) if exists($args{-editbindings});
# Create the Widget.
my $this = $args{-test_more}
? bless {%args}, $class
: $class->Curses::UI::Widget::new( %args );
$this->set_mouse_binding('mouse-button1', BUTTON1_CLICKED())
if ($Curses::UI::ncurses_mouse && ! $this->test_more);
$this->initialise(%args);
}
=item initialise( OPTIONS )
Initialises Grid object
=cut
sub initialise {
my ($this, %args) = @_;
$this->{-page_size} = $this->canvasheight - 2;
$this->add_row('header', %args, -type => 'head');
$this->create_default_cells; # if column is not FALSE add empty cells to grid
$this->create_default_rows; # if rows is not FALSE add empty rows to grid
$this->{-xpos} = 0; # X position for cursor in the document
$this->{-ypos} = 0; # Y position for cursor in the document
$this->layout_content;
$this->editable($args{-editable});
return $this;
}
=item id2cell
Return cell object, taks cell id.
=cut
sub id2cell{
my ($this, $id) = @_;
$this->{-id2cell}{$id}
}
=item id
=cut
sub id {shift()->{-id}}
=item readonly
=cut
sub readonly { shift()->{-readonly} }
=item canvasscr
Returns canva ref.
=cut
sub canvasscr { shift()->{-canvasscr} }
=item test_more
Returns flag if uses test mode.
=cut
sub test_more { shift()->{-test_more} }
=item editable( BOOLEAN )
Sets bindings model for editable gird if passed in varaible is true, otherwise
read-only model will be set.
=cut
sub editable {
my ($this, $editable) = @_;
$this->{-editable} = $editable;
lib/Curses/UI/Grid.pm view on Meta::CPAN
sub focus_cell {
my $this = shift;
return $this->focus_obj('cell'
,shift || undef
,shift
,shift );
}
=item focus_obj( TYPE, OBJECT, FORCE, DIRECTION )
Moves focus to passed in object. Takes TYPE that can be row or cell.
Force parameter is boolean and force changing focus in case focus events fails.
=cut
sub focus_obj {
my ($this, $type, $focus_to, $forced, $direction) = @_;
$direction = 1
unless defined($direction);
my $idx;
my $index = "-" . $type . "_idx";
my $index_prev = "-" . $type . "_idx_prev";
my $collection = "_" . $type . "s";
my $map2idx = "-" . $type . "id2idx";
my $map2id = "-id2" . $type;
my $onnextpage = 0;
my $onprevpage = 0;
my $cur_id = $this->{$collection}[$this->{$index}];
my $cur_obj = $this->{$map2id}{$cur_id};
$focus_to = $cur_id if(! defined $focus_to || ! $focus_to);
$direction = ($direction < 0 ? -1 : $direction );
# Find the id for a object if the argument
# is an object.
my $new_id = ref $focus_to
? $focus_to->{-id}
: $focus_to;
my $new_obj = $this->{$map2id}{$new_id};
if(defined $new_id && $direction != 0) {
# Find the new focused object.
my $idx = $this->{$map2idx}{$new_id};
my $start_idx = $idx;
undef $new_obj;
undef $new_id;
OBJECT: for(;;) {
$idx += $direction;
if($idx > @{$this->{$collection}} - 1){
if($type eq 'row') {
# if curent position is less than page size and grid is editable
# and cursor down then add new row
return $this->insert_row(-1)
if ($idx <= $this->{-page_size} && $this->{-editable});
$onnextpage = 1; #set trigger flag to next_page
}
$idx = 0;
}
if($idx < 0) {
$idx = @{$this->{$collection}}-1 ;
$onprevpage = 1
if ($type eq 'row'); #set trigger flag to prev_page
}
last if $idx == $start_idx;
my $test_obj = $this->{$map2id}{$this->{$collection}->[$idx]};
my $test_id = $test_obj->{-id};
if($test_obj->focusable) {
$new_id = $test_id;
$new_obj = $test_obj;
last OBJECT
}
}
}
# Change the focus if a focusable objects was found and tiggers not return FALSE.
if($forced or defined $new_obj and $new_obj ne $cur_obj) {
my $result = 1;
# trigger focus to new object if ret isn't FALSE and any page trigger is set
$result=$this->grid_pageup(1)
if ($result && $onprevpage);
$result=$this->grid_pagedown(1)
if ($result && $onnextpage);
$result = $cur_obj->event_onblur
if ($result && $cur_obj);
$new_obj->event_onfocus
if ($result && ref($new_obj));
}
$this;
}
=item event_onfocus
Calls supercall events onfocus
=cut
sub event_onfocus {
my $this = shift;
my $row = $this->get_foused_row;
$this->focus_row(undef,1,1) if(!ref($row) || $row->type eq 'head');
return $this->SUPER::event_onfocus(@_)
unless $this->test_more;
}
=back
=head3 Draw methods
=over
=item draw( BOOLEAN )
Draws the grid object along with the rows and cells. If BOOLEAN
is true, the screen is not updated after drawing.
By default, BOOLEAN is true so the screen is updated.
=cut
sub draw {
my ($this, $no_doupdate) = @_;
$no_doupdate ||= 0;
$this->SUPER::draw(1) or return $this
unless $this->test_more;
$this->draw_grid(1);
$this->{-nocursor} = $this->rows_count ? 0 : 1;
doupdate()
if ! $no_doupdate && ! $this->test_more;
$this;
}
=item draw_grid
Draws grid.
=cut
sub draw_grid {
my ($this, $no_doupdate) = @_;
$no_doupdate ||= 0;
$this->draw_header_vline;
my $pair = $this->set_color(
$this->{-fg},
$this->{-bg},
$this->canvasscr
);
my $rows = $this->_rows;
for (my $i = $#{$rows}; $i >= 0; $i--) {
$this->row($$rows[$i])->draw_row;
}
$this->color_off($pair, $this->canvasscr);
my $cell = $this->get_foused_cell;
my $row = $this->get_foused_row;
my $y = ref($row) ? $row->y : 0;
my $x =ref($cell) ? $cell->xabs_pos : 0;
$this->{-ypos} = $y;
$this->{-xpos} = $x;
$this->canvasscr->move($this->{-ypos}, $this->{-xpos});
$this->canvasscr->noutrefresh
if $no_doupdate;
}
=item draw_header_vline
Draws header lines.
=cut
sub draw_header_vline {
my $this = shift;
my $pair = $this->set_color(
$this->{-fg},
$this->{-bg},
$this->canvasscr
);
$this->canvasscr->addstr(0, 0,
sprintf("%-" . ($this->canvaswidth * $this->canvasheight) . "s", ' ')
);
$this->color_off($pair, $this->canvasscr);
my $fg = $this->{-bfg} && $this->{-bfg} ne '-1'
? $this->{-bfg}
lib/Curses/UI/Grid.pm view on Meta::CPAN
=item first_cell
Returns first cell object.
=cut
sub first_cell {
my $this = shift;
my $cell = $this->get_cell($this->{_cells}[0]);
$this->focus_cell($cell, 1, 0);
$this->get_foused_cell;
}
=item last_cell
Returns last cell object.
=cut
sub last_cell {
my $this = shift;
my $cell=$this->get_cell($this->{_cells}[$#{$this->{_cells}}]);
$this->focus_cell($cell, 1, 0);
$this->get_foused_cell;
}
=item prev_cell
Returns previous cell object.
=cut
sub prev_cell {
my $this = shift;
$this->focus_cell($this->get_foused_cell, undef, -1);
$this->get_foused_cell;
}
=item next_cell
Returns next cell object.
=cut
sub next_cell {
my $this = shift;
$this->focus_cell($this->get_foused_cell, undef, 1);
$this->get_foused_cell;
}
=back
=head3 Cells methods.
=over
=item cursor_left
Calls cursor left on focsed cell.
Return focued cells.
=cut
sub cursor_left {
my $this = shift;
my $cell = $this->get_foused_cell;
$cell->cursor_left;
$cell;
}
=item cursor_right
Calls cursor right on focsed cell.
Return focued cells.
=cut
sub cursor_right {
my $this = shift;
my $cell = $this->get_foused_cell;
$cell->cursor_right;
$cell;
}
=item cursor_to_home
Calls cursor home on focsed cell.
Return focued cells.
=cut
sub cursor_to_home {
my $this = shift;
my $cell = $this->get_foused_cell;
$cell->cursor_to_home;
$cell;
}
=item cursor_to_end
Calls cursor end on focsed cell.
Return focued cells.
=cut
sub cursor_to_end {
my $this = shift;
my $cell = $this->get_foused_cell;
$cell->cursor_to_end;
$cell;
}
=item delete_character
Calls delete character on focsed cell.
Return focued cells.
=cut
sub delete_character {
my $this = shift;
my $cell = $this->get_foused_cell;
$cell->delete_character(@_);
$cell;
}
=item backspace
Calls backspace on focsed cell.
Return focued cells.
=cut
sub backspace {
my $this = shift;
my $cell = $this->get_foused_cell;
$cell->backspace(@_);
$cell;
}
=item add_string
Calls add_string on focsed cell.
Return focued cells.
=cut
sub add_string {
my $this = shift;
my $cell = $this->get_foused_cell;
$cell->add_string(@_);
$cell;
}
=back
=head3 Mouse event method.
=over
=item mouse_button1
=cut
( run in 1.042 second using v1.01-cache-2.11-cpan-39bf76dae61 )