1# contains: 2# Button 3# CheckBox 4# Radio 5# SpeedButton 6# RadioGroup ( obsolete ) 7# GroupBox 8# CheckBoxGroup ( obsolete ) 9# 10# AbstractButton 11# Cluster 12 13package Prima::Buttons; 14 15use Carp; 16use Prima::Const; 17use Prima::Classes; 18use Prima::IntUtils; 19use Prima::StdBitmap; 20use strict; 21use warnings; 22 23 24package Prima::AbstractButton; 25use vars qw(@ISA); 26@ISA = qw(Prima::Widget Prima::MouseScroller); 27 28{ 29my %RNT = ( 30 %{Prima::Widget-> notification_types()}, 31 Check => nt::Default, 32); 33 34sub notification_types { return \%RNT; } 35} 36 37 38sub profile_default 39{ 40 return { 41 %{$_[ 0]-> SUPER::profile_default}, 42 hotKey => undef, 43 pressed => 0, 44 selectable => 1, 45 autoHeight => 1, 46 autoWidth => 1, 47 } 48} 49 50sub profile_check_in 51{ 52 my ( $self, $p, $default) = @_; 53 $p-> { autoWidth} = 0 54 if exists $p-> {width} || exists $p-> {size} || exists $p-> {rect} || 55 ( exists $p-> {left} && exists $p-> {right}); 56 $p-> {autoHeight} = 0 57 if exists $p-> {height} || exists $p-> {size} || exists $p-> {rect} || 58 ( exists $p-> {top} && exists $p-> {bottom}); 59 $self-> SUPER::profile_check_in( $p, $default); 60} 61 62sub on_translateaccel 63{ 64 my ( $self, $code, $key, $mod) = @_; 65 if ( 66 defined $self-> {accel} && 67 ($key == kb::NoKey) && 68 lc chr $code eq $self-> { accel} 69 ) { 70 $self-> clear_event; 71 $self-> notify( 'Click'); 72 } 73 if ( 74 defined $self-> {hotKey} && 75 ($key == kb::NoKey) && 76 lc chr $code eq $self-> {hotKey} 77 ) { 78 $self-> clear_event; 79 $self-> notify( 'Click'); 80 } 81 if ( $self-> { default} && $key == kb::Enter) { 82 $self-> clear_event; 83 $self-> notify( 'Click'); 84 } 85} 86 87sub init 88{ 89 my $self = shift; 90 my %profile = $self-> SUPER::init(@_); 91 $self-> { hotKey} = $profile{ hotKey}; 92 $self-> { pressed} = $profile{ pressed}; 93 $self-> { autoHeight} = $profile{ autoHeight}; 94 $self-> { autoWidth} = $profile{ autoWidth}; 95 return %profile; 96} 97 98sub cancel_transaction 99{ 100 my $self = $_[0]; 101 if ( $self-> {mouseTransaction} || $self-> {spaceTransaction}) { 102 $self-> {spaceTransaction} = undef; 103 $self-> capture(0) if $self-> {mouseTransaction}; 104 $self-> {mouseTransaction} = undef; 105 $self-> pressed( 0); 106 } 107} 108 109sub on_keydown 110{ 111 my ( $self, $code, $key, $mod, $repeat) = @_; 112 if ( $key == kb::Space) { 113 $self-> clear_event; 114 return if $self-> {spaceTransaction} || $self-> {mouseTransaction}; 115 $self-> { spaceTransaction} = 1; 116 $self-> pressed( 1); 117 } 118 if ( 119 defined $self-> {accel} && 120 ($key == kb::NoKey) && 121 lc chr $code eq $self-> { accel} 122 ) { 123 $self-> clear_event; 124 $self-> notify( 'Click'); 125 } 126} 127 128sub on_keyup 129{ 130 my ( $self, $code, $key, $mod) = @_; 131 132 if ( $key == kb::Space && $self-> {spaceTransaction}) { 133 $self-> {spaceTransaction} = undef; 134 $self-> capture(0) if $self-> {mouseTransaction}; 135 $self-> {mouseTransaction} = undef; 136 $self-> pressed( 0); 137 $self-> update_view; 138 $self-> clear_event; 139 $self-> notify( 'Click') 140 } 141} 142 143sub on_leave 144{ 145 my $self = $_[0]; 146 if ( $self-> {spaceTransaction} || $self-> {mouseTransaction}) { 147 $self-> cancel_transaction; 148 } else { 149 $self-> repaint; 150 } 151} 152 153sub on_mousedown 154{ 155 my ( $self, $btn, $mod, $x, $y) = @_; 156 return if $self-> {mouseTransaction} || $self-> {spaceTransaction}; 157 return if $btn != mb::Left; 158 $self-> { mouseTransaction} = 1; 159 $self-> { lastMouseOver} = 1; 160 $self-> pressed( 1); 161 $self-> capture(1); 162 $self-> clear_event; 163 $self-> scroll_timer_start if $self-> {autoRepeat}; 164} 165 166sub on_mouseclick 167{ 168 my ( $self, $btn, $mod, $x, $y, $dbl) = @_; 169 return unless $dbl; 170 return if $btn != mb::Left; 171 return if $self-> {mouseTransaction} || $self-> {spaceTransaction}; 172 $self-> { mouseTransaction} = 1; 173 $self-> { lastMouseOver} = 1; 174 $self-> pressed( 1); 175 $self-> capture(1); 176 $self-> clear_event; 177} 178 179sub on_mouseup 180{ 181 my ( $self, $btn, $mod, $x, $y) = @_; 182 return if $btn != mb::Left; 183 return unless $self-> {mouseTransaction}; 184 my @size = $self-> size; 185 $self-> {mouseTransaction} = undef; 186 $self-> {spaceTransaction} = undef; 187 $self-> {lastMouseOver} = undef; 188 $self-> capture(0); 189 $self-> pressed( 0); 190 if ( $x > 0 && $y > 0 && $x < $size[0] && $y < $size[1] ) { 191 $self-> clear_event; 192 $self-> update_view; 193 $self-> notify( 'Click'); 194 } 195} 196 197sub on_mousemove 198{ 199 my ( $self, $mod, $x, $y) = @_; 200 return unless $self-> {mouseTransaction}; 201 return if $self-> {autoRepeat} && !$self-> scroll_timer_semaphore; 202 my @size = $self-> size; 203 my $mouseOver = $x > 0 && $y > 0 && $x < $size[0] && $y < $size[1]; 204 $self-> pressed( $mouseOver) if $self-> { lastMouseOver} != $mouseOver; 205 $self-> { lastMouseOver} = $mouseOver; 206 return unless $self-> {autoRepeat}; 207 $self-> scroll_timer_stop, return 208 unless $mouseOver; 209 $self-> scroll_timer_start, return 210 unless $self-> scroll_timer_active; 211 $self-> scroll_timer_semaphore(0); 212 $self-> notify(q(Click)); 213} 214 215sub on_mouseenter 216{ 217 my $self = $_[0]; 218 if ( 219 !$self-> {spaceTransaction} && 220 !$self-> {mouseTransaction} && 221 $self-> enabled 222 ) { 223 $self-> {hilite} = 1; 224 $self-> repaint; 225 } 226} 227 228sub on_mouseleave 229{ 230 my $self = $_[0]; 231 if ( $self-> {hilite}) { 232 undef $self-> {hilite}; 233 $self-> repaint; 234 } 235} 236 237 238sub on_fontchanged 239{ 240 $_[0]-> check_auto_size; 241} 242 243sub draw_veil 244{ 245 my ($self,$canvas) = (shift, shift); 246 my $back = $self-> backColor; 247 $canvas-> set( 248 color => cl::Clear, 249 backColor => cl::Set, 250 fillPattern => fp::SimpleDots, 251 rop => rop::AndPut 252 ); 253 $canvas-> bar( @_); 254 $canvas-> set( 255 color => $back, 256 backColor => cl::Clear, 257 rop => rop::OrPut 258 ); 259 $canvas-> bar( @_); 260 $canvas-> set( 261 rop => rop::CopyPut, 262 backColor => $back, 263 ); 264} 265 266sub draw_caption 267{ 268 my ( $self, $canvas, $x, $y) = @_; 269 my ($cap, $tilde) = @{ $self-> text_wrap_shape( $self-> text, 270 undef, 271 options => tw::CalcMnemonic|tw::CollapseTilde|tw::ExpandTabs|tw::ReturnGlyphs, 272 tabs => 1, 273 ) }; 274 unless ( $self->enabled) { 275 my $z = $canvas-> color; 276 $canvas-> color( cl::White); 277 $canvas-> text_out( $cap, $x + 1, $y - 1); 278 $canvas->line( 279 $x + 1 + $tilde->{tildeStart}, $y - 1, 280 $x + 1 + $tilde->{tildeEnd}, $y - 1, 281 ) if defined $tilde->{tildeLine}; 282 $canvas-> color( $z); 283 } 284 $canvas-> text_out( $cap, $x + 1, $y - 1); 285 $canvas->line( 286 $x + $tilde->{tildeStart}, $y, 287 $x + $tilde->{tildeEnd}, $y, 288 ) if defined $tilde->{tildeLine}; 289 if ($self-> focused) { 290 my ( $fw, $fh) = ( 291 $canvas-> get_text_width( $cap), 292 $canvas-> font-> height, 293 ); 294 $canvas-> rect_focus( $x - 2, $y - 2, $x + 2 + $fw, $y + 2 + $fh) 295 } 296} 297 298sub caption_box 299{ 300 my ($self,$canvas) = @_; 301 my $cap = $self-> text; 302 $cap =~ s/~//; 303 $canvas = $self unless $canvas; 304 return $canvas-> get_text_width( $cap), $canvas-> font-> height; 305} 306 307sub calc_geom_size { $_[0]-> caption_box } 308 309sub pressed 310{ 311 return $_[0]-> {pressed} unless $#_; 312 $_[0]-> { pressed} = $_[1]; 313 $_[0]-> repaint; 314} 315 316sub set_text 317{ 318 my ( $self, $caption) = @_; 319 $self-> SUPER::set_text( $caption ); 320 $self-> {accel} = lc($1) if $caption =~ /~([a-z0-9])/i; 321 $self-> check_auto_size; 322 $self-> repaint; 323} 324 325sub on_enable { $_[0]-> repaint; } 326sub on_disable { $_[0]-> cancel_transaction; $_[0]-> repaint; } 327sub on_enter { $_[0]-> repaint; } 328 329sub hotKey { $#_ ? $_[0]->{hotKey} = $_[1] : $_[0]->{hotKey} } 330 331sub autoHeight 332{ 333 return $_[0]-> {autoHeight} unless $#_; 334 my ( $self, $a) = @_; 335 return if ( $self-> {autoHeight} ? 1 : 0) == ( $a ? 1 : 0); 336 $self-> {autoHeight} = ( $a ? 1 : 0); 337 $self-> check_auto_size if $a; 338} 339 340sub autoWidth 341{ 342 return $_[0]-> {autoWidth} unless $#_; 343 my ( $self, $a) = @_; 344 return if ( $self-> {autoWidth} ? 1 : 0) == ( $a ? 1 : 0); 345 $self-> {autoWidth} = ( $a ? 1 : 0); 346 $self-> check_auto_size if $a; 347} 348 349sub check_auto_size 350{ 351 my $self = $_[0]; 352 my %sets; 353 if ( $self-> {autoWidth} || $self-> {autoHeight}) { 354 my @geomSize = $self-> calc_geom_size; 355 $sets{ geomWidth} = $geomSize[0] if $self-> {autoWidth}; 356 $sets{ geomHeight} = $geomSize[1] if $self-> {autoHeight}; 357 $self-> set( %sets); 358 } 359} 360 361package Prima::Button; 362use vars qw(@ISA); 363@ISA = qw(Prima::AbstractButton); 364 365my %standardGlyphScheme = ( 366 glyphs => 4, 367 defaultGlyph => 0, 368 hiliteGlyph => 0, 369 disabledGlyph => 1, 370 pressedGlyph => 2, 371 holdGlyph => 3, 372); 373 374sub profile_default 375{ 376 return { 377 %{$_[ 0]-> SUPER::profile_default}, 378 autoRepeat => 0, 379 borderWidth => 2, 380 checkable => 0, 381 checked => 0, 382 default => 0, 383 flat => 0, 384 glyphs => 1, 385 height => 36, 386 image => undef, 387 imageFile => undef, 388 imageScale => 1, 389 smoothScaling => 1, 390 modalResult => 0, 391 vertical => 0, 392 width => 96, 393 widgetClass => wc::Button, 394 395 defaultGlyph => 0, 396 hiliteGlyph => 0, 397 disabledGlyph => 1, 398 pressedGlyph => 2, 399 holdGlyph => 3, 400 } 401} 402 403sub profile_check_in 404{ 405 my ( $self, $p, $default) = @_; 406 $self-> SUPER::profile_check_in( $p, $default); 407 my $checkable = exists $p-> {checkable} ? $p-> {checkable} : $default-> {checkable}; 408 $p-> { checked} = 0 unless $checkable; 409} 410 411sub init 412{ 413 my $self = shift; 414 $self-> {$_} = 0 for ( qw( 415 borderWidth checkable checked default vertical 416 defaultGlyph hiliteGlyph disabledGlyph pressedGlyph holdGlyph 417 flat modalResult autoRepeat 418 )); 419 $self-> {imageScale} = $self-> {glyphs} = 1; 420 $self-> {image} = undef; 421 my %profile = $self-> SUPER::init(@_); 422 defined $profile{image} ? 423 $self-> image( $profile{image}) : 424 $self-> imageFile( $profile{imageFile}); 425 $self-> $_( $profile{$_}) for ( qw( 426 borderWidth checkable checked default smoothScaling imageScale glyphs vertical 427 defaultGlyph hiliteGlyph disabledGlyph pressedGlyph holdGlyph 428 flat modalResult autoRepeat 429 )); 430 return %profile; 431} 432 433sub on_paint 434{ 435 my ($self,$canvas) = @_; 436 my @clr = ( $self-> color, $self-> backColor); 437 @clr = ( $self-> hiliteColor, $self-> hiliteBackColor) 438 if $self-> { default}; 439 $clr[1] = $self-> prelight_color($clr[1]) if $self->{hilite} && $self-> enabled; 440 @clr = ( $self-> disabledColor, $self-> disabledBackColor) 441 if !$self-> enabled; 442 my @size = $canvas-> size; 443 444 my @fbar = $self-> {default} ? 445 ( 1, 1, $size[0] - 2, $size[1] - 2): 446 ( 0, 0, $size[0] - 1, $size[1] - 1); 447 if ( !$self-> {flat} || $self-> {hilite}) { 448 $self-> rect_bevel( $canvas, @fbar, 449 fill => $self->transparent ? undef : $self-> new_gradient( 450 palette => [ $self-> dark3DColor, $clr[1], $self-> light3DColor ], 451 spline => [0,0.5,1,0.5], 452 vertical => 0, 453 ), 454 width => $self-> {borderWidth}, 455 concave => ( $_[0]-> { pressed} || $_[0]-> { checked}), 456 ); 457 } else { 458 $canvas-> color( $clr[ 1]); 459 $canvas-> bar( @fbar) unless $self-> transparent; 460 } 461 if ( $self-> {default}) { 462 $canvas-> color( cl::Black); 463 $canvas-> rectangle( 0, 0, $size[0]-1, $size[1]-1); 464 } 465 466 my $shift = $self-> {checked} ? 1 : 0; 467 $shift += $self-> {pressed} ? 2 : 0; 468 my $capOk = length($self-> text) > 0; 469 my ( $fw, $fh) = $capOk ? $self-> caption_box($canvas) : ( 0, 0); 470 my ( $textAtX, $textAtY); 471 472 if ( defined $self-> {image}) { 473 my $is = $self->{imageScale}; 474 my $pw = $self-> {image}-> width / $self-> { glyphs}; 475 my $ph = $self-> {image}-> height; 476 my $sw = $pw * $is; 477 my $sh = $ph * $is; 478 my $imgNo = $self-> {defaultGlyph}; 479 my $useVeil = 0; 480 my $image = $self->{image}; 481 if ( $self-> {hilite}) { 482 if ( $self->{glyphs} > 1 ) { 483 $imgNo = $self-> {hiliteGlyph} 484 if $self-> {glyphs} > $self-> {hiliteGlyph} && 485 $self-> {hiliteGlyph} >= 0; 486 } elsif ( ref($self-> {hiliteGlyph})) { 487 $image = $self->{hiliteGlyph}; 488 } 489 } 490 if ( $self-> {checked}) { 491 if ( $self->{glyphs} > 1 ) { 492 $imgNo = $self-> {holdGlyph} if 493 $self-> {glyphs} > $self-> {holdGlyph} && 494 $self-> {holdGlyph} >= 0; 495 } elsif ( ref($self->{holdGlyph})) { 496 $image = $self->{holdGlyph}; 497 } 498 } 499 if ( $self-> {pressed}) { 500 if ( $self->{glyphs} > 1 ) { 501 $imgNo = $self-> {pressedGlyph} if 502 $self-> {glyphs} > $self-> {pressedGlyph} && 503 $self-> {pressedGlyph} >= 0; 504 } elsif ( ref($self->{pressedGlyph}) ) { 505 $image = $self->{pressedGlyph}; 506 } 507 } 508 if ( !$self-> enabled) { 509 if ( $self->{glyphs} > 1 ) { 510 ( $self-> {glyphs} > $self-> {disabledGlyph} && $self-> {disabledGlyph} >= 0) ? 511 $imgNo = $self-> {disabledGlyph} : 512 $useVeil = 1; 513 } elsif (ref($self->{disabledGlyph})) { 514 $image = $self->{disabledGlyph}; 515 } else { 516 $useVeil = 1; 517 } 518 } 519 520 my ( $imAtX, $imAtY); 521 if ( $capOk) { 522 if ( $self-> { vertical}) { 523 $imAtX = ( $size[ 0] - $sw) / 2 + $shift; 524 $imAtY = ( $size[ 1] - $fh - $sh) / 3; 525 $textAtX = ( $size[0] - $fw) / 2 + $shift; 526 $textAtY = $size[ 1] - 2 * $imAtY - $fh - $sh - $shift; 527 $imAtY = $size[ 1] - $imAtY - $sh - $shift; 528 } else { 529 $imAtX = ( $size[ 0] - $fw - $sw) / 3; 530 $imAtY = ( $size[ 1] - $sh) / 2 - $shift; 531 $textAtX = 2 * $imAtX + $sw + $shift; 532 $textAtY = ( $size[1] - $fh) / 2 - $shift; 533 $imAtX += $shift; 534 } 535 } else { 536 $imAtX = ( $size[0] - $sw) / 2 + $shift; 537 $imAtY = ( $size[1] - $sh) / 2 - $shift; 538 } 539 540 if ( $image && UNIVERSAL::isa($image, 'Prima::Drawable::Metafile')) { 541 if ( !$self->enabled && $useVeil && $image->type == dbt::Bitmap) { 542 $canvas->color(cl::White); 543 $image->execute($canvas, $imAtX+1, $imAtY-1); 544 $useVeil = 0; 545 } 546 $canvas->color($clr[0]); 547 $image->execute($canvas, $imAtX, $imAtY); 548 goto CAPTION; 549 } 550 if ( $self-> {smoothScaling} && $is != 1.0 ) { 551 my $c = $self->{smooth_cache} //= { 552 zoom => -1, 553 obj => "$image", 554 cache => undef, 555 }; 556 if ( $c->{zoom} != $is || $c->{obj} ne $image ) { 557 $c->{cache} = ( $self->{glyphs} == 1 ) ? 558 $image->dup : $image->extract( $imgNo * $pw, 0, $pw, $ph); 559 $c->{cache}->ui_scale( zoom => $is ); 560 $c->{zoom} = $is; 561 $c->{obj} = "$image"; 562 } 563 $image = $c->{cache}; 564 $imgNo = 0; 565 ($pw,$ph) = $image->size; 566 ($sw,$sh) = $image->size; 567 } 568 $canvas-> put_image_indirect( 569 $image, 570 $imAtX, $imAtY, 571 $imgNo * $pw, 0, 572 $sw, $sh, 573 $pw, $ph, 574 rop::CopyPut 575 ); 576CAPTION: 577 $self-> draw_veil( $canvas, $imAtX, $imAtY, $imAtX + $sw, $imAtY + $sh) 578 if $useVeil; 579 } else { 580 $textAtX = ( $size[0] - $fw) / 2 + $shift; 581 $textAtY = ( $size[1] - $fh) / 2 - $shift; 582 } 583 $canvas-> color( $clr[0]); 584 $self-> draw_caption( $canvas, $textAtX, $textAtY) if $capOk; 585 $canvas-> rect_focus( 4, 4, $size[0] - 5, $size[1] - 5 ) if !$capOk && $self-> focused; 586} 587 588sub on_keydown 589{ 590 my ( $self, $code, $key, $mod, $repeat) = @_; 591 if ( $key == kb::Enter) { 592 $self-> clear_event; 593 return $self-> notify( 'Click') 594 } 595 $self-> SUPER::on_keydown( $code, $key, $mod, $repeat); 596} 597 598sub on_click 599{ 600 my $self = $_[0]; 601 $self-> checked( !$self-> checked) 602 if $self-> { checkable}; 603 my $owner = $self-> owner; 604 while ( $owner ) { 605 if ( 606 $owner-> isa(q(Prima::Window)) && 607 $owner-> get_modal && 608 $self-> modalResult 609 ) { 610 $owner-> modalResult( $self-> modalResult); 611 $owner-> end_modal; 612 last; 613 } else { 614 $owner = $owner-> owner; 615 } 616 } 617} 618 619sub on_check {} 620 621sub std_calc_geom_size 622{ 623 my $self = $_[0]; 624 my $capOk = length($self-> text); 625 my @sz = $capOk ? $self-> caption_box : (0,0); 626 627 $sz[$_] += 10 for 0,1; 628 629 if ( defined $self-> {image}) { 630 my $imw = $self-> {image}-> width / $self-> { glyphs} * $self-> {imageScale}; 631 my $imh = $self-> {image}-> height / $self-> { glyphs} * $self-> {imageScale}; 632 if ( $capOk) { 633 if ( $self-> { vertical}) { 634 $sz[0] = $imw if $sz[0] < $imw; 635 $sz[1] += 2 + $imh; 636 } else { 637 $sz[0] += 2 + $imw; 638 $sz[1] = $imh if $sz[1] < $imh; 639 } 640 } else { 641 $sz[0] += $imw; 642 $sz[1] += $imh; 643 } 644 } 645 $sz[$_] += 2 for 0,1; 646 $sz[$_] += $self-> {borderWidth} * 2 for 0,1; 647 return @sz; 648} 649 650sub calc_geom_size 651{ 652 my $self = shift; 653 my @sz = $self-> std_calc_geom_size; 654 my ($dx, $dy) = ( $self->font->width/7, $self->font->height/16); 655 $sz[0] = $dx * 96 if $sz[0] < $dx * 96; 656 $sz[1] = $dy * 36 if $sz[1] < $dy * 36; 657 return @sz; 658} 659 660sub autoRepeat 661{ 662 return $_[0]-> {autoRepeat} unless $#_; 663 $_[0]-> {autoRepeat} = $_[1]; 664} 665 666sub borderWidth 667{ 668 return $_[0]-> {borderWidth} unless $#_; 669 my ( $self, $bw) = @_; 670 $bw = 0 if $bw < 0; 671 $bw = int( $bw); 672 return if $bw == $self-> {borderWidth}; 673 $self-> {borderWidth} = $bw; 674 $self-> check_auto_size; 675 $self-> repaint; 676} 677 678sub checkable 679{ 680 return $_[0]-> {checkable} unless $#_; 681 $_[0]-> checked( 0) unless $_[0]-> {checkable} == $_[1]; 682 $_[0]-> {checkable} = $_[1]; 683} 684 685sub checked 686{ 687 return $_[0]-> {checked} unless $#_; 688 return unless $_[0]-> { checkable}; 689 return if $_[0]-> {checked}+0 == $_[1]+0; 690 $_[0]-> {checked} = $_[1]; 691 $_[0]-> repaint; 692 $_[0]-> notify( 'Check', $_[0]-> {checked}); 693} 694 695sub default 696{ 697 return $_[0]-> {default} unless $#_; 698 my $self = $_[0]; 699 return if $self-> {default} == $_[1]; 700 if ( $self-> { default} = $_[1]) { 701 my @widgets = $self-> owner-> widgets; 702 for ( @widgets) { 703 last if $_ == $self; 704 $_-> default(0) 705 if $_-> isa(q(Prima::Button)) && $_-> default; 706 } 707 } 708 $self-> repaint; 709} 710 711sub defaultGlyph {($#_)?($_[0]-> {defaultGlyph} = $_[1],$_[0]-> repaint) :return $_[0]-> {defaultGlyph}} 712sub hiliteGlyph {($#_)?($_[0]-> {hiliteGlyph} = $_[1],$_[0]-> repaint) :return $_[0]-> {hiliteGlyph}} 713sub disabledGlyph{($#_)?($_[0]-> {disabledGlyph}= $_[1],$_[0]-> repaint) :return $_[0]-> {disabledGlyph}} 714sub pressedGlyph {($#_)?($_[0]-> {pressedGlyph} = $_[1],$_[0]-> repaint) :return $_[0]-> {pressedGlyph}} 715sub holdGlyph {($#_)?($_[0]-> {holdGlyph} = $_[1],$_[0]-> repaint) :return $_[0]-> {holdGlyph}} 716sub flat {($#_)?($_[0]-> {flat} = $_[1],$_[0]-> repaint) :return $_[0]-> {flat}} 717 718sub image 719{ 720 return $_[0]-> {image} unless $#_; 721 my ( $self, $image) = @_; 722 $self-> {image} = $image; 723 delete $self-> {smooth_cache}; 724 $self-> check_auto_size; 725 $self-> repaint; 726} 727 728sub imageFile 729{ 730 return $_[0]-> {imageFile} unless $#_; 731 my ($self,$file) = @_; 732 $self-> image(undef), return unless defined $file; 733 my $img = Prima::Icon-> create; 734 my @fp = ($file); 735 $fp[0] =~ s/\:(\d+)$//; 736 push( @fp, 'index', $1) if defined $1; 737 return unless $img-> load(@fp); 738 $self-> {imageFile} = $file; 739 $self-> image($img); 740} 741 742sub imageScale 743{ 744 return $_[0]-> {imageScale} unless $#_; 745 my ( $self, $imageScale) = @_; 746 $self-> {imageScale} = $imageScale; 747 delete $self-> {smooth_cache}; 748 if ( $self-> {image}) { 749 $self-> check_auto_size; 750 $self-> repaint; 751 } 752} 753 754sub smoothScaling 755{ 756 return $_[0]-> {smoothScaling} unless $#_; 757 my ( $self, $smoothScaling) = @_; 758 $self-> {smoothScaling} = $smoothScaling ? 1 : 0; 759} 760 761sub vertical 762{ 763 return $_[0]-> {vertical} unless $#_; 764 my ( $self, $vertical) = @_; 765 $self-> {vertical} = $vertical; 766 $self-> check_auto_size; 767 $self-> repaint; 768} 769 770sub modalResult 771{ 772 return $_[0]-> {modalResult} unless $#_; 773 my $self = $_[0]; 774 $self-> { modalResult} = $_[1]; 775 my $owner = $self-> owner; 776 while ( $owner ) { 777 if ( 778 $owner-> isa(q(Prima::Window)) && 779 $owner-> get_modal && 780 $self-> {modalResult} 781 ) { 782 $owner-> modalResult( $self-> { modalResult}); 783 $owner-> end_modal; 784 last; 785 } else { 786 $owner = $owner-> owner; 787 } 788 } 789} 790 791sub glyphs 792{ 793 return $_[0]-> {glyphs} unless $#_; 794 my $maxG = defined $_[0]-> {image} ? $_[0]-> {image}-> width : 1; 795 $maxG = 1 unless $maxG; 796 if ( $_[1] > 0 && $_[1] <= $maxG) 797 { 798 $_[0]-> {glyphs} = $_[1]; 799 $_[0]-> repaint; 800 } 801} 802 803 804package Prima::Cluster; 805use vars qw(@ISA @images); 806@ISA = qw(Prima::AbstractButton); 807 808my @images; 809 810Prima::Application::add_startup_notification( sub { 811 my $i = 0; 812 for ( 813 sbmp::CheckBoxUnchecked, sbmp::CheckBoxUncheckedPressed, 814 sbmp::CheckBoxChecked, sbmp::CheckBoxCheckedPressed, 815 sbmp::RadioUnchecked, sbmp::RadioUncheckedPressed, 816 sbmp::RadioChecked, sbmp::RadioCheckedPressed 817 ) { 818 $images[ $i] = ( $i > 3) ? 819 Prima::StdBitmap::icon( $_) : 820 Prima::StdBitmap::image( $_); 821 $i++; 822 } 823}); 824 825sub profile_default 826{ 827 return { 828 %{$_[ 0]-> SUPER::profile_default}, 829 auto => 1, 830 checked => 0, 831 height => 36, 832 ownerBackColor => 1, 833 } 834} 835 836sub init 837{ 838 my $self = shift; 839 my %profile = $self-> SUPER::init(@_); 840 $self-> { auto } = $profile{ auto }; 841 $self-> { checked} = $profile{ checked}; 842 $self-> check_auto_size; 843 return %profile; 844} 845 846sub on_keydown 847{ 848 my ( $self, $code, $key, $mod, $repeat) = @_; 849 if ( $key == kb::Tab || $key == kb::BackTab) { 850 my ( $next, $owner) = ( $self, $self-> owner); 851 while ( $next) { 852 last unless $next-> owner == $owner && $next-> isa('Prima::Cluster'); 853 $next = $next-> next_tab( $key == kb::Tab); 854 } 855 $next-> select if $next; 856 $self-> clear_event; 857 return; 858 } 859 $self-> SUPER::on_keydown( $code, $key, $mod, $repeat); 860} 861 862sub on_click 863{ 864 my $self = $_[0]; 865 $self-> focus; 866 $self-> checked( !$self-> checked); 867} 868 869sub on_enter 870{ 871 my $self = $_[0]; 872 $self-> check if $self-> auto; 873 $self-> SUPER::on_enter; 874} 875 876sub auto { ($#_) ? $_[0]-> {auto} = $_[1] : return $_[0]-> {auto}} 877 878sub checked 879{ 880 return $_[0]-> {checked} unless $#_; 881 my $old = $_[0]-> {checked}; 882 my $new = $_[1] ? 1 : 0; 883 if ( $old != $new) { 884 $_[0]-> {checked} = $new; 885 $_[0]-> repaint; 886 $_[0]-> notify( 'Check', $_[0]-> {checked}); 887 } 888} 889 890sub toggle { my $i = $_[0]-> checked; $_[0]-> checked( !$i); return !$i;} 891sub check { $_[0]-> checked(1)} 892sub uncheck { $_[0]-> checked(0)} 893 894my @static_image0_size; 895 896sub calc_geom_size 897{ 898 my $self = $_[0]; 899 my @sz = $self-> caption_box; 900 $sz[$_] += 12 for 0,1; 901 if ( $images[0]) { 902 @static_image0_size = $images[0]-> size 903 unless @static_image0_size; 904 $sz[0] += $static_image0_size[0] * 1.5 + 2; 905 $sz[1] = $static_image0_size[1] 906 if $sz[1] < $static_image0_size[1]; 907 } else { 908 my $s = $::application->uiScaling; 909 $sz[0] += 16 * 1.5 * $s; 910 $sz[1] = 16 * $s if $sz[1] < 16 * $s; 911 } 912 return @sz; 913} 914 915package Prima::CheckBox; 916use vars qw(@ISA); 917@ISA = qw(Prima::Cluster); 918 919sub profile_default 920{ 921 return { 922 %{$_[ 0]-> SUPER::profile_default}, 923 auto => 0, 924 widgetClass => wc::CheckBox, 925 } 926} 927 928sub on_paint 929{ 930 my ($self,$canvas) = @_; 931 my @clr; 932 if ( $self-> enabled) { 933 if ( $self-> focused) { 934 @clr = ($self-> hiliteColor, $self-> hiliteBackColor); 935 } else { 936 @clr = ($self-> color, $self-> backColor); 937 } 938 $clr[1] = $self-> prelight_color($clr[1]) if $self->{hilite} && $self-> enabled; 939 } else { 940 @clr = ($self-> disabledColor, $self-> disabledBackColor); 941 } 942 943 my @size = $canvas-> size; 944 unless ( $self-> transparent) { 945 $canvas-> color( $clr[ 1]); 946 $canvas-> bar( 0, 0, @size); 947 } 948 949 my ( $image, $imNo); 950 if ( $self-> { checked}) { 951 $imNo = $self-> { pressed} ? 3 : 2; 952 } else { 953 $imNo = $self-> { pressed} ? 1 : 0; 954 }; 955 my $xStart; 956 $image = $images[ $imNo]; 957 my @c3d = ( $self-> light3DColor, $self-> dark3DColor); 958 959 if ( $image) { 960 $canvas-> put_image( 0, ( $size[1] - $image-> height) / 2, $image); 961 $xStart = $image-> width; 962 } else { 963 my $s = $::application->uiScaling; 964 $xStart = $s * 16; 965 push ( @c3d, shift @c3d) 966 if $self-> { pressed}; 967 $canvas-> rect3d( 1, ( $size[1] - $s*14) / 2, $s*15, ( $size[1] + $s*14) / 2, 1, 968 @c3d, $clr[ 1]); 969 if ( $self-> { checked}) { 970 my $at = $self-> { pressed} ? 1 : 0; 971 $canvas-> color( cl::Black); 972 $canvas-> lineWidth( 2); 973 my $yStart = ( $size[1] - $s*14) / 2; 974 $canvas-> line( 975 $at + $s*4, $yStart - $at + $s*8, 976 $at + $s*5 , $yStart - $at + $s*3 977 ); 978 $canvas-> line( 979 $at + $s*5 , $yStart - $at + $s*3, 980 $at + $s*12, $yStart - $at + $s*12 981 ); 982 $canvas-> lineWidth( 0); 983 } 984 } 985 986 $canvas-> color( $clr[ 0]); 987 my ( $fw, $fh) = $self-> caption_box( $canvas); 988 $self-> draw_caption( $canvas, $xStart * 1.5, ( $size[1] - $fh) / 2 ); 989 990} 991 992package Prima::Radio; 993use vars qw(@ISA @images); 994@ISA = qw(Prima::Cluster); 995 996sub profile_default 997{ 998 my $def = $_[ 0]-> SUPER::profile_default; 999 @$def{qw(widgetClass)} = (wc::Radio, undef); 1000 return $def; 1001} 1002 1003sub on_paint 1004{ 1005 my ($self,$canvas) = @_; 1006 my @clr; 1007 if ( $self-> enabled) { 1008 if ( $self-> focused) { 1009 @clr = ($self-> hiliteColor, $self-> hiliteBackColor); 1010 } else { 1011 @clr = ($self-> color, $self-> backColor); 1012 } 1013 $clr[1] = $self-> prelight_color($clr[1]) if $self->{hilite} && $self-> enabled; 1014 } else { 1015 @clr = ($self-> disabledColor, $self-> disabledBackColor); 1016 } 1017 1018 my @size = $canvas-> size; 1019 unless ( $self-> transparent) { 1020 $canvas-> color( $clr[ 1]); 1021 $canvas-> bar( 0, 0, @size); 1022 } 1023 1024 my ( $image, $imNo); 1025 if ( $self-> { checked}) { 1026 $imNo = $self-> { pressed} ? 7 : 6; 1027 } else { 1028 $imNo = $self-> { pressed} ? 5 : 4; 1029 }; 1030 1031 my $xStart; 1032 $image = $images[ $imNo]; 1033 if ( $image) { 1034 $canvas-> put_image( 0, ( $size[1] - $image-> height) / 2, $image); 1035 $xStart = $image-> width; 1036 } else { 1037 my $s = $::application->uiScaling; 1038 $xStart = $s * 16; 1039 my $y = ( $size[1] - $s * 16) / 2; 1040 my @xs = map { $s * $_ } ( 0, 8, 16, 8); 1041 my @ys = map { $s * $_ } ( 8, 16, 8, 0); 1042 for ( @ys) {$_+=$y}; 1043 my $i; 1044 if ( $self-> { pressed}) { 1045 $canvas-> color( cl::Black); 1046 for ( $i = -1; $i < 3; $i++) { 1047 $canvas-> line( 1048 $xs[$i], $ys[$i], 1049 $xs[$i + 1], $ys[$i + 1] 1050 ) 1051 }; 1052 } else { 1053 my @clr = $self-> {checked} ? 1054 ( $self-> light3DColor, $self-> dark3DColor) : 1055 ( $self-> dark3DColor, $self-> light3DColor); 1056 $canvas-> color( $clr[1]); 1057 for ( $i = -1; $i < 1; $i++) { 1058 $canvas-> line( 1059 $xs[$i], $ys[$i], 1060 $xs[$i + 1],$ys[$i + 1] 1061 ) 1062 }; 1063 $canvas-> color( $clr[0]); 1064 for ( $i = 1; $i < 3; $i++) { 1065 $canvas-> line( 1066 $xs[$i], $ys[$i], 1067 $xs[$i + 1],$ys[$i + 1] 1068 ) 1069 }; 1070 } 1071 if ( $self-> checked) { 1072 $canvas-> color( cl::Black); 1073 $canvas-> fillpoly( [ $s*6, $y+$s*8, $s*8, $y+$s*10, $s*10, $y+$s*8, $s*8, $y+$s*6]); 1074 } 1075 } 1076 $canvas-> color( $clr[ 0]); 1077 my ( $fw, $fh) = $self-> caption_box( $canvas); 1078 $self-> draw_caption( $canvas, $xStart * 1.5, ( $size[1] - $fh) / 2 ); 1079} 1080 1081sub on_click 1082{ 1083 my $self = $_[0]; 1084 $self-> focus; 1085 $self-> checked( 1) unless $self-> checked; 1086} 1087 1088sub checked 1089{ 1090 return $_[0]-> {checked} unless $#_; 1091 my $self = $_[0]; 1092 my $chkOk = $self-> {checked}; 1093 1094 my $old = $self-> {checked} + 0; 1095 $self-> {checked} = $_[1] + 0; 1096 if ( $old != $_[1] + 0) { 1097 $self-> repaint; 1098 $chkOk = ( $self-> {checked} != $chkOk) && $self-> {checked}; 1099 my $owner = $self-> owner; 1100 $owner-> notify( 'RadioClick', $self) 1101 if $chkOk && exists $owner-> notification_types-> {RadioClick}; 1102 $self-> notify( 'Check', $self-> {checked}); 1103 } 1104} 1105 1106 1107package Prima::SpeedButton; 1108use vars qw(@ISA); 1109@ISA = qw(Prima::Button); 1110 1111sub profile_default 1112{ 1113 my $def = $_[ 0]-> SUPER::profile_default; 1114 my $s = $::application->uiScaling; 1115 @$def{qw(selectable width height text)} = (0, $s*36, $s*36, ""); 1116 return $def; 1117} 1118 1119sub calc_geom_size 1120{ 1121 my @sz = $_[0]-> std_calc_geom_size; 1122 my $s = $::application->uiScaling; 1123 $sz[0] = $s*36 if $sz[0] < $s*36; 1124 $sz[1] = $s*36 if $sz[1] < $s*36; 1125 return @sz; 1126} 1127 1128package Prima::GroupBox; 1129use vars qw(@ISA); 1130@ISA=qw(Prima::Widget); 1131 1132{ 1133my %RNT = ( 1134 %{Prima::Cluster-> notification_types()}, 1135 RadioClick => nt::Default, 1136); 1137 1138sub notification_types { return \%RNT; } 1139} 1140 1141 1142sub profile_default 1143{ 1144 return { 1145 %{$_[ 0]-> SUPER::profile_default}, 1146 ownerBackColor => 1, 1147 autoEnableChildren => 1, 1148 } 1149} 1150 1151sub on_radioclick 1152{ 1153 my ($me,$rd) = @_; 1154 for ($me-> widgets) { 1155 next if "$rd" eq "$_"; 1156 next unless $_-> isa(q(Prima::Radio)); 1157 $_-> checked(0); 1158 } 1159} 1160 1161sub on_paint 1162{ 1163 my ( $self, $canvas) = @_; 1164 my @size = $canvas-> size; 1165 my @clr = $self-> enabled ? 1166 ( $self-> color, $self-> backColor) : 1167 ( $self-> disabledColor, $self-> disabledBackColor); 1168 unless ( $self-> transparent) { 1169 $canvas-> color( $clr[1]); 1170 $canvas-> bar( 0, 0, @size); 1171 } 1172 my $fh = $canvas-> font-> height; 1173 $canvas-> color( $self-> light3DColor); 1174 $canvas-> rectangle( 1, 0, $size[0] - 1, $size[1] - $fh / 2 - 2); 1175 $canvas-> color( $self-> dark3DColor); 1176 $canvas-> rectangle( 0, 1, $size[0] - 2, $size[1] - $fh / 2 - 1); 1177 my $c = $self->text; 1178 if ( length( $c) > 0) { 1179 $c = $self-> text_shape($c, skip_if_simple => 1) || $c; 1180 $canvas-> color( $clr[1]); 1181 $canvas-> bar ( 1182 8, $size[1] - $fh - 1, 1183 16 + $canvas-> get_text_width( $c), $size[1] - 1 1184 ); 1185 $canvas-> color( $clr[0]); 1186 $canvas-> text_out( $c, 12, $size[1] - $fh - 1); 1187 } 1188} 1189 1190sub index 1191{ 1192 my $self = $_[0]; 1193 my @c = grep { $_-> isa(q(Prima::Radio))} $self-> widgets; 1194 if ( $#_) { 1195 my $i = $_[1]; 1196 $i = 0 if $i < 0; 1197 $i = $#c if $i > $#c; 1198 $c[$i]-> check if $c[$i]; 1199 } else { 1200 my $i; 1201 for ( $i = 0; $i < scalar @c; $i++) { 1202 return $i if $c[$i]-> checked; 1203 } 1204 return -1; 1205 } 1206} 1207 1208sub text 1209{ 1210 return $_[0]-> SUPER::text unless $#_; 1211 $_[0]-> SUPER::text($_[1]); 1212 $_[0]-> repaint; 1213} 1214 1215sub value 1216{ 1217 my $self = $_[0]; 1218 my @c = grep { $_-> isa(q(Prima::CheckBox))} $self-> widgets; 1219 my $i; 1220 if ( $#_) { 1221 my $value = $_[1]; 1222 for ( $i = 0; $i < scalar @c; $i++) { 1223 $c[$i]-> checked( $value & ( 1 << $i)); 1224 } 1225 } else { 1226 my $value = 0; 1227 for ( $i = 0; $i < scalar @c; $i++) { 1228 $value |= 1 << $i if $c[$i]-> checked; 1229 } 1230 return $value; 1231 } 1232} 1233 1234package Prima::RadioGroup; use vars qw(@ISA); @ISA=qw(Prima::GroupBox); 1235package Prima::CheckBoxGroup; use vars qw(@ISA); @ISA=qw(Prima::GroupBox); 1236 12371; 1238 1239=pod 1240 1241=head1 NAME 1242 1243Prima::Buttons - button widgets and grouping widgets. 1244 1245=head1 SYNOPSIS 1246 1247 use Prima qw(Application Buttons StdBitmap); 1248 1249 my $window = Prima::MainWindow-> create; 1250 Prima::Button-> new( 1251 owner => $window, 1252 text => 'Simple button', 1253 pack => {}, 1254 ); 1255 $window-> insert( 'Prima::SpeedButton' , 1256 pack => {}, 1257 image => Prima::StdBitmap::icon(0), 1258 ); 1259 1260 run Prima; 1261 1262=for podview <img src="buttons.gif"> 1263 1264=for html <p><img src="https://raw.githubusercontent.com/dk/Prima/master/pod/Prima/buttons.gif"> 1265 1266=head1 DESCRIPTION 1267 1268Prima::Buttons provides two separate sets of classes: 1269the button widgets and the grouping widgets. The button widgets 1270include push buttons, check-boxes and radio buttons. 1271The grouping widgets are designed for usage as containers for the 1272check-boxes and radio buttons, however, any widget can be inserted 1273in a grouping widget. 1274 1275The module provides the following classes: 1276 1277 *Prima::AbstractButton ( derived from Prima::Widget and Prima::MouseScroller ) 1278 Prima::Button 1279 Prima::SpeedButton 1280 *Prima::Cluster 1281 Prima::CheckBox 1282 Prima::Radio 1283 Prima::GroupBox ( derived from Prima::Widget ) 1284 Prima::RadioGroup ( obsolete ) 1285 Prima::CheckBoxGroup ( obsolete ) 1286 1287Note: C<*> - marked classes are abstract. 1288 1289=head1 USAGE 1290 1291 use Prima::Buttons; 1292 1293 my $button = $widget-> insert( 'Prima::Button', 1294 text => 'Push button', 1295 onClick => sub { print "hey!\n" }, 1296 ); 1297 $button-> flat(1); 1298 1299 my $group = $widget-> insert( 'Prima::GroupBox', 1300 onRadioClick => sub { print $_[1]-> text, "\n"; } 1301 ); 1302 $group-> insert( 'Prima::Radio', text => 'Selection 1'); 1303 $group-> insert( 'Prima::Radio', text => 'Selection 2', pressed => 1); 1304 $group-> index(0); 1305 1306=head1 Prima::AbstractButton 1307 1308Prima::AbstractButton realizes common functionality of buttons. 1309It provides reaction on mouse and keyboard events, and calls 1310L<Click> notification when the user activates the button. The 1311mouse activation is performed either by mouse double click or 1312successive mouse down and mouse up events within the button 1313boundaries. The keyboard activation is performed on the following conditions: 1314 1315=over 1316 1317=item * 1318 1319The spacebar key is pressed 1320 1321=item * 1322 1323C<{default}> ( see L<default> property ) boolean variable is 1324set and enter key is pressed. This condition holds even if the button is out of focus. 1325 1326=item * 1327 1328C<{accel}> character variable is assigned and the corresponding character key 1329is pressed. C<{accel}> variable is extracted automatically from the text string 1330passed to L<text> property. 1331This condition holds even if the button is out of focus. 1332 1333=back 1334 1335=head2 Events 1336 1337=over 1338 1339=item Check 1340 1341Abstract callback event. 1342 1343=item Click 1344 1345Called whenever the user presses the button. 1346 1347=back 1348 1349=head2 Properties 1350 1351=over 1352 1353=item hotKey CHAR 1354 1355A key that the button will react to if pressed, even when out of focus. 1356 1357=item pressed BOOLEAN 1358 1359Represents the state of button widget, whether it is pressed or not. 1360 1361Default value: 0 1362 1363=item text STRING 1364 1365The text that is drawn in the button. If STRING contains ~ ( tilde ) character, 1366the following character is treated as a hot key, and the character is 1367underlined. If the user presses the corresponding character key then 1368L<Click> event is called. This is true even when the button is out of focus. 1369 1370=back 1371 1372=head2 Methods 1373 1374=over 1375 1376=item draw_veil CANVAS, X1, Y1, X2, Y2 1377 1378Draws a rectangular veil shape over CANVAS in given boundaries. 1379This is the default method of drawing the button in the disabled state. 1380 1381=item draw_caption CANVAS, X, Y 1382 1383Draws single line of text, stored in L<text> property on CANVAS at X, Y 1384coordinates. Performs underlining of eventual tilde-escaped character, and 1385draws the text with dimmed colors if the button is disabled. If the button 1386is focused, draws a dotted line around the text. 1387 1388=item caption_box [ CANVAS = self ] 1389 1390Calculates geometrical extensions of text string, stored in L<text> property in pixels. 1391Returns two integers, the width and the height of the string for the font selected on CANVAS. 1392If CANVAS is undefined, the widget itself is used as a graphic device. 1393 1394=back 1395 1396=head1 Prima::Button 1397 1398A push button class, that extends Prima::AbstractButton functionality by allowing 1399an image to be drawn together with the text. 1400 1401=head2 Properties 1402 1403=over 1404 1405=item autoHeight BOOLEAN 1406 1407If 1, the button height is automatically changed as text extensions 1408change. 1409 1410Default value: 1 1411 1412=item autoRepeat BOOLEAN 1413 1414If set, the button behaves like a keyboard button - after the first 1415L<Click> event, a timeout is set, after which is expired and the button 1416still pressed, L<Click> event is repeatedly called until the button is 1417released. Useful for emulating the marginal scroll-bar buttons. 1418 1419Default value: 0 1420 1421 1422=item autoWidth BOOLEAN 1423 1424If 1, the button width is automatically changed as text extensions 1425change. 1426 1427Default value: 1 1428 1429 1430=item borderWidth INTEGER 1431 1432Width of 3d-shade border around the button. 1433 1434Default value: 2 1435 1436=item checkable BOOLEAN 1437 1438Selects if the button toggles L<checked> state when the user 1439presses it. 1440 1441Default value: 0 1442 1443=item checked BOOLEAN 1444 1445Selects whether the button is checked or not. Only actual 1446when L<checkable> property is set. See also L<holdGlyph>. 1447 1448Default value: 0 1449 1450=item default BOOLEAN 1451 1452Defines if the button should react when the user presses the enter button. 1453If set, the button is drawn with the black border, indicating that it executes 1454the 'default' action. Useful for OK-buttons in dialogs. 1455 1456Default value: 0 1457 1458=item defaultGlyph INTEGER | IMAGE | METAFILE 1459 1460Selects index of the default sub-image. 1461 1462Default value: 0 1463 1464=item disabledGlyph INTEGER | IMAGE | METAFILE 1465 1466Selects index of the sub-image for the disabled button state. 1467If C<image> does not contain such sub-image, the C<defaultGlyph> 1468sub-image is drawn, and is dimmed over with L<draw_veil> method. 1469 1470Default value: 1 1471 1472=item flat BOOLEAN 1473 1474Selects special 'flat' mode, when a button is painted without 1475a border when the mouse pointer is outside the button boundaries. 1476This mode is useful for the toolbar buttons. See also L<hiliteGlyph>. 1477 1478Default value: 0 1479 1480=item glyphs INTEGER 1481 1482If a button is to be drawn with the image, it can be passed in the L<image> 1483property. If, however, the button must be drawn with several different images, 1484there are no several image-holding properties. Instead, the L<image> object 1485can be logically split vertically into several equal sub-images. This allows 1486the button resource to contain all button states into one image file. 1487The C<glyphs> property assigns how many such sub-images the image object contains. 1488 1489The sub-image indices can be assigned for rendition of the different states. 1490These indices are selected by the following integer properties: L<defaultGlyph>, 1491L<hiliteGlyph>, L<disabledGlyph>, L<pressedGlyph>, L<holdGlyph>. 1492 1493Default value: 1 1494 1495=item hiliteGlyph INTEGE | IMAGE | METAFILER 1496 1497Selects index of the sub-image for the state when the mouse pointer is 1498over the button. This image is used only when L<flat> property is set. 1499If C<image> does not contain such sub-image, the C<defaultGlyph> sub-image is drawn. 1500 1501Default value: 0 1502 1503=item holdGlyph INTEGE | IMAGE | METAFILER 1504 1505Selects index of the sub-image for the state when the button is L<checked>. 1506This image is used only when L<checkable> property is set. 1507If C<image> does not contain such sub-image, the C<defaultGlyph> sub-image is drawn. 1508 1509Default value: 3 1510 1511=item image OBJECT 1512 1513If set, the image object is drawn next with the button text, over or left to it 1514( see L<vertical> property ). If OBJECT contains several sub-images, then the 1515corresponding sub-image is drawn for each button state. See L<glyphs> property. 1516 1517Can also be a C<Prima::Drawable::Metafile> object, however, C<imageScale> factor 1518wouldn't work on it. 1519 1520Default value: undef 1521 1522=item imageFile FILENAME 1523 1524Alternative to image selection by loading an image from the file. 1525During the creation state, if set together with L<image> property, is superseded 1526by the latter. 1527 1528To allow easy multiframe image access, FILENAME string is checked if it contains 1529a number after a colon in the string end. Such, C<imageFile('image.gif:3')> call 1530would load the fourth frame in C<image.gif> file. 1531 1532=item imageScale SCALE 1533 1534Contains zoom factor for the L<image>. 1535 1536Default value: 1 1537 1538=item modalResult INTEGER 1539 1540Contains a custom integer value, preferably one of C<mb::XXX> constants. 1541If a button with non-zero C<modalResult> is owned by a currently executing 1542modal window, and is pressed, its C<modalResult> value is copied to the C<modalResult> 1543property of the owner window, and the latter is closed. 1544This scheme is helpful for the dialog design: 1545 1546 $dialog-> insert( 'Prima::Button', modalResult => mb::OK, 1547 text => '~Ok', default => 1); 1548 $dialog-> insert( 'Prima::Button', modalResult => mb::Cancel, 1549 text => 'Cancel); 1550 return if $dialog-> execute != mb::OK. 1551 1552The toolkit defines the following constants for C<modalResult> use: 1553 1554 mb::OK or mb::Ok 1555 mb::Cancel 1556 mb::Yes 1557 mb::No 1558 mb::Abort 1559 mb::Retry 1560 mb::Ignore 1561 mb::Help 1562 1563However, any other integer value can be safely used. 1564 1565Default value: 0 1566 1567=item smoothScaling BOOL 1568 1569Tries to represent the image as smooth as possible. When the system doesn't support ARGB layering, 1570icon objects smooth scaling will be restricted to integer-scaling only (i.e. 2x, 3x etc) because 1571smooth color plane will not match pixelated mask plane, and because box-scaling 1572with non-integer zooms looks ugly. 1573 1574Default value: true 1575 1576See also: L<Prima::Image/ui_scale> . 1577 1578=item pressedGlyph INTEGER | IMAGE | METAFILE 1579 1580Selects index of the sub-image for the pressed state of the button. 1581If C<image> does not contain such sub-image, the C<defaultGlyph> sub-image is drawn. 1582 1583=item transparent BOOLEAN 1584 1585See L<Prima::Widget/transparent>. If set, the background is not painted. 1586 1587=item vertical BOOLEAN 1588 1589Determines the position of image next to the text string. If 1, 1590the image is drawn above the text; left to the text if 0. 1591In a special case when L<text> is an empty string, image is centered. 1592 1593=back 1594 1595=head1 Prima::SpeedButton 1596 1597A convenience class, same as L<Prima::Button> but with default 1598square shape and text property set to an empty string. 1599 1600=head1 Prima::Cluster 1601 1602An abstract class with common functionality of L<Prima::CheckBox> and 1603L<Prima::RadioButton>. Reassigns default actions on tab and back-tab keys, so 1604the sibling cluster widgets are not selected. Has C<ownerBackColor> property 1605set to 1, to prevent usage of background color from C<wc::Button> palette. 1606 1607=head2 Properties 1608 1609=over 1610 1611=item auto BOOLEAN 1612 1613If set, the button is automatically checked when the button is in focus. This 1614functionality allows arrow key walking by the radio buttons without pressing 1615spacebar key. It is also has a drawback, that if a radio button gets focused 1616without user intervention, or indirectly, it also gets checked, so that behavior 1617might cause confusion. The said can be exemplified when an unchecked radio button 1618in a notebook widget gets active by turning the notebook page. 1619 1620Although this property is present on the L<Prima::CheckBox>, it is not used in there. 1621 1622=back 1623 1624=head2 Methods 1625 1626=over 1627 1628=item check 1629 1630Alias to C<checked(1)> 1631 1632=item uncheck 1633 1634Alias to C<checked(0)> 1635 1636=item toggle 1637 1638Reverts the C<checked> state of the button and returns the new state. 1639 1640=back 1641 1642=head1 Prima::Radio 1643 1644Represents a standard radio button, that can be either in checked, or in unchecked state. 1645When checked, delivers L<RadioClick> event to the owner ( if the latter provides one ). 1646 1647The button uses the standard toolkit images with C<sbmp::RadioXXX> indices. 1648If the images can not be loaded, the button is drawn with the graphic primitives. 1649 1650=head2 Events 1651 1652=over 1653 1654=item Check 1655 1656Called when a button is checked. 1657 1658=back 1659 1660=head1 Prima::CheckBox 1661 1662Represents a standard check box button, that can be either in checked, or in unchecked state. 1663 1664The button uses the standard toolkit images with C<sbmp::CheckBoxXXX> indices. 1665If the images can not be loaded, the button is drawn with graphic primitives. 1666 1667=head1 Prima::GroupBox 1668 1669The class to be used as a container of radio and check-box buttons. 1670It can, however, contain any other widgets. 1671 1672The widget draws a 3d-shaded box on its boundaries and a text string in its 1673upper left corner. Uses C<transparent> property to determine if it needs to 1674paint its background. 1675 1676The class does not provide a method to calculate the extension of the inner rectangle. 1677However, it can be safely assumed that all offsets except the upper are 5 pixels. 1678The upper offset is dependent on a font, and constitutes the half of the font height. 1679 1680=head2 Events 1681 1682=over 1683 1684=item RadioClick BUTTON 1685 1686Called whenever one of children radio buttons is checked. BUTTON 1687parameter contains the newly checked button. 1688 1689The default action of the class is that all checked buttons, 1690except BUTTON, are unchecked. Since the flow type of C<RadioClick> event 1691is C<nt::PrivateFirst>, C<on_radioclick> method must be directly overloaded 1692to disable this functionality. 1693 1694=back 1695 1696=head2 Properties 1697 1698=over 1699 1700=item index INTEGER 1701 1702Checks the child radio button with C<index>. The indexing is 1703based on the index in the widget list, returned by C<Prima::Widget::widgets> method. 1704 1705=item value BITFIELD 1706 1707BITFIELD is an unsigned integer, where each bit corresponds to the 1708C<checked> state of a child check-box button. The indexing is 1709based on the index in the widget list, returned by C<Prima::Widget::widgets> method. 1710 1711=back 1712 1713=head1 Prima::RadioGroup 1714 1715This class is obsolete and is same as C<Prima::GroupBox>. 1716 1717=head1 Prima::CheckBoxGroup 1718 1719This class is obsolete and is same as C<Prima::GroupBox>. 1720 1721=head1 BUGS 1722 1723The push button is not capable of drawing anything other than single line of text and 1724single image. If an extended functionality is needed, instead of fully rewriting 1725the painting procedure, it might be reasonable to overload C<put_image_indirect> 1726method of C<Prima::Button>, and perform custom output there. 1727 1728Tilde escaping in C<text> is not realized, but is planned to. There currently is no way 1729to avoid tilde underscoring. 1730 1731Radio buttons can get unexpectedly checked when used in notebooks. See L<auto>. 1732 1733C<Prima::GroupBox::value> parameter is an integer, which size is architecture-dependent. 1734Shift towards a vector is considered a good idea. 1735 1736=head1 AUTHOR 1737 1738Dmitry Karasik, E<lt>dmitry@karasik.eu.orgE<gt>. 1739 1740=head1 SEE ALSO 1741 1742L<Prima>, L<Prima::Widget>, L<Prima::Window>, L<Prima::IntUtils>, 1743L<Prima::Drawable::Metafile>, 1744L<Prima::StdBitmap>, F<examples/buttons.pl>, F<examples/buttons2.pl>. 1745 1746=cut 1747