1package Prima::Grids;
2
3use strict;
4use warnings;
5use Prima;
6use Prima::IntUtils;
7
8package
9    ci;
10
11BEGIN {
12	eval 'use constant Grid           => 1 + MaxId;' unless exists $ci::{Grid};
13	eval 'use constant IndentCellFore => 2 + MaxId;' unless exists $ci::{IndentCellFore};
14	eval 'use constant IndentCellBack => 3 + MaxId;' unless exists $ci::{IndentCellBack};
15}
16
17package
18    gsci;
19
20use constant COL_INDEX => 0;
21use constant ROW_INDEX => 1;
22use constant V_FULL    => 2;
23use constant V_LEFT    => 3;
24use constant V_BOTTOM  => 4;
25use constant V_RIGHT   => 5;
26use constant V_TOP     => 6;
27use constant V_RECT    => 3,4,5,6;
28use constant LEFT      => 7;
29use constant BOTTOM    => 8;
30use constant RIGHT     => 9;
31use constant TOP       => 10;
32use constant RECT      => 7,8,9,10;
33
34package Prima::AbstractGridViewer;
35use vars qw(@ISA);
36@ISA = qw(Prima::Widget Prima::MouseScroller Prima::GroupScroller Prima::ListBoxUtils);
37
38{
39my %RNT = (
40	%{Prima::Widget-> notification_types()},
41	DrawCell      => nt::Action,
42	GetRange      => nt::Action,
43	GetAlignment  => nt::Action,
44	Measure       => nt::Action,
45	SelectCell    => nt::Default,
46	SetExtent     => nt::Action,
47	Stringify     => nt::Action,
48);
49
50sub notification_types { return \%RNT; }
51}
52
53sub profile_default
54{
55	my $def = $_[ 0]-> SUPER::profile_default;
56	my %prf = (
57		allowChangeCellHeight   => 0,
58		allowChangeCellWidth    => 0,
59		autoHScroll             => 1,
60		autoVScroll             => 1,
61		borderWidth             => 2,
62		cellIndents             => [ 0, 0, 0, 0],
63		clipCells               => 1,
64		columns                 => 1,
65		constantCellWidth       => undef,
66		constantCellHeight      => undef,
67		drawHGrid               => 1,
68		drawVGrid               => 1,
69		focusedCell             => [0, 0],
70		gridColor               => cl::Black,
71		gridGravity             => 3,
72		indentCellBackColor     => cl::Gray,
73		indentCellColor         => cl::Black,
74		hScroll                 => 0,
75		leftCell                => 0,
76		multiSelect             => 0,
77		rows                    => 1,
78		topCell                 => 0,
79		scaleChildren           => 0,
80		scrollBarClass          => 'Prima::ScrollBar',
81		hScrollBarProfile       => {},
82		vScrollBarProfile       => {},
83		selectable              => 1,
84		vScroll                 => 1,
85		widgetClass             => wc::ListBox,
86	);
87	@$def{keys %prf} = values %prf;
88	return $def;
89}
90
91sub profile_check_in
92{
93	my ( $self, $p, $default) = @_;
94	$self-> SUPER::profile_check_in( $p, $default);
95	$p-> {autoHScroll} = 0 if exists $p-> {hScroll};
96	$p-> {autoVScroll} = 0 if exists $p-> {vScroll};
97}
98
99sub init
100{
101	my $self = shift;
102
103	$self-> {$_} = -1 for qw( leftCell topCell);
104	$self-> {$_} = 0 for qw(
105		autoHScroll autoVScroll scrollTransaction gridColor hScroll vScroll dx dy
106		leftCell topCell multiSelect borderWidth visibleCols visibleRows
107		indentCellColor indentCellBackColor clipCells cache_geometry_requests
108		allowChangeCellWidth allowChangeCellHeight gridGravity
109	);
110	$self-> {$_} = 1 for qw( drawHGrid drawVGrid columns rows);
111	$self-> {focusedCell} = [0,0];
112	$self-> {cellIndents} = [0,0,0,0];
113	$self-> {selection} = [-1,-1,-1,-1];
114
115	my %profile = $self-> SUPER::init(@_);
116	$self-> setup_indents;
117
118	$self->{$_} = $profile{$_} for qw(scrollBarClass hScrollBarProfile vScrollBarProfile);
119	$self-> $_( $profile{ $_}) for qw(
120		allowChangeCellHeight allowChangeCellWidth
121		constantCellWidth constantCellHeight
122		autoHScroll autoVScroll drawHGrid drawVGrid gridColor hScroll vScroll
123		columns rows cellIndents
124		leftCell topCell multiSelect focusedCell borderWidth indentCellColor
125		indentCellBackColor clipCells gridGravity
126	);
127	$self-> reset;
128	return %profile;
129}
130
131sub cache_geometry_requests
132{
133	my ( $self, $do_cache) = @_;
134	return if $self-> {cache_geometry_requests} == $do_cache;
135	if (( $self-> {cache_geometry_requests} = $do_cache)) {
136		$self-> {geometry_cache_row} = {};
137		$self-> {geometry_cache_column} = {};
138	} else {
139		delete $self-> {geometry_cache_row};
140		delete $self-> {geometry_cache_column};
141	}
142}
143
144sub deselect_all {
145	my $self = $_[0];
146	$self-> selection(-1,-1,-1,-1);
147}
148
149sub draw_cells
150{
151	my ($self, $canvas, $cols, $rows, $active_area) = @_;
152	my ( $notifier, @notifyParms) = $self-> get_notify_sub(q(DrawCell));
153	my @selection = $self-> selection;
154	my @f = $self-> focused ? $self-> focusedCell : ( -1, -1);
155	$self-> push_event;
156	my @prelight    = @{$self->{prelight} // [-1,-1]};
157	my ( $xsel, $ysel, $xprelight, $yprelight);
158	my ( $clipV, $clipH) = ( $self-> {clipCells} == 1, $self-> {clipCells} == 2);
159	for ( @$cols) {
160		my ( $col, $xtype, $br, $x1, $x2, $X1, $X2) = @$_;
161		$canvas-> clipRect( $x1, $$active_area[1], $x2, $$active_area[3]) if $clipV;
162		$xsel = $col >= $selection[0] && $col <= $selection[2];
163		$xprelight = $col == $prelight[0];
164		for ( @$rows) {
165			my ( $row, $ytype, $br, $y1, $y2, $Y1, $Y2) = @$_;
166			$ysel = $row >= $selection[1] && $row <= $selection[3] if $xsel;
167			$yprelight = $row == $prelight[1];
168			$canvas-> clipRect( $x1, $y1, $x2, $y2) if $clipH;
169			$notifier-> ( @notifyParms,
170				$canvas,
171				$col, $row, $xtype || $ytype,
172				$x1, $y1, $x2, $y2,
173				$X1, $Y1, $X2, $Y2,
174				$xsel && $ysel,
175				( $col == $f[0] && $row == $f[1]) ? 1 : 0,
176				$xprelight && $yprelight
177			);
178		}
179	}
180	$self-> pop_event;
181}
182
183sub draw_text_cells
184{
185	my ( $self, $canvas, $screen_rects, $cell_rects, $cell_indices, $font_height) = @_;
186	my $i;
187	my @clip = $canvas-> clipRect if $self-> {clipCells} == 2;
188	for ( $i = 0; $i < @$screen_rects; $i++) {
189		my @r = @{$$cell_rects[$i]};
190		$canvas-> clipRect( @{$$screen_rects[$i]}) if $self-> {clipCells} == 2;
191		my ($ha, $va) = $self-> get_cell_alignment(@{$$cell_indices[$i]});
192		my ( $x, $y ) = ($r[0], $r[1]);
193		my $text = $self-> get_cell_text( @{$$cell_indices[$i]});
194		if ( $ha == ta::Right ) {
195			my $dx = $r[2] - $r[0] - $self-> get_text_width($text);
196			$x += $dx if $dx > 0;
197		} elsif ( $ha == ta::Center ) {
198			my $dx = ($r[2] - $r[0] - $self-> get_text_width($text)) / 2;
199			$x += $dx if $dx > 0;
200		}
201		if ( $va == ta::Top ) {
202			my $dy = $r[3] - $r[1] - $font_height;
203			$y += $dy if $dy > 0;
204		} elsif ( $va == ta::Middle ) {
205			my $dy = ($r[3] - $r[1] - $font_height) / 2;
206			$y += $dy if $dy > 0;
207		}
208		$canvas-> text_shape_out( $text, $x, $y);
209	}
210	$canvas-> clipRect( @clip) if $self-> {clipCells} == 2;
211}
212
213sub get_cell_area
214{
215	my ( $self, @size) = @_;
216	my @a = $self-> get_active_area( 1, @size);
217	my @r;
218	my @px = @{$self-> {pixelCellIndents}};
219	$r[0] = $a[0] + $px[0];
220	$r[1] = $a[1] + $px[3];
221	$r[2] = $a[2] - $px[2];
222	$r[3] = $a[3] - $px[1];
223	if ( $self-> {lastColEmpty}) {
224		$r[2]-- if $self-> {drawVGrid};
225	}
226	if ( $self-> {lastRowEmpty}) {
227		$r[3]-- if $self-> {drawHGrid};
228	}
229	return @r;
230}
231
232sub get_cell_text
233{
234	my ( $self, $col, $row) = @_;
235	my $txt = '';
236	$self-> notify(q(Stringify), $col, $row, \$txt);
237	return $txt;
238}
239
240sub get_cell_alignment
241{
242	my ( $self, $col, $row) = @_;
243	my ($ha, $va) = ( ta::Left, ta::Middle );
244	$self-> notify(q(GetAlignment), $col, $row, \$ha, \$va);
245	return $ha, $va;
246}
247
248sub get_range
249{
250	my ( $self, $vertical, $index) = @_;
251	my ( $min, $max) = ( 1, 16384 ); # actually, no real restriction on $max -
252					# just a reasonable non-undef value
253	$self-> notify(q(GetRange), $vertical, $index, \$min, \$max);
254	$min = 1 if $min < 1;
255	$max = $min if $max < $min;
256	return $min, $max;
257}
258
259sub get_screen_cell_info
260{
261	my ( $self, $x, $y) = @_;
262	my ( $colsDraw, $rowsDraw) = ( $self-> {colsDraw}, $self-> {rowsDraw});
263	my ( $col, $row, $c, $r, $i);
264	$i = 0;
265	for ( @$colsDraw) {
266		$i++, next unless $x == $$_[0];
267		$col = $i;
268		$c = $_;
269	}
270	return unless defined $col;
271	$i = 0;
272	for ( @$rowsDraw) {
273		$i++, next unless $y == $$_[0];
274		$row = $i;
275		$r = $_;
276	}
277	return unless defined $row;
278	my ( $dx, $dy) = ( $self-> {dx}, $self-> {dy});
279	return
280		$col, $row,
281		(
282		( $$c[3] == $$c[5]) && ( $$c[4] == $$c[6]) &&
283		( $$r[3] == $$r[5]) && ( $$r[4] == $$r[6])
284		),
285		$$c[3]-$dx, $$r[3]+$dy, $$c[4]-$dx+1, $$r[4]+$dy+1,
286		$$c[5]-$dx, $$r[5]+$dy, $$c[6]-$dx+1, $$r[6]+$dy+1,
287	;
288}
289
290sub has_selection
291{
292	return $_[0]-> {selection}-> [0] >= 0;
293}
294
295sub point2cell
296{
297	my ( $self, $x, $y, $NoGrid) = @_;
298	my @a = $self-> get_active_area( 0);
299
300	$x += $self-> {dx};
301	$y -= $self-> {dy};
302
303	my ($cx, $cy, %hints) = (-2, -2);
304	my ( $colsDraw, $rowsDraw) = ( $self-> {colsDraw}, $self-> {rowsDraw});
305
306	# check widget borders first
307	if ( $x < $a[0]) {
308		# left border
309		$cx = -1;
310		$hints{x} = -1;
311	} elsif ( $x >= $a[2] -
312			(($self-> {drawVGrid} && $self-> {cellIndents}-> [2] > 0) ?
313				$self-> {drawVGrid} : 0)) {
314		# right border
315		$cx = -1;
316		$hints{x} = +2;
317	}
318	if ( $y < $a[1]) {
319		# bottom border
320		$cy = -1;
321		$hints{y} = +2;
322	} elsif ( $y >= $a[3] -
323		(($self-> {drawHGrid} && $self-> {cellIndents}-> [1] > 0) ?
324			$self-> {drawVGrid} : 0)) {
325		# top border
326		$cy = -1;
327		$hints{y} = -1;
328	}
329	return $cx, $cy, %hints, 'exterior', 1
330		if defined $hints{x} && defined $hints{y};
331
332	# check if it is the grid
333	if ( !$NoGrid && $self-> {drawVGrid}) {
334		my $i = -1;
335		my $lax = $self-> allowChangeCellWidth ? $self-> {gridGravity} : 0;
336		my $skipLast = ( $self-> {cellIndents}-> [2] > 0) ? scalar(@{$self-> {vGrid}}) - 1 : -1;
337		for ( @{$self-> {vGrid}}) {
338			$i++;
339			next if $x < $$_[0] - $lax || $x > $$_[0] + $lax || $i == $skipLast;
340			$hints{x_grid}    = 1;
341			$hints{grid}      = 1;
342			if ( $self-> {cellIndents}-> [2] > 0 &&
343				$i >= scalar(@{$self-> {vGrid}}) - $self-> {cellIndents}-> [2] - 1) {
344				$hints{x_right} = 1;
345				$i++ unless $self-> {lastColEmpty};
346			} else {
347				$hints{x_left} = 1;
348			}
349			$hints{x} = 0;
350			$hints{y} = +1 unless defined $hints{y};
351			return $$colsDraw[$i][0], $cy, %hints;
352		}
353	}
354	if ( !$NoGrid && $self-> {drawHGrid}) {
355		my $i = -1;
356		my $lax = $self-> allowChangeCellHeight ? $self-> {gridGravity} : 0;
357		my $skipLast = ( $self-> {cellIndents}-> [3] > 0) ?
358			scalar(@{$self-> {hGrid}}) - 1 : -1;
359
360		for ( @{$self-> {hGrid}}) {
361			$i++;
362			next if $y < $$_[0] - $lax || $y > $$_[0] + $lax || $i == $skipLast;
363
364			$hints{y_grid}    = 1;
365			$hints{grid}      = 1;
366			if ( $self-> {cellIndents}-> [3] > 0 &&
367				$i >= scalar(@{$self-> {hGrid}}) - $self-> {cellIndents}-> [3] - 1) {
368				$hints{y_bottom} = 1;
369				$i++ unless $self-> {lastRowEmpty};
370			} else {
371				$hints{y_top} = 1;
372			}
373
374			$hints{x} = +1 unless defined $hints{x};
375			$hints{y} = 0;
376
377			return $cx, $$rowsDraw[$i][0], %hints;
378		}
379	}
380
381	# check other areas
382	if ( defined $hints{x}) {
383		# nop
384	} elsif ( $x > $$colsDraw[-1][4] + $self-> {drawVGrid}) {
385		# right whitespace
386		$cx = -1;
387		$hints{x} = +1;
388	} elsif ( $self-> {lastColEmpty} &&
389		$x < $a[2] - $self-> {pixelCellIndents}-> [2] &&
390		$x > $a[2] - $self-> {pixelCellIndents}-> [2] - $self-> {lastColTail} -
391			(($self-> {cellIndents}-> [2] > 0) ? $self-> {drawVGrid} : 0)
392	) {
393		# gap
394		$cx = -1;
395		$hints{x} = +1;
396		$hints{x_gap} = 1;
397		$hints{x_type} = 1;
398	} else {
399	# cycle cells to find who is it
400		my $i = 0;
401		my $dv = $self-> {drawVGrid};
402		for ( @$colsDraw) {
403			if ( $x <= $$_[4]) {
404				$cx = $$_[0];
405				$hints{x} = 0;
406				if (( $hints{x_type} = $$_[1]) != 0) {
407					$hints{x_type} =
408						( $x > $a[0] + $self-> {pixelCellIndents}-> [0]) ?
409							(( $x < $a[2] - $self-> {pixelCellIndents}-> [2] - 1) ?
410							0 : +1)
411						: -1;
412				}
413				last;
414			}
415			$i++;
416		}
417		unless ( defined $hints{x}) { # last column grid not catched when $NoGrid is set
418			$hints{x} = 0;
419			$hints{x_type} = 0; # XXX unsure
420			$cx = $$colsDraw[-1][0];
421		}
422	}
423
424	if ( defined $hints{y}) {
425		# nop
426	} elsif ( $y < $$rowsDraw[-1][3] + $self-> {drawHGrid}) {
427		# bottom whitespace
428		$cy = -1;
429		$hints{y} = +1;
430	} elsif (
431		$self-> {lastRowEmpty} &&
432		$y > $a[1] + $self-> {pixelCellIndents}-> [3] &&
433		$y < $a[1] + $self-> {pixelCellIndents}-> [3] + $self-> {lastRowTail} -
434			(($self-> {cellIndents}-> [3] > 0) ? $self-> {drawHGrid} : 0)
435	) {
436		# gap
437		$cy = -1;
438		$hints{y} = +1;
439		$hints{y_gap} = 1;
440		$hints{y_type} = 1;
441	} else {
442	# cycle cells to find who is it
443		my $i = 0;
444		my $dh = $self-> {drawHGrid};
445		for ( @$rowsDraw) {
446			if ( $y >= $$_[3]) {
447				$cy = $$_[0];
448				$hints{y} = 0;
449				if (( $hints{y_type} = $$_[1]) != 0) {
450					$hints{y_type} = ( $y < $a[3] - $self-> {pixelCellIndents}-> [1] - 1) ?
451					(( $y > $a[1] + $self-> {pixelCellIndents}-> [3]) ? 0 : +1) : -1;
452				}
453				last;
454			}
455			$i++;
456		}
457		unless ( defined $hints{y}) { # last row grid not catched when $NoGrid is set
458			$hints{y} = 0;
459			$hints{y_type} = 0; # XXX unsure
460			$cy = $$colsDraw[-1][0];
461		}
462	}
463
464	# area type
465	if ( $hints{x} == 0 && $hints{y} == 0) {
466		if ( $hints{x_type} == 0 && $hints{y_type} == 0) {
467			$hints{normal} = 1;
468		} else {
469			$hints{indent} = 1;
470		}
471	} else {
472		$hints{exterior} = 1;
473	}
474
475	return $cx, $cy, %hints;
476}
477
478
479sub redraw_cell
480{
481	my ( $self, $x, $y) = @_;
482	my @info = $self-> get_screen_cell_info( $x, $y);
483	return unless scalar @info;
484	$self-> invalidate_rect( @info[gsci::V_RECT]);
485}
486
487# Because grid is non-linear, x or y position shift results in that
488# number visible cells and rows is different . Therefore grid operates
489# two scroll modes - pixel and cell. The cell mode is the default, where
490# the scroll step is a cell. If, however, a cell is single and cannot be
491# fit, scrolling is switched to pixel-wise. This behavior is reflected via
492# {colUnits} and {rowUnits} boolean fields. The limits are {columns} and
493# {colSpan}, and {rows} and {rowSpan} respectively for either mode.
494# {colSpan} and {rowSpan} are used internally for cell unit mode also.
495
496sub reset
497{
498	my ( $self, @par_sz) = @_;
499	my ( $O, $T, $r, $c, $dh, $dv) = (
500		$self-> {leftCell}, $self-> {topCell}, $self-> {rows}, $self-> {columns},
501		$self-> {drawHGrid}, $self-> {drawVGrid});
502	my ( $i, $W, $H, $lastw, $lasth) = ( 0, 0, 0, 0, 0);
503	my @scroll_steps = ( 0, 0);
504	@par_sz = $self-> size unless @par_sz;
505
506	$self-> cache_geometry_requests(1);
507	$self-> begin_paint_info unless $self-> {NoBulkPaintInfo};
508
509	my @in = @{$self-> {cellIndents}};
510	my @px = ( 0,0,0,0);
511	for ( $i = 0; $i < $in[0]; $i++) {
512		$px[0] += $self-> columnWidth($i) + $dv;
513	}
514	for ( $i = 0; $i < $in[1]; $i++) {
515		$px[1] += $self-> rowHeight($i) + $dh;
516	}
517	for ( $i = 0; $i < $in[2]; $i++) {
518		$px[2] += $self-> columnWidth($c - $i - 1) + $dv;
519	}
520	for ( $i = 0; $i < $in[3]; $i++) {
521		$px[3] += $self-> rowHeight($r - $i - 1) + $dh;
522	}
523	$self-> {pixelCellIndents} = \@px;
524
525	# calculate dimension of a minimal operational field
526	$W += $self-> columnWidth( $O++) + $dv
527		if $c > $in[0] + $in[2];
528	$H += $self-> rowHeight( $T++) + $dh
529		if $r > $in[1] + $in[3];
530
531	# select unit mode
532REPEAT_LAYOUT:
533	my ( $w, $h, $o, $t) = ( $W, $H, $O, $T);
534	my @sz = $self-> get_active_area( 2, @par_sz);
535	$self-> {colUnits} = ( $w + $px[0] + $px[2] <= $sz[0] ) ? 1 : 0;
536	$self-> {rowUnits} = ( $h + $px[1] + $px[3] <= $sz[1] ) ? 1 : 0;
537
538	# calculate the last possible visible row
539	$i = $r - $in[3] - 1;
540	my $maxh = $sz[1] - $px[1] - $px[3];
541	my $yh = $self-> rowHeight( $i) + $dh;
542	while ( $i > $in[1] ) {
543		my $dh = $self-> rowHeight( $i - 1) + $dh;
544		last if $yh + $dh > $maxh;
545		$yh += $dh;
546		$i--;
547	}
548	$self-> {rowMax} = $i;
549
550	# calculate the last possible visible column
551	my $maxw = $sz[0] - $px[0] - $px[2];
552	$i = $c - $in[2] - 1;
553	my $xw = $self-> columnWidth( $i) + $dv;
554	while ( $i > $in[0] ) {
555		my $dw = $self-> columnWidth( $i - 1) + $dv;
556		last if $xw + $dw > $maxw;
557		$xw += $dw;
558		$i--;
559	}
560	$self-> {colMax} = $i;
561
562	# if span is more than minimal, calculate how many cells can be fit in screen
563	if ( $self-> {colUnits}) {
564		$sz[0] -= $px[0] + $px[2];
565		while ( $w < $sz[0] && $o < $c - $in[2]) {
566			$lastw = $w;
567			$w += $self-> columnWidth( $o++) + $dv;
568		}
569		$self-> {dx} = 0;
570		$self-> {lastColEmpty} = ($in[2] > 0) ? $w < $sz[0] : 0;
571		$self-> {lastColTail} = ( $w > $sz[0] ) ?
572			$sz[0] - $lastw :
573			(( $in[2] > 0) ? $sz[0] - $w : 0);
574		$self-> {colSpan} = $lastw +
575			( $self-> {lastColEmpty} ? $self-> {lastColTail} : 0);
576	} else {
577		$self-> {lastColEmpty} = 0;
578		$self-> {lastColTail}  = 0;
579		$self-> {colSpan} = $w + $px[0] + $px[2];
580		$self-> {dx} = $self-> {colSpan} - $sz[0]
581			if $self-> {dx} > $self-> {colSpan} - $sz[0];
582	}
583	if ( $self-> {rowUnits}) {
584		$sz[1] -= $px[1] + $px[3];
585		while ( $h < $sz[1] && $t < $r - $in[3]) {
586			$lasth = $h;
587			$h += $self-> rowHeight( $t++) + $dh;
588		}
589		$self-> {dy} = 0;
590		$self-> {lastRowEmpty} = ( $in[3] > 0) ? $h < $sz[1] : 0;
591		$self-> {lastRowTail} = ( $h > $sz[1] ) ?
592			$sz[1] - $lasth :
593			(( $in[3] > 0) ? $sz[1] - $h : 0);
594		$self-> {rowSpan} = $lasth + ($self-> {lastRowEmpty} ?
595			$self-> {lastRowTail} : 0);
596	} else {
597		$self-> {lastRowEmpty} = 0;
598		$self-> {lastRowTail} = 0;
599		$self-> {rowSpan} = $h + $px[1] + $px[3];
600		$self-> {dy} = $self-> {rowSpan} - $sz[1]
601			if $self-> {dy} > $self-> {rowSpan} - $sz[1];
602	}
603	$self-> {visibleCols} = $o - $self-> {leftCell};
604	$self-> {visibleRows} = $t - $self-> {topCell};
605	$self-> {fullCols} =  $self-> {visibleCols} -
606		(( !$self-> {lastColEmpty} && $self-> {lastColTail} > 0) ? 1 : 0);
607	$self-> {fullRows} =  $self-> {visibleRows} -
608		(( !$self-> {lastRowEmpty} && $self-> {lastRowTail} > 0) ? 1 : 0);
609
610	my $vr = $self-> {visibleRows} + $in[1] + $in[3];
611	my $vc = $self-> {visibleCols} + $in[0] + $in[2];
612
613	# calculate breadth vectors
614	my ( @colsDraw, @rowsDraw) = ();
615
616	# Determine cells to be drawn
617	#
618	# colsDraw and rowsDraw contain arrays of cell and row geometry, with each
619	# item laid out as follows:
620	# 0: cell #
621	# 1: type; 0 - normal cell, 1 - indent cell
622	# 2: visible cell breadth
623	# 3: visible cell start
624	# 4: visible cell end
625	# 5: real cell start
626	# 6: real cell end
627	# The coordinates are in inclusive-inclusive coordinate system, and
628	# do not include eventual grid space, and gaps between indent and
629	# normal cells.
630	$o = $self-> {leftCell};
631	$t = $self-> {topCell};
632	# horizontal
633	push @colsDraw, map {[$_, 1, $self-> columnWidth($_) + $dv]} 0 .. $in[0] - 1
634		if $in[0] > 0;
635	if ( $self-> {colUnits}) {
636		push @colsDraw, map {[$_, 0, $self-> columnWidth($_) + $dv]}
637			$o .. $o + $self-> {visibleCols} - 1;
638		if ( !$self-> {lastColEmpty} && $self-> {lastColTail} > 0) {
639			$colsDraw[-1][6] = $colsDraw[-1][2] - $self-> {lastColTail};
640			$colsDraw[-1][2] = $self-> {lastColTail};
641		}
642	} else {
643		push @colsDraw, [ $o, 0, $self-> columnWidth($o) + $dv];
644	}
645	push @colsDraw, map {[$_, 1, $self-> columnWidth($_) + $dv]} $c - $in[2] .. $c - 1
646	if $in[2] > 0;
647	# and vertical
648	push @rowsDraw, map {[$_, 1, $self-> rowHeight($_) + $dh]} 0 .. $in[1] - 1
649	if $in[1] > 0;
650	if ( $self-> {rowUnits}) {
651		push @rowsDraw, map {[$_, 0, $self-> rowHeight($_) + $dh]}
652			$t .. $t + $self-> {visibleRows} - 1;
653		if ( !$self-> {lastRowEmpty} && $self-> {lastRowTail} > 0) {
654			$rowsDraw[-1][5] = $self-> {lastRowTail} + $dh - $rowsDraw[-1][2];
655			$rowsDraw[-1][2] = $self-> {lastRowTail};
656		}
657	} else {
658		push @rowsDraw, [ $t, 0, $self-> rowHeight($t) + $dh];
659	}
660	push @rowsDraw, map {[$_, 1, $self-> rowHeight($_) + $dh]} $r - $in[3] .. $r - 1
661		if $in[3] > 0;
662
663	$i = $self-> {indents}-> [0];
664	my $j = 0;
665	for ( @colsDraw) {
666		$$_[3] = $i;
667		$$_[4] = $i + $$_[2] - 1 - $dv;
668		$$_[5] += $$_[3];
669		$$_[6] += $$_[4];
670		$i += $$_[2];
671		$i += $self-> {lastColTail}
672			if $self-> {lastColEmpty} && $in[2] > 0 && $$_[0] == $c - $in[2] - 1;
673		$j++;
674	}
675
676	$i = $par_sz[1] - $self-> {indents}-> [3];
677	$j = 0;
678	for ( @rowsDraw) {
679		$$_[3] = $i - $$_[2] + $dh;
680		$$_[4] = $i - 1;
681		$$_[5] += $$_[3];
682		$$_[6] += $$_[4];
683		$i -= $$_[2];
684		$i -= $self-> {lastRowTail}
685			if $self-> {lastRowEmpty} && $in[3] > 0 && $$_[0] == $r - $in[3] - 1;
686	}
687
688	$self-> {colsDraw} = \@colsDraw;
689	$self-> {rowsDraw} = \@rowsDraw;
690
691	# assign grid anchor points
692	my ( @vgrid, @hgrid);
693	if ( $dh) {
694		@hgrid = map {[ $$_[3] - 1, $colsDraw[-1][4], $colsDraw[0][3]]} @rowsDraw;
695		splice @hgrid, -$in[3], 0,
696			[$rowsDraw[-$in[3]][4] + $dh, $colsDraw[-1][4], $colsDraw[0][3]]
697				if $self-> {rowUnits} && $self-> {lastRowEmpty} && $in[3] > 0;
698		# split lines over the gap
699		if ( $self-> {lastColEmpty}) {
700			my %excludes = ( $#hgrid => 1, $#hgrid - $in[3] => 1);
701			$excludes{$in[1]-1} = 1 if $in[1] > 0;
702			$i = 0;
703			for ( @hgrid) {
704				next if $excludes{$i++};
705				splice @$_, 2, 0, $colsDraw[-$in[2]][3], $colsDraw[-$in[2]-1][4];
706			}
707		}
708	}
709	$self-> {hGrid} = \@hgrid;
710	if ( $dv) {
711		@vgrid = map {[ $$_[4] + 1, $rowsDraw[-1][3], $rowsDraw[0][4]]} @colsDraw;
712		splice @vgrid, -$in[2], 0,
713			[$colsDraw[-$in[2]][3] - $dv, $rowsDraw[-1][3], $rowsDraw[0][4]]
714				if $self-> {colUnits} && $self-> {lastColEmpty} && $in[2] > 0;
715		# split lines over the gap
716		if ( $self-> {lastRowEmpty}) {
717			my %excludes = ( $#vgrid => 1, $#vgrid - $in[2] => 1);
718			$excludes{$in[0]-1} = 1 if $in[0] > 0;
719			$i = 0;
720			for ( @vgrid) {
721				next if $excludes{$i++};
722				splice @$_, 2, 0, $rowsDraw[-$in[3]][4], $rowsDraw[-$in[3]-1][3];
723			}
724		}
725	}
726	$self-> {vGrid} = \@vgrid;
727
728	# scroll bars may change geometry and cause repaints
729	$self-> end_paint_info unless $self-> {NoBulkPaintInfo};
730
731	# adjust scrollbars
732	my @scrolls = ( $self-> {hScroll}, $self-> {vScroll});
733	if ( !($self-> {scrollTransaction} & 1)) {
734		if ( $self-> {rowUnits}) {
735			$self-> vScroll( $vr < $r)
736				if $self-> {autoVScroll};
737			$self-> {vScrollBar}-> set(
738				max      => $self-> {rowMax} - $in[1],
739				pageStep => $vr,
740				whole    => $r,
741				partial  => $vr,
742				value    => $self-> {topCell} - $in[1],
743			) if $self-> {vScroll};
744		} else {
745			$self-> vScroll( $self-> {dy} < $self-> {rowSpan})
746				if $self-> {autoVScroll};
747			my @sz = $self-> get_active_area(2);
748			$self-> {vScrollBar}-> set(
749				max      => $self-> {rowSpan} - $sz[1],
750				pageStep => $sz[1],
751				whole    => $self-> {rowSpan},
752				partial  => $sz[1],
753				value    => $self-> {dy},
754			) if $self-> {vScroll};
755		}
756	}
757	if ( !($self-> {scrollTransaction} & 2)) {
758		if ( $self-> {colUnits}) {
759			$self-> hScroll( $vc < $c)
760				if $self-> {autoHScroll};
761			$self-> {hScrollBar}-> set(
762				max      => $self-> {colMax} - $in[0],
763				pageStep => $vc,
764				whole    => $c,
765				partial  => $vc,
766				value    => $self-> {leftCell} - $in[0],
767			) if $self-> {hScroll};
768		} else {
769			$self-> hScroll( $self-> {dx} < $self-> {colSpan})
770				if $self-> {autoHScroll};
771			my @sz = $self-> get_active_area(2);
772			$self-> {hScrollBar}-> set(
773				max      => $self-> {colSpan} - $sz[0],
774				pageStep => $sz[0],
775				whole    => $self-> {colSpan},
776				partial  => $sz[0],
777				value    => $self-> {dx},
778			) if $self-> {hScroll};
779		}
780	}
781
782	# check if auto-scrolling changed the layout, and reset it again,
783	# but no more than once for each dimension
784	if ( $self-> {hScroll} != $scrolls[0] || $self-> {vScroll} != $scrolls[1] ) {
785		$scroll_steps[0]++ if $self-> {hScroll} != $scrolls[0];
786		$scroll_steps[1]++ if $self-> {vScroll} != $scrolls[1];
787		if ( $scroll_steps[0] < 2 && $scroll_steps[1] < 2) {
788			$lastw = $lasth = 0;
789			$self-> begin_paint_info unless $self-> {NoBulkPaintInfo};
790			goto REPEAT_LAYOUT
791		}
792	}
793
794	$self-> cache_geometry_requests(0);
795}
796
797sub select_all {
798	my $self = $_[0];
799	$self-> selection(0,0,$self-> {columns},$self-> {rows});
800}
801
802sub std_draw_text_cells
803{
804	my ($self, $canvas, $cols, $rows, $active_area) = @_;
805	my @colors = (
806		$self-> color,
807		$self-> backColor,
808		$self-> hiliteColor,
809		$self-> hiliteBackColor,
810		$self-> indentCellColor,
811		$self-> indentCellBackColor,
812		$self-> gridColor,
813	);
814	my @selection = $self-> selection;
815	my @f = $self-> focused ? $self-> focusedCell : ( -1, -1);
816	my @focRect;
817	my $font_height = $self-> font-> height;
818	my @prelight    = @{$self->{prelight} // [-1,-1]};
819	my ( $xsel, $ysel, $xprelight, $yprelight);
820	my ( $clipV, $clipH) = ( $self-> {clipCells} == 1, $self-> {clipCells} == 2);
821	my @clipRect = $canvas-> clipRect;
822	for ( @$cols) {
823		my ( $col, $xtype, $br, $x1, $x2, $X1, $X2) = @$_;
824		$canvas-> clipRect( $x1, $$active_area[1], $x2, $$active_area[3])
825			if $clipV;
826		$xsel      = $col >= $selection[0] && $col <= $selection[2];
827		$xprelight = $col == $prelight[0];
828		my $last_type;
829		my @bars;
830		my @rects;
831		my @cellids;
832		for ( @$rows) {
833			my ( $row, $ytype, $br, $y1, $y2, $Y1, $Y2) = @$_;
834			$ysel      = $row >= $selection[1] && $row <= $selection[3] if $xsel;
835			$yprelight = $row == $prelight[1] if $xprelight;
836			my $type = ($xtype || $ytype) ? 2 : (($xsel && $ysel) ? 1 : 0);
837			$type |= 4 if $xprelight && $yprelight;
838			if ( defined($last_type) && $type != $last_type) {
839				$canvas-> set(
840					color     => $colors[($last_type & 3) * 2],
841					backColor => $colors[($last_type & 3) * 2 + 1],
842				);
843				$self-> draw_item_background($canvas, @$_, $last_type & 4) for @bars;
844				$self-> draw_text_cells( $canvas, \@bars, \@rects, \@cellids, $font_height);
845				@bars = @rects = @cellids = ();
846			}
847			$last_type = $type;
848			push @bars,  [$x1, $y1, $x2, $y2];
849			push @rects, [$X1, $Y1, $X2, $Y2];
850			push @cellids, [ $col, $row ];
851			@focRect = ($x1, $y1, $x2, $y2) if $col == $f[0] && $row == $f[1];
852		}
853
854		if ( defined $last_type) {
855			$canvas-> set(
856				color     => $colors[($last_type & 3) * 2],
857				backColor => $colors[($last_type & 3) * 2 + 1],
858			);
859			$self-> draw_item_background($canvas, @$_, $last_type & 4) for @bars;
860			$self-> draw_text_cells( $canvas, \@bars, \@rects, \@cellids, $font_height);
861		}
862	}
863	$canvas-> clipRect( @clipRect);
864	$canvas-> rect_focus( @focRect) if @focRect;
865}
866
867sub on_size
868{
869	my ( $self, $ox, $oy, $x, $y) = @_;
870	$self-> reset( $x, $y);
871}
872
873sub on_disable { $_[0]-> repaint; }
874sub on_enable  { $_[0]-> repaint; }
875sub on_enter   { $_[0]-> redraw_cell( $_[0]-> focusedCell); }
876
877sub on_keydown
878{
879	my ( $self, $code, $key, $mod) = @_;
880	$self-> notify(q(MouseUp),0,0,0) if defined $self-> {mouseTransaction};
881	return if $mod & km::DeadKey;
882
883	$mod &= ( km::Shift|km::Ctrl|km::Alt);
884
885	if ( scalar grep { $key == $_ }
886		(kb::Left,kb::Right,kb::Up,kb::Down,kb::Home,kb::End,kb::PgUp,kb::PgDn))
887	{
888		my @f = @{$self-> {focusedCell}};
889		my $doSelect;
890		if ( $mod == 0 || ( $mod & (km::Shift|km::Ctrl))) {
891				if ( $key == kb::Up)   { $f[1]-- }
892			elsif ( $key == kb::Down) { $f[1]++ }
893			elsif ( $key == kb::Left) { $f[0]-- }
894			elsif ( $key == kb::Right){ $f[0]++ }
895			elsif ( $key == kb::Home) {
896				$f[0] = (($mod & km::Ctrl) ? 0 :
897					($self-> {leftCell} - (( $f[0] == $self-> {leftCell}) ?
898						$self-> {fullCols} : 0)));
899			}
900			elsif ( $key == kb::End)  {
901				my $e = $self-> {leftCell} + $self-> {fullCols} - 1;
902				$f[0] = (($mod & km::Ctrl) ?
903					$self-> {columns} :
904					$e + (
905						($f[0] == $e) ?
906						$self-> {fullCols} : 0
907					));
908			}
909			elsif ( $key == kb::PgUp) {
910				$f[1] = (($mod & km::Ctrl) ? 0 :
911					($self-> {topCell} - (( $f[1] == $self-> {topCell}) ?
912						$self-> {fullRows} :
913						0)
914					));
915			}
916			elsif ( $key == kb::PgDn) {
917				my $e = $self-> {topCell} + $self-> {fullRows} - 1;
918				$f[1] = (($mod & km::Ctrl) ? $self-> {rows} :
919						($e + (($f[1] == $e ) ?
920							$self-> {fullRows} :
921							0)
922						));
923			}
924			$doSelect = $mod & km::Shift;
925		}
926		if ( $doSelect ) {
927			my @sel = exists($self-> {anchor}) ?
928				@{$self-> {anchor}} :
929				@{$self-> {focusedCell}};
930
931			$self-> selection( @sel, @f);
932			$self-> {anchor} = [ $self-> focusedCell ] unless exists $self-> {anchor};
933		} else {
934			$self-> selection( @f, @f ) if exists $self-> {anchor};
935			delete $self-> {anchor};
936		}
937		$self-> focusedCell( @f);
938		$self-> clear_event;
939		return;
940	} else {
941		delete $self-> {anchor};
942	}
943
944	if ( $mod == 0 && ( $key == kb::Space || $key == kb::Enter)) {
945		$self-> clear_event;
946		$self-> notify(q(Click)) if $key == kb::Enter;
947		return;
948	}
949}
950
951sub on_leave
952{
953	my $self = $_[0];
954	if ( $self-> {mouseTransaction})
955	{
956		$self-> capture(0) if $self-> {mouseTransaction};
957		$self-> {mouseTransaction} = undef;
958	}
959	$self-> redraw_cell( $self-> focusedCell);
960}
961
962sub on_mouseclick
963{
964	my ( $self, $btn, $mod, $x, $y, $dbl) = @_;
965	$self-> clear_event;
966	return if $btn != mb::Left || !$dbl;
967
968	my ( $cx, $cy, %hints) = $self-> point2cell( $x, $y);
969	$self-> notify(q(Click)) if $hints{normal} || $hints{indent};
970}
971
972sub on_mousedown
973{
974	my ( $self, $btn, $mod, $x, $y) = @_;
975	return if $self-> {mouseTransaction};
976	return if $btn != mb::Left;
977
978	my ( $cx, $cy, %hints) = $self-> point2cell( $x, $y);
979	# print "$_($hints{$_})," for keys %hints; print "X($cx),Y($cy)\n";return;
980	if ( $hints{normal}) {
981		if ( $self-> {multiSelect}) {
982			if ( $mod & km::Shift) {
983				$self-> selection( $cx, $cy, @{$self-> {focusedCell}});
984			} else {
985				$self-> selection( $cx, $cy, $cx, $cy);
986			}
987			$self-> {anchor} = [ $cx, $cy ];
988		}
989		$self-> focusedCell( $cx, $cy);
990		$self-> {mouseTransaction} = 1;
991		$self-> capture(1);
992		$self-> clear_event;
993		return;
994	}
995
996	if ( defined($hints{x_grid}) && $self-> allowChangeCellWidth) {
997		$self-> pointerType( cr::SizeWE);
998		my %d;
999		if ( $hints{x_right}) {
1000			my @info = $self-> get_screen_cell_info( $cx, $self-> {topCell});
1001			$d{range}    = [ $self-> get_range( 0, $cx) ];
1002			$d{v_begins} = $info[gsci::V_LEFT] - $self-> {lastColTail};
1003			$d{v_ends}   = $self-> right - $self-> {indents}-> [2] - 1;
1004			$d{index}    = $cx;
1005			$d{mode}     = 0;
1006			$d{offset}   = $info[gsci::RIGHT];
1007		} else {
1008			my @info = $self-> get_screen_cell_info( $cx, $self-> {topCell});
1009			$d{range}    = [ $self-> get_range( 0, $cx) ];
1010			$d{offset}   = $info[gsci::LEFT];
1011			$d{v_begins} = $d{offset} + $d{range}-> [0];
1012			$d{v_begins} = $info[gsci::V_LEFT] if $d{v_begins} < $info[gsci::V_LEFT];
1013			$d{v_ends}   = $self-> right - $self-> {indents}-> [2] - 1;
1014			$d{index}    = $cx;
1015			$d{mode}     = 1;
1016		}
1017		$d{breadth} = $self-> columnWidth($d{index});
1018		$self-> {dragSizeInfo} = \%d;
1019		$self-> {mouseTransaction} = 2;
1020		$self-> capture(1);
1021		$self-> clear_event;
1022		return;
1023	} elsif ( defined($hints{y_grid}) && $self-> allowChangeCellHeight) {
1024		$self-> pointerType( cr::SizeNS);
1025		my %d;
1026		if ( $hints{y_bottom}) {
1027			my @info = $self-> get_screen_cell_info( $self-> {leftCell}, $cy);
1028			$d{range}    = [ $self-> get_range( 1, $cy) ];
1029			$d{v_begins} = $info[gsci::V_TOP]  + $self-> {lastRowTail};
1030			$d{v_ends}   = $self-> bottom - $self-> {indents}-> [3] - 1;
1031			$d{index}    = $cy;
1032			$d{mode}     = 0;
1033			$d{offset}   = $info[gsci::V_BOTTOM];
1034		} else {
1035			my @info = $self-> get_screen_cell_info( $self-> {leftCell}, $cy);
1036			$d{range}    = [ $self-> get_range( 1, $cy) ];
1037			$d{offset}   = $info[gsci::TOP];
1038			$d{v_begins} = $d{offset} + $d{range}-> [0];
1039			$d{v_begins} = $info[gsci::V_TOP] if $d{v_begins} < $info[gsci::V_TOP];
1040			$d{v_ends}   = $self-> bottom - $self-> {indents}-> [3] - 1;
1041			$d{index}    = $cy;
1042			$d{mode}     = 1;
1043		}
1044		$d{breadth} = $self-> rowHeight($d{index});
1045		$self-> {dragSizeInfo} = \%d;
1046		$self-> {mouseTransaction} = 3;
1047		$self-> capture(1);
1048		$self-> clear_event;
1049		return;
1050	}
1051}
1052
1053sub update_prelight_and_pointer
1054{
1055	my ( $self, $x, $y) = @_;
1056	return delete $self->{prelight} if $self-> {mouseTransaction};
1057	return unless $self-> enabled;
1058
1059	my ( $cx, $cy, %hints) = $self-> point2cell( $x, $y );
1060	my @prelight = (-1,-1);
1061	my @old      = @{$self->{prelight} // [-1,-1]};
1062	if ( defined($hints{x_grid}) && $self-> allowChangeCellWidth) {
1063		$self-> pointerType( cr::SizeWE);
1064	} elsif ( defined($hints{y_grid}) && $self-> allowChangeCellHeight) {
1065		$self-> pointerType( cr::SizeNS);
1066	} else {
1067		@prelight = ($cx, $cy) if $hints{normal};
1068		$self-> pointerType( cr::Default);
1069	}
1070
1071	if ( join('.', @prelight ) ne join('.', @old)) {
1072		$self->{prelight} = ( $prelight[0] < 0 ) ? undef : \@prelight;
1073		$self->redraw_cell( @old )      if $old[0]      >= 0;
1074		$self->redraw_cell( @prelight ) if $prelight[0] >= 0;
1075	}
1076}
1077
1078sub on_mousemove
1079{
1080	my ( $self, $mod, $x, $y) = @_;
1081	$self-> clear_event;
1082	$self-> update_prelight_and_pointer($x,$y);
1083	return unless $self-> {mouseTransaction};
1084
1085	my ( $cx, $cy, %hints) = $self-> point2cell( $x, $y, defined($self-> {mouseTransaction}));
1086
1087	if ( $self-> {mouseTransaction} == 1) {
1088		unless ( $hints{normal}) {
1089			$self-> scroll_timer_start unless $self-> scroll_timer_active;
1090			return unless $self-> scroll_timer_semaphore;
1091			$self-> scroll_timer_semaphore(0);
1092		} else {
1093			$self-> scroll_timer_stop;
1094		}
1095
1096		my ( $t, $o);
1097		if ( $hints{x} != 0 || (defined( $hints{x_type}) && $hints{x_type} != 0)) {
1098			my ( $x1, $x2) = (
1099				$self-> {leftCell},
1100				$self-> {leftCell} + $self-> {fullCols} - 1
1101			);
1102			my $xd = ( $hints{x} == 0) ? $hints{x_type} : $hints{x};
1103			if ( $xd < 0) {
1104				if ( $self-> {focusedCell}-> [0] > $x1) {
1105					$cx = $x1;
1106				} else {
1107					$o = $self-> {leftCell} - 1;
1108					$cx = $x1 - 1;
1109				}
1110			} else {
1111				$cx = $self-> {focusedCell}-> [0] + 1;
1112				$cx = $x2 + 1 if $cx < $x1 || $cx > $x2 + 1;
1113			}
1114		}
1115		if ( $hints{y} != 0 || (defined( $hints{y_type}) && $hints{y_type} != 0)) {
1116			my ( $y1, $y2) = (
1117				$self-> {topCell},
1118				$self-> {topCell} + $self-> {fullRows} - 1
1119			);
1120			my $yd = ( $hints{y} == 0) ? $hints{y_type} : $hints{y};
1121			if ( $yd < 0) {
1122				if ( $cy > $y1) {
1123					$cy = $y1;
1124				} else {
1125					$t = $self-> {topCell} - 1;
1126					$cy = $y1 - 1;
1127				}
1128			} else {
1129				$cy = $self-> {focusedCell}-> [1] + 1;
1130				$cy = $y2 + 1 if $cy < $y1 || $cy > $y2 + 1;
1131			}
1132		}
1133		$self-> selection( $cx, $cy, @{$self-> {anchor}}) if $self-> {anchor};
1134		$self-> leftCell( $o) if defined $o;
1135		$self-> topCell( $t) if defined $t;
1136		$self-> focusedCell( $cx, $cy);
1137	} elsif ( $self-> {mouseTransaction} == 2) {
1138		my @a = $self-> get_active_area( 1);
1139		$x = $a[0] if $x < $a[0];
1140		$x = $a[2] if $x > $a[2];
1141		my $d = $self-> {dragSizeInfo};
1142		$x = $d-> {v_begins} if $x < $d-> {v_begins};
1143		$x = $d-> {v_ends} if $x > $d-> {v_ends};
1144		$x = $d-> {mode} ? $x - $d-> {offset} : $d-> {offset} - $x;
1145		$x = $d-> {range}-> [0] if $x < $d-> {range}-> [0];
1146		$x = $d-> {range}-> [1] if $x > $d-> {range}-> [1];
1147		if ( $x != $d-> {breadth}) {
1148			$self-> columnWidth( $d-> {index}, $x);
1149			$d-> {breadth} = $self-> columnWidth( $d-> {index});
1150		}
1151	} elsif ( $self-> {mouseTransaction} == 3) {
1152		my @a = $self-> get_active_area( 1);
1153		$y = $a[1] if $y < $a[1];
1154		$y = $a[3] if $y > $a[3];
1155		my $d = $self-> {dragSizeInfo};
1156		$y = $d-> {v_begins} if $y > $d-> {v_begins};
1157		$y = $d-> {v_ends} if $y < $d-> {v_ends};
1158		$y = $d-> {mode} ? $d-> {offset} - $y :$y - $d-> {offset};
1159		$y = $d-> {range}-> [0] if $y < $d-> {range}-> [0];
1160		$y = $d-> {range}-> [1] if $y > $d-> {range}-> [1];
1161		if ( $y != $d-> {breadth}) {
1162			$self-> rowHeight( $d-> {index}, $y);
1163			$d-> {breadth} = $self-> rowHeight( $d-> {index});
1164		}
1165	}
1166}
1167
1168sub on_mouseup
1169{
1170	my ( $self, $btn, $mod, $x, $y) = @_;
1171	return if $btn != mb::Left;
1172	return unless defined $self-> {mouseTransaction};
1173
1174	delete $self-> {mouseTransaction};
1175	delete $self-> {anchor};
1176	delete $self-> {dragSizeInfo};
1177	$self-> capture(0);
1178	$self-> clear_event;
1179
1180	my ( $cx, $cy, %hints) = $self-> point2cell( $x, $y);
1181	if ( defined($hints{x_grid}) && $self-> allowChangeCellWidth) {
1182		$self-> pointerType( cr::SizeWE);
1183	} elsif ( defined($hints{y_grid}) && $self-> allowChangeCellHeight) {
1184		$self-> pointerType( cr::SizeNS);
1185	} else {
1186		$self-> pointerType( cr::Default);
1187	}
1188}
1189
1190sub on_mousewheel
1191{
1192	my ( $self, $mod, $x, $y, $z) = @_;
1193	$z = (abs($z) > 120) ? int($z/120) : (($z > 0) ? 1 : -1);
1194	$z *= ( $self-> {visibleRows} || 1) if $mod & km::Ctrl;
1195	my $newTop = $self-> {topCell} - $z;
1196	$self-> topCell( $newTop);
1197	$self-> update_prelight_and_pointer($x, $y);
1198}
1199
1200sub on_mouseleave
1201{
1202	my $self = shift;
1203	my $prelight = delete $self->{prelight};
1204	$self-> redraw_cell( @$prelight ) if defined $prelight;
1205}
1206
1207sub on_paint
1208{
1209	my ($self,$canvas)   = @_;
1210	my @size   = $canvas-> size;
1211	unless ( $self-> enabled) {
1212		$self-> color( $self-> disabledColor);
1213		$self-> backColor( $self-> disabledBackColor);
1214	}
1215	my ( $r, $c, $o, $t, $dv, $dh, $dx, $dy) = (
1216		$self-> {rows}, $self-> {columns},
1217		$self-> {leftCell}, $self-> {topCell}, $self-> {drawVGrid}, $self-> {drawHGrid},
1218		$self-> {dx}, $self-> {dy},
1219	);
1220	my @a = $self-> get_active_area( 1, @size);
1221	my ($i,$j);
1222	my @px = @{$self-> {pixelCellIndents}};
1223	my @clipRect = $canvas-> clipRect;
1224	$self-> draw_border( $canvas, undef, @size);
1225	$canvas-> clipRect( @a);
1226	if ( $self-> {visibleCols} <= 0 || $self-> {visibleRows} <= 0) {
1227		$canvas-> clear( @a);
1228		return;
1229	}
1230	# intersect @clipRect with @a to avoid drawing cells behind scrollbars
1231	for ( 0, 1) {
1232		$clipRect[$_] = $a[$_] if $clipRect[$_] < $a[$_];
1233		$clipRect[$_+2] = $a[$_+2] if $clipRect[$_+2] > $a[$_+2];
1234	}
1235
1236	my @clipCells;
1237	my @colsDraw = map { [ @$_ ] } @{$self-> {colsDraw}};
1238	my @rowsDraw = map { [ @$_ ] } @{$self-> {rowsDraw}};
1239	my @in = @{$self-> {cellIndents}};
1240
1241	# find columns to draw, by assigning @clipCells, a clipRect in cell units and
1242	# calculating the final geometry of cells
1243	$j = 0;
1244	for ( @colsDraw) {
1245		my $c = $_;
1246		$$c[$_] -= $dx for 3..6;
1247		$clipCells[0] = $j
1248			if !defined($clipCells[0]) && $$c[4] + $dv >= $clipRect[0];
1249		$clipCells[2] = $j
1250			if !defined($clipCells[2]) && $$c[4] + $dv >= $clipRect[2];
1251		$j++;
1252	}
1253	$clipCells[0] = 0 unless defined $clipCells[0];
1254	$clipCells[2] = $#colsDraw unless defined $clipCells[2];
1255
1256	$j = 0;
1257	for ( @rowsDraw) {
1258		my $c = $_;
1259		$$c[$_] += $dy for 3..6;
1260		$clipCells[3] = $j
1261			if !defined($clipCells[3]) && $$c[3] - $dv <= $clipRect[3];
1262		$clipCells[1] = $j
1263			if !defined($clipCells[1]) && $$c[3] - $dv <= $clipRect[1];
1264		$j++;
1265	}
1266	$clipCells[3] = 0 unless defined $clipCells[3];
1267	$clipCells[1] = $#rowsDraw unless defined $clipCells[1];
1268
1269	# if right and top indent cells present, the space for them must
1270	# be allocated +1 pixel for extra line between indent and empty space
1271	my @extras = (
1272		($px[0] > 0) ? $dv : 0, ($px[1] > 0) ? $dh : 0,
1273		($px[2] > 0) ? $dv : 0, ($px[3] > 0) ? $dh : 0
1274	);
1275
1276	# clear undrawable area
1277	if ( !$self-> {colUnits} || $px[2] == 0) {
1278		$canvas-> clear( $colsDraw[-1][4] + $dv + 1, @a[1..3])
1279			if $colsDraw[-1][4] < $a[2];
1280	} elsif ( $self-> {lastColEmpty}) {
1281		my $right  = $a[2] - $px[2] - $extras[2];
1282		my $left   = $a[2] - $px[2] - $self-> {lastColTail} + 1;
1283		my $bk = $canvas-> backColor;
1284		if ( $self-> {lastColTail} > $dv) {
1285			if ( $self-> {rowUnits}) {
1286				$canvas-> clear( $left, $a[1] + $px[3] + $extras[3],
1287									$right, $a[3] - $px[1]);
1288			} else {
1289				$canvas-> clear( $left,
1290					$a[3] + $px[3] + $extras[3] + $dy - $self-> {rowSpan} + 1,
1291					$right,
1292					$a[3] - $px[1] + $dy);
1293			}
1294		}
1295		$canvas-> backColor( $self-> {indentCellBackColor});
1296		if ( $self-> {rowUnits}) {
1297			$canvas-> clear(
1298				$left, $a[3] - $px[1] + $extras[1] + 1,
1299				$right, $a[3]
1300			) if $px[1] > $dh;
1301			$canvas-> clear(
1302				$left, $a[1] + $dh,
1303				$right, $a[1] + $px[3] - 1
1304			) if $px[3] > $dh;
1305		} else {
1306			$canvas-> clear(
1307				$left, $a[3] - $px[1] + $extras[1] + 1 + $dy,
1308				$right, $a[3] + $dy
1309			) if $px[1] > $dh;
1310			$canvas-> clear(
1311				$left, $a[3] - $self-> {rowSpan} + $dy + $dh,
1312				$right, $a[3] - $self-> {rowSpan} + $dy + $dh + $px[3] - 1
1313			) if $px[3] > $dh;
1314		}
1315		$canvas-> backColor( $bk);
1316	}
1317
1318	# and horizontal area
1319	if ( !$self-> {rowUnits} || $in[3] == 0) {
1320		$canvas-> clear( @a[0..2], $rowsDraw[-1][3] - 1 - $dh)
1321			if $rowsDraw[-1][3] > $a[1];
1322	} elsif ( $self-> {lastRowEmpty} ) {
1323		my $bottom = $a[1] + $px[3] + $extras[3];
1324		my $top = $a[1] + $px[3] + $self-> {lastRowTail} - $dh;
1325		my $bk = $canvas-> backColor;
1326		if ( $self-> {lastRowTail} > $dh) {
1327			if ( $self-> {colUnits}) {
1328				$canvas-> clear(
1329					$a[0] + $px[0], $bottom,
1330					$a[2] - $px[2] - $extras[2], $top
1331				);
1332			} else {
1333				$canvas-> clear(
1334					$a[0] + $px[0] - $dx, $bottom,
1335					$a[0] - $px[2] - $extras[2] - $dx - $dv + $self-> {colSpan}, $top
1336				);
1337			}
1338		}
1339		$canvas-> backColor( $self-> {indentCellBackColor});
1340		if ( $self-> {colUnits}) {
1341			$canvas-> clear(
1342				$a[0], $bottom,
1343				$a[0] + $px[0] - 1 - $extras[0], $top
1344			) if $px[0] > $dv;
1345			$canvas-> clear(
1346				$a[2] - $px[2] + 1, $bottom,
1347				$a[2] - $dv, $top
1348			) if $px[2] > $dv;
1349		} else {
1350			$canvas-> clear(
1351				$a[0] - $dx, $bottom,
1352				$a[0] + $px[0] - 1 - $extras[0] - $dx, $top
1353			) if $px[0] > $dv;
1354			$canvas-> clear(
1355				$a[0] - $px[2] + $self-> {colSpan} - $dx , $bottom,
1356				$a[0] - $dx - $dv + $self-> {colSpan}, $top
1357			) if $px[2] > $dv;
1358		}
1359		$canvas-> backColor( $bk);
1360	}
1361
1362	# prepare indent grid line array
1363	my @grid;
1364	for ( @{$self-> {vGrid}}) {
1365		my $x = $$_[0] - $dx;
1366		for ( $i = 1; $i < @$_; $i += 2) {
1367			push @grid, $x, $$_[$i], $x, $$_[$i+1];
1368		}
1369	}
1370	for ( @{$self-> {hGrid}}) {
1371		my $y = $$_[0] + $dy;
1372		for ( $i = 1; $i < @$_; $i += 2) {
1373			push @grid, $$_[$i], $y, $$_[$i+1], $y;
1374		}
1375	}
1376
1377	# remove clipped cells
1378	splice( @colsDraw, $clipCells[2] + 1);
1379	splice( @colsDraw,  0, $clipCells[0]);
1380	@colsDraw = grep { $$_[2] > 0 } @colsDraw;
1381
1382	splice( @rowsDraw, $clipCells[1] + 1);
1383	splice( @rowsDraw,  0, $clipCells[3]);
1384	@rowsDraw = grep { $$_[2] > 0 } @rowsDraw;
1385
1386	# adjust cells rectangles not to overhang the active area
1387	for ( @colsDraw) {
1388		$$_[3] = $a[0] if $$_[3] < $a[0];
1389		$$_[4] = $a[2] if $$_[4] > $a[2];
1390	}
1391	for ( @rowsDraw) {
1392		$$_[3] = $a[1] if $$_[3] < $a[1];
1393		$$_[4] = $a[3] if $$_[4] > $a[3];
1394	}
1395
1396	$canvas-> color( $self-> {gridColor});
1397	$canvas-> lines( \@grid) if @grid;
1398
1399	$self-> draw_cells( $canvas, \@colsDraw, \@rowsDraw, \@a);
1400}
1401
1402#sub on_stringify
1403#{
1404#   my ( $self, $index, $sref) = @_;
1405#   $$sref = '';
1406#}
1407
1408sub set_border_width
1409{
1410	my ( $self, $bw) = @_;
1411	my $obw = $self-> {borderWidth};
1412	$self-> SUPER::set_border_width( $bw);
1413	return if $obw == $self-> {borderWidth};
1414	$self-> reset;
1415	$self-> repaint;
1416}
1417
1418sub set_h_scroll
1419{
1420	my ( $self, $hs) = @_;
1421	return if $hs == $self-> {hScroll};
1422
1423	$self-> SUPER::set_h_scroll( $hs);
1424
1425	if ( !($self-> {scrollTransaction} & 2)) {
1426		$self-> {scrollTransaction} |= 2;
1427		$self-> reset;
1428		$self-> {scrollTransaction} &= ~2;
1429	}
1430	$self-> repaint;
1431}
1432
1433sub set_v_scroll
1434{
1435	my ( $self, $vs) = @_;
1436	return if $vs == $self-> {vScroll};
1437
1438	$self-> SUPER::set_v_scroll( $vs);
1439
1440	if ( !($self-> {scrollTransaction} & 1)) {
1441		$self-> {scrollTransaction} |= 1;
1442		$self-> reset;
1443		$self-> {scrollTransaction} &= ~1;
1444	}
1445	$self-> repaint;
1446}
1447
1448sub VScroll_Change
1449{
1450	my ( $self, $scr) = @_;
1451	return if $self-> {scrollTransaction} & 1;
1452
1453	$self-> {scrollTransaction} |= 1;
1454	$self-> {rowUnits} ?
1455		$self-> topCell( $scr-> value + $self-> {cellIndents}-> [1]) :
1456		$self-> dy( $scr-> value);
1457	$self-> {scrollTransaction} &= ~1;
1458}
1459
1460sub HScroll_Change
1461{
1462	my ( $self, $scr) = @_;
1463	return if $self-> {scrollTransaction} & 2;
1464
1465	$self-> {scrollTransaction} |= 2;
1466	$self-> {colUnits} ?
1467		$self-> leftCell( $scr-> value + $self-> {cellIndents}-> [0]) :
1468		$self-> dx( $scr-> value);
1469	$self-> {scrollTransaction} &= ~2;
1470}
1471
1472sub allowChangeCellHeight
1473{
1474	return $_[0]-> {constantCellHeight} ? 0 : $_[0]-> {allowChangeCellHeight} unless $#_;
1475
1476	my ( $self, $h) = @_;
1477	$self-> {allowChangeCellHeight} = $h;
1478}
1479
1480sub allowChangeCellWidth
1481{
1482	return $_[0]-> {constantCellWidth} ? 0 : $_[0]-> {allowChangeCellWidth} unless $#_;
1483
1484	my ( $self, $w) = @_;
1485	$self-> {allowChangeCellWidth} = $w;
1486}
1487
1488sub cellIndents
1489{
1490	return wantarray ? @{$_[0]-> {cellIndents}} : [@{$_[0]-> {cellIndents}}] unless $#_;
1491
1492	my ( $self, @indents) = @_;
1493	@indents = @{$indents[0]} if ( scalar(@indents) == 1) && ( ref($indents[0]) eq 'ARRAY');
1494	for ( @indents) {
1495		$_ = 0 if $_ < 0;
1496	}
1497	if ( $indents[2] + $indents[0] > $self-> {columns}) {
1498		$indents[2] = $self-> {columns} - $indents[0];
1499		if ( $indents[2] < 0) {
1500			$indents[2] = 0;
1501			$indents[0] = $self-> {columns};
1502		}
1503	}
1504	if ( $indents[3] + $indents[1] > $self-> {columns}) {
1505		$indents[3] = $self-> {rows} - $indents[1];
1506		if ( $indents[3] < 0) {
1507			$indents[3] = 0;
1508			$indents[1] = $self-> {rows};
1509		}
1510	}
1511
1512	$self-> {leftCell}          += $indents[0] - $self-> {cellIndents}-> [0];
1513	$self-> {topCell}           += $indents[1] - $self-> {cellIndents}-> [1];
1514	$self-> {focusedCell}-> [0]  += $indents[0] - $self-> {cellIndents}-> [0];
1515	$self-> {focusedCell}-> [1]  += $indents[1] - $self-> {cellIndents}-> [1];
1516	$self-> {cellIndents} = \@indents;
1517
1518	$self-> reset;
1519	$self-> repaint;
1520}
1521
1522sub clipCells
1523{
1524	return $_[0]-> {clipCells} unless $#_;
1525	$_[0]-> {clipCells} = $_[1];
1526}
1527
1528sub colorIndex
1529{
1530	my ( $self, $index, $color) = @_;
1531	if ( $#_ < 2) {
1532		return $self-> {gridColor}           if $index == ci::Grid;
1533		return $self-> {indentCellColor}     if $index == ci::IndentCellFore;
1534		return $self-> {indentCellBackColor} if $index == ci::IndentCellBack;
1535		return $self-> SUPER::colorIndex( $index)
1536	} else {
1537		my $notify = 1;
1538		if ( $index == ci::Grid) {
1539			$self-> gridColor( $color);
1540		} elsif ( $index == ci::IndentCellFore) {
1541			$self-> indentCellColor( $color);
1542		} elsif ( $index == ci::IndentCellBack) {
1543			$self-> indentCellBackColor( $color);
1544		} else {
1545			$self-> SUPER::colorIndex( $index, $color);
1546			$notify = 0;
1547		}
1548		$self-> notify(q(ColorChanged), $index) if $notify;
1549	}
1550}
1551
1552sub columns
1553{
1554	return $_[0]-> {columns} unless $#_;
1555	my ( $self, $c) = @_;
1556	my $lim = $self-> {cellIndents}-> [0] + $self-> {cellIndents}-> [2];
1557	$lim = 1 if $lim < 1;
1558	$c = $lim if $c < $lim;
1559	$self-> {columns} = $c;
1560	$self-> reset;
1561	my @f = $self-> focusedCell;
1562	$self-> focusedCell( $c - $self-> {cellIndents}-> [2] - 1, $f[1])
1563		if $f[0] >= $c - $self-> {cellIndents}-> [2];
1564	$self-> reset;
1565	$self-> repaint;
1566}
1567
1568sub columnWidth
1569{
1570	my ( $self, $col, $width) = @_;
1571	if ( $#_ <= 1) {
1572		return $self-> {constantCellWidth} if $self-> {constantCellWidth};
1573		return $self-> {geometry_cache_column}-> {$col}
1574			if $self-> {cache_geometry_requests} &&
1575				exists $self-> {geometry_cache}-> {$col};
1576		my $ref = 0;
1577		$self-> notify(q(Measure), 0, $col, \$ref);
1578		$ref = 1 if $ref < 1;
1579		$self-> {geometry_cache_column}-> {$col} = $ref
1580			if $self-> {cache_geometry_requests};
1581		return $ref;
1582	} elsif ( !$self-> {constantCellWidth}) {
1583		$self-> notify(q(SetExtent), 0, $col, $width);
1584		$self-> reset;
1585		$self-> repaint;
1586	} else {
1587		$self-> constantCellWidth( $width);
1588	}
1589}
1590
1591sub constantCellHeight
1592{
1593	return $_[0]-> {constantCellHeight} unless $#_;
1594	my ( $self, $h) = @_;
1595	return if !defined( $self-> {constantCellHeight}) && !defined $h;
1596	return if
1597		defined($self-> {constantCellHeight}) &&
1598		defined($h) &&
1599		$self-> {constantCellHeight} == $h;
1600	$h = 1 if defined $h && $h < 1;
1601	$self-> {constantCellHeight} = $h;
1602	$self-> reset;
1603	$self-> repaint;
1604}
1605
1606sub constantCellWidth
1607{
1608	return $_[0]-> {constantCellWidth} unless $#_;
1609	my ( $self, $w) = @_;
1610	return if !defined( $self-> {constantCellWidth}) && !defined $w;
1611	return if
1612		defined($self-> {constantCellWidth}) &&
1613		defined($w) &&
1614		$self-> {constantCellWidth} == $w;
1615	$w = 1 if defined $w && $w < 1;
1616	$self-> {constantCellWidth} = $w;
1617	$self-> reset;
1618	$self-> repaint;
1619}
1620
1621sub drawHGrid
1622{
1623	return $_[0]-> {drawHGrid} unless $#_;
1624	my ( $self, $dh) = @_;
1625	$dh = $dh ? 1 : 0;
1626	return if $dh == $self-> {drawHGrid};
1627	$self-> {drawHGrid} = $dh;
1628	$self-> reset;
1629	$self-> repaint;
1630}
1631
1632sub drawVGrid
1633{
1634	return $_[0]-> {drawVGrid} unless $#_;
1635	my ( $self, $dv) = @_;
1636	$dv = $dv ? 1 : 0;
1637	return if $dv == $self-> {drawVGrid};
1638	$self-> {drawVGrid} = $dv;
1639	$self-> reset;
1640	$self-> repaint;
1641}
1642
1643sub dx
1644{
1645	return $_[0]-> {dx} unless $#_;
1646	my ( $self, $dx) = @_;
1647	return if $self-> {colUnits};
1648	my @size = $self-> size;
1649	my @a  = $self-> get_active_area(0, @size);
1650	my $w  = $a[2] - $a[0];
1651	$dx = 0 if $dx < 0;
1652	$dx = $self-> {colSpan} - $w if $dx > $self-> {colSpan} - $w;
1653	my $delta = $self-> {dx} - $dx;
1654	$self-> {dx} = $dx;
1655	if ( $self-> {hScroll} && !($self-> {scrollTransaction} & 2)) {
1656		$self-> {scrollTransaction} |= 2;
1657		$self-> {hScrollBar}-> value($dx);
1658		$self-> {scrollTransaction} &= ~2;
1659	}
1660	$self-> scroll( $delta, 0, clipRect => \@a);
1661	my @info = $self-> get_screen_cell_info( $self-> focusedCell);
1662	$self-> invalidate_rect( @info[ gsci::V_RECT] ) if scalar @info;
1663}
1664
1665sub dy
1666{
1667	return $_[0]-> {dy} unless $#_;
1668	my ( $self, $dy) = @_;
1669	return if $self-> {rowUnits};
1670	my @size = $self-> size;
1671	my @a  = $self-> get_active_area(0, @size);
1672	my $h  = $a[3] - $a[1];
1673	$dy = 0 if $dy < 0;
1674	$dy = $self-> {rowSpan} - $h if $dy > $self-> {rowSpan} - $h;
1675	my $delta = $dy - $self-> {dy};
1676	$self-> {dy} = $dy;
1677	if ( $self-> {vScroll} && !($self-> {scrollTransaction} & 1)) {
1678		$self-> {scrollTransaction} |= 1;
1679		$self-> {vScrollBar}-> value($dy);
1680		$self-> {scrollTransaction} &= ~1;
1681	}
1682	$self-> scroll( 0, $delta, clipRect => \@a);
1683	my @info = $self-> get_screen_cell_info( $self-> focusedCell);
1684	$self-> invalidate_rect( @info[ gsci::V_RECT ] ) if scalar @info;
1685}
1686
1687sub focusedCell
1688{
1689	return @{$_[0]-> {focusedCell}} unless $#_;
1690	my ( $self, @f) = @_;
1691	@f = @{$f[0]} if ( scalar(@f) == 1) && ( ref($f[0]) eq 'ARRAY');
1692
1693	my @in = @{$self-> {cellIndents}};
1694	my ( $c, $r) = ( $self-> {columns}, $self-> {rows});
1695	$f[0] = $in[0] if $f[0] < $in[0];
1696	$f[1] = $in[1] if $f[1] < $in[1];
1697	$f[0] = $c - $in[2] - 1 if $f[0] >= $c - $in[2];
1698	$f[1] = $r - $in[3] - 1 if $f[1] >= $r - $in[3];
1699	my @o = @{$self-> {focusedCell}};
1700	return if $o[0] == $f[0] && $o[1] == $f[1];
1701
1702	$self-> notify(q(SelectCell), @f);
1703	my @old = $self-> get_screen_cell_info( @o);
1704	my @new = $self-> get_screen_cell_info( @f);
1705	@{$self-> {focusedCell}} = @f;
1706
1707	if ( $new[gsci::V_FULL ]) {
1708		# the new cell is fully visible, need no scrolling
1709		$self-> invalidate_rect( @new[gsci::V_RECT]);
1710		$self-> invalidate_rect( @old[gsci::V_RECT]) if @old;
1711	} else {
1712		my @r = $self-> get_cell_area;
1713		my ( $x1, $y1, $x2, $y2) = (
1714			$self-> {leftCell}, $self-> {topCell},
1715			$self-> {leftCell} + $self-> {fullCols} - 1,
1716			$self-> {topCell}  + $self-> {fullRows} - 1
1717		);
1718		my ( $o, $t) = ( $x1, $y1);
1719
1720		$self-> begin_paint_info unless $self-> {NoBulkPaintInfo};
1721		if ( $f[0] > $x2) {
1722			$o = $f[0];
1723			my $maxw = $r[2] - $r[0] + 1 - $self-> columnWidth( $o) - $self-> {drawVGrid};
1724			while ( 1) {
1725				$maxw -= $self-> columnWidth( $o - 1) + $self-> {drawVGrid};
1726				last if $maxw < 0;
1727				$o--;
1728			}
1729		} elsif ( $f[0] < $x1) {
1730			$o = $f[0];
1731		}
1732		if ( $f[1] > $y2) {
1733			$t = $f[1];
1734			my $maxh = $r[3] - $r[1] + 1 - $self-> rowHeight( $t) - $self-> {drawHGrid};
1735			while ( 1) {
1736				$maxh -= $self-> rowHeight( $t - 1) + $self-> {drawHGrid};
1737				last if $maxh < 0;
1738				$t--;
1739			}
1740		} elsif ( $f[1] < $y1) {
1741			$t = $f[1];
1742		}
1743
1744		$self-> end_paint_info unless $self-> {NoBulkPaintInfo};
1745		$self-> leftCell( $o);
1746		$self-> topCell( $t);
1747		@old = $self-> get_screen_cell_info( @o);
1748		@new = $self-> get_screen_cell_info( @f);
1749		$self-> invalidate_rect( @new[gsci::V_RECT]) if @new;
1750		$self-> invalidate_rect( @old[gsci::V_RECT]) if @old;
1751	}
1752}
1753
1754sub gridColor
1755{
1756	return $_[0]-> {gridColor} unless $#_;
1757	my ( $self, $gc) = @_;
1758	return if $gc == $self-> {gridColor};
1759
1760	$self-> {gridColor} = $gc;
1761	$self-> repaint if $self-> {drawVGrid} || $self-> {drawHGrid};
1762}
1763
1764sub gridGravity
1765{
1766	return $_[0]-> {gridGravity} unless $#_;
1767
1768	my ( $self, $gg) = @_;
1769	$gg = 0 if $gg < 0;
1770	$self-> {gridGravity} = $gg;
1771}
1772
1773sub indentCellBackColor
1774{
1775	return $_[0]-> {indentCellBackColor} unless $#_;
1776	my ( $self, $c) = @_;
1777	return if $c == $self-> {indentCellBackColor};
1778
1779	$self-> {indentCellBackColor} = $c;
1780	$self-> repaint if grep { $_ > 0 } @{$self-> {cellIndents}};
1781}
1782
1783sub indentCellColor
1784{
1785	return $_[0]-> {indentCellColor} unless $#_;
1786	my ( $self, $c) = @_;
1787	return if $c == $self-> {indentCellColor};
1788
1789	$self-> {indentCellColor} = $c;
1790	$self-> repaint if grep { $_ > 0 } @{$self-> {cellIndents}};
1791}
1792
1793sub leftCell
1794{
1795	return $_[0]-> {leftCell} unless $#_;
1796
1797	my ( $self, $c) = @_;
1798	return if defined( $self-> {mouseTransaction}) && $self-> {mouseTransaction} == 2;
1799
1800	$c = $self-> {cellIndents}-> [0] if $c < $self-> {cellIndents}-> [0];
1801	$c = $self-> {colMax} if $c > $self-> {colMax};
1802	return if $c == $self-> {leftCell};
1803
1804	my ( $old, $unit, $span, $dv) = (
1805		$self-> {leftCell}, $self-> {colUnits}, $self-> {colSpan}, $self-> {drawVGrid});
1806	my @a = $self-> get_active_area( 0);
1807	my $width = $a[2] -
1808		$a[0] -
1809		$self-> {pixelCellIndents}-> [0] -
1810		$self-> {pixelCellIndents}-> [2];
1811	$self-> {leftCell} = $c;
1812	$self-> reset;
1813
1814	# see if the geometry changed too much after the reset
1815	if ( $unit != $self-> {colUnits}) {
1816		$self-> invalidate_rect( @a);
1817		return;
1818	}
1819	# When units are pixels, no scrolling can be done, just effective repaints.
1820	if ( !$unit) {
1821		$a[0] += $self-> {pixelCellIndents}-> [0];
1822		$self-> invalidate_rect( @a);
1823		return;
1824	}
1825
1826	# see if can do scrolling - calculate distance between
1827	# current and new x coordinates, not too far though
1828	my $w = 0;
1829	my $i = $old;
1830	$self-> begin_paint_info unless $self-> {NoBulkPaintInfo};
1831	if ( $i < $c) {
1832		while ( $w < $width && $i < $c) {
1833			$w += $self-> columnWidth( $i++) + $dv;
1834		}
1835	} else {
1836		while ( $w < $width && $i > $c) {
1837			$w += $self-> columnWidth( --$i) + $dv;
1838		}
1839	}
1840	$self-> end_paint_info unless $self-> {NoBulkPaintInfo};
1841	$a[0] += $self-> {pixelCellIndents}-> [0];
1842	$a[2] -= $self-> {pixelCellIndents}-> [2] + $dv;
1843	if ( $w < $width) {
1844		$w *= -1 if $old < $c;
1845		$self-> scroll( $w, 0, clipRect => \@a);
1846	} else {
1847		$self-> invalidate_rect( @a);
1848	}
1849}
1850
1851sub multiSelect
1852{
1853	return $_[0]-> {multiSelect} unless $#_;
1854	my ( $self, $ms) = @_;
1855	return if $ms == $self-> {multiSelect};
1856
1857	$self-> selection(-1,-1,-1,-1) if $self-> {multiSelect};
1858	$self-> {multiSelect} = $ms;
1859}
1860
1861sub rows
1862{
1863	return $_[0]-> {rows} unless $#_;
1864	my ( $self, $r) = @_;
1865	my $lim = $self-> {cellIndents}-> [1] + $self-> {cellIndents}-> [3];
1866	$lim = 1 if $lim < 1;
1867	$r = $lim if $r < $lim;
1868	$self-> {rows} = $r;
1869	$self-> reset;
1870	my @f = $self-> focusedCell;
1871	$self-> focusedCell( $f[0], $r - $self-> {cellIndents}-> [3] - 1)
1872		if $f[1] >= $r - $self-> {cellIndents}-> [3];
1873	$self-> reset;
1874	$self-> repaint;
1875}
1876
1877sub topCell
1878{
1879	return $_[0]-> {topCell} unless $#_;
1880
1881	my ( $self, $c) = @_;
1882	return if defined( $self-> {mouseTransaction}) && $self-> {mouseTransaction} == 3;
1883
1884	$c = $self-> {cellIndents}-> [1] if $c < $self-> {cellIndents}-> [1];
1885	$c = $self-> {rowMax} if $c > $self-> {rowMax};
1886	return if $c == $self-> {topCell};
1887
1888	my ( $old, $unit, $span, $dh) = (
1889		$self-> {topCell}, $self-> {rowUnits}, $self-> {rowSpan}, $self-> {drawHGrid});
1890	my @a = $self-> get_active_area( 0);
1891	my $height = $a[3] -
1892		$a[1] -
1893		$self-> {pixelCellIndents}-> [3] -
1894		$self-> {pixelCellIndents}-> [1];
1895	$self-> {topCell} = $c;
1896	$self-> reset;
1897
1898	# see if the geometry changed too much after the reset
1899	if ( $unit != $self-> {rowUnits}) {
1900		$self-> invalidate_rect( @a);
1901		return;
1902	}
1903	# When units are pixels, no scrolling can be done, just effective repaints.
1904	if ( !$unit) {
1905		$a[3] -= $self-> {pixelCellIndents}-> [1];
1906		$self-> invalidate_rect( @a);
1907		return;
1908	}
1909
1910	# see if can do scrolling - calculate distance between
1911	# current and new x coordinates, not too far though
1912	my $h = 0;
1913	my $i = $old;
1914	$self-> cache_geometry_requests(1);
1915	if ( $i < $c) {
1916		while ( $h < $height && $i < $c) {
1917			$h += $self-> rowHeight( $i++) + $dh;
1918		}
1919	} else {
1920		while ( $h < $height && $i > $c) {
1921			$h += $self-> rowHeight( --$i) + $dh;
1922		}
1923	}
1924	$self-> cache_geometry_requests(0);
1925	$a[1] += $self-> {pixelCellIndents}-> [3] + $dh;
1926	$a[3] -= $self-> {pixelCellIndents}-> [1];
1927	if ( $h < $height) {
1928		$h *= -1 if $old > $c;
1929		$self-> scroll( 0, $h, clipRect => \@a);
1930	} else {
1931		$self-> invalidate_rect( @a);
1932	}
1933}
1934
1935sub rowHeight
1936{
1937	my ( $self, $row, $height) = @_;
1938	if ( $#_ <= 1) {
1939		return $self-> {constantCellHeight} if $self-> {constantCellHeight};
1940		return $self-> {geometry_cache_row}-> {$row}
1941			if $self-> {cache_geometry_requests} &&
1942				exists $self-> {geometry_cache}-> {$row};
1943
1944		my $ref = 0;
1945		$self-> notify(q(Measure), 1, $row, \$ref);
1946		$ref = 1 if $ref < 1;
1947		$self-> {geometry_cache_row}-> {$row} = $ref
1948			if $self-> {cache_geometry_requests};
1949
1950		return $ref;
1951	} elsif ( !$self-> {constantCellHeight}) {
1952		$self-> notify(q(SetExtent), 1, $row, $height);
1953		$self-> reset;
1954		$self-> repaint;
1955	} else {
1956		$self-> constantCellHeight( $height);
1957	}
1958}
1959
1960sub selection
1961{
1962	return $_[0]-> {multiSelect} ?
1963		@{$_[0]-> {selection}} :
1964		(@{$_[0]-> {focusedCell}}, @{$_[0]-> {focusedCell}})
1965			unless $#_;
1966	return unless $_[0]-> {multiSelect};
1967
1968	my ( $self, $x1, $y1, $x2, $y2) = @_;
1969	( $x1, $x2) = ( $x2, $x1) if $x1 > $x2;
1970	( $y1, $y2) = ( $y2, $y1) if $y1 > $y2;
1971
1972	my @in = @{$self-> {cellIndents}};
1973	my ( $c, $r) = ( $self-> {columns}, $self-> {rows});
1974	if ( $x1 < 0 || $y1 < 0 || $x2 < 0 || $y2 < 0) {
1975		$x1 = $y1 = $x2 = $y2 = -1;
1976	} else {
1977		$x1 = $in[0] if $x1 < $in[0];
1978		$x1 = $c - $in[2] - 1 if $x1 >= $c - $in[2];
1979		$x2 = $in[0] if $x2 < $in[0];
1980		$x2 = $c - $in[2] - 1 if $x2 >= $c - $in[2];
1981		$y1 = $in[1] if $y1 < $in[1];
1982		$y1 = $r - $in[3] - 1 if $y1 >= $r - $in[3];
1983		$y2 = $in[1] if $y2 < $in[1];
1984		$y2 = $r - $in[3] - 1 if $y2 >= $r - $in[3];
1985	}
1986
1987	my ( $ox1, $oy1, $ox2, $oy2) = @{$self-> {selection}};
1988	return if $x1 == $ox1 && $y1 == $oy1 && $x2 == $ox2 && $y2 == $oy2;
1989
1990	$self-> {selection} = [$x1, $y1, $x2, $y2];
1991
1992	# union cell change
1993	$x1 = $ox1 if $x1 > $ox1;
1994	$x2 = $ox2 if $x2 < $ox2;
1995	$y1 = $oy1 if $y1 > $oy1;
1996	$y2 = $oy2 if $y2 < $oy2;
1997
1998	# intersect with screen cells, leave if the result is empty
1999	$ox1 = $self-> {leftCell};
2000	$oy1 = $self-> {topCell};
2001	$ox2 = $ox1 + $self-> {visibleCols};
2002	$oy2 = $oy1 + $self-> {visibleRows};
2003	return if $x1 > $ox2 || $x2 < $ox1 || $y1 > $oy2 || $y2 < $oy1;
2004	$x1 = $ox1 if $x1 < $ox1;
2005	$x2 = $ox2 if $x2 > $ox2;
2006	$y1 = $oy1 if $y1 < $oy1;
2007	$y2 = $oy2 if $y2 > $oy2;
2008
2009	# normalize
2010	( $x1, $x2) = ( $x2, $x1) if $x1 > $x2;
2011	( $y1, $y2) = ( $y2, $y1) if $y1 > $y2;
2012
2013	# get pixel coordinates
2014	my @info1 = $self-> get_screen_cell_info( $x1, $y2);
2015	my @info2 = $self-> get_screen_cell_info( $x2, $y1);
2016	if ( @info1 && @info2) {
2017		$self-> invalidate_rect(
2018			@info1[gsci::V_LEFT,gsci::V_BOTTOM],
2019			@info2[gsci::V_RIGHT,gsci::V_TOP]
2020		);
2021	} else {
2022		$self-> repaint;
2023	}
2024}
2025
2026package Prima::AbstractGrid;
2027use vars qw(@ISA);
2028@ISA = qw(Prima::AbstractGridViewer);
2029
2030sub draw_cells
2031{
2032	shift-> std_draw_text_cells(@_);
2033}
2034
2035sub on_fontchanged
2036{
2037	my $self = $_[0];
2038	$self-> constantCellHeight( $self-> font-> height + 2 ) if
2039		$self-> constantCellHeight;
2040}
2041
2042sub on_getrange
2043{
2044	my ( $self, $column, $index, $min, $max) = @_;
2045	$$min = $self-> font-> height + 2 unless $column;
2046}
2047
2048sub on_measure
2049{
2050	my ( $self, $vertical, $index, $sref) = @_;
2051	if ( $vertical) {
2052		$$sref = $self-> font-> height + 2;
2053	} else {
2054		$$sref = 0;
2055		for ( my $i = 0; $i < $self-> {colMax}; $i++ ) {
2056			my $w = $self-> get_text_width( $self->get_cell_text($i, $index), 1);
2057			$$sref = $w if $$sref < $w;
2058		}
2059	}
2060}
2061
2062package Prima::GridViewer;
2063use vars qw(@ISA);
2064@ISA = qw(Prima::AbstractGridViewer);
2065
2066sub profile_default
2067{
2068	my $def = $_[ 0]-> SUPER::profile_default;
2069	my %prf = (
2070		allowChangeCellHeight => 1,
2071		allowChangeCellWidth  => 1,
2072		cells                 => [['']],
2073	);
2074	@$def{keys %prf} = values %prf;
2075	return $def;
2076}
2077
2078sub init
2079{
2080	my $self = shift;
2081	$self-> {cells}      = [];
2082	$self-> {widths}     = [];
2083	$self-> {heights}    = [];
2084	my %profile = $self-> SUPER::init(@_);
2085	$self-> cells($profile{cells});
2086	return %profile;
2087}
2088
2089sub columnWidth
2090{
2091	my ( $self, $col, $width) = @_;
2092	if ( $#_ <= 1) {
2093		unless ( defined $self-> {widths}-> [$col]) {
2094			if ( defined $self-> {constantCellWidth}) {
2095				$self-> {widths}-> [$col] = $self-> {constantCellWidth};
2096			} else {
2097				my $ref = 0;
2098				$self-> notify(q(Measure), 0, $col, \$ref);
2099				$ref = 1 if $ref < 1;
2100				$self-> {widths}-> [$col] = $ref;
2101			}
2102		}
2103		return $self-> {widths}-> [$col];
2104	} elsif ( !$self-> {constantCellWidth}) {
2105		$width = 1 if $width < 1;
2106		return if
2107			defined($self-> {widths}-> [$col]) &&
2108			$width == $self-> {widths}-> [$col];
2109		$self-> {widths}-> [$col] = $width;
2110		$self-> notify(q(SetExtent), 0, $col, $width);
2111		$self-> reset;
2112		$self-> repaint;
2113	} else {
2114		$self-> constantCellWidth( $width);
2115	}
2116}
2117
2118sub rowHeight
2119{
2120	my ( $self, $row, $height) = @_;
2121	if ( $#_ <= 1) {
2122		unless ( defined $self-> {heights}-> [$row]) {
2123			if ( defined $self-> {constantCellHeight}) {
2124				$self-> {heights}-> [$row] = $self-> {constantCellHeight};
2125			} else {
2126				my $ref = 0;
2127				$self-> notify(q(Measure), 1, $row, \$ref);
2128				$ref = 1 if $ref < 1;
2129				$self-> {heights}-> [$row] = $ref;
2130			}
2131		}
2132		return $self-> {heights}-> [$row];
2133	} elsif ( !$self-> {constantCellHeight}) {
2134		$height = 1 if $height < 1;
2135		return if
2136			defined($self-> {heights}-> [$row]) &&
2137			$height == $self-> {heights}-> [$row];
2138		$self-> {heights}-> [$row] = $height;
2139		$self-> notify(q(SetExtent), 1, $row, $height);
2140		$self-> reset;
2141		$self-> repaint;
2142	} else {
2143		$self-> constantCellHeight( $height);
2144	}
2145}
2146
2147sub columns
2148{
2149	return $_[0]-> {columns} unless $#_;
2150	$_[0]-> raise_ro('columns') if $_[0]-> alive != 2; # AbstractGrid does it inside init
2151}
2152
2153sub rows
2154{
2155	return $_[0]-> {rows} unless $#_;
2156	$_[0]-> raise_ro('rows') if $_[0]-> alive != 2 # AbstractGrid does it inside init;
2157}
2158
2159sub constantCellWidth
2160{
2161	return $_[0]-> {constantCellWidth} unless $#_;
2162	my ( $self, $w) = @_;
2163	$self-> {widths}  = [( $self-> {constantCellWidth}  ) x $self-> {columns}];
2164	$self-> SUPER::constantCellWidth( $w);
2165}
2166
2167sub constantCellHeight
2168{
2169	return $_[0]-> {constantCellHeight} unless $#_;
2170	my ( $self, $h) = @_;
2171	$self-> {heights} = [( $self-> {constantCellHeight} ) x $self-> {rows}];
2172	$self-> SUPER::constantCellHeight( $h);
2173}
2174
2175sub cell
2176{
2177	my ( $self, $x, $y, $data) = @_;
2178	my ( $r, $c) = ( $self-> {rows}, $self-> {columns});
2179	return if $x < 0 || $x >= $c || $y < 0 || $y >= $c;
2180	if ( $#_ <= 2) {
2181		return $self-> {cells}-> [$y]-> [$x];
2182	} else {
2183		$self-> {cells}-> [$y]-> [$x] = $data;
2184	}
2185}
2186
2187sub cells
2188{
2189	return map { [ @$_ ] } @{$_[0]-> {cells}} unless $#_;
2190	my ( $self, @cells) = @_;
2191	@cells = @{$cells[0]} if ( scalar(@cells) == 1) && ( ref($cells[0]) eq 'ARRAY');
2192	$self-> {cells} = \@cells;
2193	$self-> SUPER::columns( scalar @{$cells[0]});
2194	$self-> SUPER::rows( scalar @cells);
2195	$self-> {widths}  = [( $self-> {constantCellWidth}  ) x $self-> {columns}];
2196	$self-> {heights} = [( $self-> {constantCellHeight} ) x $self-> {rows}];
2197}
2198
2199sub add_column
2200{
2201	my $self = shift;
2202	$self-> insert_column( $self-> {columns}, @_);
2203}
2204
2205sub add_columns
2206{
2207	my $self = shift;
2208	$self-> insert_columns( $self-> {columns}, @_);
2209}
2210
2211sub add_row
2212{
2213	my $self = shift;
2214	$self-> insert_row( $self-> {rows}, @_);
2215}
2216
2217sub add_rows
2218{
2219	my $self = shift;
2220	$self-> insert_rows( $self-> {rows}, @_);
2221}
2222
2223sub delete_columns
2224{
2225	my ( $self, $column, $how_many) = @_;
2226	my $c = $self-> {columns};
2227	$column = $c if $column > $c;
2228	splice( @$_, $column, $how_many) for @{$self-> {cells}};
2229	splice( @{$self-> {widths}}, $column, $how_many);
2230	$self-> SUPER::columns( scalar @{$self-> {cells}-> [0]});
2231}
2232
2233sub delete_rows
2234{
2235	my ( $self, $row, $how_many) = @_;
2236	my $r = $self-> {rows};
2237	$row = $r if $row > $r;
2238	splice( @{$self-> {cells}}, $row, $how_many);
2239	splice( @{$self-> {heights}}, $row, $how_many);
2240	$self-> SUPER::rows( scalar @{$self-> {cells}});
2241}
2242
2243sub insert_column
2244{
2245	my ( $self, $column, @cells) = @_;
2246	my $c = $self-> {columns};
2247	$column = $c if $column > $c;
2248	my $i;
2249	my $lim = ( scalar(@cells) < $c) ? scalar(@cells) : $c;
2250	for ( $i = 0; $i < $lim; $i++) {
2251		$c = $self-> {cells}-> [$i];
2252		splice( @$c, $column, 0, $cells[$i]);
2253	}
2254	splice( @{$self-> {widths}}, $column, 0, $self-> {constantCellWidths});
2255	$self-> SUPER::columns( scalar @{$self-> {cells}-> [0]});
2256}
2257
2258sub insert_columns
2259{
2260	my ( $self, $column, @cells) = @_;
2261	my $c = $self-> {columns};
2262	$column = $c if $column > $c;
2263	my $i;
2264	my $lim = ( scalar(@cells) < $c) ? scalar(@cells) : $c;
2265	for ( $i = 0; $i < $lim; $i++) {
2266		$c = $self-> {cells}-> [$i];
2267		splice( @$c, $column, 0, @{$cells[$i]});
2268	}
2269	splice( @{$self-> {widths}}, $column, 0, ( $self-> {constantCellWidths} ) x scalar(@cells));
2270	$self-> SUPER::columns( scalar @{$self-> {cells}-> [0]});
2271}
2272
2273sub insert_row
2274{
2275	my ( $self, $row, @cells) = @_;
2276	my $r = $self-> {rows};
2277	$row = $r if $row > $r;
2278	splice( @{$self-> {cells}},   $row, 0, [@cells]);
2279	splice( @{$self-> {heights}}, $row, 0, $self-> {constantCellHeight});
2280	$self-> SUPER::rows( scalar @{$self-> {cells}});
2281}
2282
2283sub insert_rows
2284{
2285	my ( $self, $row, @cells) = @_;
2286	my $r = $self-> {rows};
2287	$row = $r if $row > $r;
2288	splice( @{$self-> {cells}},   $row, 0, @cells);
2289	splice( @{$self-> {heights}}, $row, 0, ( $self-> {constantCellHeight} ) x scalar(@cells));
2290	$self-> SUPER::rows( scalar @{$self-> {cells}});
2291}
2292
2293package Prima::Grid;
2294use vars qw(@ISA);
2295@ISA = qw(Prima::GridViewer);
2296
2297sub draw_cells
2298{
2299	shift-> std_draw_text_cells(@_);
2300}
2301
2302sub get_cell_text
2303{
2304	my ( $self, $col, $row) = @_;
2305	return $self-> {cells}-> [$row]-> [$col];
2306}
2307
2308sub get_cell_alignment
2309{
2310	return (ta::Left, ta::Middle);
2311}
2312
2313sub on_getrange
2314{
2315	my ( $self, $column, $index, $min, $max) = @_;
2316	$$min = $self-> font-> height + 2 unless $column;
2317}
2318
2319sub on_fontchanged
2320{
2321	my $self = $_[0];
2322	$self-> constantCellHeight( $self-> font-> height + 2 ) if
2323		$self-> constantCellHeight;
2324}
2325
2326sub on_measure
2327{
2328	my ( $self, $vertical, $index, $sref) = @_;
2329	if ( $vertical) {
2330		$$sref = $self-> font-> height + 2;
2331	} else {
2332		$$sref = 0;
2333		for ( @{$self-> {cells}}) {
2334			my $w = $self-> get_text_width( $$_[$index], 1);
2335			$$sref = $w if $$sref < $w;
2336		}
2337	}
2338}
2339
2340sub on_stringify
2341{
2342	my ( $self, $col, $row, $sref) = @_;
2343	$$sref = $self-> {cells}-> [$row]-> [$col];
2344}
2345
23461;
2347
2348=pod
2349
2350=head1 NAME
2351
2352Prima::Grids - grid widgets
2353
2354=head2 SYNOPSIS
2355
2356	use Prima qw(Grids Application);
2357
2358	my $grid = Prima::Grid-> new(
2359		cells       => [
2360			[qw(1.First 1.Second 1.Third)],
2361			[qw(2.First 2.Second 2.Third)],
2362			[qw(3.First 3.Second 3.Third)],
2363		],
2364		onClick     => sub {
2365			print $_[0]-> get_cell_text( $_[0]-> focusedCell), " is selected\n";
2366		}
2367	);
2368	run Prima;
2369
2370=for podview <img src="grid.gif">
2371
2372=for html <p><img src="https://raw.githubusercontent.com/dk/Prima/master/pod/Prima/grid.gif">
2373
2374=head1 DESCRIPTION
2375
2376The module provides classes for several abstraction layers
2377of grid representation. The classes hierarchy is as follows:
2378
2379	AbstractGridViewer
2380		AbstractGrid
2381		GridViewer
2382			Grid
2383
2384The root class, C<Prima::AbstractGridViewer>, provides common
2385interface, while by itself it is not directly usable.
2386The main differences between classes
2387are centered around the way the cell data are stored. The simplest
2388organization of a text-only cell, provided by C<Prima::Grid>,
2389stores data as a two-dimensional array of text scalars. More elaborated storage
2390and representation types are not realized, and the programmer is urged
2391to use the more abstract classes to derive own mechanisms.
2392To organize an item storage, different from C<Prima::Grid>, it is
2393usually enough to overload either the C<Stringify>, C<Measure>,
2394and C<DrawCell> events, or their method counterparts: C<get_cell_text>,
2395C<columnWidth>, C<rowHeight>, and C<draw_items>.
2396
2397The grid widget is designed to contain cells of variable extents, of two types, normal and
2398indent. The indent rows and columns are displayed in grid margins, and their
2399cell are drawn with distinguished colors.
2400An example use for a bottom indent row is a sum row in a spreadsheet application;
2401the top indent row can be used for displaying columns' headers. The normal cells
2402can be selected by the user, scrolled, and selected. The cell selection
2403can only contain rectangular areas, and therefore is operated with
2404two integer pairs with the beginning and the end of the selection.
2405
2406The widget operates in two visual scrolling modes; when the space allows,
2407the scrollbars affect the leftmost and the topmost cell. When the widget is
2408not large enough to accommodate at least one cell and all indent cells, the layout
2409is scrolled pixel-wise. These modes are named 'cell' and 'pixel', after the scrolling
2410units.
2411
2412The widget allows the interactive changing of cell widths and heights by dragging
2413the grid lines between the cells.
2414
2415=head1 Prima::AbstractGridViewer
2416
2417C<Prima::AbstractGridViewer>, the base for all grid widgets in the module,
2418provides interface to generic grid browsing functionality,
2419plus functionality for text-oriented grids. The class is not usable directly.
2420
2421C<Prima::AbstractGridViewer> is a descendant of C<Prima::GroupScroller>,
2422and some properties are not described here. See L<Prima::IntUtils/"Prima::GroupScroller">.
2423
2424=head2 Properties
2425
2426=over
2427
2428=item allowChangeCellHeight BOOLEAN
2429
2430If 1, the user is allowed to change vertical extents of cells by dragging the
2431horizontal grid lines. Prerequisites to the options are:
2432the lines must be set visible via C<drawHGrid> property, C<constantCellHeight>
2433property set to 0, and the changes to the vertical extents can be recorded
2434via C<SetExtent> notification.
2435
2436Default value: 0
2437
2438=item allowChangeCellWidth BOOLEAN
2439
2440If 1, the user is allowed to change horizontal extents of cells by dragging the
2441horizontal grid lines. Prerequisites to the options are:
2442the lines must be set visible via C<drawVGrid> property, C<constantCellWidth>
2443property set to 0, and the changes to the horizontal extents can be recorded
2444via C<SetExtent> notification.
2445
2446Default value: 0
2447
2448=item cellIndents X1, Y1, X2, Y2
2449
2450Marks the marginal rows and columns as 'indent' cells. The indent cells
2451are drawn with another color pair ( see L<indentCellColor>, L<indentCellBackColor> ),
2452cannot be selected and scrolled. X1 and X2 correspond to amount of indent columns,
2453and Y1 and Y2, - to the indent rows.
2454
2455C<leftCell> and C<topCell> do not count the indent cells as the leftmost or topmost
2456visible cell; in other words, X1 and Y1 are minimal values for C<leftCell> and C<topCell>
2457properties.
2458
2459Default value: 0,0,0,0
2460
2461=item clipCells INTEGER
2462
2463A three-state integer property, that governs the way clipping is applied
2464when cells are drawn. Depending on kind of graphic in cells, the clipping
2465may be necessary, or unnecessary.
2466
2467If the value is 1, the clipping is applied for every column drawn, as the
2468default drawing routines proceed column-wise. If the value is 2, the clipping
2469as applied for every cell. This setting reduces the drawing speed significantly.
2470If the value is 0, no clipping is applied.
2471
2472This property is destined for custom-drawn grid widgets, when it is the
2473developer's task to decide what kind of clipping suits better. Text grid
2474widgets, C<Prima::AbstractGrid> and C<Prima::Grid>, are safe with C<clipCells>
2475set to 1.
2476
2477Default value: 1
2478
2479=item columns INTEGER
2480
2481Sets number of columns, including the indent columns. The number of
2482columns must be larger than the number of indent columns.
2483
2484Default value: 0.
2485
2486=item columnWidth COLUMN [ WIDTH ]
2487
2488A run-time property, selects width of a column. To acquire or set
2489the width, C<Measure> and C<SetExtent> notifications can be invoked.
2490Result of C<Measure> may be cached internally using C<cache_geometry_requests>
2491method.
2492
2493The width does not include widths of eventual vertical grid lines.
2494
2495If C<constantCellWidth> is defined, the property is used as its alias.
2496
2497=item constantCellHeight HEIGHT
2498
2499If defined, all rows have equal height, HEIGHT pixels. If C<undef>,
2500rows have different heights.
2501
2502Default value: undef
2503
2504=item constantCellWidth WIDTH
2505
2506If defined, all rows have equal width, WIDTH pixels. If C<undef>,
2507columns have different widths.
2508
2509Default value: undef
2510
2511=item drawHGrid BOOLEAN
2512
2513If 1, horizontal grid lines between cells are drawn with C<gridColor>.
2514
2515Default value: 1
2516
2517=item drawVGrid
2518
2519If 1, vertical grid lines between cells are drawn with C<gridColor>.
2520
2521Default value: 1
2522
2523=item dx INTEGER
2524
2525A run-time property. Selects horizontal offset in pixels of grid layout
2526in pixel mode.
2527
2528=item dy INTEGER
2529
2530A run-time property. Selects vertical offset in pixels of grid layout
2531in pixel mode.
2532
2533=item focusedCell X, Y
2534
2535Selects coordinates or the focused cell.
2536
2537=item gridColor COLOR
2538
2539Selects the color of grid lines.
2540
2541Default value: C<cl::Black> .
2542
2543=item gridGravity INTEGER
2544
2545The property selects the breadth of area around the grid lines, that
2546reacts on grid-dragging mouse events. The minimal value, 0, marks
2547only grid lines as the drag area, but makes the dragging operation inconvenient
2548for the user.
2549Larger values make the dragging more convenient, but increase the chance that
2550the user will not be able to select too narrow cells with the mouse.
2551
2552Default value: 3
2553
2554=item indentCellBackColor COLOR
2555
2556Selects the background color of indent cells.
2557
2558Default value: C<cl::Gray> .
2559
2560=item indentCellColor
2561
2562Selects the foreground color of indent cells.
2563
2564Default value: C<cl::Gray> .
2565
2566=item leftCell INTEGER
2567
2568Selects index of the leftmost visible normal cell.
2569
2570=item multiSelect BOOLEAN
2571
2572If 1, the normal cells in an arbitrary rectangular area can be marked
2573as selected ( see L<selection> ). If 0, only one cell at a time
2574can be selected.
2575
2576Default value: 0
2577
2578=item rows INTEGER
2579
2580Sets number of rows, including the indent rows. The number of
2581rows must be larger than the number of indent rows.
2582
2583Default value: 0.
2584
2585=item topCell
2586
2587Selects index of the topmost visible normal cell.
2588
2589=item rowHeight INTEGER
2590
2591A run-time property, selects height of a row. To acquire or set
2592the height, C<Measure> and C<SetExtent> notifications can be invoked.
2593Result of C<Measure> may be cached internally using C<cache_geometry_requests>
2594method.
2595
2596The height does not include widths of eventual horizontal grid lines.
2597
2598If C<constantCellHeight> is defined, the property is used as its alias.
2599
2600=item selection X1, Y1, X2, Y2
2601
2602If C<multiSelect> is 1, governs the extents of a rectangular area, that
2603contains selected cells. If no such area is present, selection
2604is (-1,-1,-1,-1), and C<has_selection> returns 0 .
2605
2606If C<multiSelect> is 0, in get-mode returns the focused cell, and discards
2607the parameters in the set-mode.
2608
2609=back
2610
2611=head2 Methods
2612
2613=over
2614
2615=item cache_geometry_requests CACHE
2616
2617If CACHE is 1, starts caching results of C<Measure> notification, thus lighting the
2618subsequent C<columnWidth> and C<rowHeight> calls; if CACHE is 0, flushes the cache.
2619
2620If a significant geometry change was during the caching, the cache is not updated, so it is the
2621caller's responsibility to flush the cache.
2622
2623=item deselect_all
2624
2625Nullifies the selection, if C<multiSelect> is 1.
2626
2627=item draw_cells CANVAS, COLUMNS, ROWS, AREA
2628
2629A bulk draw routine, called from C<onPaint> to draw cells.
2630AREA is an array of four integers with inclusive-inclusive
2631coordinates of the widget inferior without borders and scrollbars
2632( result of C<get_active_area(2)> call; see L<Prima::IntUtils/get_active_area> ).
2633
2634COLUMNS and ROWS are structures that reflect the columns and rows of the cells
2635to be drawn. Each item in these corresponds to a column or row, and is an
2636array with the following layout:
2637
2638	0: column or row index
2639	1: type; 0 - normal cell, 1 - indent cell
2640	2: visible cell breadth
2641	3: visible cell start
2642	4: visible cell end
2643	5: real cell start
2644	6: real cell end
2645
2646The coordinates are in inclusive-inclusive coordinate system, and
2647do not include eventual grid space, nor gaps between indent and
2648normal cells. By default, internal arrays C<{colsDraw}> and
2649C<{rowsDraw}> are passed as COLUMNS and ROWS parameters.
2650
2651In C<Prima::AbstractGrid> and C<Prima::Grid> classes <draw_cells> is overloaded to
2652transfer the call to C<std_draw_text_cells>, the text-oriented optimized routine.
2653
2654=item draw_text_cells SCREEN_RECTANGLES, CELL_RECTANGLES, CELL_INDECES, FONT_HEIGHT
2655
2656A bulk routine for drawing text cells, called from C<std_draw_text_cells> .
2657
2658SCREEN_RECTANGLES and CELL_RECTANGLES are arrays, where each item is a rectangle
2659with exterior of a cell. SCREEN_RECTANGLES contains rectangles that cover the
2660cell visible area; CELL_RECTANGLES contains rectangles that span the cell extents
2661disregarding its eventual partial visibility. For example, a 100-pixel cell with
2662only its left half visible, would contain corresponding arrays [150,150,200,250]
2663in SCREEN_RECTANGLES, and [150,150,250,250] in CELL_RECTANGLES.
2664
2665CELL_INDECES contains arrays of the cell coordinates; each array item is an array of
2666integer pair where item 0 is column, and item 1 is row of the cell.
2667
2668FONT_HEIGHT is a current font height value, cached since C<draw_text_cells> is
2669often used for text operations and may require vertical text justification.
2670
2671=item get_cell_area [ WIDTH, HEIGHT ]
2672
2673Returns screen area in inclusive-inclusive pixel coordinates, that is used
2674to display normal cells. The extensions are related to the current size of a widget,
2675however, can be overridden by specifying WIDTH and HEIGHT.
2676
2677=item get_cell_alignment COLUMN, ROW
2678
2679Returns two C<ta::> constants for horizontal and vertical cell text alignment.
2680Since the class does not assume the item storage organization,
2681the values are queried via C<GetAlignment> notification.
2682
2683=item get_cell_text COLUMN, ROW
2684
2685Returns text string assigned to cell in COLUMN and ROW.
2686Since the class does not assume the item storage organization,
2687the text is queried via C<Stringify> notification.
2688
2689=item get_range VERTICAL, INDEX
2690
2691Returns a pair of integers, minimal and maximal breadth of INDEXth column
2692or row in pixels. If VERTICAL is 1, the rows are queried; if 0, the columns.
2693
2694The method calls C<GetRange> notification.
2695
2696=item get_screen_cell_info COLUMN, ROW
2697
2698Returns information about a cell in COLUMN and ROW, if it is currently visible.
2699The returned parameters are indexed by C<gsci::XXX> constants,
2700and explained below:
2701
2702	gsci::COL_INDEX - visual column number where the cell displayed
2703	gsci::ROW_INDEX - visual row number where the cell displayed
2704	gsci::V_FULL    - cell is fully visible
2705
2706	gsci::V_LEFT    - inclusive-inclusive rectangle of the visible
2707	gsci::V_BOTTOM    part of the cell. These four indices are grouped
2708	gsci::V_RIGHT     under list constant, gsci::V_RECT.
2709	gsci::V_TOP
2710
2711	gsci::LEFT      - inclusive-inclusive rectangle of the cell, as if
2712	gsci::BOTTOM      it is fully visible. These four indices are grouped
2713	gsci::RIGHT       under list constant, gsci::RECT. If gsci::V_FULL
2714	gsci::TOP         is 1, these values are identical to these in gsci::V_RECT.
2715
2716If the cell is not visible, returns empty array.
2717
2718=item has_selection
2719
2720Returns a boolean value, indicating whether the grid contains a selection (1) or not (0).
2721
2722=item point2cell X, Y, [ OMIT_GRID = 0 ]
2723
2724Return information about point X, Y in widget coordinates. The method
2725returns two integers, CX and CY, with cell coordinates, and
2726eventual HINTS hash, with more information about pixe localtion. If OMIT_GRID is set to 1
2727and the pixel belongs to a grid, the pixels is treated a part of adjacent cell.
2728The call syntax:
2729
2730	( $CX, $CY, %HINTS) = $self->point2cell( $X, $Y);
2731
2732If the pixel lies within cell boundaries by either coordinate, CX and/or CY
2733are correspondingly set to cell column and/or row. When the pixel is outside
2734cell space, CX and/or CY are set to -1.
2735
2736HINTS may contain the following values:
2737
2738=over
2739
2740=item C<x> and C<y>
2741
2742If 0, the coordinate lies within boundaries of a cell.
2743
2744If -1, the coordinate is on the left/top to the cell body.
2745
2746If +1, the coordinate is on the right/bottom to the cell body, but within
2747the widget.
2748
2749If +2, the coordinate is on the right/bottom to the cell body, but outside
2750the widget.
2751
2752=item C<x_type> and C<y_type>
2753
2754Present when C<x> or C<y> values are 0.
2755
2756If 0, the cell is a normal cell.
2757
2758If -1, the cell is left/top indent cell.
2759
2760If +1, the cell is right/bottom indent cell.
2761
2762=item C<x_grid> and C<y_grid>
2763
2764If 1, the point is over a grid line. This case can only happen when OMIT_GRID is 0.
2765If C<allowChangeCellHeight> and/or C<allowChangeCellWidth> are set, treats also
2766C<gridGravity>-broad pixels strips on both sides of the line as the grid area.
2767
2768Also values of C<x_left>/C<x_right> or C<y_bottom>/C<y_top> might be set.
2769
2770=item C<x_left>/C<x_right> and C<y_bottom>/C<y_top>
2771
2772Present together with C<x_grid> or C<y_grid>. Select indices of
2773cells adjacent to the grid line.
2774
2775=item C<x_gap> and C<y_gap>
2776
2777If 1, the point is within a gap between the last normal cell and the first
2778right/bottom indent cell.
2779
2780=item C<normal>
2781
2782If 1, the point lies within the boundaries of a normal cell.
2783
2784=item C<indent>
2785
2786If 1, the point lies within the boundaries of an indent cell.
2787
2788=item C<grid>
2789
2790If 1, the point is over a grid line.
2791
2792=item C<exterior>
2793
2794If 1, the point is in inoperable area or outside the widget boundaries.
2795
2796=back
2797
2798=item redraw_cell X, Y
2799
2800Repaints cell with coordinates X and Y.
2801
2802=item reset
2803
2804Recalculates internal geometry variables.
2805
2806=item select_all
2807
2808Marks all cells as selected, if C<multiSelect> is 1.
2809
2810=item std_draw_text_cells CANVAS, COLUMNS, ROWS, AREA
2811
2812An optimized bulk routine for text-oriented grid widgets. The optimization
2813is achieved under assumption that each cell is drawn with two colors only,
2814so the color switching can be reduced.
2815
2816The routine itself paints the cells background, and calls C<draw_text_cells>
2817to draw text and/or otherwise draw the cell content.
2818
2819For explanation of COLUMNS, ROWS, and AREA parameters see L<draw_cells> .
2820
2821=back
2822
2823=head2 Events
2824
2825=over
2826
2827=item DrawCell CANVAS, COLUMN, ROW, INDENT, @SCREEN_RECT, @CELL_RECT, SELECTED, FOCUSED, PRELIGHT
2828
2829Called when a cell with COLUMN and ROW coordinates is to be drawn on CANVAS.
2830SCREEN_RECT is a cell rectangle in widget coordinates,
2831where the item is to be drawn. CELL_RECT is same as SCREEN_RECT, but calculated
2832as if the cell is fully visible.
2833
2834SELECTED, FOCUSED, and PRELIGHT are boolean flagss, if the cell must be drawn
2835correspondingly in selected, focused, and pre-lighted states.
2836
2837=item GetAlignment COLUMN, ROW, HORIZONTAL_ALIGN_REF, VERTICAL_ALIGN_REF
2838
2839Puts two text alignment C<ta::> constants, assigned to cell with COLUMN and ROW coordinates,
2840into HORIZONTAL_ALIGN_REF and VERTICAL_ALIGN_REF scalar references.
2841
2842=item GetRange VERTICAL, INDEX, MIN, MAX
2843
2844Puts minimal and maximal breadth of INDEXth column ( VERTICAL = 0 ) or row ( VERTICAL = 1)
2845in corresponding MIN and MAX scalar references.
2846
2847=item Measure VERTICAL, INDEX, BREADTH
2848
2849Puts breadth in pixels of INDEXth column ( VERTICAL = 0 ) or row ( VERTICAL = 1)
2850into BREADTH scalar reference.
2851
2852This notification by default may be called from within
2853C<begin_paint_info/end_paint_info> brackets. To disable this feature
2854set internal flag C<{NoBulkPaintInfo}> to 1.
2855
2856=item SelectCell COLUMN, ROW
2857
2858Called when a cell with COLUMN and ROW coordinates is focused.
2859
2860=item SetExtent VERTICAL, INDEX, BREADTH
2861
2862Reports breadth in pixels of INDEXth column ( VERTICAL = 0 ) or row ( VERTICAL = 1),
2863as a response to C<columnWidth> and C<rowHeight> calls.
2864
2865=item Stringify COLUMN, ROW, TEXT_REF
2866
2867Puts text string, assigned to cell with COLUMN and ROW coordinates, into TEXT_REF
2868scalar reference.
2869
2870=back
2871
2872=head1 Prima::AbstractGrid
2873
2874Exactly the same as its ascendant, C<Prima::AbstractGridViewer>,
2875except that it does not propagate C<DrawItem> message,
2876assuming that the items must be drawn as text.
2877
2878=head1 Prima::GridViewer
2879
2880The class implements cells data and geometry storage mechanism, but leaves
2881the cell data format to the programmer. The cells are accessible via
2882C<cells> property and several other helper routines.
2883
2884The cell data are stored in an array, where each item corresponds to a row,
2885and contains array of scalars, where each corresponds to a column. All
2886data managing routines, that accept two-dimensional arrays, assume that
2887the columns arrays are of the same widths.
2888
2889For example, C<[[1,2,3]]]> is a valid one-row, three-column structure, and
2890C<[[1,2],[2,3],[3,4]]> is a valid three-row, two-column structure.
2891The structure C<[[1],[2,3],[3,4]]> is invalid, since its first row has
2892one column, while the others have two.
2893
2894C<Prima::GridViewer> is derived from C<Prima::AbstractGridViewer>.
2895
2896=head2 Properties
2897
2898=over
2899
2900=item allowChangeCellHeight
2901
2902Default value: 1
2903
2904=item allowChangeCellWidth
2905
2906Default value: 1
2907
2908=item cell COLUMN, ROW, [ DATA ]
2909
2910Run-time property. Selects the data in cell with COLUMN and ROW coordinates.
2911
2912=item cells [ ARRAY ]
2913
2914The property accepts or returns all cells as a two-dimensional
2915rectangular array or scalars.
2916
2917=item columns INDEX
2918
2919A read-only property; returns number of columns.
2920
2921=item rows INDEX
2922
2923A read-only property; returns number of rows.
2924
2925=back
2926
2927=head2 Methods
2928
2929=over
2930
2931=item add_column CELLS
2932
2933Inserts one-dimensional array of scalars to the end of columns.
2934
2935=item add_columns CELLS
2936
2937Inserts two-dimensional array of scalars to the end of columns.
2938
2939=item add_row CELLS
2940
2941Inserts one-dimensional array of scalars to the end of rows.
2942
2943=item add_rows CELLS
2944
2945Inserts two-dimensional array of scalars to the end of rows.
2946
2947=item delete_columns OFFSET, LENGTH
2948
2949Removes LENGTH columns starting from OFFSET. Negative values
2950are accepted.
2951
2952=item delete_rows OFFSET, LENGTH
2953
2954Removes LENGTH rows starting from OFFSET. Negative values
2955are accepted.
2956
2957=item insert_column OFFSET, CELLS
2958
2959Inserts one-dimensional array of scalars as column OFFSET.
2960Negative values are accepted.
2961
2962=item insert_columns OFFSET, CELLS
2963
2964Inserts two-dimensional array of scalars in column OFFSET.
2965Negative values are accepted.
2966
2967=item insert_row
2968
2969Inserts one-dimensional array of scalars as row OFFSET.
2970Negative values are accepted.
2971
2972=item insert_rows
2973
2974Inserts two-dimensional array of scalars in row OFFSET.
2975Negative values are accepted.
2976
2977=back
2978
2979=head1 Prima::Grid
2980
2981Descendant of C<Prima::GridViewer>, declares format of cells
2982as a single text string. Incorporating all functionality of
2983its ascendants, provides a standard text grid widget.
2984
2985=head2 Methods
2986
2987=over
2988
2989=item get_cell_alignment COLUMN, ROW
2990
2991Returns two C<ta::> constants for horizontal and vertical cell text alignment.
2992Since the item storage organization is implemented, does
2993so without calling C<GetAlignment> notification.
2994
2995=item get_cell_text COLUMN, ROW
2996
2997Returns text string assigned to cell in COLUMN and ROW.
2998Since the item storage organization is implemented, does
2999so without calling C<Stringify> notification.
3000
3001=back
3002
3003=head1 AUTHOR
3004
3005Dmitry Karasik, E<lt>dmitry@karasik.eu.orgE<gt>.
3006
3007=head1 SEE ALSO
3008
3009L<Prima>, L<Prima::Widget>, F<examples/grid.pl>
3010
3011=cut
3012
3013