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 )