1# Created by: 2# Dmitry Karasik <dk@plab.ku.dk> 3# Anton Berezin <tobez@tobez.org> 4package Prima::Lists; 5 6# contains: 7# AbstractListViewer 8# AbstractListBox 9# ListViewer 10# ListBox 11# ProtectedListBox 12 13use strict; 14use warnings; 15use Prima::Const; 16use Prima::Classes; 17use Prima::ScrollBar; 18use Prima::StdBitmap; 19use Prima::IntUtils; 20use Prima::Utils; 21 22package 23 ci; 24 25BEGIN { 26eval 'use constant Grid => 1 + MaxId;' unless exists $ci::{Grid}; 27} 28 29package Prima::AbstractListViewer; 30use vars qw(@ISA); 31@ISA = qw(Prima::Widget Prima::MouseScroller Prima::GroupScroller Prima::ListBoxUtils); 32 33use Prima::Classes; 34 35{ 36my %RNT = ( 37 %{Prima::Widget-> notification_types()}, 38 SelectItem => nt::Default, 39 DrawItem => nt::Action, 40 Stringify => nt::Action, 41 MeasureItem => nt::Action, 42 DragItem => nt::Default, 43); 44 45sub notification_types { return \%RNT; } 46} 47 48sub profile_default 49{ 50 my $def = $_[ 0]-> SUPER::profile_default; 51 my %prf = ( 52 align => ta::Left, 53 autoHeight => 1, 54 autoHScroll => 1, 55 autoVScroll => 1, 56 borderWidth => 2, 57 extendedSelect => 0, 58 drawGrid => 1, 59 dragable => 0, 60 focusedItem => -1, 61 gridColor => cl::Black, 62 hScroll => 0, 63 integralHeight => 0, 64 integralWidth => 0, 65 itemHeight => $def-> {font}-> {height}, 66 itemWidth => $def-> {width} - 2, 67 multiSelect => 0, 68 multiColumn => 0, 69 offset => 0, 70 topItem => 0, 71 scaleChildren => 0, 72 scrollBarClass => 'Prima::ScrollBar', 73 hScrollBarProfile=>{}, 74 vScrollBarProfile=>{}, 75 selectable => 1, 76 selectedItems => [], 77 vertical => 1, 78 vScroll => 1, 79 widgetClass => wc::ListBox, 80 ); 81 @$def{keys %prf} = values %prf; 82 return $def; 83} 84 85sub profile_check_in 86{ 87 my ( $self, $p, $default) = @_; 88 $self-> SUPER::profile_check_in( $p, $default); 89 $p-> { multiSelect} = 1 if 90 exists $p-> { extendedSelect} && 91 $p-> {extendedSelect} && 92 !exists $p-> {multiSelect}; 93 $p-> { autoHeight} = 0 if 94 exists $p-> { itemHeight} && 95 !exists $p-> {autoHeight}; 96 my $multi_column = exists($p->{multiColumn}) ? 97 $p->{multiColumn} : $default->{multiColumn}; 98 my $vertical = exists($p->{vertical}) ? 99 $p->{vertical} : $default->{vertical}; 100 $p-> { integralHeight} = 1 if 101 ! exists $p->{integralHeight} and 102 ( not($multi_column) or $vertical); 103 $p-> { integralWidth} = 1 if 104 ! exists $p->{integralWidth} and 105 $multi_column and not($vertical); 106 $p-> {autoHScroll} = 0 if exists $p-> {hScroll}; 107 $p-> {autoVScroll} = 0 if exists $p-> {vScroll}; 108} 109 110sub init 111{ 112 my $self = shift; 113 for ( qw( lastItem topItem focusedItem)) 114 { $self-> {$_} = -1; } 115 for ( qw( 116 autoHScroll autoVScroll scrollTransaction gridColor dx dy hScroll vScroll 117 itemWidth offset multiColumn count autoHeight multiSelect 118 extendedSelect borderWidth dragable )) 119 { $self-> {$_} = 0; } 120 for ( qw( drawGrid itemHeight integralWidth integralHeight vertical align)) 121 { $self-> {$_} = 1; } 122 $self-> {selectedItems} = {}; 123 my %profile = $self-> SUPER::init(@_); 124 $self-> setup_indents; 125 $self-> {selectedItems} = {} unless $profile{multiSelect}; 126 $self->{$_} = $profile{$_} for qw(scrollBarClass hScrollBarProfile vScrollBarProfile); 127 for ( qw( 128 autoHScroll autoVScroll gridColor hScroll vScroll offset multiColumn 129 itemHeight autoHeight itemWidth multiSelect extendedSelect integralHeight 130 integralWidth focusedItem topItem selectedItems borderWidth dragable 131 vertical drawGrid align)) 132 { $self-> $_( $profile{ $_}); } 133 $self-> reset; 134 $self-> reset_scrolls; 135 return %profile; 136} 137 138 139sub draw_items 140{ 141 my ($self, $canvas) = (shift, shift); 142 my ( $notifier, @notifyParms) = $self-> get_notify_sub(q(DrawItem)); 143 $self-> push_event; 144 for ( @_) { $notifier-> ( @notifyParms, $canvas, @$_); } 145 $self-> pop_event; 146} 147 148sub item2rect 149{ 150 my ( $self, $item, @size) = @_; 151 my @a = $self-> get_active_area( 0, @size); 152 153 if ( $self-> {multiColumn}) { 154 $item -= $self-> {topItem}; 155 my $who = $self-> {vertical} ? 'rows' : 'columns'; 156 my ($j,$i,$ih,$iw,$dg) = ( 157 $self-> {$who} ? ( 158 int( $item / $self-> {$who} - (( $item < 0) ? 1 : 0)), 159 $item % $self-> {$who} 160 ) : (-1, 1), 161 $self-> {itemHeight}, 162 $self-> {itemWidth}, 163 $self-> {drawGrid} 164 ); 165 ($i,$j)=($j,$i) unless $self->{vertical}; 166 167 return 168 $a[0] + $j * ( $iw + $dg), 169 $a[3] - $ih * ( $i + 1), 170 $a[0] + $j * ( $iw + $dg) + $iw, 171 $a[3] - $ih * ( $i + 1) + $ih; 172 } else { 173 my ($i,$ih) = ( $item - $self-> {topItem}, $self-> {itemHeight}); 174 return $a[0], $a[3] - $ih * ( $i + 1), $a[2], $a[3] - $ih * $i; 175 } 176} 177 178sub on_paint 179{ 180 my ($self,$canvas) = @_; 181 my @size = $canvas-> size; 182 183 unless ( $self-> enabled) { 184 $self-> color( $self-> disabledColor); 185 $self-> backColor( $self-> disabledBackColor); 186 } 187 my ( $ih, $iw, $dg, @a) = ( 188 $self-> { itemHeight}, 189 $self-> {itemWidth}, $self-> {drawGrid}, 190 $self-> get_active_area( 1, @size) 191 ); 192 193 my $i; 194 my $j; 195 my $locWidth = $a[2] - $a[0] + 1; 196 my @invalidRect = $canvas-> clipRect; 197 $self-> draw_border( $canvas, undef, @size); 198 199 if ( $self-> {multiColumn}) { 200 my $xstart = $a[0]; 201 my $yend = $size[1] - $self-> {active_rows} * $ih - 1; 202 my $uncover = $self->{uncover}; 203 my $ymiddle = $a[1] + $uncover->{y} + $self->{yedge} - 1 204 if defined($uncover); 205 206 for ( $i = 0; $i < $self-> {partial_columns}; $i++) { 207 my $y = ( 208 defined($uncover) and 209 $i >= $uncover->{x} and 210 $i < $self-> {active_columns} 211 ) ? 212 $ymiddle : 213 (( $i < $self->{active_columns}) ? 214 $yend : 215 $a[3] 216 ); 217 $canvas-> clear( 218 $xstart, $a[1], 219 ( $xstart + $iw - 1 > $a[2]) ? 220 $a[2] : 221 $xstart + $iw - 1, 222 $y 223 ) if $xstart >= $a[0] and $y >= $a[1]; 224 $xstart += $iw + $dg; 225 } 226 227 if ( $self-> {drawGrid}) { 228 my $c = $canvas-> color; 229 $canvas-> color( $self-> {gridColor}); 230 for ( $i = 1; $i < 1 + $self-> {whole_columns}; $i++) { 231 $canvas-> line( 232 $a[0] + $i * ( $iw + $dg) - 1, $a[1], 233 $a[0] + $i * ( $iw + $dg) - 1, $a[3] 234 ); 235 } 236 $canvas-> color( $c); 237 } 238 } else { 239 $canvas-> clear( @a[0..2], $a[1] + $self-> {uncover}) 240 if defined $self-> {uncover}; 241 } 242 243 my $focusedState = $self-> focused ? ( exists $self-> {unfocState} ? 0 : 1) : 0; 244 $self-> {unfocVeil} = ( $focusedState && $self-> {focusedItem} < 0 && $locWidth > 0) ? 1 : 0; 245 my $foci = $self-> {focusedItem}; 246 247 if ( $self-> {count} > 0 && $locWidth > 0) { 248 $canvas-> clipRect( @a); 249 my @paintArray; 250 my $item = $self-> {topItem}; 251 if ( $self-> {multiColumn}) 252 { 253 my $di = $self-> {vertical} ? 1 : $self-> {active_columns}; 254 MAIN:for ( $j = 0; $j < $self-> {active_columns}; $j++) 255 { 256 $item = $self-> {topItem} + $j unless $self-> {vertical}; 257 for ( $i = 0; $i < $self-> {active_rows}; $i++) 258 { 259 if ( $self-> {vertical}) { 260 last MAIN if $item > $self-> {lastItem}; 261 } else { 262 last if $item > $self-> {lastItem}; 263 } 264 my @itemRect = ( 265 $a[0] + $j * ( $iw + $dg), 266 $a[3] - $ih * ( $i + 1) + 1, 267 $a[0] + $j * ( $iw + $dg) + $iw, 268 $a[3] - $ih * ( $i + 1) + $ih + 1 269 ); 270 $item += $di, next if 271 $itemRect[3] < $invalidRect[1] || 272 $itemRect[1] > $invalidRect[3] || 273 $itemRect[2] < $invalidRect[0] || 274 $itemRect[0] > $invalidRect[2]; 275 276 my $sel = $self-> {multiSelect} ? 277 exists $self-> {selectedItems}-> {$item} : 278 (( $self-> {focusedItem} == $item) ? 1 : 0); 279 my $foc = ( $foci == $item) ? $focusedState : 0; 280 $foc = 1 if $item == 0 && $self-> {unfocVeil}; 281 my $prelight = (defined($self->{prelight}) && ($self->{prelight} == $item)) ? 1 : 0; 282 283 push( @paintArray, [ 284 $item, # item number 285 $itemRect[0], $itemRect[1], 286 $itemRect[2]-1, $itemRect[3]-1, 287 $sel, $foc, $prelight, # selected and focused states 288 $j, # column 289 ]); 290 $item += $di; 291 } 292 } 293 } else { 294 for ( $i = 0; $i < $self-> {rows}; $i++) { 295 last if $item > $self-> {lastItem}; 296 my @itemRect = ( 297 $a[0], $a[3] - $ih * ( $i + 1) + 1, 298 $a[2], $a[3] - $ih * $i 299 ); 300 $item++, next if 301 $itemRect[3] < $invalidRect[1] || 302 $itemRect[1] > $invalidRect[3]; 303 304 my $sel = $self-> {multiSelect} ? 305 exists $self-> {selectedItems}-> {$item} : 306 (( $foci == $item) ? 1 : 0); 307 my $foc = ( $foci == $item) ? $focusedState : 0; 308 $foc = 1 if $item == 0 && $self-> {unfocVeil}; 309 my $prelight = (defined($self->{prelight}) && ($self->{prelight} == $item)) ? 1 : 0; 310 311 push( @paintArray, [ 312 $item, # item number 313 $itemRect[0] - $self-> {offset}, $itemRect[1], # logic rect 314 $itemRect[2], $itemRect[3], # 315 $sel, $foc, $prelight, # selected and focused state 316 0, #column, 317 ]); 318 $item++; 319 } 320 } 321 $self-> draw_items( $canvas, @paintArray); 322 } 323} 324 325sub is_default_selection 326{ 327 return $_[0]-> {unfocVeil}; 328} 329 330sub on_enable { $_[0]-> repaint; } 331sub on_disable { $_[0]-> repaint; } 332sub on_enter { $_[0]-> redraw_items( $_[0]-> focusedItem); } 333 334sub on_keydown 335{ 336 my ( $self, $code, $key, $mod) = @_; 337 return if $mod & km::DeadKey; 338 339 $mod &= ( km::Shift|km::Ctrl|km::Alt); 340 $self-> notify(q(MouseUp),0,0,0) if defined $self-> {mouseTransaction}; 341 342 if ( $mod & km::Ctrl && $self-> {multiSelect}) { 343 my $c = chr ( $code & 0xFF); 344 if ( $c eq '/' || $c eq chr(ord('\\')-ord('@'))) { 345 $self-> selectedItems(( $c eq '/') ? [0..$self-> {count}-1] : []); 346 $self-> clear_event; 347 return; 348 } 349 } 350 return if ( $code & 0xFF) && ( $key == kb::NoKey); 351 352 if ( scalar grep { $key == $_ } ( 353 kb::Left,kb::Right,kb::Up,kb::Down,kb::Home,kb::End,kb::PgUp,kb::PgDn 354 )) { 355 my $newItem = $self-> {focusedItem}; 356 my $doSelect = 0; 357 if ( 358 $mod == 0 || 359 ( $mod & km::Shift && $self-> {multiSelect} && $self-> { extendedSelect}) 360 ) { 361 my $pgStep = $self-> {whole_rows} - 1; 362 $pgStep = 1 if $pgStep <= 0; 363 my $cols = $self-> {whole_columns}; 364 my $mc = $self-> {multiColumn}; 365 my $dx = $self-> {vertical} ? $self-> {rows} : 1; 366 my $dy = $self-> {vertical} ? 1 : $self-> {active_columns}; 367 if ( $key == kb::Up) { 368 $newItem -= $dy; 369 } elsif ( $key == kb::Down) { 370 $newItem += $dy; 371 } elsif ( $key == kb::Left) { 372 $newItem -= $dx if $mc 373 } elsif ( $key == kb::Right) { 374 $newItem += $dx if $mc 375 } elsif ( $key == kb::Home) { 376 $newItem = $self-> {topItem} 377 } elsif ( $key == kb::End) { 378 $newItem = $mc ? 379 $self-> {topItem} + $self-> {whole_rows} * $cols - 1 : 380 $self-> {topItem} + $pgStep; 381 } elsif ( $key == kb::PgDn) { 382 $newItem += $mc ? 383 $self-> {whole_rows} * $cols : 384 $pgStep 385 } elsif ( $key == kb::PgUp) { 386 $newItem -= $mc ? 387 $self-> {whole_rows} * $cols : 388 $pgStep 389 }; 390 $doSelect = $mod & km::Shift; 391 } 392 393 if ( 394 ( $mod & km::Ctrl) || 395 ( 396 (( $mod & ( km::Shift|km::Ctrl))==(km::Shift|km::Ctrl)) && 397 $self-> {multiSelect} && 398 $self-> { extendedSelect} 399 ) 400 ) { 401 if ( $key == kb::PgUp || $key == kb::Home) { $newItem = 0}; 402 if ( $key == kb::PgDn || $key == kb::End) { $newItem = $self-> {count} - 1}; 403 $doSelect = $mod & km::Shift; 404 } 405 if ( $doSelect ) { 406 my ( $a, $b) = ( 407 defined $self-> {anchor} ? 408 $self-> {anchor} : 409 $self-> {focusedItem}, 410 $newItem 411 ); 412 ( $a, $b) = ( $b, $a) if $a > $b; 413 $self-> selectedItems([$a..$b]); 414 $self-> {anchor} = $self-> {focusedItem} unless defined $self-> {anchor}; 415 } else { 416 $self-> selectedItems([$self-> focusedItem]) if exists $self-> {anchor}; 417 delete $self-> {anchor}; 418 } 419 $self-> offset( $self-> {offset} + 5 * (( $key == kb::Left) ? -1 : 1)) 420 if !$self-> {multiColumn} && ($key == kb::Left || $key == kb::Right); 421 $self-> focusedItem( $newItem >= 0 ? $newItem : 0); 422 $self-> clear_event; 423 return; 424 } else { 425 delete $self-> {anchor}; 426 } 427 428 if ( $mod == 0 && ( $key == kb::Space || $key == kb::Enter)) { 429 $self-> toggle_item( $self-> {focusedItem}) if 430 $key == kb::Space && 431 $self-> {multiSelect} && 432 !$self-> {extendedSelect}; 433 434 $self-> clear_event; 435 $self-> notify(q(Click)) if $key == kb::Enter && ($self-> focusedItem >= 0); 436 return; 437 } 438} 439 440sub on_leave 441{ 442 my $self = $_[0]; 443 if ( $self-> {mouseTransaction}) { 444 $self-> capture(0) if $self-> {mouseTransaction}; 445 $self-> {mouseTransaction} = undef; 446 } 447 $self-> redraw_items( $self-> focusedItem); 448} 449 450sub point2item 451{ 452 my ( $self, $x, $y) = @_; 453 my ( $ih, @a) = ( $self-> {itemHeight}, $self-> get_active_area); 454 455 if ( $self-> {multiColumn}) { 456 my ( $r, $t, $l, $c, $ac) = ( 457 $self-> {active_rows}, $self-> {topItem}, $self-> {lastItem}, 458 $self-> {whole_columns}, $self-> {active_columns}, 459 ); 460 $x -= $a[0]; 461 $y -= $a[1] + $self-> {yedge} + ( $self-> {rows} - $self->{active_rows} ) * $ih; 462 $x /= $self-> {itemWidth} + $self-> {drawGrid}; 463 $y /= $ih; 464 if ( $self->{whole_rows} > 0) { 465 $r -= $self->{rows} - $self->{whole_rows}; 466 } else { 467 $y++; 468 } 469 $y = $r - $y; 470 $x = int( $x - (( $x < 0) ? 1 : 0)); 471 $y = int( $y - (( $y < 0) ? 1 : 0)); 472 $y = $r if $y > $r; 473 474 if ( $self-> {vertical}) { 475 return $t - $r if $y < 0 && $x < 1; 476 return $t + $r * $x, -1 if $y < 0 && $x >= 0 && $x < $c; 477 return $t + $r * $c if $y < 0 && $x >= $c; 478 return 479 $l + $y + 1 - (( $c and $l < $self->{count}-1) ? $r : 0), 480 $ac <= $c ? 0 : $r 481 if $x > $c && $y >= 0 && $y < $r; 482 return $t + $y - $r if $x < 0 && $y >= 0 && $y < $r; 483 return $l + $r if $x >= $c - 1 && $y >= $r; 484 return $t + $r * ($x + 1)-1, 485 ( $l < $self->{count} -1 ) ? 1 : 0 486 if $y >= $r && $x >= 0 && $x < $c; 487 return $t + $r - 1 if $x < 0 && $y >= $r; 488 return $x * $self->{rows} + $y + $t; 489 } else { 490 if ( $y >= $r) { 491 $x = 0 if $x < 0; 492 $x = $ac - 1 if $x >= $ac; 493 my $i = $t + $y * $ac + $x; 494 return $i if $i <= $self->{count}; 495 return 496 $t + ($r - 1) * $ac + $x, 497 ( $t + $y * $ac <= $self->{count}) ? 1 : 0 498 } 499 if ( $y < 0) { 500 $x = 0 if $x < 0; 501 $x = $ac - 1 if $x >= $ac; 502 my $i = $t - $ac + $x; 503 return ( $i < 0 && $t == 0) ? $x : $i; 504 } 505 return $t + $y * $ac, -1 if $x < 0; 506 return $t + ( $y + 1) * $ac - 1, 507 ( $l < $self->{count} -1 ) ? 1 : 0 508 if $x >= $ac; 509 return $t + $y * $ac + $x; 510 } 511 } else { 512 return $self-> {topItem} - 1 if $y >= $a[3]; 513 return $self-> {topItem} + $self-> {rows} if $y <= $a[1]; 514 my $h = $a[3]; 515 516 my $i = $self-> {topItem}; 517 while ( $y > 0) { 518 return $i if $y <= $h && $y > $h - $ih; 519 $h -= $ih; 520 $i++; 521 } 522 } 523} 524 525sub on_mousedown 526{ 527 my ( $self, $btn, $mod, $x, $y) = @_; 528 529 my $bw = $self-> { borderWidth}; 530 $self-> clear_event; 531 return if $btn != mb::Left; 532 533 my @a = $self-> get_active_area; 534 return if defined $self-> {mouseTransaction} || 535 $y < $a[1] || $y >= $a[3] || 536 $x < $a[0] || $x >= $a[2]; 537 538 my $item = $self-> point2item( $x, $y); 539 my $foc = $item >= 0 ? $item : 0; 540 541 if ( $self-> {multiSelect}) { 542 if ( $self-> {extendedSelect}) { 543 if ($mod & km::Shift) { 544 my $foc = $self-> focusedItem; 545 return $self-> selectedItems(( $foc < $item) ? 546 [$foc..$item] : 547 [$item..$foc] 548 ); 549 } elsif ( $mod & km::Ctrl) { 550 return $self-> toggle_item( $item); 551 } elsif ( !$mod) { 552 $self-> {anchor} = $item; 553 $self-> selectedItems([$foc]); 554 } 555 } elsif ( $mod & (km::Ctrl||km::Shift)) { 556 return $self-> toggle_item( $item); 557 } 558 } 559 560 $self-> {mouseTransaction} = 561 (( $mod & ( km::Alt | ($self-> {multiSelect} ? 0 : km::Ctrl))) && $self-> {dragable}) ? 562 2 : 1; 563 if ( $self-> {mouseTransaction} == 2) { 564 $self-> {dragItem} = $foc; 565 $self-> {mousePtr} = $self-> pointer; 566 $self-> pointer( cr::Move); 567 } 568 $self-> focusedItem( $foc); 569 $self-> capture(1); 570} 571 572sub on_mouseclick 573{ 574 my ( $self, $btn, $mod, $x, $y, $dbl) = @_; 575 $self-> clear_event; 576 return if $btn != mb::Left || !$dbl; 577 578 $self-> notify(q(Click)) if $self-> focusedItem >= 0; 579} 580 581sub update_prelight 582{ 583 my ( $self, $x, $y ) = @_; 584 return delete $self->{prelight} if $self->{mouseTransaction}; 585 return unless $self->enabled; 586 587 my @a = $self-> get_active_area; 588 my $prelight; 589 if ( $y >= $a[1] && $y < $a[3] && $x >= $a[0] && $x < $a[2]) { 590 my ($item, $aux) = $self-> point2item( $x, $y); 591 $prelight = ($item >= 0) ? $item : undef unless defined $aux; 592 } 593 if ( ( $self->{prelight} // -1 ) != ( $prelight // -1 )) { 594 my @redraw = ( 595 $self->{prelight} // (), 596 $prelight // () 597 ); 598 $self->{prelight} = $prelight; 599 $self->redraw_items( @redraw ); 600 } 601} 602 603sub on_mousemove 604{ 605 my ( $self, $mod, $x, $y) = @_; 606 $self-> update_prelight($x,$y); 607 return unless defined $self-> {mouseTransaction}; 608 609 my $bw = $self-> { borderWidth}; 610 my ($item, $aux) = $self-> point2item( $x, $y); 611 my @a = $self-> get_active_area; 612 613 if ( $y >= $a[3] || $y < $a[1] || $x >= $a[2] || $x < $a[0]) { 614 $self-> scroll_timer_start unless $self-> scroll_timer_active; 615 return unless $self-> scroll_timer_semaphore; 616 $self-> scroll_timer_semaphore(0); 617 } else { 618 $self-> scroll_timer_stop; 619 } 620 621 if ( $aux) { 622 my $top = $self-> {topItem}; 623 $self-> topItem( $self-> {topItem} + $aux); 624 $item += (( $top != $self-> {topItem}) ? $aux : 0); 625 } 626 627 if ( 628 $self-> {multiSelect} && 629 $self-> {extendedSelect} && 630 exists $self-> {anchor} && 631 $self-> {mouseTransaction} != 2 632 ) { 633 my ( $a, $b, $c) = ( $self-> {anchor}, $item, $self-> {focusedItem}); 634 my $globSelect = 0; 635 if (( $b <= $a && $c > $a) || ( $b >= $a && $c < $a)) { 636 $globSelect = 1 637 } elsif ( $b > $a) { 638 if ( $c < $b) { 639 $self-> add_selection([$c + 1..$b], 1) 640 } elsif ( $c > $b) { 641 $self-> add_selection([$b + 1..$c], 0) 642 } else { 643 $globSelect = 1 644 } 645 } elsif ( $b < $a) { 646 if ( $c < $b) { 647 $self-> add_selection([$c..$b], 0) 648 } elsif ( $c > $b) { 649 $self-> add_selection([$b..$c], 1) 650 } else { 651 $globSelect = 1 652 } 653 } else { 654 $globSelect = 1 655 } 656 657 if ( $globSelect ) { 658 ( $a, $b) = ( $b, $a) if $a > $b; 659 $self-> selectedItems([$a..$b]); 660 } 661 } 662 663 $self-> focusedItem( $item >= 0 ? $item : 0); 664 $self-> offset( $self-> {offset} + 5 * (( $x < $a[0]) ? -1 : 1)) 665 if $x >= $a[2] || $x < $a[0]; 666} 667 668sub on_mouseup 669{ 670 my ( $self, $btn, $mod, $x, $y) = @_; 671 return if $btn != mb::Left; 672 return unless defined $self-> {mouseTransaction}; 673 674 my @dragnotify; 675 if ( $self-> {mouseTransaction} == 2) { 676 $self-> pointer( $self-> {mousePtr}); 677 my $fci = $self-> focusedItem; 678 @dragnotify = ($self-> {dragItem}, $fci) 679 if $fci != $self-> {dragItem} and $self-> {dragItem} >= 0; 680 } 681 682 delete $self-> {mouseTransaction}; 683 delete $self-> {mouseHorizontal}; 684 delete $self-> {anchor}; 685 686 $self-> capture(0); 687 $self-> clear_event; 688 $self-> notify(q(DragItem), @dragnotify) if @dragnotify; 689} 690 691sub on_mouseleave 692{ 693 my $self = shift; 694 my $prelight = delete $self->{prelight}; 695 $self-> redraw_items( $prelight ) if defined $prelight; 696} 697 698sub on_mousewheel 699{ 700 my ( $self, $mod, $x, $y, $z) = @_; 701 702 $z = (abs($z) > 120) ? int($z/120) : (($z > 0) ? 1 : -1); 703 $z *= $self-> {whole_columns} 704 if $self-> {multiColumn} and not $self->{vertical}; 705 $z *= $self-> {whole_rows} if $mod & km::Ctrl; 706 my $newTop = $self-> topItem - $z; 707 my $cols = $self-> {whole_columns}; 708 my $maxTop = $self-> {count} - $self-> {whole_rows} * $cols; 709 710 $self-> topItem( $newTop > $maxTop ? $maxTop : $newTop); 711 $self-> update_prelight($x,$y); 712} 713 714sub on_size 715{ 716 my $self = $_[0]; 717 $self-> reset; 718 $self-> reset_scrolls; 719} 720 721sub reset 722{ 723 my $self = $_[0]; 724 725 my @size = $self-> get_active_area( 2); 726 my $ih = $self-> {itemHeight}; 727 my $iw = $self-> {itemWidth}; 728 729 $self-> {whole_rows} = int( $size[1] / $ih); 730 $self-> {partial_rows} = ( $size[1] > $self-> {whole_rows} * $ih ) ? 1 : 0; 731 $self-> {whole_rows} = 0 if $self-> {whole_rows} < 0; 732 $self-> {partial_rows} += $self-> {whole_rows}; 733 $self-> {yedge} = $size[1] - $self-> {whole_rows} * $ih; 734 $self-> {yedge} = 0 if $self-> {yedge} < 0; 735 736 if ( $self-> {multiColumn}) { 737 my $top = $self-> {topItem}; 738 my $max = $self-> {count} - 1; 739 my $dg = $self-> {drawGrid}; 740 741 $self-> {whole_columns} = int( $size[0] / ( $dg + $iw)); 742 $self-> {partial_columns} = ( $size[0] > $self-> {whole_columns} * ( $dg + $iw)) 743 ? 1 : 0; 744 $self-> {whole_columns} = 0 if $self-> {whole_columns} < 0; 745 $self-> {partial_columns} += $self-> {whole_columns}; 746 $self-> {uncover} = undef; 747 748 $self-> {rows} = $self-> {integralHeight} ? 749 ( $self-> {whole_rows} || $self-> {partial_rows} ) : 750 $self-> {partial_rows}; 751 $self-> {columns} = $self-> {integralWidth} ? 752 ( $self-> {whole_columns} || $self-> {partial_columns} ) : 753 $self-> {partial_columns}; 754 755 my $seen_items = $self->{rows} * $self-> {columns}; 756 $self-> {lastItem} = ( $top + $seen_items - 1 > $max) ? 757 $max : $top + $seen_items - 1; 758 $seen_items = $self-> {lastItem} - $top + 1; 759 760 if ( $self-> {vertical} ) { 761 if ( $self-> {rows} > 0) { 762 $self-> {active_rows} = ( $seen_items > $self-> {rows} ) ? 763 $self->{rows} : $seen_items; 764 $self-> {active_columns} = 765 int( $seen_items / $self-> {rows}) + 766 (( $seen_items % $self-> {rows}) ? 1 : 0); 767 $seen_items %= $self->{rows}; 768 $self-> {uncover} = { 769 x => $self-> {active_columns} - 1, 770 y => $ih * ($self-> {whole_rows} - $seen_items) 771 } if $seen_items 772 } else { 773 $self-> {active_columns} = $self-> {active_rows} = 0; 774 } 775 } else { 776 if ( $self-> {columns} > 0) { 777 $self-> {active_columns} = ( $seen_items > $self-> {columns} ) ? 778 $self-> {columns} : $seen_items; 779 $self-> {active_rows} = 780 int( $seen_items / $self-> {columns}) + 781 (int( $seen_items % $self-> {columns}) > 0); 782 $seen_items %= $self->{columns}; 783 $self-> {uncover} = { 784 x => $seen_items, 785 y => $ih * ($self-> {whole_rows} - $self-> {active_rows} + 1), 786 } if $seen_items 787 } else { 788 $self-> {active_columns} = $self-> {active_rows} = 0; 789 } 790 } 791 $self-> {xedge} = $size[0] - $self-> {whole_columns} * ($iw + $dg); 792 $self-> {xedge} = 0 if $self-> {xedge} < 0; 793 } else { 794 $self-> {$_} = 1 for qw(partial_columns whole_columns active_columns columns); 795 $self-> {xedge} = 0; 796 $self-> {rows} = ( 797 $self-> {integralHeight} and 798 $self-> {whole_rows} > 0 799 ) ? 800 $self-> {whole_rows} : 801 $self-> {partial_rows}; 802 my ($max, $last) = ( 803 $self-> {count} - 1, 804 $self-> {topItem} + $self-> {rows} - 1 805 ); 806 $self-> {lastItem} = $max > $last ? $last : $max; 807 $self-> {active_rows} = $self->{lastItem} - $self-> {topItem} + 1; 808 $self-> {uncover} = $size[1] - $self-> {active_rows} * $ih - 1 809 if $self->{active_rows} < $self-> {partial_rows}; 810 } 811 $self-> {uncover} = undef if $size[0] <= 0 or $size[1] <= 0; 812} 813 814sub reset_scrolls 815{ 816 my $self = $_[0]; 817 818 my $count = $self-> {count}; 819 my $cols = $self-> {whole_columns}; 820 my $rows = $self-> {whole_rows}; 821 $cols++ if ( 822 $self->{whole_columns} == 0 and 823 $self->{active_columns} > 0 824 ) or ( 825 $self->{partial_columns} > $self->{whole_columns} and 826 $self->{yedge} > $self-> {itemHeight} * 0.66 827 ); 828 $rows++ if ( 829 $self->{whole_rows} == 0 and 830 $self->{active_rows} > 0 831 ) or ( 832 $self->{partial_rows} > $self->{whole_rows} and 833 $self->{xedge} > $self-> {itemWidth} * 0.66 834 ); 835 836 if ( !($self-> {scrollTransaction} & 1)) { 837 $self-> vScroll( $self->{whole_rows} * $self->{whole_columns} < $count) 838 if $self-> {autoVScroll}; 839 840 $self-> {vScrollBar}-> set( 841 step => ( $self-> {multiColumn} and not $self->{vertical}) ? 842 $self-> {active_columns} : 1, 843 max => $count - $self->{whole_rows} * $self->{whole_columns}, 844 whole => $count, 845 partial => $rows * $cols, 846 value => $self-> {topItem}, 847 pageStep => $rows, 848 ) if $self-> {vScroll}; 849 } 850 if ( !($self-> {scrollTransaction} & 2)) { 851 if ( $self-> {multiColumn}) { 852 $self-> hScroll( $self->{whole_rows} * $self->{whole_columns} < $count) 853 if $self-> {autoHScroll}; 854 $self-> {hScrollBar}-> set( 855 max => $count - $self->{whole_rows} * $self->{whole_columns}, 856 step => $rows, 857 pageStep => $rows * $cols, 858 whole => $count, 859 partial => $rows * $cols, 860 value => $self-> {topItem}, 861 ) if $self-> {hScroll}; 862 } else { 863 my @sz = $self-> get_active_area( 2); 864 my $iw = $self-> {itemWidth}; 865 866 if ( $self-> {autoHScroll}) { 867 my $hs = ( $sz[0] < $iw) ? 1 : 0; 868 if ( $hs != $self-> {hScroll}) { 869 $self-> hScroll( $hs); 870 @sz = $self-> get_active_area( 2); 871 } 872 } 873 874 $self-> {hScrollBar}-> set( 875 max => $iw - $sz[0], 876 whole => $iw, 877 value => $self-> {offset}, 878 partial => $sz[0], 879 pageStep => $iw / 5, 880 ) if $self-> {hScroll}; 881 } 882 } 883} 884 885sub select_all { 886 my $self = $_[0]; 887 $self-> selectedItems([0..$self-> {count}-1]); 888} 889 890sub deselect_all { 891 my $self = $_[0]; 892 $self-> selectedItems([]); 893} 894 895sub set_auto_height 896{ 897 my ( $self, $auto) = @_; 898 899 $self-> itemHeight( $self-> font-> height) if $auto; 900 $self-> {autoHeight} = $auto; 901} 902 903sub set_align 904{ 905 my ( $self, $align) = @_; 906 907 $self-> {align} = $align; 908 $self-> repaint; 909} 910 911sub reset_indents 912{ 913 my ( $self) = @_; 914 $self-> reset; 915 $self-> reset_scrolls; 916 $self-> repaint; 917} 918 919 920sub set_count 921{ 922 my ( $self, $count) = @_; 923 $count = 0 if $count < 0; 924 my $oldCount = $self-> {count}; 925 $self-> { count} = $count; 926 my $doFoc = undef; 927 if ( $oldCount > $count) { 928 for ( keys %{$self-> {selectedItems}}) { 929 delete $self-> {selectedItems}-> {$_} if $_ >= $count; 930 } 931 } 932 $self-> reset; 933 $self-> reset_scrolls; 934 $self-> focusedItem( -1) if $self-> {focusedItem} >= $count; 935 $self-> repaint; 936} 937 938sub set_extended_select 939{ 940 my ( $self, $esel) = @_; 941 $self-> {extendedSelect} = $esel; 942} 943 944sub set_focused_item 945{ 946 my ( $self, $foc) = @_; 947 my $oldFoc = $self-> {focusedItem}; 948 $foc = $self-> {count} - 1 if $foc >= $self-> {count}; 949 $foc = -1 if $foc < -1; 950 return if $self-> {focusedItem} == $foc; 951 return if $foc < -1; 952 953 $self-> {focusedItem} = $foc; 954 $self-> selectedItems([$foc]) 955 if $self-> {multiSelect} && $self-> {extendedSelect} 956 && ! exists $self-> {anchor} && 957 ( !defined($self-> {mouseTransaction}) || $self-> {mouseTransaction} != 2); 958 $self-> notify(q(SelectItem), [ $foc], 1) 959 if $foc >= 0 && !exists $self-> {selectedItems}-> {$foc}; 960 961 my $topSet = undef; 962 if ( $foc >= 0) { 963 my $mc = $self-> {multiColumn}; 964 if ( $mc ) { 965 my ( $rows, $cols) = ($mc and not $self->{vertical}) ? 966 ($self-> {columns} || 1, $self-> {whole_rows} || 1) : 967 ($self-> {rows} || 1, $self-> {whole_columns} || 1); 968 if ( $foc < $self-> {topItem}) { 969 $topSet = $foc - $foc % $rows; 970 } elsif ( $foc >= $self-> {topItem} + $rows * $cols - 1) { 971 $topSet = $foc - $foc % $rows - $rows * ( $cols - 1); 972 } 973 } else { 974 if ( $foc < $self-> {topItem}) { 975 $topSet = $foc; 976 } elsif ( $foc >= $self-> {topItem} + $self->{whole_rows}) { 977 $topSet = $foc - $self->{whole_rows} + 1; 978 } 979 } 980 } 981 $oldFoc = 0 if $oldFoc < 0; 982 $self-> redraw_items( $foc, $oldFoc); 983 if ( 984 !$self-> {multiSelect} && !$self-> {extendedSelect} && 985 defined($topSet) && 986 ($self->{topItem} - $topSet) == ($oldFoc - $foc) 987 ) { 988 $self-> set_top_item($topSet, $oldFoc - $foc); 989 } else { 990 $self-> topItem( $topSet) if defined $topSet; 991 } 992} 993 994sub colorIndex 995{ 996 my ( $self, $index, $color) = @_; 997 return ( $index == ci::Grid) ? 998 $self-> {gridColor} : $self-> SUPER::colorIndex( $index) 999 if $#_ < 2; 1000 ( $index == ci::Grid) ? 1001 ( $self-> gridColor( $color), $self-> notify(q(ColorChanged), ci::Grid)) : 1002 ( $self-> SUPER::colorIndex( $index, $color)); 1003} 1004 1005sub dragable 1006{ 1007 return $_[0]-> {dragable} unless $#_; 1008 $_[0]-> {dragable} = $_[1]; 1009} 1010 1011sub set_draw_grid 1012{ 1013 my ( $self, $dg) = @_; 1014 $dg = ( $dg ? 1 : 0); 1015 return if $dg == $self-> {drawGrid}; 1016 1017 $self-> {drawGrid} = $dg; 1018 $self-> reset; 1019 $self-> reset_scrolls; 1020 $self-> repaint; 1021} 1022 1023sub set_grid_color 1024{ 1025 my ( $self, $gc) = @_; 1026 return if $gc == $self-> {gridColor}; 1027 $self-> {gridColor} = $gc; 1028 $self-> repaint if $self-> {drawGrid}; 1029} 1030 1031sub set_integral_height 1032{ 1033 my ( $self, $ih) = @_; 1034 return if $self-> {integralHeight} == $ih; 1035 $self-> {integralHeight} = $ih; 1036 $self-> reset; 1037 $self-> reset_scrolls; 1038 $self-> repaint; 1039} 1040 1041sub set_integral_width 1042{ 1043 my ( $self, $iw) = @_; 1044 return if $self-> {integralWidth} == $iw; 1045 $self-> {integralWidth} = $iw; 1046 $self-> reset; 1047 $self-> reset_scrolls; 1048 $self-> repaint; 1049} 1050 1051sub set_item_height 1052{ 1053 my ( $self, $ih) = @_; 1054 $ih = 1 if $ih < 1; 1055 $self-> autoHeight(0); 1056 return if $ih == $self-> {itemHeight}; 1057 $self-> {itemHeight} = $ih; 1058 $self-> reset; 1059 $self-> reset_scrolls; 1060 $self-> repaint; 1061} 1062 1063sub set_item_width 1064{ 1065 my ( $self, $iw) = @_; 1066 $iw = 1 if $iw < 1; 1067 return if $iw == $self-> {itemWidth}; 1068 $self-> {itemWidth} = $iw; 1069 $self-> reset; 1070 $self-> reset_scrolls; 1071 $self-> repaint; 1072} 1073 1074sub set_multi_column 1075{ 1076 my ( $self, $mc) = @_; 1077 return if $mc == $self-> {multiColumn}; 1078 $self-> offset(0) if $self-> {multiColumn} = $mc; 1079 $self-> reset; 1080 $self-> reset_scrolls; 1081 $self-> repaint; 1082} 1083 1084sub set_multi_select 1085{ 1086 my ( $self, $ms) = @_; 1087 return if $ms == $self-> {multiSelect}; 1088 1089 unless ( $self-> {multiSelect} = $ms) { 1090 $self-> selectedItems([]); 1091 $self-> repaint; 1092 } else { 1093 $self-> selectedItems([$self-> focusedItem]); 1094 } 1095} 1096 1097sub set_offset 1098{ 1099 my ( $self, $offset) = @_; 1100 $self-> {offset} = 0, return if $self-> {multiColumn}; 1101 my @sz = $self-> size; 1102 my ( $iw, @a) = ( $self-> {itemWidth}, $self-> get_active_area( 0, @sz)); 1103 my $lc = $a[2] - $a[0]; 1104 if ( $iw > $lc) { 1105 $offset = $iw - $lc if $offset > $iw - $lc; 1106 $offset = 0 if $offset < 0; 1107 } else { 1108 $offset = 0; 1109 } 1110 return if $self-> {offset} == $offset; 1111 1112 my $oldOfs = $self-> {offset}; 1113 $self-> {offset} = $offset; 1114 my $dt = $offset - $oldOfs; 1115 $self-> reset; 1116 1117 if ( $self-> {hScroll} && !$self-> {multiColumn} && !($self-> {scrollTransaction} & 2)) { 1118 $self-> {scrollTransaction} |= 2; 1119 $self-> {hScrollBar}-> value( $offset); 1120 $self-> {scrollTransaction} &= ~2; 1121 } 1122 1123 $self-> scroll( -$dt, 0, clipRect => \@a); 1124 if ( $self-> focused) { 1125 my $focId = ( $self-> {focusedItem} >= 0) ? $self-> {focusedItem} : 0; 1126 $self-> invalidate_rect( $self-> item2rect( $focId, @sz)); 1127 } 1128} 1129 1130sub redraw_items 1131{ 1132 my $self = shift; 1133 my @sz = $self-> size; 1134 $self-> invalidate_rect( $self-> item2rect( $_, @sz)) for @_; 1135} 1136 1137sub set_selected_items 1138{ 1139 my ( $self, $items) = @_; 1140 return if !$self-> { multiSelect} && ( scalar @{$items} > 0); 1141 1142 my $ptr = $::application-> pointer; 1143 $::application-> pointer( cr::Wait) 1144 if scalar @{$items} > 500; 1145 1146 my $sc = $self-> {count}; 1147 my %newItems; 1148 for (@{$items}) { 1149 $newItems{$_}=1 if $_>=0 && $_<$sc; 1150 } 1151 1152 my @stateChangers; # $#stateChangers = scalar @{$items}; 1153 my $k; 1154 while (defined($k = each %{$self-> {selectedItems}})) { 1155 next if exists $newItems{$k}; 1156 push( @stateChangers, $k); 1157 }; 1158 1159 my @indices; 1160 my $sel = $self-> {selectedItems}; 1161 $self-> {selectedItems} = \%newItems; 1162 $self-> notify(q(SelectItem), [@stateChangers], 0) if scalar @stateChangers; 1163 1164 while (defined($k = each %newItems)) { 1165 next if exists $sel-> {$k}; 1166 push( @stateChangers, $k); 1167 push( @indices, $k); 1168 }; 1169 $self-> notify(q(SelectItem), [@indices], 1) if scalar @indices; 1170 1171 $::application-> pointer( $ptr); 1172 1173 return unless scalar @stateChangers; 1174 $self-> redraw_items( @stateChangers); 1175} 1176 1177sub get_selected_items 1178{ 1179 return $_[0]-> {multiSelect} ? 1180 [ sort { $a<=>$b } keys %{$_[0]-> {selectedItems}}] : 1181 ( 1182 ( $_[0]-> {focusedItem} < 0) ? [] : [$_[0]-> {focusedItem}] 1183 ); 1184} 1185 1186sub get_selected_count 1187{ 1188 return scalar keys %{$_[0]-> {selectedItems}}; 1189} 1190 1191sub is_selected 1192{ 1193 return exists($_[0]-> {selectedItems}-> {$_[1]}) ? 1 : 0; 1194} 1195 1196sub set_item_selected 1197{ 1198 my ( $self, $index, $sel) = @_; 1199 return unless $self-> {multiSelect}; 1200 return if $index < 0 || $index >= $self-> {count}; 1201 return if $sel == exists $self-> {selectedItems}-> {$index}; 1202 1203 $sel ? 1204 $self-> {selectedItems}-> {$index} = 1 : 1205 delete $self-> {selectedItems}-> {$index}; 1206 $self-> notify(q(SelectItem), [ $index], $sel); 1207 $self-> invalidate_rect( $self-> item2rect( $index)); 1208} 1209 1210sub select_item { $_[0]-> set_item_selected( $_[1], 1); } 1211sub unselect_item { $_[0]-> set_item_selected( $_[1], 0); } 1212sub toggle_item { $_[0]-> set_item_selected( $_[1], $_[0]-> is_selected( $_[1]) ? 0 : 1)} 1213 1214sub add_selection 1215{ 1216 my ( $self, $items, $sel) = @_; 1217 return unless $self-> {multiSelect}; 1218 my @notifiers; 1219 my $count = $self-> {count}; 1220 my @sz = $self-> size; 1221 for ( @{$items}) 1222 { 1223 next if $_ < 0 || $_ >= $count; 1224 next if exists $self-> {selectedItems}-> {$_} == $sel; 1225 1226 $sel ? 1227 $self-> {selectedItems}-> {$_} = 1 : 1228 delete $self-> {selectedItems}-> {$_}; 1229 push ( @notifiers, $_); 1230 $self-> invalidate_rect( $self-> item2rect( $_, @sz)); 1231 } 1232 return unless scalar @notifiers; 1233 $self-> notify(q(SelectItem), [ @notifiers], $sel) if scalar @notifiers; 1234} 1235 1236sub set_top_item 1237{ 1238 my ( $self, $topItem, $with_focus_shift) = @_; 1239 $topItem = 0 if $topItem < 0; # first validation 1240 $topItem = $self-> {count} - 1 if $topItem >= $self-> {count}; 1241 $topItem = 0 if $topItem < 0; # count = 0 case 1242 return if $topItem == $self-> {topItem}; 1243 1244 my $oldTop = $self-> {topItem}; 1245 $self-> {topItem} = $topItem; 1246 my ( $ih, $iw, @a) = ( $self-> {itemHeight}, $self-> {itemWidth}, $self-> get_active_area); 1247 my $dt = $topItem - $oldTop; 1248 $self-> reset; 1249 1250 if ( !($self-> {scrollTransaction} & 1) && $self-> {vScroll}) { 1251 $self-> {scrollTransaction} |= 1; 1252 $self-> {vScrollBar}-> value( $topItem); 1253 $self-> {scrollTransaction} &= ~1; 1254 } 1255 1256 if ( !($self-> {scrollTransaction} & 2) && $self-> {hScroll} && $self-> {multiColumn}) { 1257 $self-> {scrollTransaction} |= 2; 1258 $self-> {hScrollBar}-> value( $topItem); 1259 $self-> {scrollTransaction} &= ~2; 1260 } 1261 1262 if ( $self-> { multiColumn}) { 1263 $iw += $self-> {drawGrid}; 1264 if ( $self-> {vertical}) { 1265 if ($self->{rows} != 0 && abs($dt) % $self->{rows}) { 1266 $a[1] += $self->{yedge} if $self->{integralHeight}; 1267 $self-> scroll( 0, $ih * $dt, clipRect => \@a); 1268 return; 1269 } 1270 1271 if ($self->{integralWidth}) { 1272 $a[2] -= $self->{xedge}; 1273 } elsif ( !defined $with_focus_shift || $with_focus_shift < 0 ) { 1274 # invalid xedge on the right and exposed stripe on the left make clipRect too large 1275 $self-> invalidate_rect($a[2] - $self->{xedge}, $a[1], $a[2], $a[3]); 1276 } 1277 if ( defined $with_focus_shift ) { 1278 if ( $with_focus_shift < 0 ) { 1279 my $dx = $iw + ($self->{integralWidth} ? 0 : $self->{xedge}); 1280 $self-> invalidate_rect($a[2] - $dx, $a[1], $a[2], $a[3]); 1281 $a[2] -= $dx; 1282 } else { 1283 $self-> invalidate_rect($a[0], $a[1], $a[0] + $iw, $a[3]); 1284 $a[0] += $iw; 1285 } 1286 } 1287 $self-> scroll( 1288 -( $dt / $self-> {rows}) * $iw, 0, 1289 clipRect => \@a 1290 ); 1291 } else { 1292 if ($self->{columns} > 0 && abs($dt) % $self->{columns}) { 1293 $a[2] -= $self->{xedge} if $self->{integralWidth}; 1294 $self-> scroll(- $iw * $dt, 0, clipRect => \@a); 1295 return; 1296 } 1297 1298 if ($self->{integralHeight}) { 1299 $a[1] += $self->{yedge}; 1300 } elsif ( !defined $with_focus_shift || $with_focus_shift < 0 ) { 1301 $self-> invalidate_rect($a[0], $a[1], $a[2], $a[1] + $self->{yedge}) 1302 } 1303 if ( defined $with_focus_shift ) { 1304 if ( $with_focus_shift < 0 ) { 1305 my $dy = $ih + ($self->{integralHeight} ? 0 : $self->{yedge}); 1306 $self-> invalidate_rect($a[0], $a[1], $a[2], $a[1] + $dy); 1307 $a[1] += $dy; 1308 } else { 1309 $a[3] -= $ih; 1310 $self-> invalidate_rect($a[0], $a[3], $a[2], $a[3] + $ih); 1311 } 1312 } 1313 $self-> scroll( 1314 0, ( $dt / $self-> {columns}) * $ih, 1315 clipRect => \@a 1316 ); 1317 } 1318 } else { 1319 $a[1] += $self-> {yedge} 1320 if $self-> {integralHeight} and $self-> {whole_rows} > 0; 1321 if ( defined $with_focus_shift ) { 1322 if ( $with_focus_shift < 0 ) { 1323 $a[1] += $ih; 1324 $a[1] += $self->{yedge} unless $self->{integralHeight}; 1325 } else { 1326 $a[3] -= $ih; 1327 } 1328 } 1329 $self-> scroll( 0, $dt * $ih, clipRect => \@a); 1330 } 1331 $self-> update_view; 1332} 1333 1334sub set_vertical 1335{ 1336 my ( $self, $vertical) = @_; 1337 return if $self-> {vertical} == $vertical; 1338 $self-> {vertical} = $vertical; 1339 $self-> reset; 1340 $self-> reset_scrolls; 1341 $self-> repaint; 1342} 1343 1344 1345sub VScroll_Change 1346{ 1347 my ( $self, $scr) = @_; 1348 return if $self-> {scrollTransaction} & 1; 1349 $self-> {scrollTransaction} |= 1; 1350 $self-> topItem( $scr-> value); 1351 $self-> {scrollTransaction} &= ~1; 1352} 1353 1354sub HScroll_Change 1355{ 1356 my ( $self, $scr) = @_; 1357 return if $self-> {scrollTransaction} & 2; 1358 $self-> {scrollTransaction} |= 2; 1359 $self-> {multiColumn} ? 1360 $self-> topItem( $scr-> value) : 1361 $self-> offset( $scr-> value); 1362 $self-> {scrollTransaction} &= ~2; 1363} 1364 1365#sub on_drawitem 1366#{ 1367# my ($self, $canvas, $itemIndex, $x, $y, $x2, $y2, $selected, $focused, $prelight, $column) = @_; 1368#} 1369 1370#sub on_selectitem 1371#{ 1372# my ($self, $itemIndex, $selectState) = @_; 1373#} 1374 1375#sub on_dragitem 1376#{ 1377# my ( $self, $from, $to) = @_; 1378#} 1379 1380sub autoHeight {($#_)?$_[0]-> set_auto_height ($_[1]):return $_[0]-> {autoHeight} } 1381sub align {($#_)?$_[0]-> set_align ($_[1]):return $_[0]-> {align} } 1382sub count {($#_)?$_[0]-> set_count ($_[1]):return $_[0]-> {count} } 1383sub extendedSelect{($#_)?$_[0]-> set_extended_select($_[1]):return $_[0]-> {extendedSelect} } 1384sub drawGrid {($#_)?$_[0]-> set_draw_grid ($_[1]):return $_[0]-> {drawGrid} } 1385sub gridColor {($#_)?$_[0]-> set_grid_color ($_[1]):return $_[0]-> {gridColor} } 1386sub focusedItem {($#_)?$_[0]-> set_focused_item ($_[1]):return $_[0]-> {focusedItem} } 1387sub integralHeight{($#_)?$_[0]-> set_integral_height($_[1]):return $_[0]-> {integralHeight} } 1388sub integralWidth {($#_)?$_[0]-> set_integral_width ($_[1]):return $_[0]-> {integralWidth } } 1389sub itemHeight {($#_)?$_[0]-> set_item_height ($_[1]):return $_[0]-> {itemHeight} } 1390sub itemWidth {($#_)?$_[0]-> set_item_width ($_[1]):return $_[0]-> {itemWidth} } 1391sub multiSelect {($#_)?$_[0]-> set_multi_select ($_[1]):return $_[0]-> {multiSelect} } 1392sub multiColumn {($#_)?$_[0]-> set_multi_column ($_[1]):return $_[0]-> {multiColumn} } 1393sub offset {($#_)?$_[0]-> set_offset ($_[1]):return $_[0]-> {offset} } 1394sub selectedCount {($#_)?$_[0]-> raise_ro("selectedCount") :return $_[0]-> get_selected_count;} 1395sub selectedItems {($#_)?shift-> set_selected_items (@_):return $_[0]-> get_selected_items;} 1396sub topItem {($#_)?$_[0]-> set_top_item ($_[1]):return $_[0]-> {topItem} } 1397sub vertical {($#_)?$_[0]-> set_vertical ($_[1]):return $_[0]-> {vertical} } 1398 1399# section for item text representation 1400 1401sub get_item_text 1402{ 1403 my ( $self, $index) = @_; 1404 my $txt = ''; 1405 $self-> notify(q(Stringify), $index, \$txt); 1406 return $txt; 1407} 1408 1409sub get_item_width 1410{ 1411 my ( $self, $index) = @_; 1412 my $w = 0; 1413 $self-> notify(q(MeasureItem), $index, \$w); 1414 return $w; 1415} 1416 1417sub on_stringify 1418{ 1419 my ( $self, $index, $sref) = @_; 1420 $$sref = ''; 1421} 1422 1423 1424sub on_measureitem 1425{ 1426 my ( $self, $index, $sref) = @_; 1427 $$sref = 0; 1428} 1429 1430sub draw_text_items 1431{ 1432 my ( $self, $canvas, $first, $last, $step, $x, $y, $textShift, $clipRect) = @_; 1433 my ($i,$j); 1434 my ($dx,$iw) = (0); 1435 if ( $self->{align} != ta::Left ) { 1436 my @a = $self->item2rect( $first ); 1437 $iw = $a[2] - $a[0]; 1438 $iw = $self->{itemWidth} if $iw < $self->{itemWidth}; 1439 } 1440 for ( $i = $first, $j = 1; $i <= $last; $i += $step, $j++) { 1441 my $width = $self-> get_item_width( $i); 1442 next if $width + $self-> {offset} + $x + 1 < $clipRect-> [0]; 1443 if ( $self->{align} == ta::Center) { 1444 $dx = ($iw > $width) ? ($iw - $width) / 2 : 0; 1445 } elsif ( $self->{align} == ta::Right) { 1446 $dx = ($iw > $width) ? $iw - $width : 0; 1447 } 1448 $canvas-> text_shape_out( $self-> get_item_text( $i), 1449 $x + $dx, $y + $textShift - $j * $self-> {itemHeight} + 1 1450 ); 1451 } 1452} 1453 1454sub std_draw_text_items 1455{ 1456 my ($self,$canvas) = (shift,shift); 1457 my @clrs = ( 1458 $self-> color, 1459 $self-> backColor, 1460 $self-> colorIndex( ci::HiliteText), 1461 $self-> colorIndex( ci::Hilite) 1462 ); 1463 1464 my @clipRect = $canvas-> clipRect; 1465 my $i; 1466 my $drawVeilFoc = -1; 1467 my $atY = int(( $self-> {itemHeight} - $canvas-> font-> height) / 2 + .5); 1468 my $ih = $self-> {itemHeight}; 1469 my $offset = $self-> {offset}; 1470 my $step = ( $self-> {multiColumn} and !$self-> {vertical}) ? 1471 $self-> {active_columns} : 1; 1472 1473 my @colContainer; 1474 for ( $i = 0; $i < $self-> {columns}; $i++){ 1475 push ( @colContainer, []) 1476 }; 1477 for ( $i = 0; $i < scalar @_; $i++) { 1478 push ( @{$colContainer[ $_[$i]-> [8]]}, $_[$i]); 1479 $drawVeilFoc = $i if $_[$i]-> [6]; 1480 } 1481 my ( $lc, $lbc) = @clrs[0,1]; 1482 for ( @colContainer) { 1483 my @normals; 1484 my @selected; 1485 my @prelight; 1486 my ( $lastNormal, $lastSelected) = (undef, undef); 1487 # sorting items in single column 1488 { $_ = [ sort { $$a[0]<=>$$b[0] } @$_]; } 1489 # calculating conjoint bars 1490 for ( $i = 0; $i < scalar @$_; $i++) { 1491 my ( $itemIndex, $x, $y, $x2, $y2, $selected, $focusedItem, $prelighted) = @{$$_[$i]}; 1492 if ( $prelighted ) { 1493 push ( @prelight, [ 1494 $x, $y, $x2, $y2, 1495 $$_[$i]-> [0], $$_[$i]-> [0], $selected ? 3 : 2, 1496 ]); 1497 } elsif ( $selected) { 1498 if ( 1499 defined $lastSelected && 1500 ( $y2 + 1 == $lastSelected) 1501 ) { 1502 ${$selected[-1]}[1] = $y; 1503 ${$selected[-1]}[5] = $$_[$i]-> [0]; 1504 } else { 1505 push ( @selected, [ 1506 $x, $y, $x2, $y2, 1507 $$_[$i]-> [0], $$_[$i]-> [0], 1 1508 ]); 1509 } 1510 $lastSelected = $y; 1511 } else { 1512 if ( 1513 defined $lastNormal && 1514 ( $y2 + 1 == $lastNormal) && 1515 ( ${$normals[-1]}[3] - $lastNormal < 100)) 1516 { 1517 ${$normals[-1]}[1] = $y; 1518 ${$normals[-1]}[5] = $$_[$i]-> [0]; 1519 } else { 1520 push ( @normals, [ 1521 $x, $y, $x2, $y2, 1522 $$_[$i]-> [0], $$_[$i]-> [0], 0 1523 ]); 1524 } 1525 $lastNormal = $y; 1526 } 1527 } 1528 # draw items 1529 1530 for ( @normals, @selected, @prelight) { 1531 my ( $x, $y, $x2, $y2, $first, $last, $selected) = @$_; 1532 my $c; 1533 my $prelight; 1534 if ($selected & 2) { 1535 $selected -= 2; 1536 $prelight = 1; 1537 } 1538 1539 $c = $clrs[ $selected ? 3 : 1]; 1540 if ( $c != $lbc) { 1541 $canvas-> backColor( $c); 1542 $lbc = $c; 1543 } 1544 1545 $self-> draw_item_background( $canvas, $x, $y, $x2, $y2, $prelight); 1546 1547 $c = $clrs[ $selected ? 2 : 0]; 1548 if ( $c != $lc) { 1549 $canvas-> color( $c); 1550 $lc = $c; 1551 } 1552 1553 $self-> draw_text_items( $canvas, $first, $last, $step, 1554 $x, $y2, $atY, \@clipRect); 1555 } 1556 } 1557 1558 # draw veil 1559 if ( $drawVeilFoc >= 0) { 1560 my ( $itemIndex, $x, $y, $x2, $y2) = @{$_[$drawVeilFoc]}; 1561 $canvas-> rect_focus( $x + $self-> {offset}, $y, $x2, $y2); 1562 } 1563} 1564 1565package Prima::AbstractListBox; 1566use vars qw(@ISA); 1567@ISA = qw(Prima::AbstractListViewer); 1568 1569sub draw_items 1570{ 1571 shift-> std_draw_text_items(@_); 1572} 1573 1574sub on_measureitem 1575{ 1576 my ( $self, $index, $sref) = @_; 1577 $$sref = $self-> get_text_width( $self-> get_item_text( $index)); 1578} 1579 1580package Prima::ListViewer; 1581use vars qw(@ISA); 1582@ISA = qw(Prima::AbstractListViewer); 1583 1584sub profile_default 1585{ 1586 my $def = $_[ 0]-> SUPER::profile_default; 1587 my %prf = ( 1588 items => [], 1589 autoWidth => 1, 1590 ); 1591 @$def{keys %prf} = values %prf; 1592 return $def; 1593} 1594 1595sub init 1596{ 1597 my $self = shift; 1598 $self-> {items} = []; 1599 $self-> {widths} = []; 1600 $self-> {maxWidth} = 0; 1601 $self-> {autoWidth} = 0; 1602 1603 my %profile = $self-> SUPER::init(@_); 1604 1605 $self-> autoWidth( $profile{autoWidth}); 1606 $self-> items ( $profile{items}); 1607 $self-> focusedItem ( $profile{focusedItem}); 1608 return %profile; 1609} 1610 1611 1612sub calibrate 1613{ 1614 my $self = $_[0]; 1615 $self-> recalc_widths; 1616 $self-> itemWidth( $self-> {maxWidth}) if $self-> {autoWidth}; 1617 $self-> offset( $self-> offset); 1618} 1619 1620sub get_item_width 1621{ 1622 return $_[0]-> {widths}-> [$_[1]]; 1623} 1624 1625sub on_fontchanged 1626{ 1627 my $self = $_[0]; 1628 1629 $self-> itemHeight( $self-> font-> height), $self-> {autoHeight} = 1 if $self-> { autoHeight}; 1630 $self-> calibrate; 1631} 1632 1633sub recalc_widths 1634{ 1635 my $self = $_[0]; 1636 1637 my @w; 1638 my $maxWidth = 0; 1639 my $i; 1640 1641 my ( $notifier, @notifyParms) = $self-> get_notify_sub(q(MeasureItem)); 1642 $self-> push_event; 1643 $self-> begin_paint_info; 1644 1645 for ( $i = 0; $i < scalar @{$self-> {items}}; $i++) { 1646 my $iw = 0; 1647 $notifier-> ( @notifyParms, $i, \$iw); 1648 $maxWidth = $iw if $maxWidth < $iw; 1649 push ( @w, $iw); 1650 } 1651 1652 $self-> end_paint_info; 1653 $self-> pop_event; 1654 $self-> {widths} = [@w]; 1655 $self-> {maxWidth} = $maxWidth; 1656} 1657 1658sub set_items 1659{ 1660 my ( $self, $items) = @_; 1661 return unless ref $items eq q(ARRAY); 1662 1663 my $oldCount = $self-> {count}; 1664 $self-> {items} = [@{$items}]; 1665 $self-> recalc_widths; 1666 $self-> reset; 1667 scalar @$items == $oldCount ? $self-> repaint : $self-> SUPER::count( scalar @$items); 1668 1669 $self-> itemWidth( $self-> {maxWidth}) if $self-> {autoWidth}; 1670 $self-> offset( $self-> offset); 1671 $self-> selectedItems([]); 1672} 1673 1674sub get_items 1675{ 1676 my $self = shift; 1677 my @inds = (@_ == 1 and ref($_[0]) eq q(ARRAY)) ? @{$_[0]} : @_; 1678 1679 my ($c,$i) = ($self-> {count}, $self-> {items}); 1680 for ( @inds) { $_ = ( $_ >= 0 && $_ < $c) ? $i-> [$_] : undef; } 1681 return wantarray ? @inds : $inds[0]; 1682} 1683 1684sub insert_items 1685{ 1686 my ( $self, $where) = ( shift, shift); 1687 $where = $self-> {count} if $where < 0; 1688 my ( $is, $iw, $mw) = ( $self-> {items}, $self-> {widths}, $self-> {maxWidth}); 1689 if (@_ == 1 and ref($_[0]) eq q(ARRAY)) { 1690 return unless scalar @{$_[0]}; 1691 $self-> {items} = [@{$_[0]}]; 1692 } else { 1693 return unless scalar @_; 1694 $self-> {items} = [@_]; 1695 } 1696 1697 $self-> {widths} = []; 1698 my $num = scalar @{$self-> {items}}; 1699 $self-> recalc_widths; 1700 splice( @{$is}, $where, 0, @{$self-> {items}}); 1701 splice( @{$iw}, $where, 0, @{$self-> {widths}}); 1702 ( $self-> {items}, $self-> {widths}) = ( $is, $iw); 1703 $self-> itemWidth( $self-> {maxWidth} = $mw) 1704 if $self-> {autoWidth} && $self-> {maxWidth} < $mw; 1705 1706 $self-> SUPER::count( scalar @{$self-> {items}}); 1707 1708 $self-> itemWidth( $self-> {maxWidth}) if $self-> {autoWidth}; 1709 $self-> focusedItem( $self-> {focusedItem} + $num) 1710 if $self-> {focusedItem} >= 0 && $self-> {focusedItem} >= $where; 1711 $self-> offset( $self-> offset); 1712 1713 my @shifters; 1714 for ( keys %{$self-> {selectedItems}}) { 1715 next if $_ < $where; 1716 push ( @shifters, $_); 1717 } 1718 for ( @shifters) { delete $self-> {selectedItems}-> {$_}; } 1719 for ( @shifters) { $self-> {selectedItems}-> {$_ + $num} = 1; } 1720 $self-> repaint if scalar @shifters; 1721} 1722 1723sub replace_items 1724{ 1725 my ( $self, $where) = ( shift, shift); 1726 return if $where < 0; 1727 1728 my ( $is, $iw) = ( $self-> {items}, $self-> {widths}); 1729 my $new; 1730 if (@_ == 1 and ref($_[0]) eq q(ARRAY)) { 1731 return unless scalar @{$_[0]}; 1732 $new = [@{$_[0]}]; 1733 } else { 1734 return unless scalar @_; 1735 $new = [@_]; 1736 } 1737 1738 my $num = scalar @$new; 1739 if ( $num + $where >= $self-> {count}) { 1740 $num = $self-> {count} - $where; 1741 return if $num <= 0; 1742 splice @$new, $num; 1743 } 1744 1745 $self-> {items} = $new; 1746 $self-> {widths} = []; 1747 $self-> recalc_widths; 1748 splice( @{$is}, $where, $num, @{$self-> {items}}); 1749 splice( @{$iw}, $where, $num, @{$self-> {widths}}); 1750 ( $self-> {items}, $self-> {widths}) = ( $is, $iw); 1751 1752 if ( $self-> {autoWidth}) { 1753 my $mw = 0; 1754 for (@{$iw}) { 1755 $mw = $_ if $mw < $_; 1756 } 1757 $self-> itemWidth( $self-> {maxWidth} = $mw); 1758 $self-> offset( $self-> offset); 1759 } 1760 1761 if ( $where <= $self-> {lastItem} && $where + $num >= $self-> {topItem}) { 1762 $self-> redraw_items( $where .. $where + $num); 1763 } 1764} 1765 1766sub add_items { shift-> insert_items( -1, @_); } 1767 1768sub delete_items 1769{ 1770 my $self = shift; 1771 my ( $is, $iw, $mw) = ( $self-> {items}, $self-> {widths}, $self-> {maxWidth}); 1772 1773 my %indices; 1774 if (@_ == 1 and ref($_[0]) eq q(ARRAY)) { 1775 return unless scalar @{$_[0]}; 1776 %indices = map{$_=>1}@{$_[0]}; 1777 } else { 1778 return unless scalar @_; 1779 %indices = map{$_=>1}@_; 1780 } 1781 1782 my @removed; 1783 my $wantarray = wantarray; 1784 my @newItems; 1785 my @newWidths; 1786 my $i; 1787 my $num = scalar keys %indices; 1788 my ( $items, $widths) = ( $self-> {items}, $self-> {widths}); 1789 1790 $self-> focusedItem( -1) if exists $indices{$self-> {focusedItem}}; 1791 1792 for ( $i = 0; $i < scalar @{$self-> {items}}; $i++) { 1793 unless ( exists $indices{$i}) { 1794 push ( @newItems, $$items[$i]); 1795 push ( @newWidths, $$widths[$i]); 1796 } else { 1797 push ( @removed, $$items[$i]) if $wantarray; 1798 } 1799 } 1800 1801 my $newFoc = $self-> {focusedItem}; 1802 for ( keys %indices) { $newFoc-- if $newFoc >= 0 && $_ < $newFoc; } 1803 1804 my @selected = sort {$a<=>$b} keys %{$self-> {selectedItems}}; 1805 $i = 0; 1806 my $dec = 0; 1807 my $d; 1808 for $d ( sort {$a<=>$b} keys %indices) { 1809 while ($i < scalar(@selected) and $d > $selected[$i]) { $selected[$i] -= $dec; $i++; } 1810 last if $i >= scalar @selected; 1811 $selected[$i++] = -1 if $d == $selected[$i]; 1812 $dec++; 1813 } 1814 while ($i < scalar(@selected)) { $selected[$i] -= $dec; $i++; } 1815 $self-> {selectedItems} = {}; 1816 for ( @selected) {$self-> {selectedItems}-> {$_} = 1;} 1817 delete $self-> {selectedItems}-> {-1}; 1818 1819 ( $self-> {items}, $self-> {widths}) = ([@newItems], [@newWidths]); 1820 my $maxWidth = 0; 1821 for ( @newWidths) { $maxWidth = $_ if $maxWidth < $_; } 1822 1823 $self-> lock; 1824 $self-> itemWidth( $self-> {maxWidth} = $maxWidth) 1825 if $self-> {autoWidth} && $self-> {maxWidth} > $maxWidth; 1826 $self-> SUPER::count( scalar @{$self-> {items}}); 1827 $self-> focusedItem( $newFoc); 1828 $self-> unlock; 1829 1830 return @removed if $wantarray; 1831} 1832 1833sub on_keydown 1834{ 1835 my ( $self, $code, $key, $mod) = @_; 1836 $self-> notify(q(MouseUp),0,0,0) if defined $self-> {mouseTransaction}; 1837 return if $mod & km::DeadKey; 1838 1839 if ( 1840 (( $code & 0xFF) >= ord(' ')) && 1841 ( $key == kb::NoKey) && 1842 !($mod & (km::Ctrl|km::Alt)) && 1843 $self-> {count} 1844 ) { 1845 my $i; 1846 my ( $c, $hit, $items) = ( lc chr ( $code & 0xFF), undef, $self-> {items}); 1847 for ( $i = $self-> {focusedItem} + 1; $i < $self-> {count}; $i++) { 1848 my $fc = substr( $self-> get_item_text($i), 0, 1); 1849 next unless defined $fc; 1850 $hit = $i, last if lc $fc eq $c; 1851 } 1852 unless ( defined $hit) { 1853 for ( $i = 0; $i < $self-> {focusedItem}; $i++) { 1854 my $fc = substr( $self-> get_item_text($i), 0, 1); 1855 next unless defined $fc; 1856 $hit = $i, last if lc $fc eq $c; 1857 } 1858 } 1859 if ( defined $hit) { 1860 $self-> focusedItem( $hit); 1861 $self-> clear_event; 1862 return; 1863 } 1864 } 1865 $self-> SUPER::on_keydown( $code, $key, $mod); 1866} 1867 1868sub on_dragitem 1869{ 1870 my ( $self, $from, $to) = @_; 1871 my ( $is, $iw) = ( $self-> {items}, $self-> {widths}); 1872 if ( $self-> {multiSelect}) { 1873 my @k = sort { $b <=> $a } keys %{$self-> {selectedItems}}; 1874 my @is = @$is[@k]; 1875 my @iw = @$iw[@k]; 1876 my $nto = $to; 1877 for my $k ( @k) { 1878 $nto-- if $k <= $to; 1879 splice( @$is, $k, 1); 1880 splice( @$iw, $k, 1); 1881 } 1882 $nto++ if $nto != $to; 1883 splice( @$is, $nto, 0, reverse @is); 1884 splice( @$iw, $nto, 0, reverse @iw); 1885 @{$self-> {selectedItems}}{$nto .. $nto + @k - 1} = 1886 delete @{$self-> {selectedItems}}{@k}; 1887 } else { 1888 splice( @$is, $to, 0, splice( @$is, $from, 1)); 1889 splice( @$iw, $to, 0, splice( @$iw, $from, 1)); 1890 } 1891 $self-> repaint; 1892 $self-> clear_event; 1893} 1894 1895sub autoWidth {($#_)?$_[0]-> {autoWidth} = $_[1] :return $_[0]-> {autoWidth} } 1896sub count {($#_)?$_[0]-> raise_ro('count') :return $_[0]-> {count} } 1897sub items {($#_)?$_[0]-> set_items( $_[1]) :return $_[0]-> {items} } 1898 1899package Prima::ProtectedListBox; 1900use vars qw(@ISA); 1901@ISA = qw(Prima::ListViewer); 1902 1903BEGIN { 1904 for ( qw( 1905 font color backColor rop rop2 1906 linePattern lineWidth lineEnd textOutBaseline 1907 fillPattern clipRect) 1908 ) { 1909 my $sb = $_; 1910 $sb =~ s/([A-Z]+)/"_\L$1"/eg; 1911 $sb = "set_$sb"; 1912 eval <<PROC; 1913 sub $sb 1914 { 1915 my \$self = shift; 1916 \$self->SUPER::$sb(\@_); 1917 \$self->{protect}->{$_} = 1 if exists \$self->{protect}; 1918 } 1919PROC 1920 } 1921} 1922 1923sub draw_items 1924{ 1925 my ( $self, $canvas, @items) = @_; 1926 return if $canvas != $self; # this does not support 'uncertain' drawings due that 1927 my %protect; # it's impossible to override $canvas's methods dynamically 1928 for ( qw( 1929 font color backColor rop rop2 linePattern lineWidth 1930 lineEnd textOutBaseline fillPattern) 1931 ) { $protect{$_} = $canvas-> $_(); } 1932 1933 my @clipRect = $canvas-> clipRect; 1934 $self-> {protect} = {}; 1935 1936 my ( $notifier, @notifyParms) = $self-> get_notify_sub(q(DrawItem)); 1937 $self-> push_event; 1938 1939 for ( @items) { 1940 $notifier-> ( @notifyParms, $canvas, @$_); 1941 1942 $canvas-> clipRect( @clipRect), delete $self-> {protect}-> {clipRect} 1943 if exists $self-> {protect}-> {clipRect}; 1944 for ( keys %{$self-> {protect}}) { $self-> $_($protect{$_}); } 1945 $self-> {protect} = {}; 1946 } 1947 1948 $self-> pop_event; 1949 delete $self-> {protect}; 1950} 1951 1952package Prima::ListBox; 1953use vars qw(@ISA); 1954@ISA = qw(Prima::ListViewer); 1955 1956sub get_item_text { return $_[0]-> {items}-> [$_[1]]; } 1957 1958sub on_stringify 1959{ 1960 my ( $self, $index, $sref) = @_; 1961 $$sref = $self-> {items}-> [$index]; 1962} 1963 1964sub on_measureitem 1965{ 1966 my ( $self, $index, $sref) = @_; 1967 $$sref = $self-> get_text_width( $self-> {items}-> [$index]); 1968} 1969 1970sub draw_items 1971{ 1972 shift-> std_draw_text_items(@_) 1973} 1974 19751; 1976 1977=pod 1978 1979=head1 NAME 1980 1981Prima::Lists - user-selectable item list widgets 1982 1983=head1 DESCRIPTION 1984 1985The module provides classes for several abstraction layers 1986of item representation. The hierarchy of classes is as follows: 1987 1988 AbstractListViewer 1989 AbstractListBox 1990 ListViewer 1991 ProtectedListBox 1992 ListBox 1993 1994The root class, C<Prima::AbstractListViewer>, provides common 1995interface, while by itself it is not directly usable. 1996The main differences between classes 1997are centered around the way the item list is stored. The simplest 1998organization of a text-only item list, provided by C<Prima::ListBox>, 1999stores an array of text scalars in a widget. More elaborated storage 2000and representation types are not realized, and the programmer is urged 2001to use the more abstract classes to derive own mechanisms. 2002For example, for a list of items that contain text strings and icons 2003see L<Prima::Dialog::FileDialog/"Prima::DirectoryListBox">. 2004To organize an item storage, different from C<Prima::ListBox>, it is 2005usually enough to overload either the C<Stringify>, C<MeasureItem>, 2006and C<DrawItem> events, or their method counterparts: C<get_item_text>, 2007C<get_item_width>, and C<draw_items>. 2008 2009=head1 Prima::AbstractListViewer 2010 2011C<Prima::AbstractListViewer> is a descendant of C<Prima::GroupScroller>, 2012and some properties are not described here. See L<Prima::IntUtils/"Prima::GroupScroller">. 2013 2014The class provides interface to generic list browsing functionality, 2015plus functionality for text-oriented lists. The class is not usable directly. 2016 2017=head2 Properties 2018 2019=over 2020 2021=item autoHeight BOOLEAN 2022 2023If 1, the item height is changed automatically 2024when the widget font is changed; this is useful for text items. 2025If 0, item height is not changed; this is useful for non-text items. 2026 2027Default value: 1 2028 2029=item count INTEGER 2030 2031An integer property, destined to reflect number of items in the list. 2032Since it is tied to the item storage organization, and hence, 2033to possibility of changing the number of items, this property 2034is often declared as read-only in descendants of C<Prima::AbstractListViewer>. 2035 2036=item dragable BOOLEAN 2037 2038If 1, allows the items to be dragged interactively by pressing control key 2039together with left mouse button. If 0, item dragging is disabled. 2040 2041Default value: 1 2042 2043=item drawGrid BOOLEAN 2044 2045If 1, vertical grid lines between columns are drawn with C<gridColor>. 2046Actual only in multi-column mode. 2047 2048Default value: 1 2049 2050=item extendedSelect BOOLEAN 2051 2052Regards the way the user selects multiple items and is only actual 2053when C<multiSelect> is 1. If 0, the user must click each item 2054in order to mark as selected. If 1, the user can drag mouse 2055or use C<Shift> key plus arrow keys to perform range selection; 2056the C<Control> key can be used to select individual items. 2057 2058Default value: 0 2059 2060=item focusedItem INDEX 2061 2062Selects the focused item index. If -1, no item is focused. 2063It is mostly a run-time property, however, it can be set 2064during the widget creation stage given that the item list is 2065accessible on this stage as well. 2066 2067Default value: -1 2068 2069=item gridColor COLOR 2070 2071Color, used for drawing vertical divider lines for multi-column 2072list widgets. The list classes support also the indirect way 2073of setting the grid color, as well as widget does, via 2074the C<colorIndex> property. To achieve this, C<ci::Grid> constant 2075is declared ( for more detail see L<Prima::Widget/colorIndex> ). 2076 2077Default value: C<cl::Black>. 2078 2079=item integralHeight BOOLEAN 2080 2081If 1, only the items that fit vertically in the widget interiors 2082are drawn. If 0, the items that are partially visible are drawn also. 2083 2084Default value: 0 2085 2086=item integralWidth BOOLEAN 2087 2088If 1, only the items that fit horizontally in the widget interiors 2089are drawn. If 0, the items that are partially visible are drawn also. 2090Actual only in multi-column mode. 2091 2092Default value: 0 2093 2094 2095=item itemHeight INTEGER 2096 2097Selects the height of the items in pixels. Since the list classes do 2098not support items with different dimensions, changes to this property 2099affect all items. 2100 2101Default value: default font height 2102 2103=item itemWidth INTEGER 2104 2105Selects the width of the items in pixels. Since the list classes do 2106not support items with different dimensions, changes to this property 2107affect all items. 2108 2109Default value: default widget width 2110 2111=item multiSelect BOOLEAN 2112 2113If 0, the user can only select one item, and it is reported by 2114the C<focusedItem> property. If 1, the user can select more than one item. 2115In this case, C<focusedItem>'th item is not necessarily selected. 2116To access selected item list, use C<selectedItems> property. 2117 2118Default value: 0 2119 2120=item multiColumn BOOLEAN 2121 2122If 0, the items are arrayed vertically in one column, and the main scroll bar 2123is vertical. If 1, the items are arrayed in several columns, C<itemWidth> 2124pixels wide each. In this case, the main scroll bar is horizontal. 2125 2126=item offset INTEGER 2127 2128Horizontal offset of an item list in pixels. 2129 2130=item topItem INTEGER 2131 2132Selects the first item drawn. 2133 2134=item selectedCount INTEGER 2135 2136A read-only property. Returns number of selected items. 2137 2138=item selectedItems ARRAY 2139 2140ARRAY is an array of integer indices of selected items. 2141 2142=item vertical BOOLEAN 2143 2144Sets general direction of items in multi-column mode. If 1, items increase 2145down-to-right. Otherwise, right-to-down. 2146 2147Doesn't have any effect in single-column mode. 2148Default value: 1. 2149 2150=back 2151 2152=head2 Methods 2153 2154=over 2155 2156=item add_selection ARRAY, FLAG 2157 2158Sets item indices from ARRAY in selected 2159or deselected state, depending on FLAG value, correspondingly 1 or 0. 2160 2161Only for multi-select mode. 2162 2163=item deselect_all 2164 2165Removes selection from all items. 2166 2167Only for multi-select mode. 2168 2169=item draw_items CANVAS, ITEM_DRAW_DATA 2170 2171Called from within C<Paint> notification to draw items. The default behavior is 2172to call C<DrawItem> notification for every item in ITEM_DRAW_DATA array. 2173ITEM_DRAW_DATA is an array or arrays, where each array consists of parameters, 2174passed to C<DrawItem> notification. 2175 2176This method is overridden in some descendant classes, to increase the speed of 2177drawing routine. For example, C<std_draw_text_items> is the optimized routine 2178for drawing unified text-based items. It is used in C<Prima::ListBox> class. 2179 2180See L<DrawItem> for parameters description. 2181 2182=item draw_text_items CANVAS, FIRST, LAST, STEP, X, Y, OFFSET, CLIP_RECT 2183 2184Called by C<std_draw_text_items> to draw sequence of text items with 2185indices from FIRST to LAST, by STEP, on CANVAS, starting at point X, Y, and 2186incrementing the vertical position with OFFSET. CLIP_RECT is a reference 2187to array of four integers with inclusive-inclusive coordinates of the active 2188clipping rectangle. 2189 2190Note that OFFSET must be an integer, otherwise bad effects will be observed 2191when text is drawn below Y=0 2192 2193=item get_item_text INDEX 2194 2195Returns text string assigned to INDEXth item. 2196Since the class does not assume the item storage organization, 2197the text is queried via C<Stringify> notification. 2198 2199=item get_item_width INDEX 2200 2201Returns width in pixels of INDEXth item. 2202Since the class does not assume the item storage organization, 2203the value is queried via C<MeasureItem> notification. 2204 2205=item is_selected INDEX 2206 2207Returns 1 if INDEXth item is selected, 0 if it is not. 2208 2209=item item2rect INDEX, [ WIDTH, HEIGHT ] 2210 2211Calculates and returns four integers with rectangle coordinates 2212of INDEXth item within the widget. WIDTH and HEIGHT are optional 2213parameters with pre-fetched dimension of the widget; if not set, 2214the dimensions are queried by calling C<size> property. If set, however, 2215the C<size> property is not called, thus some speed-up can be achieved. 2216 2217=item point2item X, Y 2218 2219Returns the index of an item that contains point (X,Y). If the point 2220belongs to the item outside the widget's interior, returns the index 2221of the first item outside the widget's interior in the direction of the point. 2222 2223=item redraw_items INDICES 2224 2225Redraws all items in INDICES array. 2226 2227=item select_all 2228 2229Selects all items. 2230 2231Only for multi-select mode. 2232 2233=item set_item_selected INDEX, FLAG 2234 2235Sets selection flag of INDEXth item. 2236If FLAG is 1, the item is selected. If 0, it is deselected. 2237 2238Only for multi-select mode. 2239 2240=item select_item INDEX 2241 2242Selects INDEXth item. 2243 2244Only for multi-select mode. 2245 2246=item std_draw_text_items CANVAS, ITEM_DRAW_DATA 2247 2248An optimized method, draws unified text-based items. 2249It is fully compatible to C<draw_items> interface, 2250and is used in C<Prima::ListBox> class. 2251 2252The optimization is derived from the assumption that items 2253maintain common background and foreground colors, that differ 2254in selected and non-selected states only. The routine groups 2255drawing requests for selected and non-selected items, and 2256draws items with reduced number of calls to C<color> property. 2257While the background is drawn by the routine itself, the foreground 2258( usually text ) is delegated to the C<draw_text_items> method, so 2259the text positioning and eventual decorations would not require 2260full rewrite of code. 2261 2262ITEM_DRAW_DATA is an array of arrays of scalars, where each array 2263contains parameters of C<DrawItem> notification. 2264See L<DrawItem> for parameters description. 2265 2266=item toggle_item INDEX 2267 2268Toggles selection of INDEXth item. 2269 2270Only for multi-select mode. 2271 2272=item unselect_item INDEX 2273 2274Deselects INDEXth item. 2275 2276Only for multi-select mode. 2277 2278=back 2279 2280=head2 Events 2281 2282=over 2283 2284=item Click 2285 2286Called when the user presses return key or double-clicks on 2287an item. The index of the item is stored in C<focusedItem>. 2288 2289=item DragItem OLD_INDEX, NEW_INDEX 2290 2291Called when the user finishes the drag of an item 2292from OLD_INDEX to NEW_INDEX position. The default action 2293rearranges the item list in accord with the dragging action. 2294 2295=item DrawItem CANVAS, INDEX, X1, Y1, X2, Y2, SELECTED, FOCUSED, PRELIGHT, COLUMN 2296 2297Called when an INDEXth item is to be drawn on CANVAS. 2298X1, Y1, X2, Y2 designate the item rectangle in widget coordinates, 2299where the item is to be drawn. SELECTED, FOCUSED, and PRELIGHT are boolean 2300flags, if the item must be drawn correspondingly in selected and 2301focused states, with or without the prelight effect. 2302 2303=item MeasureItem INDEX, REF 2304 2305Puts width in pixels of INDEXth item into REF 2306scalar reference. This notification must be called 2307from within C<begin_paint_info/end_paint_info> block. 2308 2309=item SelectItem INDEX, FLAG 2310 2311Called when the item changed its selection state. 2312INDEX is the index of the item, FLAG is its new selection 2313state: 1 if it is selected, 0 if it is not. 2314 2315=item Stringify INDEX, TEXT_REF 2316 2317Puts text string, assigned to INDEXth item into TEXT_REF 2318scalar reference. 2319 2320=back 2321 2322=head1 Prima::AbstractListBox 2323 2324Exactly the same as its ascendant, C<Prima::AbstractListViewer>, 2325except that it does not propagate C<DrawItem> message, 2326assuming that the items must be drawn as text. 2327 2328=head1 Prima::ListViewer 2329 2330The class implements items storage mechanism, but leaves 2331the items format to the programmer. The items are accessible via 2332C<items> property and several other helper routines. 2333 2334The class also defines the user navigation, by accepting character 2335keyboard input and jumping to the items that have text assigned 2336with the first letter that match the input. 2337 2338C<Prima::ListViewer> is derived from C<Prima::AbstractListViewer>. 2339 2340=head2 Properties 2341 2342=over 2343 2344=item autoWidth BOOLEAN 2345 2346Selects if the gross item width must be recalculated automatically 2347when either the font changes or item list is changed. 2348 2349Default value: 1 2350 2351=item count INTEGER 2352 2353A read-only property; returns number of items. 2354 2355=item items ARRAY 2356 2357Accesses the storage array of items. The format of items is not 2358defined, it is merely treated as one scalar per index. 2359 2360=back 2361 2362=head2 Methods 2363 2364=over 2365 2366=item add_items ITEMS 2367 2368Appends array of ITEMS to the end of the list. 2369 2370=item calibrate 2371 2372Recalculates all item widths and adjusts C<itemWidth> if 2373C<autoWidth> is set. 2374 2375=item delete_items INDICES 2376 2377Deletes items from the list. INDICES can be either an array, 2378or a reference to an array of item indices. 2379 2380=item get_item_width INDEX 2381 2382Returns width in pixels of INDEXth item from internal cache. 2383 2384=item get_items INDICES 2385 2386Returns array of items. INDICES can be either an array, or a reference to an 2387array of item indices. Depending on the caller context, the results are 2388different: in array context the item list is returned; in scalar - only the 2389first item from the list. The semantic of the last call is naturally usable 2390only for single item retrieval. 2391 2392=item insert_items OFFSET, ITEMS 2393 2394Inserts array of items at OFFSET index in the list. Offset must be a valid 2395index; to insert items at the end of the list use C<add_items> method. 2396 2397ITEMS can be either an array, or a reference to an array of items. 2398 2399=item replace_items OFFSET, ITEMS 2400 2401Replaces existing items at OFFSET index in the list. Offset must be a valid 2402index. 2403 2404ITEMS can be either an array, or a reference to an array of items. 2405 2406=back 2407 2408=head1 Prima::ProtectedListBox 2409 2410A semi-demonstrational class, derived from C<Prima::ListViewer>, 2411that applies certain protection for every item drawing session. 2412Assuming that several item drawing routines can be assembled in one 2413widget, C<Prima::ProtectedListBox> provides a safety layer between 2414these, so, for example, one drawing routine that selects a font 2415or a color and does not care to restore the old value back, 2416does not affect the outlook of the other items. 2417 2418This functionality is implementing by overloading C<draw_items> 2419method and also all graphic properties. 2420 2421=head1 Prima::ListBox 2422 2423Descendant of C<Prima::ListViewer>, declares format of items 2424as a single text string. Incorporating all of functionality of 2425its predecessors, provides a standard listbox widget. 2426 2427=head2 Synopsis 2428 2429 my $lb = Prima::ListBox-> create( 2430 items => [qw(First Second Third)], 2431 focusedItem => 2, 2432 onClick => sub { 2433 print $_[0]-> get_items( $_[0]-> focusedItem), " is selected\n"; 2434 } 2435 ); 2436 2437=head2 Methods 2438 2439=over 2440 2441=item get_item_text INDEX 2442 2443Returns text string assigned to INDEXth item. 2444Since the item storage organization is implemented, does 2445so without calling C<Stringify> notification. 2446 2447=back 2448 2449=head1 AUTHOR 2450 2451Dmitry Karasik, E<lt>dmitry@karasik.eu.orgE<gt>. 2452 2453=head1 SEE ALSO 2454 2455L<Prima>, L<Prima::Widget>, L<Prima::ComboBox>, L<Prima::Dialog::FileDialog>, F<examples/editor.pl> 2456 2457=cut 2458