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