File Coverage

lib/Curses/UI/Grid.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Curses::UI::Grid;
2              
3             ###############################################################################
4             # subclass of Curses::UI::Grid is a widget that can be used to display
5             # and manipulate data in grid model
6             #
7             # (c) 2004 by Adrian Witas. All rights reserved.
8             #
9             # This program is free software; you can redistribute it and/or modify it
10             # under the same terms as perl itself.
11             ###############################################################################
12              
13 3     3   104628 use strict;
  3         7  
  3         125  
14 3     3   18 use warnings;
  3         5  
  3         93  
15              
16              
17 3     3   1701 use Curses;
  0            
  0            
18             use Curses::UI::Widget;
19             use Curses::UI::Common;
20              
21             use vars qw(
22             $VERSION
23             @ISA
24             );
25              
26             $VERSION = '0.15';
27              
28              
29             @ISA = qw(
30             Curses::UI::Common
31             Curses::UI::Widget
32             );
33              
34              
35              
36              
37             =head1 NAME
38              
39             Curses::UI::Grid - Create and manipulate data in grid model
40              
41             =head1 CLASS HIERARCHY
42              
43             Curses::UI::Widget
44             |
45             +----Curses::UI::Grid
46              
47              
48             =head1 SYNOPSIS
49              
50             use Curses::UI;
51             my $cui = new Curses::UI;
52             my $win = $cui->add('window_id', 'Window');
53             my $grid =$win->add(
54             'mygrid', 'Grid'
55             -rows => 3,
56             -columns => 5,
57             );
58              
59             # set header desc
60             $grid->set_label("cell$_", "Head $_")
61             for (1 .. 5);
62              
63             # add some data
64             $grid->set_cell_value("row1", "cell$_", "value $_")
65             for 1 .. 5;
66             my $val = $grid->get_value("row1", "cell2");
67              
68              
69             =head1 DESCRIPTION
70              
71              
72             Curses::UI::Grid is a widget that can be used to
73             browsing or manipulate data in grid model
74              
75              
76             See exampes/grid-demo.pl in the distribution for a short demo.
77              
78              
79             =head1 STANDARD OPTIONS
80              
81             -parent, -x, -y, -width, -height, -pad, -padleft,
82             -padright, -padtop, -padbottom, -ipad, -ipadleft,
83             -ipadright, -ipadtop, -ipadbottom, -title,
84             -titlefull-width, -titlereverse, -onfocus, -onblur,
85             -fg,-bg,-bfg,-bbg
86              
87             =head1 WIDGET-SPECIFIC OPTIONS
88              
89             =over
90              
91             =item * B<-basebindings> < HASHREF >
92              
93             Basebindings is assigned to bindings with editbindings
94             if editable option is set.
95              
96             Hash key is a keystroke and the value is a routines that will be bound. In the event key is empty,
97             the corresponding routine will become the default routine for all not mapped keys.
98              
99              
100             B applies to unmatched keystrokes it receives.
101              
102             By default, the following mappings are used for basebindings:
103              
104             KEY ROUTINE
105             ------------------ ----------
106             CUI_TAB next_cell
107             KEY_ENTER() next_cell
108             KEY_BTAB() prev-cell
109             KEY_UP() prev_row
110             KEY_DOWN() next_row
111             KEY_RIGHT() cursor_right
112             KEY_LEFT() cursor_left
113             KEY_HOME() cursor_home
114             KEY_END() cursor_end
115             KEY_PPAGE() grid_pageup
116             KEY_NPAGE() grid_pagedown
117              
118             =cut
119              
120             my %basebindings = (
121             CUI_TAB() => 'next-cell',
122             KEY_ENTER() => 'next-cell',
123             KEY_BTAB() => 'prev-cell',
124             KEY_UP() => 'prev-row',
125             KEY_DOWN() => 'next-row',
126             KEY_RIGHT() => 'cursor-right',
127             KEY_LEFT() => 'cursor-left',
128             KEY_HOME() => 'cursor-home',
129             KEY_END() => 'cursor-end',
130             KEY_PPAGE() => 'grid-pageup',
131             KEY_NPAGE() => 'grid-pagedown',
132             );
133              
134             =item * B<-editindings> < HASHREF >
135              
136             By default, the following mappings are used for basebindings:
137              
138              
139             KEY ROUTINE
140             ------------------ ----------
141             any add_string
142             KEY_DC() delete_character
143             KEY_BACKSPACE() backspace
144             KEY_IC() insert_row
145             KEY_SDC() delete_row
146              
147             =cut
148              
149             my %editbindings = (
150             '' => 'add-string',
151             KEY_IC() => 'insert-row',
152             KEY_SDC() => 'delete-row',
153             KEY_DC() => 'delete-character',
154             KEY_BACKSPACE() => 'backspace',
155             );
156              
157              
158             =item * B<-routines> < HASHREF >
159              
160             ROUTINE ACTION
161             ---------- -------------------------
162             loose_focus loose grid focus
163             first_row make first row active
164             last_row make last row active
165             grid-pageup trigger event -onnextpage
166             grid-pagedown trigger event -onprevpage
167              
168             next_row make next row active
169             prev_row make prev row active
170              
171              
172             next_cell make next cell active
173             prev_cell make prev cell active
174             first_cell make first row active
175             last_cell make last row active
176              
177             cursor_home move cursor into home pos in focused cell
178             cursor_end move cursor into end pos in focused cell
179             cursor_righ move cursor right in focused cell
180             cursor_left move cursor left in focused cell
181             add_string add string to focused cell
182             delete_row delete active row from grid, shift rows upstairs
183             insert_row insert row in current position
184              
185             delete_character delete_character from focused cell
186             backspace delete_character from focused cell
187              
188             =cut
189              
190              
191             my %routines = (
192             'loose-focus' => \&loose_focus,
193             'cursor-right' => \&cursor_right,
194             'cursor-left' => \&cursor_left,
195             'add-string' => \&add_string,
196             'grid-pageup' => \&grid_pageup,
197             'grid-pagedown' => \&grid_pagedown,
198             'cursor-home' => \&cursor_to_home,
199             'cursor-end' => \&cursor_to_end,
200             'next-cell' => \&next_cell,
201             'prev-cell' => \&prev_cell,
202             'next-row' => \&next_row,
203             'prev-row' => \&prev_row,
204             'insert-row' => \&insert_row,
205             'delete-row' => \&delete_row,
206             'delete-character' => \&delete_character,
207             'backspace' => \&backspace,
208             'mouse-button1' => \&mouse_button1,
209             );
210              
211              
212             =item * B<-editable> < BOOLEAN >
213              
214             The grid widget will be created as a editable grid for the truth value,
215             otherwise it will be in read only mode (data viewer)
216             Default value is true.
217              
218              
219             =item * B<-columns> < COLUMNS >
220              
221             This option control how many cell objects should be created for the grid widget.
222             Default value is 0. If this value is set to non FALSE, construtor creates empty cells.
223              
224              
225             =item * B<-rows> < ROWS >
226              
227             This option control how many row objects should be created for the grid widget.
228             Default value is 0. If this value is set to non FALSE, construtor creates empty rows.
229              
230              
231             =item * B<-count> < COUNT >
232              
233             This option store logical number of all rows.
234             It can be used for calculating vertical scroll.
235              
236              
237             =item * B<-page> < NUMBER >
238              
239             This option store logical number of current page.
240             It can be used for calculating vertical scroll.
241              
242             =back
243              
244             =head2 GRID EVENTS
245              
246             =over
247              
248             =item * B<-onnextpage> < CODEREF >
249              
250             This sets the onnextpage event handler for the widget.
251             If the widget trigger event nextpage, the code in CODEREF will
252             be executed.
253              
254             =item * B<-onprevpage> < CODEREF >
255              
256             This sets the onnextpage event handler for the widget.
257             If the widget trigger event previouspage, the code in CODEREF will
258             be executed.
259              
260             =back
261              
262             =head2 GRID-ROW-SPECIFIC OPTIONS
263              
264             =over
265              
266             =item * B<-onrowdraw> < CODEREF >
267              
268             This sets the onrowdraw event handler for the widget.
269             If the widget trigger event rowdraw, the code in CODEREF will
270             be executed.
271             This can be useful for dynamically setting colors
272             appropriate to some conditions.
273              
274             my $grid=$w->add('grid'
275             'Grid',
276             -rows => 3,
277             -columns => 4,
278             -onrowdraw => sub {
279             my $row = shift;
280             #check conditions and set color for row
281             my $value = $row->get_value('cell0');
282             # some stuff here
283             #....
284             if ( .... ) {
285             $row->bg('black');
286             $row->fg('yellow');
287             } else {
288             # return back to origin color
289             $row->bg('');
290             $row->fg('');
291             }
292             },
293             );
294              
295              
296             =item * B<-onrowfocus> < CODEREF >
297              
298             This sets the onrowfocus event handler for the widget.
299             If the widget trigger event rowfocus, the code in CODEREF will
300             be executed.
301              
302             =item * B<-onrowblur> < CODEREF >
303              
304             This sets the onrowblur event handler for the widget.
305             If the widget trigger event rowblur, the code in CODEREF will
306             be executed. The CODEREF can return FALSE to cancel rowblur
307             action and current row will not lose the focus.
308              
309             =item * B<-onrowchange> < CODEREF >
310              
311             This sets the onrowchange event handler for the widget.
312             If the widget trigger event rowchange, the code in CODEREF will
313             be executed. The CODEREF can return FALSE to cancel onrowblur
314             action and current row will not lose the focus.
315              
316             =item * B<-onbeforerowinsert> < CODEREF >
317              
318             This sets the onbeforerowinsert event handler for the widget.
319             If the widget trigger event onbeforerowinsert, the code in CODEREF will
320             be executed. The CODEREF can return FALSE to cancel insert_row action
321             See more about insert_row method.
322              
323             =item * B<-onrowinsert> < CODEREF >
324              
325             This sets the oninsert event handler for the widget.
326             If the row widget trigger event onrowinsert, the code in CODEREF will
327             be executed. See more about insert_row method.
328              
329              
330             =item * B<-onrowdelete> < CODEREF >
331              
332             This sets the onrowdelete event handler for the widget.
333             If the widget trigger event onrowdelete, the code in CODEREF will
334             be executed. The CODEREF can return FALSE to cancel delete_row action
335             See more about delete_row method.
336              
337             =item * B<-onfterrowdelete> < CODEREF >
338             This sets the onrowdelete event handler for the widget.
339             If the widget trigger event onrowdelete, the code in CODEREF will
340             be executed. See more about delete_row method
341              
342             =back
343              
344             =head2 GRID-CELL-SPECIFIC OPTIONS
345              
346             =over
347              
348             =item * B<-oncelldraw> < CODEREF >
349              
350             This sets the oncelldraw event handler for the widget.
351             If the widget trigger event celldraw, the code in CODEREF will
352             be executed. Gets the cell widget reference as its
353             argument.
354              
355              
356             =item * B<-oncellfocus> < CODEREF >
357              
358             This sets the oncellfocus event handler for the widget.
359             If the widget trigger event cellfocus, the code in CODEREF will
360             be executed. Gets the cell widget reference as its
361             argument.
362              
363              
364             =item * B<-oncellblur> < CODEREF >
365              
366             This sets the oncellblur event handler for the widget.
367             If the widget trigger event cellblur, the code in CODEREF will
368             be executed. Gets the cell widget reference as its
369             argument. The CODEREF can return FALSE to cancel oncellblur
370             action and current cell will not lose the focus.
371              
372             my $grid = $w->add('grid'
373             'Grid'
374             -rows => 3,
375             -columns => 4,
376             -oncellblur => sub {
377             my $cell=shift;
378             # some validation
379             if(... ) {
380             return 0; # cancel oncellblur event
381             }
382             $cell;
383             },
384             );
385              
386             =item * B<-oncellchange> < CODEREF >
387              
388             This sets the oncellchange event handler for the widget.
389             If the widget trigger event cellchange, the code in CODEREF will
390             be executed. Gets the cell widget reference as its
391             argument. The CODEREF can return FALSE to cancel oncellblur
392             action and current cell will not lose the focus.
393              
394              
395             my $grid=$w->add('grid'
396             'Grid',
397             -rows => 3,
398             -columns => 4,
399             -oncellblur => sub {
400             my $cell=shift;
401             my $old_value=$cell->text_undo;
402             my $value=$cell->text;
403             # some validation
404             if(... ) {
405             return 0; # cancell oncellchange and oncellblur event
406             }
407             return $cell;
408             }
409             );
410              
411              
412             =item * B<-oncellkeypress> < CODEREF >
413              
414             This sets the oncellkeypress event handler for the widget.
415             If the widget trigger event cellkeypress, the code in CODEREF will
416             be executed. Gets the cell widget reference and added string as its
417             argument. The cellkeypress event is called by method add_string
418             in cell obejct. The CODEREF can return FALSE to cancel add_string action.
419              
420              
421             =item * B<-oncelllayout> < CODEREF >
422              
423             This sets the oncelllayout event handler for the widget.
424             If the widget trigger event cellkeypress, the code in CODEREF will
425             be executed. Gets the cell widget reference and value as its
426             argument. The CODEREF can return any text which will be proceeded
427             insted of the orgin value.
428              
429             my $grid = $w->add('grid'
430             'Grid',
431             -rows => 3,
432             -columns => 4,
433             -oncelllayout => sub {
434             my $cell = shift;
435             my $value = $cell->text;
436             # mask your value
437             ....
438             return $value;
439             }
440             );
441              
442             =cut
443              
444             =back
445              
446             =head2 METHODS
447              
448             =over
449              
450             =item debug_msg
451              
452             Debugs messages.
453              
454             =cut
455              
456             sub debug_msg {
457             return unless ($Curses::UI::debug);
458             my $caller = (caller(1))[3];
459             my $msg = shift || '';
460             my $indent = ($msg =~ /^(\s+)/ ? $1 : '');
461             $msg =~ s/\n/\nDEBUG: $indent/mg;
462              
463             warn 'DEBUG: ' .
464             ($msg ?
465             "$msg in $caller" :
466             "$caller() called by " . ((caller(2))[3] || 'main')
467             ) .
468             "().\n";
469             }
470              
471              
472             =item new( OPTIONS )
473              
474             Constructs a new grid object.
475             Takes list of options as parameters.
476              
477             =cut
478              
479             sub new {
480             my $class = shift;
481             my %userargs = @_;
482             keys_to_lowercase(\%userargs);
483            
484             # support only arguments listed in @valid_args;
485             my @valid_args = (
486             'x', 'y', 'width', 'height',
487             'pad', 'padleft', 'padright', 'padtop', 'padbottom',
488             'ipad', 'ipadleft', 'ipadright', 'ipadtop', 'ipadbottom',
489             'border','bg', 'fg' ,'bfg' ,'bbg','titlereverse',
490             'intellidraw',
491             'onrowchange',
492             'onfocus','onblur','onnextpage','onprevpage',
493             'onrowdraw','onrowfocus','onrowblur','onrowchange',
494             'onbeforerowinsert','onrowinsert','onrowdelete','onafterrowdelete',
495             'oncelldraw','oncellfocus','oncellblur','oncellchange','oncelllayout','oncellkeypress',
496             'routines', 'basebindings','editbindings',
497             'parent',
498             'rows','columns','editable',
499             'test_more',
500             'sh','sw',
501             'canvasscr',
502             );
503            
504             foreach my $arg (keys %userargs) {
505             unless (grep($arg eq "-$_", @valid_args)) {
506             debug_msg (" deleting invalid arg '$arg'");
507             delete $userargs{$arg};
508             }
509             }
510              
511             my %args = (
512             # Parent info
513             -parent => undef, # the parent object
514              
515             # Position and size
516             -x => 0, # horizontal position (rel. to -window)
517             -y => 0, # vertical position (rel. to -window)
518             -width => undef, # horizontal editsize, undef = stretch
519             -height => undef, # vertical editsize, undef = stretch
520              
521             # Initial state
522             -xpos => 0, # cursor position
523             -ypos => 0, # cursor position
524              
525             # General options
526             -border => undef, # use border?
527             -frozen_with => 0,
528             -x_offsetbar => 0, # show vertical scrollbar
529             -hscrollbar => 0, # show horizontal scrollbar
530             -x_offset => 0, # vertical offset
531             -hscroll => 0, # horizontal offset
532             -editable => 1, # 0 - only used as viewer
533             -focus => 1,
534              
535             # Events
536             # grid event
537             -onfocus => undef,
538             -onblur => undef,
539             -onnextpage => undef,
540             -onprevpage => undef,
541              
542             # row event
543             -onrowblur => undef,
544             -onrowfocus => undef,
545             -onrowchange => undef,
546             -onrowdraw => undef,
547             -onbeforerowinsert => undef,
548             -onrowinsert => undef,
549             -onrowdelete => undef,
550             -onafterrowdelete => undef,
551             # cell event
552             -oncellblur => undef,
553             -oncellfocus => undef,
554             -oncellchange => undef,
555             -oncelldraw => undef,
556             -oncellkeypress => undef,
557             -oncelllayout => undef,
558              
559              
560             # Grid model
561             -columns => 0, # number of coluns
562             -rows => 0, # number of rows
563             -page_size => 0, # max number rows in grid = canvasheight - 2
564             -row_idx_prev => 0, # previous row idx
565             -row_idx => 0, # current index idx
566             -cell_idx_prev => 0, # current cell idx
567             -cell_idx => 0, # current cell idx
568             -count => 0, # numbers of all rows from data source
569             -page => 0, # current page
570             _cells => [], # collection of cells id
571             _rows => [], # collection of rows id
572             -rowid2idx => {},
573             -focusable => 1,
574             -test_more => undef,
575             %userargs,
576             -routines => {%routines}, # binding routines
577              
578             # Init values
579             -focus => 0,
580             );
581              
582              
583             #overwrite base bindings
584             %basebindings = (%{$args{-basebindings}}) if exists($args{-basebindings});
585             #overwrite base editbindings
586             %editbindings = (%{$args{-editbindings}}) if exists($args{-editbindings});
587            
588             # Create the Widget.
589             my $this = $args{-test_more}
590             ? bless {%args}, $class
591             : $class->Curses::UI::Widget::new( %args );
592            
593             $this->set_mouse_binding('mouse-button1', BUTTON1_CLICKED())
594             if ($Curses::UI::ncurses_mouse && ! $this->test_more);
595            
596             $this->initialise(%args);
597             }
598              
599              
600             =item initialise( OPTIONS )
601              
602             Initialises Grid object
603              
604             =cut
605              
606             sub initialise {
607             my ($this, %args) = @_;
608             $this->{-page_size} = $this->canvasheight - 2;
609             $this->add_row('header', %args, -type => 'head');
610             $this->create_default_cells; # if column is not FALSE add empty cells to grid
611             $this->create_default_rows; # if rows is not FALSE add empty rows to grid
612             $this->{-xpos} = 0; # X position for cursor in the document
613             $this->{-ypos} = 0; # Y position for cursor in the document
614             $this->layout_content;
615             $this->editable($args{-editable});
616             return $this;
617             }
618              
619              
620             =item id2cell
621              
622             Return cell object, taks cell id.
623              
624             =cut
625              
626             sub id2cell{
627             my ($this, $id) = @_;
628             $this->{-id2cell}{$id}
629             }
630              
631              
632             =item id
633              
634             =cut
635              
636             sub id {shift()->{-id}}
637              
638              
639             =item readonly
640              
641             =cut
642              
643             sub readonly { shift()->{-readonly} }
644              
645              
646             =item canvasscr
647              
648             Returns canva ref.
649              
650             =cut
651              
652             sub canvasscr { shift()->{-canvasscr} }
653              
654              
655             =item test_more
656              
657             Returns flag if uses test mode.
658              
659             =cut
660              
661             sub test_more { shift()->{-test_more} }
662              
663              
664             =item editable( BOOLEAN )
665              
666             Sets bindings model for editable gird if passed in varaible is true, otherwise
667             read-only model will be set.
668              
669             =cut
670              
671             sub editable {
672             my ($this, $editable) = @_;
673             $this->{-editable} = $editable;
674              
675             if ($editable) {
676             $this->{-bindings} = {
677             %basebindings,
678             %editbindings
679             };
680              
681             } else {
682             $this->{-bindings} = {%basebindings};
683             }
684             $this;
685             }
686              
687              
688             =item bindings
689              
690             Sets/gets binigs for Grid.
691              
692             =cut
693              
694             sub bindings {
695             my ($this, $bindings) = @_;
696             $this->{-bindings} = $bindings
697             if defined $bindings;
698             $this->{-bindings};
699             }
700              
701             =item create_default_rows( OPTIONS )
702              
703             Creates defaults rows for grid.
704             Takes grid options as parameters.
705             Number of rows to be created is taken from
706             -rows options passed in or from -rows grid's attribute
707              
708             =cut
709              
710             sub create_default_rows {
711             my ($this, %userargs) = @_;
712             keys_to_lowercase(\%userargs);
713             my $rows = exists $userargs{-rows}
714             ? $userargs{-rows}
715             : $this->{-rows};
716            
717             for my $i (1 .. $rows ) {
718             $this->add_row("row$i",
719             -type => 'data',
720             -fg => -1,
721             -bg => -1,
722             -cells =>{},
723             );
724             }
725             }
726              
727              
728             =item create_default_cells( OPTIONS )
729              
730             Creates defaults cells for grid.
731             Takes grid options as parameters.
732             Number of rows to be created is taken from
733             -cells options passed in or from -cells grid's attribute
734              
735             =cut
736              
737             sub create_default_cells {
738             my ($this, %userargs) = @_;
739             keys_to_lowercase(\%userargs);
740             my $cols = $this->{-columns};
741             $this->add_cell("cell$_",
742             -width => 10,
743             -align => 'L',
744             %userargs,
745             ) for 1 .. $cols;
746            
747             }
748              
749              
750             =item current_cell_index
751              
752             Sets/gets current cell index.
753              
754             =cut
755              
756             sub current_cell_index {
757             my ($this, $index) = @_;
758             $this->{-cell_idx} = $index
759             if defined $index;
760             $this->{-cell_idx};
761             }
762              
763              
764             =item cell_id ( INDEX )
765              
766             Returns current cell id. Takes cell position index.
767              
768             =cut
769              
770             sub cell_id {
771             my ($this, $index) = @_;
772             $index ||= $this->current_cell_index;
773             my $cells = $this->_cells;
774             $cells->[$index]
775             }
776              
777              
778             =item cell_index_for_id
779              
780             =cut
781              
782             sub cell_index_for_id {
783             my ($this, $id, $index) = @_;
784             my $cellid2idx = $this->{-cellid2idx};
785             $cellid2idx->{$id} = $index
786             if defined $index;
787             $cellid2idx->{$id};
788             }
789              
790              
791             =item cell( ID, cell )
792              
793             Sets/gets cell for passed in id.
794              
795             =cut
796              
797             sub cell {
798             my ($this, $id, $cell) = @_;
799             my $id2cell = $this->{-id2cell};
800             $id2cell->{$id} = $cell
801             if defined $cell;
802             $id2cell->{$id};
803             }
804              
805              
806             =item _cells
807              
808             Returns id cells list.
809              
810             =cut
811              
812             sub _cells {
813             my $this = shift;
814             $this->{_cells};
815             }
816              
817             =item cells_count
818              
819             Returns number of defined cells - 1;
820              
821             =cut
822              
823             sub cells_count {
824             my $this = shift;
825             $#{$this->{_cells}};
826             }
827              
828              
829             =item cell_for_x_position
830              
831             Return cell for passed in x position.
832              
833             =cut
834              
835             sub cell_for_x_position {
836             my ($this, $x) = @_;
837             my $cells = $this->{_cells};
838             my $event_cell = $cells->[0];
839             my $line_width = 0;
840            
841             for my $i (0 ..$#{$cells}) {
842             my $cell = $this->id2cell($cells->[$i]);
843             unless ($cell->hidden) {
844             return $event_cell
845             if ($x >= $line_width && $x < $line_width + $cell->current_width + 1);
846             $line_width += $cell->current_width+1;
847             }
848             }
849             $event_cell;
850             }
851              
852              
853             =item current_row_index
854              
855             Sets/gets current row index.
856              
857             =cut
858              
859             sub current_row_index {
860             my ($this, $index) = @_;
861             $this->{-row_idx} = $index
862             if defined $index;
863             $this->{-row_idx};
864             }
865              
866              
867             =item row_id ( INDEX )
868              
869             Returns current row id. Takes row position index.
870              
871             =cut
872              
873             sub row_id {
874             my ($this, $index) = @_;
875             $index ||= $this->current_row_index;
876             my $rows = $this->_rows;
877             $rows->[$index]
878             }
879              
880              
881             =item row_index_for_id
882              
883             =cut
884              
885             sub row_index_for_id {
886             my ($this, $id, $index) = @_;
887             my $rowid2idx = $this->{-rowid2idx};
888             $rowid2idx->{$id} = $index
889             if defined $index;
890             $rowid2idx->{$id};
891             }
892              
893              
894             =item row( ID, ROW )
895              
896             Sets/gets row for passed in id.
897              
898             =cut
899              
900             sub row {
901             my ($this, $id, $row) = @_;
902             return unless $id;
903             my $id2row = $this->{-id2row};
904             $id2row->{$id} = $row
905             if defined $row;
906             $id2row->{$id};
907             }
908              
909              
910             =item row_for_index
911              
912             Returns row for passed in y position
913              
914             =cut
915              
916             sub row_for_index {
917             my ($this, $index) = @_;
918             if($index <= $this->rows_count) {
919             return $this->row($this->row_id($index));
920             }
921             }
922              
923              
924             =item _rows
925              
926             Returns is rows list.
927              
928             =cut
929              
930             sub _rows {
931             my $this = shift;
932             $this->{_rows} ||= [];
933             }
934              
935              
936             =item rows_count
937              
938             Returns number of defined rows -1;
939              
940             =cut
941              
942             sub rows_count {
943             my ($this) = @_;
944             $#{$this->{_rows}};
945             }
946              
947             =item add_row( ID, OPTIONS )
948              
949             Adds a row to grid. Takes the row id and row options.
950             For available options see Curses::UI::Grid::Row.
951             Returns the row object or undef on failure.
952              
953             =cut
954              
955             sub add_row {
956             my ($this, $id, %args) =@_;
957             my $idx;
958             my $rows = $this->_rows;
959             if ($args{-type} && $args{-type} eq 'head') {
960             $idx = 0;
961             $args{-focusable} = 0;
962            
963             } else {
964             $args{-type} = 'data';
965             $idx = @$rows;
966             $args{-focusable} = 1;
967             $id = "row$idx"
968             unless (defined $id);
969             }
970              
971             return if(exists($this->{-id2row}{$id}));
972             return if($idx > $this->{-page_size});
973             push @$rows, $id;
974             $this->row_index_for_id($id, $idx);
975             $this->row($id, $this->add($id,
976             'Curses::UI::Grid::Row',
977             %args,
978             -x => 0,
979             -id => $id,
980             -y => $idx,
981             ));
982             }
983              
984              
985             =item delete_row( POSITION )
986              
987             This routine will delete row data from passed in possition - default current position.
988             The function calls event onrowdelete, shifts others row up
989             and runs event onafterrowdelete and remove last row if onafterrowdelete
990             CODEREF doesn't return FALSE.
991              
992             Note. If onrowdelete CODEREF returns FALSE then
993             the delete_row routine will be cancelled.
994             Returns TRUE or FALSE on failure.
995              
996             =cut
997              
998             sub delete_row {
999             my ($this, $position) = @_;
1000             my $rows = $this->_rows;
1001             my $row = $this->get_foused_row;
1002              
1003             $this->run_event('-onrowdelete', $row)
1004             or return;
1005            
1006             $position = $this->{-rowid2idx}{$row->{-id}}
1007             unless defined $position;
1008             $this->reorganize_rows($position, -1);
1009             $this->run_event('-onafterrowdelete', $row);
1010             $this->_delete_row;
1011             $row = $this->get_foused_row;
1012             $row->event_onfocus
1013             if ref($row);
1014             $this->draw;
1015             $this;
1016             }
1017              
1018              
1019             =item insert_row( POSITION )
1020              
1021             This routine will add empty row data to grid at cureent position.
1022             All row data below curent row will be shifted down.
1023             Add_row method will be called if rows number is less than page size.
1024             Then onrowinsert event is called with row object as parameter.
1025             Returns the row object or undef on failure.
1026              
1027             =cut
1028              
1029             sub insert_row {
1030             my ($this, $position) = @_;
1031             $position ||= 0;
1032             my $rows = $this->_rows;
1033             my $page_size = $this->{-page_size};
1034             my $row = $this->get_foused_row;
1035              
1036             $row->event_onblur
1037             or return
1038             if (ref($row));
1039              
1040             $this->run_event('-onbeforerowinsert', $this)
1041             or return;
1042              
1043             # add row obj to end
1044             $row = $this->{-id2row}{$$rows[-1]}
1045             if ($position == -1);
1046              
1047             my $current_position =
1048             exists($this->{-rowid2idx}{ $row->{-id}})
1049             ? $this->{-rowid2idx}{$row->{-id}}
1050             : 0;
1051              
1052             if($current_position <= $page_size) {
1053             $this->add_row;
1054             $row = $this->row($$rows[-1])
1055             if ($current_position == 0);
1056             }
1057            
1058             # adding row to end not requires shift rows
1059             if ($position == -1) {
1060             $row = $this->{-id2row}{$$rows[-1]};
1061              
1062             } elsif ($current_position) {
1063             $this->reorganize_rows($current_position, 1);
1064              
1065             }
1066              
1067             # triggers event oninsertrow
1068             $this->run_event('-onrowinsert', $row);
1069              
1070             # makes row focused
1071             $this->draw;
1072             $row->event_onfocus();
1073             $row;
1074             }
1075              
1076              
1077             =item reorganize_rows( POSITION, DIRECTION )
1078              
1079             Rewrites rows properties from grid, starts from passed in position,
1080             direction tells if shifts or pops other rows up or down
1081              
1082             =cut
1083              
1084             sub reorganize_rows {
1085             my ($this, $pos, $dir) = @_;
1086             $dir ||= -1;
1087             my $rows = $this->_rows;
1088             if($dir == 1 && $#{$rows} > 1) {
1089             for (my $i = $#{$rows}; $i >= $pos + 1; $i--) {
1090             my $dst = $this->{-id2row}{$$rows[$i]};
1091             my $src = $this->{-id2row}{$$rows[$i - 1]};
1092             $$dst{-cells} = $$src{-cells};
1093             $$dst{$_} = $$src{$_}
1094             for (qw(-fg_ -bg_ -fg -bg));
1095             }
1096              
1097             my $dst = $this->{-id2row}{$$rows[$pos]};
1098             $$dst{$_} = '' for (qw(-fg_ -bg_));
1099             $$dst{-cells} = {};
1100            
1101             } elsif ($dir == -1) {
1102             for my $i($pos .. $#{$rows} - 1) {
1103             my $dst = $this->{-id2row}{$$rows[$i]};
1104             my $src = $this->{-id2row}{$$rows[$i + 1]};
1105             $$dst{-cells} = $$src{-cells};
1106             for (qw(-fg_ -bg_ -fg -bg)) {
1107             $$dst{$_} = $$src{$_} if($$src{$_});
1108             }
1109             }
1110            
1111             my $dst = $this->{-id2row}{$$rows[-1]};
1112             $$dst{$_} = '' for (qw(-fg_ -bg_));
1113             $$dst{-cells} = {};
1114             }
1115             }
1116              
1117              
1118             =item _delete_row( BOOLEAN )
1119              
1120             This routine will delete last row object from grid.
1121             Redraws immediate grid if passed in value is TRUE.
1122             Returns TRUE or FALSE on failure.
1123              
1124             =cut
1125              
1126             sub _delete_row {
1127             my ($this, $redraw) = @_;
1128             my $rows = $this->_rows;
1129             return
1130             unless $#{$rows};
1131            
1132             my $id = $rows->[-1];
1133             my $focused_row = $this->get_foused_row;
1134            
1135             $this->focus_row($focused_row, 1, -1)
1136             if ($id eq $focused_row->id);
1137            
1138             my $row = $this->row($id);
1139             return
1140             if (ref($row) && ! $row->isa('Curses::UI::Grid::Row'));
1141             pop @$rows;
1142              
1143             $row->cleanup;
1144             $this->draw(1)
1145             if $redraw;
1146              
1147             return 1;
1148             }
1149              
1150              
1151             =item add_cell( ID, OPTIONS )
1152              
1153             This routine will add cell to grid using options in the HASH options.
1154             For available options see Curses::UI::Grid::Cell.
1155             Returns the cell object or undef on failure.
1156              
1157             =cut
1158              
1159             sub add_cell {
1160             my ($this, $id, %options) = @_;;
1161             my $cells = $this->_cells;
1162             my $idx = $#{$cells} + 1;
1163              
1164             $this->{-id2cell}{$id}= $this->add($id, 'Curses::UI::Grid::Cell',
1165             -x => 0,
1166             -focusable => 1,
1167             -id => $id,
1168             %options,
1169             );
1170            
1171             $this->{_cells}[$idx]=$id;
1172             $this->{-cellid2idx}{$id}=$idx;
1173             $this->{-columns}++;
1174             }
1175              
1176              
1177             =item _delete_cell( ID )
1178              
1179             This routine will delete given cell.
1180             Returns TRUE or FALSE on failure.
1181              
1182             =cut
1183              
1184             sub _delete_cell {
1185             my ($this, $id) = @_;
1186             my $cell = $this->id2cell($id);
1187             return 0 unless defined $cell;
1188             my $idx = $this->id2idx($id);
1189             splice(@{$this->{_cells}}, $idx, 1);
1190             $cell->cleanup;
1191             $this->layout_content;
1192             $this;
1193             }
1194              
1195              
1196             =item add( ID, CLASS, OPTIONS )
1197              
1198             Adds cell or row object to grid.
1199              
1200             =cut
1201              
1202             sub add {
1203             my ($this, $id, $class, %options) = @_;
1204             $this->root->usemodule($class)
1205             unless $this->test_more;
1206             my $object = $class->new(
1207             %options,
1208             -parent => $this
1209             );
1210              
1211             # begin by AGX: inherith parent background color!
1212             if (defined($object->{-bg})) {
1213             if ($object->{-bg} eq '-1') {
1214             $object->{-bg} = $this->{-bg}
1215             if defined($this->{-bg});
1216             }
1217             }
1218              
1219             # begin by AGX: inherith parent foreground color!
1220             if (defined( $object->{-fg})) {
1221             if ($object->{-fg} eq '-1') {
1222             $object->{-fg} = $this->{-fg}
1223             if defined($this->{-fg});
1224             }
1225             }
1226             $object;
1227             }
1228              
1229              
1230             =item get_cell( ID | CELL )
1231              
1232             Returns cell, takes cell's id or cell.
1233              
1234             =cut
1235              
1236             sub get_cell {
1237             my ($this, $cell) = @_;
1238             ref($cell) && $cell->isa('Curses::UI::Grid::Cell')
1239             ? $cell
1240             : $this->id2cell($cell);
1241             }
1242              
1243              
1244             =item get_row( ID | ROW )
1245              
1246             Returns row, takes row's id or row object.
1247              
1248             =cut
1249              
1250             sub get_row {
1251             my ($this, $row) = @_;
1252             ref($row) && $row->isa('Curses::UI::Grid::Row')
1253             ? $row
1254             : $this->row($row);
1255             }
1256              
1257              
1258             =item set_label( CELL_ID, LABEL )
1259              
1260             Sets lable for passed in cell object.
1261              
1262             =cut
1263              
1264             sub set_label {
1265             my ($this, $cell, $label) = @_;
1266             my $cell_obj = ref($cell)
1267             ? $cell
1268             : $this->id2cell($cell);
1269             $cell_obj->label($label);
1270             }
1271              
1272              
1273             =item set_cell_width( CELL_ID, WIDTH )
1274              
1275             Sets width for passed in cell object.
1276              
1277             =cut
1278              
1279             sub set_cell_width {
1280             my ($this, $cell, $width) = @_;
1281             my $cell_obj = ref($cell)
1282             ? $cell
1283             : $this->id2cell($cell);
1284             $cell_obj->width($width);
1285             $this->layout_content;
1286             $this;
1287             }
1288              
1289              
1290             =item get_foused_row
1291              
1292             Return focused row if rows defined.
1293              
1294             =cut
1295              
1296             sub get_foused_row {
1297             my $this = shift;
1298             my $rows = $this->_rows;
1299             my $current_row_index = $this->current_row_index;
1300             my $rows_count = $this->rows_count;
1301             return if ($rows_count < $current_row_index);
1302             my $row = $this->row($this->row_id($current_row_index));
1303            
1304             if($row && $row->type eq 'head') {
1305             $row = $this->row($this->row_id($current_row_index + 1));
1306             $row->event_onfocus if $row;
1307             }
1308             if($row->hidden) {
1309             $row = $this->get_last_row;
1310             $row->event_onfocus;
1311             }
1312             $row;
1313             }
1314              
1315              
1316             =item getfocusrow
1317              
1318             See L.
1319              
1320             =cut
1321              
1322             *getfocusrow = \&get_foused_row;
1323              
1324              
1325             =item get_foused_cell
1326              
1327             Return focused cell if cells defined.
1328              
1329             =cut
1330              
1331             sub get_foused_cell {
1332             my $this = shift;
1333             my $cell = $this->id2cell($this->{_cells}[$this->{-cell_idx}]);
1334             return undef unless defined $cell;
1335             $cell->row($this->get_foused_row);
1336             $cell;
1337             }
1338              
1339              
1340             =item getfocuscell
1341              
1342             See L.
1343              
1344             =cut
1345              
1346             *getfocuscell = \&get_foused_cell;
1347              
1348              
1349             =item get_last_row
1350              
1351             Returns last row.
1352              
1353             =cut
1354              
1355             sub get_last_row {
1356             my $this = shift;
1357             my $rows = $this->_rows;
1358             for (my $i = $#{$rows}; $i > 1; $i--) {
1359             my $row = $this->get_row($rows->[$i]);
1360             return $row if ($row->focusable && ! $this->hidden);
1361             }
1362             }
1363              
1364              
1365             =item get_first_row
1366              
1367             Returns first row.
1368              
1369             =cut
1370              
1371             sub get_first_row {
1372             my $this = shift;
1373             $this->get_row($this->row_id(1));
1374             }
1375              
1376              
1377             =item page
1378              
1379             Sets/gets page number.
1380              
1381             =cut
1382              
1383             sub page {
1384             my ($this, $page) = @_;
1385             $this->{-page} = $page
1386             if(defined $page);
1387             $this->{-page};
1388             }
1389              
1390              
1391             =item page_size
1392              
1393             Sets/gets page size.
1394              
1395             =cut
1396              
1397             sub page_size($;) {
1398             my ($this, $page_size) = @_;
1399             $this->{-page_size} = $page_size
1400             if(defined $page_size);
1401             $this->{-page_size};
1402             }
1403              
1404              
1405             =back
1406              
1407             =head3 Layout methods
1408              
1409             =over
1410              
1411             =item layout
1412              
1413             Lays out the grid object with rows and cells, makes sure it fits
1414             on the available screen.
1415              
1416             =cut
1417              
1418             sub layout {
1419              
1420             my $this = shift;
1421             $this->SUPER::layout() or return
1422             unless $this->test_more;
1423             $this->layout_content();
1424             return $this;
1425             }
1426              
1427              
1428             =item layout_content
1429              
1430             =cut
1431              
1432             sub layout_content {
1433             my $this = shift;
1434             return $this if $Curses::UI::screen_too_small;
1435             $this->layout_cells;
1436             $this->layout_vertical_scrollbar;
1437             return $this;
1438             }
1439              
1440              
1441             =item layout_cells
1442              
1443             Horizontal cells layout of the screen.
1444              
1445             =cut
1446              
1447             sub layout_cells {
1448             my $this = shift;
1449             my $canvas_width = $this->canvaswidth;
1450             my $virtual_scroll = $this->x_offset;
1451             my $line_width = 0;
1452             my $frozen_width = 0;
1453             my $cells_width = 0;
1454             $this->clear_vline();
1455             my $cells = $this->{_cells};
1456             for my $i( 0 ..$#{$cells} ) {
1457             my $cell = $this->id2cell($cells->[$i]);
1458             $cell->hide;
1459             my ($cell_width, $is_frozen) = ($cell->width, $cell->frozen);
1460             $cells_width += $cell->width;
1461             if($is_frozen) {
1462             $cell->set_position($line_width++, $cell_width, 1);
1463             $frozen_width += $cell_width + 1;
1464            
1465             } elsif (($virtual_scroll + $frozen_width) <= $line_width) {
1466             if ($canvas_width >= ($line_width + $cell_width)) {
1467             $cell_width = $cell->set_position($line_width++, $cell_width, 1);
1468            
1469             } elsif($canvas_width > ($line_width) ) {
1470             $cell_width = $cell->set_position($line_width++, $canvas_width - $line_width + 1, 1);
1471            
1472             }
1473              
1474             } elsif($virtual_scroll < $cell_width) {
1475             $cell_width = $cell->set_position($line_width++, $cell_width - $virtual_scroll - 1, 0);
1476             $virtual_scroll = 0;
1477            
1478             } else {
1479             $virtual_scroll -= $cell_width;
1480             $cell_width = 0;
1481            
1482             }
1483              
1484             $line_width += $cell_width;
1485             $this->add_vline($line_width - 1)
1486             if ($line_width > 0
1487             && $line_width < $canvas_width
1488             && ! $cell->hidden);
1489             }
1490             $this->layout_horizontal_scrollbar($cells_width);
1491             }
1492              
1493              
1494             =item layout_horizontal_scrollbar
1495              
1496             Layouts horizontal scrollbar, takes total cells width
1497              
1498             =cut
1499              
1500             sub layout_horizontal_scrollbar {
1501             my ($this, $cells_width) = @_;
1502              
1503             if ($this->{-hscrollbar}) {
1504             my $longest_line = $cells_width;
1505             $this->{-hscrolllen} = $longest_line + 1;
1506             $this->{-hscrollpos} = $this->{-xpos} + $this->x_offset;
1507             } else {
1508             $this->{-hscrolllen} = 0;
1509             $this->{-hscrollpos} = 0;
1510             }
1511             }
1512              
1513              
1514             =item layout_vertical_scrollbar
1515              
1516             Layouts vertical scrollbar, takes total cells width
1517              
1518             =cut
1519              
1520             sub layout_vertical_scrollbar {
1521             my ($this) = @_;
1522             if ($this->{-x_offsetbar}) {
1523             $this->{-x_offsetlen} = $this->{-pages} * $this->rows;
1524             $this->{-x_offsetpos} = $this->{-pages} + $this->{-y} - 1;
1525             } else {
1526             $this->{-x_offsetlen} = 0;
1527             $this->{-x_offsetpos} = 0;
1528             }
1529             $this;
1530             }
1531              
1532              
1533             =item get_x_offset_to_obj
1534              
1535             Returns position to the x_offset for a new focued cell, takes a cell object as parameter.
1536              
1537             =cut
1538              
1539             sub get_x_offset_to_obj {
1540             my ($this, $current_cell) = @_;
1541             my $cell_idx_prev = $this->{-cell_idx_prev} || 0;
1542             my $idx = $this->{-cell_idx};
1543             my $cells = $this->{_cells};
1544            
1545            
1546             my $offset_to_current_cell = 0;
1547             my $canvas_width = $this->canvaswidth;
1548             my $forward = $cell_idx_prev < $idx;
1549             my $x_offset = $this->x_offset;
1550             my $result;
1551             my $cell;
1552             for (my $i = 0; $i <= $#{$cells}; $i++) {
1553             $cell = $this->id2cell($$cells[$i]);
1554             $offset_to_current_cell += $cell->width +
1555             ($canvas_width > $offset_to_current_cell ? 1 : 0);
1556             last if($cell eq $current_cell);
1557             }
1558              
1559             if($offset_to_current_cell >= $canvas_width) {
1560             $result = $forward || ! $x_offset
1561             ? $offset_to_current_cell - $canvas_width - 2
1562             : $offset_to_current_cell - $canvas_width + $cell->width;
1563            
1564             } else {
1565             $result = ! $forward || $x_offset
1566             ? 0
1567             : $offset_to_current_cell - $canvas_width + $cell->width;
1568             }
1569              
1570             $result;
1571             }
1572              
1573              
1574             =item vertical_lines
1575              
1576             Gets/sets vertical lines.
1577              
1578             =cut
1579              
1580             sub vertical_lines {
1581             my ($this, $vertical_lines) = @_;
1582             $this->{-vertical_lines} = $vertical_lines
1583             if defined $vertical_lines;
1584             $this->{-vertical_lines};
1585             }
1586              
1587             =item add_vline
1588              
1589             Adds vline that will be drawn.
1590              
1591             =cut
1592              
1593             sub add_vline {
1594             my ($this, $line) = @_;
1595             push @{$this->vertical_lines}, $line;
1596             }
1597              
1598              
1599             =item clear_vline
1600              
1601             Clears vertcal lines position.
1602              
1603             =cut
1604              
1605              
1606             sub clear_vline() {
1607             my $this = shift;
1608             $this->vertical_lines([]);
1609             }
1610              
1611              
1612             =item focus_row( ROW, FORCE, DIRECTION )
1613              
1614             Moves focus to passed in row.
1615              
1616             =cut
1617              
1618             sub focus_row {
1619             my $this = shift;
1620             return $this->focus_obj('row'
1621             ,shift || undef
1622             ,shift
1623             ,shift );
1624             }
1625              
1626              
1627             =item focus_cell( ROW, FORCE, DIRECTION )
1628              
1629             Moves focus to passed in cell.
1630              
1631             =cut
1632              
1633             sub focus_cell {
1634             my $this = shift;
1635             return $this->focus_obj('cell'
1636             ,shift || undef
1637             ,shift
1638             ,shift );
1639              
1640             }
1641              
1642              
1643             =item focus_obj( TYPE, OBJECT, FORCE, DIRECTION )
1644              
1645             Moves focus to passed in object. Takes TYPE that can be row or cell.
1646             Force parameter is boolean and force changing focus in case focus events fails.
1647              
1648             =cut
1649              
1650             sub focus_obj {
1651             my ($this, $type, $focus_to, $forced, $direction) = @_;
1652             $direction = 1
1653             unless defined($direction);
1654             my $idx;
1655             my $index = "-" . $type . "_idx";
1656             my $index_prev = "-" . $type . "_idx_prev";
1657             my $collection = "_" . $type . "s";
1658             my $map2idx = "-" . $type . "id2idx";
1659             my $map2id = "-id2" . $type;
1660             my $onnextpage = 0;
1661             my $onprevpage = 0;
1662              
1663             my $cur_id = $this->{$collection}[$this->{$index}];
1664             my $cur_obj = $this->{$map2id}{$cur_id};
1665              
1666             $focus_to = $cur_id if(! defined $focus_to || ! $focus_to);
1667             $direction = ($direction < 0 ? -1 : $direction );
1668              
1669             # Find the id for a object if the argument
1670             # is an object.
1671             my $new_id = ref $focus_to
1672             ? $focus_to->{-id}
1673             : $focus_to;
1674              
1675              
1676             my $new_obj = $this->{$map2id}{$new_id};
1677              
1678             if(defined $new_id && $direction != 0) {
1679             # Find the new focused object.
1680             my $idx = $this->{$map2idx}{$new_id};
1681             my $start_idx = $idx;
1682              
1683             undef $new_obj;
1684             undef $new_id;
1685              
1686             OBJECT: for(;;) {
1687             $idx += $direction;
1688             if($idx > @{$this->{$collection}} - 1){
1689              
1690             if($type eq 'row') {
1691             # if curent position is less than page size and grid is editable
1692             # and cursor down then add new row
1693             return $this->insert_row(-1)
1694             if ($idx <= $this->{-page_size} && $this->{-editable});
1695             $onnextpage = 1; #set trigger flag to next_page
1696             }
1697             $idx = 0;
1698             }
1699              
1700             if($idx < 0) {
1701             $idx = @{$this->{$collection}}-1 ;
1702             $onprevpage = 1
1703             if ($type eq 'row'); #set trigger flag to prev_page
1704             }
1705              
1706             last if $idx == $start_idx;
1707              
1708             my $test_obj = $this->{$map2id}{$this->{$collection}->[$idx]};
1709             my $test_id = $test_obj->{-id};
1710              
1711             if($test_obj->focusable) {
1712             $new_id = $test_id;
1713             $new_obj = $test_obj;
1714             last OBJECT
1715             }
1716             }
1717             }
1718              
1719             # Change the focus if a focusable objects was found and tiggers not return FALSE.
1720             if($forced or defined $new_obj and $new_obj ne $cur_obj) {
1721             my $result = 1;
1722             # trigger focus to new object if ret isn't FALSE and any page trigger is set
1723             $result=$this->grid_pageup(1)
1724             if ($result && $onprevpage);
1725             $result=$this->grid_pagedown(1)
1726             if ($result && $onnextpage);
1727              
1728             $result = $cur_obj->event_onblur
1729             if ($result && $cur_obj);
1730             $new_obj->event_onfocus
1731             if ($result && ref($new_obj));
1732             }
1733            
1734             $this;
1735             }
1736              
1737              
1738             =item event_onfocus
1739              
1740             Calls supercall events onfocus
1741              
1742             =cut
1743              
1744             sub event_onfocus {
1745             my $this = shift;
1746             my $row = $this->get_foused_row;
1747             $this->focus_row(undef,1,1) if(!ref($row) || $row->type eq 'head');
1748             return $this->SUPER::event_onfocus(@_)
1749             unless $this->test_more;
1750             }
1751              
1752              
1753             =back
1754              
1755             =head3 Draw methods
1756              
1757             =over
1758              
1759             =item draw( BOOLEAN )
1760              
1761             Draws the grid object along with the rows and cells. If BOOLEAN
1762             is true, the screen is not updated after drawing.
1763              
1764             By default, BOOLEAN is true so the screen is updated.
1765              
1766             =cut
1767              
1768             sub draw {
1769             my ($this, $no_doupdate) = @_;
1770             $no_doupdate ||= 0;
1771              
1772             $this->SUPER::draw(1) or return $this
1773             unless $this->test_more;
1774             $this->draw_grid(1);
1775             $this->{-nocursor} = $this->rows_count ? 0 : 1;
1776             doupdate()
1777             if ! $no_doupdate && ! $this->test_more;
1778             $this;
1779             }
1780              
1781              
1782             =item draw_grid
1783              
1784             Draws grid.
1785              
1786             =cut
1787              
1788             sub draw_grid {
1789             my ($this, $no_doupdate) = @_;
1790             $no_doupdate ||= 0;
1791              
1792             $this->draw_header_vline;
1793             my $pair = $this->set_color(
1794             $this->{-fg},
1795             $this->{-bg},
1796             $this->canvasscr
1797             );
1798             my $rows = $this->_rows;
1799             for (my $i = $#{$rows}; $i >= 0; $i--) {
1800             $this->row($$rows[$i])->draw_row;
1801             }
1802             $this->color_off($pair, $this->canvasscr);
1803              
1804             my $cell = $this->get_foused_cell;
1805             my $row = $this->get_foused_row;
1806             my $y = ref($row) ? $row->y : 0;
1807             my $x =ref($cell) ? $cell->xabs_pos : 0;
1808             $this->{-ypos} = $y;
1809             $this->{-xpos} = $x;
1810             $this->canvasscr->move($this->{-ypos}, $this->{-xpos});
1811             $this->canvasscr->noutrefresh
1812             if $no_doupdate;
1813             }
1814              
1815              
1816             =item draw_header_vline
1817              
1818             Draws header lines.
1819              
1820             =cut
1821              
1822             sub draw_header_vline {
1823             my $this = shift;
1824             my $pair = $this->set_color(
1825             $this->{-fg},
1826             $this->{-bg},
1827             $this->canvasscr
1828             );
1829             $this->canvasscr->addstr(0, 0,
1830             sprintf("%-" . ($this->canvaswidth * $this->canvasheight) . "s", ' ')
1831             );
1832             $this->color_off($pair, $this->canvasscr);
1833              
1834             my $fg = $this->{-bfg} && $this->{-bfg} ne '-1'
1835             ? $this->{-bfg}
1836             : $this->{-fg};
1837             my $bg = $this->{-bbg} && $this->{-bbg} ne '-1'
1838             ? $this->{-bbg}
1839             : $this->{-bg};
1840             my $column_number = 0;
1841            
1842             $this->canvasscr->move(1, 0);
1843             $pair = $this->set_color($fg, $bg, $this->canvasscr);
1844             $this->canvasscr->hline(ACS_HLINE, $this->canvaswidth);
1845              
1846             if($this->rows_count > 0 ) {
1847             foreach my $x (@{$this->vertical_lines}) {
1848             $column_number++;
1849             if($this->canvaswidth - 1 == $x && $column_number == $this->{-columns}) {
1850             $this->canvasscr->move(1, $x) ;
1851             $this->canvasscr->vline(ACS_URCORNER, 1);
1852             next;
1853             }
1854              
1855             $this->canvasscr->move(1, $x);
1856             $this->canvasscr->vline(ACS_TTEE, 1);
1857             }
1858             }
1859             $this->color_off($pair, $this->canvasscr);
1860             return $this;
1861             }
1862              
1863              
1864             =item x_offset
1865              
1866             Sets/gets x offset for grid.
1867              
1868             =cut
1869              
1870             sub x_offset {
1871             my ($this, $x_offset) = @_;
1872             if(defined $x_offset) {
1873             $this->{-x_offset} = $x_offset;
1874             $this->layout_content();
1875             $this->draw(1);
1876             }
1877             $this->{-x_offset};
1878             }
1879              
1880              
1881             =back
1882              
1883             =head3 Colors methods
1884              
1885             =over
1886              
1887             =item set_color( BG_COLOR, FG_COLOR, CANVAS )
1888              
1889             Sets color for passed in canvaed object. Returns color pair.
1890              
1891             =cut
1892              
1893             sub set_color($;) {
1894             my ($this, $bg, $fg, $canvas) = @_;
1895             return
1896             unless ref($canvas);
1897             $bg ||= -1;
1898             $fg ||= -1;
1899             return
1900             if($fg eq '-1' || $bg eq '-1');
1901              
1902             my $color_pair;
1903             if($Curses::UI::color_support && $bg && $fg) {
1904             my $color = $Curses::UI::color_object;
1905             $canvas->attron(A_REVERSE);
1906             $color_pair = $color->get_color_pair($fg, $bg);
1907             $canvas->attron(COLOR_PAIR($color_pair));
1908             $this->canvasscr->attron(COLOR_PAIR($color_pair));
1909             }
1910             $color_pair;
1911             }
1912              
1913              
1914             =item color_off( COLOR_PAIR, CANVAS )
1915              
1916             Sets color for passed in canvaed object
1917              
1918             =cut
1919              
1920             sub color_off {
1921             my ($this, $color_pair, $canvas) = @_;
1922             if($Curses::UI::color_support && $color_pair) {
1923             $canvas->attroff(A_REVERSE);
1924             $canvas->attroff(COLOR_PAIR($color_pair));
1925             }
1926             }
1927              
1928              
1929             =back
1930              
1931             =head3 Event functions
1932              
1933             =over
1934              
1935             =item run_event( EVENT, OBJECT)
1936              
1937             Runs passed event, takes event name as first parameter
1938             and row or cell or grid object as caller.
1939              
1940             =cut
1941              
1942             sub run_event {
1943             my ($this, $event, $obj) = @_;
1944             return $this
1945             unless $this->is_event_defined($event);
1946             my $callback = $this->{$event};
1947             if (defined $callback) {
1948             if (ref $callback eq 'CODE') {
1949             return $callback->(ref($obj) ? $obj : $this);
1950            
1951             } else {
1952             $this->root->fatalerror(
1953             "$event callback for $this "
1954             . "($callback) is no CODE reference"
1955             );
1956             }
1957             }
1958             $this;
1959             }
1960              
1961              
1962             =item is_event_defined( EVENT )
1963              
1964             Returns true if passed in event is defined.
1965              
1966             =cut
1967              
1968             sub is_event_defined {
1969             my ($this, $event, $obj) = @_;
1970             my $callback = $this->{$event};
1971             $callback && ref($callback) eq 'CODE';
1972             }
1973              
1974             =back
1975              
1976             =head3 Data maipulation methods
1977              
1978             =over
1979              
1980             =item set_value( ROW , CELL , VALUE )
1981              
1982             This routine will set value for given row and cell.
1983             CELL can by either cell object or id cell.
1984             ROW can by either row object or id row.
1985              
1986             =cut
1987              
1988             sub set_value {
1989             my ($this, $row, $cell, $data) = @_;
1990             $row = $this->get_row($row);
1991             $cell = $this->get_row($cell);
1992             $row->set_value($cell, $data)
1993             if ref($row);
1994             }
1995              
1996              
1997             =item set_values( ROW , HASH )
1998              
1999             This routine will set values for given row.
2000             HASH should contain cells id as keys and coredpondend values.
2001             ROW can by either row object or id row.
2002              
2003             $grid->set_values('row1',cell1=>'cell 1',cell4=>'cell 4');
2004              
2005             $grid->set_values('row1',cell2=>'cell 2',cell3=>'cell 3');
2006              
2007             This method will not affect cells which are not given in HASH.
2008              
2009             =cut
2010              
2011             sub set_values {
2012             my ($this, $row, %data) = @_;
2013             $row = $this->get_row($row);
2014             $row->set_values(%data)
2015             if ref($row);
2016             }
2017              
2018              
2019             =item get_value( ROW, CELL )
2020              
2021             This routine will return value for given row and cell.
2022             CELL can by either cell object or id cell.
2023             ROW can by either row object or id row.
2024              
2025             =cut
2026              
2027             sub get_value {
2028             my ($this, $row, $cell) = @_;
2029             $row = $this->get_row($row);
2030             $cell = $this->get_cell($cell);
2031             $row->get_value($cell)
2032             if ref($row);
2033             }
2034              
2035             =item get_values ( ROW )
2036              
2037             This routine will return HASH values for given row.
2038             HASH will be contain cell id as key.
2039             ROW can by either row object or id row.
2040              
2041             =cut
2042              
2043             sub get_values {
2044             my ($this, $row) = @_;
2045             $row = $this->get_row($row);
2046             $row->get_values
2047             if ref($row);
2048             }
2049              
2050              
2051             =item get_values_ref ( ROW )
2052              
2053             This routine will return HASH reference for given row values.
2054             ROW can by either row object or id row.
2055              
2056             my $ref=$grid->get_values_ref('row1');
2057             $$ref{cell1} = 'cell 1 ';
2058             $$ref{cell2} = 'cell 2 ';
2059             $$ref{cell3} = 'cell 3 ';
2060             $grid->draw();
2061              
2062             Note. After seting values by reference you should call draw method.
2063              
2064             =cut
2065              
2066             sub get_values_ref {
2067             my ($this, $row, $ref) = @_;
2068             $row = $this->get_row($row);
2069             $row->get_values_ref
2070             if ref($row);
2071             }
2072              
2073              
2074             =back
2075              
2076             =head3 Navigation methods.
2077              
2078             =over
2079              
2080             =item next_row
2081              
2082             Return next row object.
2083              
2084             =cut
2085              
2086             sub next_row {
2087             my $this = shift;
2088             my $row = $this->get_foused_row;
2089             $this->focus_row($this->get_foused_row, undef, 1);
2090             $this->get_foused_row;
2091             }
2092              
2093              
2094             =item prev_row
2095              
2096             Return previous row object.
2097              
2098             =cut
2099              
2100             sub prev_row {
2101             my $this = shift;
2102             $this->focus_row($this->get_foused_row,undef,-1);
2103             $this->get_foused_row;
2104             }
2105              
2106              
2107             =item first_row
2108              
2109             Return first row object.
2110              
2111             =cut
2112              
2113             sub first_row {
2114             my $this = shift;
2115             my $row = $this->get_first_row;
2116             $this->focus_row($row,1,0);
2117             $this->get_foused_row;
2118             }
2119              
2120              
2121             =item last_row
2122              
2123             Return last row object.
2124              
2125             =cut
2126              
2127             sub last_row {
2128             my $this = shift;
2129             my $row = $this->get_last_row;
2130             $this->focus_row($row, 1, 0);
2131             $this->get_foused_row;
2132             }
2133              
2134              
2135             =item grid_pageup( BOOLEAN )
2136              
2137             Calls grid onprevpage event.
2138             Redraws immediate grid if passed in value is TRUE.
2139              
2140             =cut
2141              
2142             sub grid_pageup {
2143             my ($this, $do_draw) = @_;
2144             $this->run_event('-onprevpage', $this)
2145             or return;
2146             $this->draw(1)
2147             if $do_draw;
2148             $this->focus_row($this->get_foused_row, 1, 0)
2149             if ($do_draw && $do_draw != 1);
2150             $this;
2151             }
2152              
2153              
2154             =item grid_pagedown( BOOLEAN )
2155              
2156             Calls grid onnextpage event.
2157             Redraws immediate grid if passed in value is TRUE.
2158              
2159             =cut
2160              
2161             sub grid_pagedown($;) {
2162             my ($this, $do_draw) = @_;
2163             $this->run_event('-onnextpage', $this)
2164             or return;
2165             $this->draw(1)
2166             if $do_draw;
2167             $this->focus_row($this->get_foused_row, 1, 0)
2168             if ($do_draw && $do_draw != 1);
2169             $this;
2170             }
2171              
2172              
2173             =item first_cell
2174              
2175             Returns first cell object.
2176              
2177             =cut
2178              
2179             sub first_cell {
2180             my $this = shift;
2181             my $cell = $this->get_cell($this->{_cells}[0]);
2182             $this->focus_cell($cell, 1, 0);
2183             $this->get_foused_cell;
2184             }
2185              
2186              
2187             =item last_cell
2188              
2189             Returns last cell object.
2190              
2191             =cut
2192              
2193             sub last_cell {
2194             my $this = shift;
2195             my $cell=$this->get_cell($this->{_cells}[$#{$this->{_cells}}]);
2196             $this->focus_cell($cell, 1, 0);
2197             $this->get_foused_cell;
2198             }
2199              
2200              
2201             =item prev_cell
2202              
2203             Returns previous cell object.
2204              
2205             =cut
2206              
2207             sub prev_cell {
2208             my $this = shift;
2209             $this->focus_cell($this->get_foused_cell, undef, -1);
2210             $this->get_foused_cell;
2211             }
2212              
2213              
2214             =item next_cell
2215              
2216             Returns next cell object.
2217              
2218             =cut
2219              
2220             sub next_cell {
2221             my $this = shift;
2222             $this->focus_cell($this->get_foused_cell, undef, 1);
2223             $this->get_foused_cell;
2224             }
2225              
2226              
2227             =back
2228              
2229             =head3 Cells methods.
2230              
2231             =over
2232              
2233             =item cursor_left
2234              
2235             Calls cursor left on focsed cell.
2236             Return focued cells.
2237              
2238             =cut
2239              
2240             sub cursor_left {
2241             my $this = shift;
2242             my $cell = $this->get_foused_cell;
2243             $cell->cursor_left;
2244             $cell;
2245             }
2246              
2247              
2248             =item cursor_right
2249              
2250             Calls cursor right on focsed cell.
2251             Return focued cells.
2252              
2253             =cut
2254              
2255             sub cursor_right {
2256             my $this = shift;
2257             my $cell = $this->get_foused_cell;
2258             $cell->cursor_right;
2259             $cell;
2260             }
2261              
2262              
2263             =item cursor_to_home
2264              
2265             Calls cursor home on focsed cell.
2266             Return focued cells.
2267              
2268             =cut
2269              
2270             sub cursor_to_home {
2271             my $this = shift;
2272             my $cell = $this->get_foused_cell;
2273             $cell->cursor_to_home;
2274             $cell;
2275              
2276             }
2277              
2278              
2279             =item cursor_to_end
2280              
2281             Calls cursor end on focsed cell.
2282             Return focued cells.
2283              
2284             =cut
2285              
2286             sub cursor_to_end {
2287             my $this = shift;
2288             my $cell = $this->get_foused_cell;
2289             $cell->cursor_to_end;
2290             $cell;
2291             }
2292              
2293              
2294             =item delete_character
2295              
2296             Calls delete character on focsed cell.
2297             Return focued cells.
2298              
2299             =cut
2300              
2301             sub delete_character {
2302             my $this = shift;
2303             my $cell = $this->get_foused_cell;
2304             $cell->delete_character(@_);
2305             $cell;
2306             }
2307              
2308              
2309             =item backspace
2310              
2311             Calls backspace on focsed cell.
2312             Return focued cells.
2313              
2314             =cut
2315              
2316             sub backspace {
2317             my $this = shift;
2318             my $cell = $this->get_foused_cell;
2319             $cell->backspace(@_);
2320             $cell;
2321             }
2322              
2323              
2324             =item add_string
2325              
2326             Calls add_string on focsed cell.
2327             Return focued cells.
2328              
2329             =cut
2330              
2331             sub add_string {
2332             my $this = shift;
2333             my $cell = $this->get_foused_cell;
2334             $cell->add_string(@_);
2335             $cell;
2336             }
2337              
2338             =back
2339              
2340             =head3 Mouse event method.
2341              
2342             =over
2343              
2344             =item mouse_button1
2345              
2346             =cut
2347              
2348              
2349              
2350             sub mouse_button1 {
2351             my ($this, $event, $x, $y) = @_;
2352             my $row = $this->row_for_index($y -1 );
2353             if( ref $row ) {
2354             my $cell = $this->cell_for_x_position($x);
2355             $this->focus_row($row, undef, 0);
2356             $this->focus_cell($cell, undef, 0)
2357             if ref($cell);
2358             }
2359              
2360             return $this;
2361             }
2362              
2363              
2364             1;
2365              
2366             __END__