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