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