1# File: canvist.tcl
2
3# Purpose: a 1-dimension list using a canvas
4
5#
6# Copyright (c) 1997-2001 Tim Baker
7#
8# This software may be copied and distributed for educational, research, and
9# not for profit purposes provided that this copyright and statement are
10# included in all such copies.
11#
12
13namespace eval NSCanvist {
14
15	variable Priv
16
17	set Priv(scan,afterId) {}
18	set Priv(canvistPrev) -1
19
20# namespace eval NSCanvist
21}
22
23# NSCanvist::NSCanvist --
24#
25#	Object constructor called by NSObject::New.
26#
27# Arguments:
28#	arg1					about arg1
29#
30# Results:
31#	What happened.
32
33proc NSCanvist::NSCanvist {oop parent rowHgt wid hgt newRowCmd highlightCmd} {
34
35	global NSCanvist
36	variable Priv
37
38	set c $parent.canvist$oop
39    canvas $c \
40        -scrollregion [list 0 0 $wid 0] -width $wid -height $hgt \
41        -relief flat -background white -highlightthickness 0 \
42        -yscrollincrement $rowHgt -takefocus 1
43
44	#
45	# Do stuff when the canvas is clicked
46	#
47
48	bind $c <ButtonPress-1> "NSCanvist::Button1 $oop %x %y 0"
49	bind $c <Button1-Motion> "NSCanvist::Motion1 $oop %x %y"
50	bind $c <Double-ButtonPress-1> "NSCanvist::Double1 $oop %x %y"
51	bind $c <ButtonRelease-1> "NSCanvist::Release1 $oop %x %y"
52	bind $c <Button1-Leave> "NSCanvist::Leave1 $oop %x %y"
53	bind $c <Button1-Enter> "NSCanvist::CancelRepeat $oop"
54
55	# KeyPress bindings
56	bind $c <KeyPress-Home> "$c yview moveto 0 ; break"
57	bind $c <KeyPress-End> "$c yview moveto 1 ; break"
58	bind $c <KeyPress-Prior> "$c yview scroll -1 pages ; break"
59	bind $c <KeyPress-Next> "$c yview scroll 1 pages ; break"
60	bind $c <KeyPress-Up> "NSCanvist::UpDown $oop -1 ; break"
61	bind $c <KeyPress-Down> "NSCanvist::UpDown $oop +1 ; break"
62
63	#
64	# The Control key toggles selected rows.
65	#
66
67	bind $c <Control-ButtonPress-1> "NSCanvist::Button1 $oop %x %y 1"
68
69	# Destroy the object along with the canvas (later)
70	NSUtils::DestroyObjectWithWidget NSCanvist $oop $c
71
72	# Allows client to draw selection depending on focus
73	bindtags $c [concat [bindtags $c] NSCanvistBindTag$oop]
74	bind NSCanvistBindTag$oop <FocusIn> \
75		"NSCanvist::Activate $oop 1"
76	bind NSCanvistBindTag$oop <FocusOut> \
77		"NSCanvist::Activate $oop 0"
78
79	set Priv(stroke) 0
80
81	set NSCanvist($oop,canvas) $c
82	set NSCanvist($oop,rowHgt) $rowHgt
83	set NSCanvist($oop,newRowCmd) $newRowCmd
84	set NSCanvist($oop,highlightCmd) $highlightCmd
85	set NSCanvist($oop,invokeCmd) {}
86	set NSCanvist($oop,selectionCmd) {}
87	set NSCanvist($oop,count) 0
88	set NSCanvist($oop,nextRowTag) 0
89	set NSCanvist($oop,rowTags) {}
90	set NSCanvist($oop,selection) {}
91	set NSCanvist($oop,rowsEnabled) 1
92	set NSCanvist($oop,nearest) 0
93	set NSCanvist($oop,stroke) 0
94	set NSCanvist($oop,trackIgnore) 0
95	set NSCanvist($oop,clickCmd) {}
96
97	# Total hack -- PDjam module uses Drag & Drop
98	set NSCanvist($oop,dragSpecial) 0
99
100	return
101}
102
103# NSCanvist::~NSCanvist --
104#
105#	Object destructor called by NSObject::Delete.
106#
107# Arguments:
108#	arg1					about arg1
109#
110# Results:
111#	What happened.
112
113proc NSCanvist::~NSCanvist {oop} {
114
115	return
116}
117
118# NSCanvist::Info --
119#
120#	Query and modify info.
121#
122# Arguments:
123#	arg1					about arg1
124#
125# Results:
126#	What happened.
127
128proc NSCanvist::Info {oop info args} {
129
130	global NSCanvist
131
132	# Verify the object
133	NSObject::CheckObject NSCanvist $oop
134
135	# Set info
136	if {[llength $args]} {
137		switch -- $info {
138			default {
139				set NSCanvist($oop,$info) [lindex $args 0]
140			}
141		}
142
143	# Get info
144	} else {
145		switch -- $info {
146			default {
147				return $NSCanvist($oop,$info)
148			}
149		}
150	}
151
152	return
153}
154
155# NSCanvist::Insert --
156#
157#	Insert a row at the given index.
158#
159# Arguments:
160#	arg1					about arg1
161#
162# Results:
163#	What happened.
164
165proc NSCanvist::Insert {oop index args} {
166
167	global NSCanvist
168
169	set canvas $NSCanvist($oop,canvas)
170	set count $NSCanvist($oop,count)
171	set rowHgt $NSCanvist($oop,rowHgt)
172
173	if {$index == "end"} {set index $count}
174	if {$index < 0} {set index 0}
175	if {$index > $count} {set index $count}
176
177	set y [expr {$rowHgt * $index}]
178
179	#
180	# Move following rows down by one.
181	#
182
183	if {$index < $count} {
184		foreach rowTag [lrange $NSCanvist($oop,rowTags) $index end] {
185			$canvas move $rowTag 0 $rowHgt
186		}
187	}
188
189	#
190	# The newRowCmd returns a list of all items added that are
191	# on the new row. They get tagged with a common "group tag"
192	# of the form ":N" where N is some integer.
193	#
194
195	set itemIdList [uplevel #0 $NSCanvist($oop,newRowCmd) $oop $y $args]
196	set rowTag ":$NSCanvist($oop,nextRowTag)"
197	foreach itemId $itemIdList {
198		$canvas addtag $rowTag withtag $itemId
199	}
200
201	# Insert
202	if {$index < $count} {
203
204		# Remember the tag applied to all items on this row
205		set NSCanvist($oop,rowTags) \
206			[linsert $NSCanvist($oop,rowTags) $index $rowTag]
207
208		# This row is not selected
209		set NSCanvist($oop,selection) \
210			[linsert $NSCanvist($oop,selection) $index 0]
211
212	# Append
213	} else {
214		lappend NSCanvist($oop,rowTags) $rowTag
215		lappend NSCanvist($oop,selection) 0
216	}
217
218	incr NSCanvist($oop,count)
219	incr NSCanvist($oop,nextRowTag)
220	Synch $oop
221
222	return
223}
224
225# NSCanvist::InsertMany --
226#
227#	Insert multiple rows at the given index.
228#
229# Arguments:
230#	arg1					about arg1
231#
232# Results:
233#	What happened.
234
235proc NSCanvist::InsertMany {oop index itemList} {
236
237	global NSCanvist
238
239	set canvas $NSCanvist($oop,canvas)
240	set count $NSCanvist($oop,count)
241	set rowHgt $NSCanvist($oop,rowHgt)
242
243	set itemListCount [llength $itemList]
244	if {!$itemListCount} return
245
246	if {$index == "end"} {set index $count}
247	if {$index < 0} {set index 0}
248	if {$index > $count} {set index $count}
249
250	set y [expr {$rowHgt * $index}]
251
252	#
253	# Move following rows down.
254	#
255
256	if {$index < $count} {
257		set offset [expr {$itemListCount * $rowHgt}]
258		foreach rowTag [lrange $NSCanvist($oop,rowTags) $index end] {
259			$canvas move $rowTag 0 $offset
260		}
261	}
262
263	set newRowTag {}
264	set newSelected {}
265
266	foreach item $itemList {
267
268		#
269		# The newRowCmd returns a list of all items added that are
270		# on the new row. They get tagged with a common "group tag"
271		# of the form ":N" where N is some integer.
272		#
273
274		set itemIdList [uplevel #0 $NSCanvist($oop,newRowCmd) $oop $y $item]
275		set rowTag ":$NSCanvist($oop,nextRowTag)"
276		foreach itemId $itemIdList {
277			$canvas addtag $rowTag withtag $itemId
278		}
279
280		# Remember the tag applied to all items on this row
281		lappend newRowTag $rowTag
282
283		# This row is not selected
284		lappend newSelected 0
285
286		incr NSCanvist($oop,nextRowTag)
287
288		incr y $rowHgt
289	}
290
291	if {$index < $count} {
292		set NSCanvist($oop,rowTags) \
293			[eval linsert [list $NSCanvist($oop,rowTags)] $index $newRowTag]
294		set NSCanvist($oop,selection) \
295			[eval linsert [list $NSCanvist($oop,selection)] $index $newSelected]
296	} else {
297		eval lappend NSCanvist($oop,rowTags) $newRowTag
298		eval lappend NSCanvist($oop,selection) $newSelected
299	}
300
301	incr NSCanvist($oop,count) $itemListCount
302#	incr NSCanvist($oop,nextRowTag) $count
303
304	Synch $oop
305
306	return
307}
308
309# NSCanvist::Delete --
310#
311#	Delete one or more rows from the list.
312#
313# Arguments:
314#	arg1					about arg1
315#
316# Results:
317#	What happened.
318
319proc NSCanvist::Delete {oop index1 index2} {
320
321	global NSCanvist
322
323	set canvas $NSCanvist($oop,canvas)
324	set count $NSCanvist($oop,count)
325	set rowHgt $NSCanvist($oop,rowHgt)
326
327	# Nothing to delete
328	if {$count == 0} return
329
330	if {$index1 >= $count} {set index1 [expr {$count - 1}]}
331	if {$index1 < 0} {set index1 0}
332	if {$index2 == "end"} {set index2 $count}
333	if {$index2 >= $count} {set index2 [expr {$count - 1}]}
334	if {$index2 < 0} {set index2 0}
335
336	set num [expr {$index2 - $index1 + 1}]
337	if {!$num} return
338
339	# Call client's selectionCmd if given
340	set command $NSCanvist($oop,selectionCmd)
341	if {[string length $command]} {
342		set deselect {}
343		for {set row $index1} {$row <= $index2} {incr row} {
344			if {[IsRowSelected $oop $row]} {
345				set NSCanvist($oop,selection) \
346					[lreplace $NSCanvist($oop,selection) $row $row 0]
347				lappend deselect $row
348			}
349		}
350		if {[llength $deselect]} {
351			uplevel #0 $command $oop [list {} $deselect]
352		}
353	}
354
355	#
356	# Delete all canvas items on each deleted row
357	#
358
359	foreach rowTag [lrange $NSCanvist($oop,rowTags) $index1 $index2] {
360		$canvas delete $rowTag
361	}
362
363	#
364	# Move following rows up.
365	#
366
367	incr index2
368	foreach rowTag [lrange $NSCanvist($oop,rowTags) $index2 end] {
369		$canvas move $rowTag 0 -[expr {$rowHgt * $num}]
370	}
371
372	# Delete row tags from list of row tags for deleted rows.
373	incr index2 -1
374	set NSCanvist($oop,rowTags) [lreplace $NSCanvist($oop,rowTags) $index1 $index2]
375
376	# Delete selection info for deleted rows
377	set NSCanvist($oop,selection) \
378		[lreplace $NSCanvist($oop,selection) $index1 $index2]
379
380	# Debug
381	if {$num > $count} {
382		NSUtils::ProgError "NSCanvist::Delete: $num > $count"
383		set $num $count
384	}
385
386	incr NSCanvist($oop,count) -$num
387
388	Synch $oop
389
390	return
391}
392
393# NSCanvist::DeleteAll --
394#
395#	Delete all the rows.
396#
397# Arguments:
398#	arg1					about arg1
399#
400# Results:
401#	What happened.
402
403proc NSCanvist::DeleteAll {oop} {
404
405	global NSCanvist
406
407	set canvas $NSCanvist($oop,canvas)
408
409	# Call client's selectionCmd if given
410	set command $NSCanvist($oop,selectionCmd)
411	if {[string length $command]} {
412		set selection [Selection $oop]
413		if {[llength $selection]} {
414			set NSCanvist($oop,selection) {}
415			uplevel #0 $command $oop [list {} $selection]
416		}
417	}
418
419	# Bye-bye, suckers!
420	$canvas delete all
421
422	set NSCanvist($oop,count) 0
423	set NSCanvist($oop,rowTags) {}
424	set NSCanvist($oop,selection) {}
425
426	Synch $oop
427
428	return
429}
430
431# NSCanvist::_GetRowTag --
432#
433#	Get the tag common to all items on a row containing the given
434#	item.
435#
436# Arguments:
437#	arg1					about arg1
438#
439# Results:
440#	What happened.
441
442proc NSCanvist::_GetRowTag {oop tagOrId} {
443
444	global NSCanvist
445
446	set canvas $NSCanvist($oop,canvas)
447
448	# Get list of tags for item
449	set tagList [$canvas gettags $tagOrId]
450
451	# Items without enabled tag are considered "disabled"
452	if {[lsearch $tagList "enabled"] == -1} {return {}}
453
454	# Search list of tags for grouping tag (eg ":1", ":2" etc)
455	set idx [lsearch $tagList ":*"]
456
457	return [lindex $tagList $idx]
458}
459
460# NSCanvist::RemoveSelection --
461#
462#	Remove the selection from all rows.
463#
464# Arguments:
465#	arg1					about arg1
466#
467# Results:
468#	What happened.
469
470proc NSCanvist::RemoveSelection {oop} {
471
472if 1 {
473	UpdateSelection $oop {} all
474} else {
475	global NSCanvist
476
477	set row 0
478	foreach state $NSCanvist($oop,selection) {
479		if {[IsRowSelected $oop $row]} {
480			DeselectRow $oop $row
481		}
482		incr row
483	}
484}
485	return
486}
487
488# NSCanvist::SelectRow --
489#
490#	Select the given row.
491#
492# Arguments:
493#	arg1					about arg1
494#
495# Results:
496#	What happened.
497
498proc NSCanvist::SelectRow {oop row} {
499
500	global NSCanvist
501
502	# Get the widget command
503	set canvas $NSCanvist($oop,canvas)
504
505	set rowTag [lindex $NSCanvist($oop,rowTags) $row]
506	set itemIdList [$canvas find withtag $rowTag]
507
508	# Call user's command to highlight this row
509	uplevel #0 $NSCanvist($oop,highlightCmd) $oop 1 $itemIdList
510
511	# Mark the row as selected
512	set NSCanvist($oop,selection) \
513		[lreplace $NSCanvist($oop,selection) $row $row 1]
514
515	return
516}
517
518# NSCanvist::DeselectRow --
519#
520#	Deselect the given row.
521#
522# Arguments:
523#	arg1					about arg1
524#
525# Results:
526#	What happened.
527
528proc NSCanvist::DeselectRow {oop row} {
529
530	global NSCanvist
531
532	# Get the widget command
533	set canvas $NSCanvist($oop,canvas)
534
535	# Get list of items on this row
536	set rowTag [lindex $NSCanvist($oop,rowTags) $row]
537	set itemIdList [$canvas find withtag $rowTag]
538
539	# Mark the row as un-selected
540	set NSCanvist($oop,selection) \
541		[lreplace $NSCanvist($oop,selection) $row $row 0]
542
543	# Call user's command to un-highlight this row
544	uplevel #0 $NSCanvist($oop,highlightCmd) $oop 0 $itemIdList
545
546	return
547}
548
549# NSCanvist::IsRowSelected --
550#
551#	Is a given row selected?
552#
553# Arguments:
554#	arg1					about arg1
555#
556# Results:
557#	What happened.
558
559proc NSCanvist::IsRowSelected {oop row} {
560
561	global NSCanvist
562
563	set count [expr {$NSCanvist($oop,count) - 1}]
564	if {($row < 0) || ($row > $count)} {
565		error "bad row \"$row\": must be from 0 to $count"
566	}
567	return [lindex $NSCanvist($oop,selection) $row]
568}
569
570# NSCanvist::UpdateSelection --
571#
572#	Select and deselect some rows.
573#	When the selection changes, call client's routine (if any).
574#
575# Arguments:
576#	arg1					about arg1
577#
578# Results:
579#	What happened.
580
581proc NSCanvist::UpdateSelection {oop selected deselected} {
582
583	global NSCanvist
584
585	# "Selected" takes precedence over "deselected"
586
587	set doneRows {}
588
589	if {([llength $selected] == 1) && ($selected == "all")} {
590		set selected {}
591		set count [Info $oop count]
592		for {set row 0} {$row < $count} {incr row} {
593			lappend selected $row
594		}
595	}
596	if {([llength $deselected] == 1) && ($deselected == "all")} {
597		set deselected [Selection $oop]
598	}
599
600	set newlySelected {}
601	foreach row $selected {
602		if {[lsearch -exact $doneRows $row] >= 0} continue
603		lappend doneRows $row
604		if {[IsRowSelected $oop $row]} continue
605		lappend newlySelected $row
606	}
607
608	set newlyDeselected {}
609	foreach row $deselected {
610		if {[lsearch -exact $doneRows $row] >= 0} continue
611		lappend doneRows $row
612		if {![IsRowSelected $oop $row]} continue
613		lappend newlyDeselected $row
614	}
615
616	if {[llength $newlySelected] || [llength $newlyDeselected]} {
617
618		lsort -integer $newlySelected
619		lsort -integer $newlyDeselected
620
621		# Select rows
622		foreach row $newlySelected {
623			SelectRow $oop $row
624		}
625
626		# Deselect rows
627		foreach row $newlyDeselected {
628			DeselectRow $oop $row
629		}
630
631		# Call client's selectionCmd if given
632		set command $NSCanvist($oop,selectionCmd)
633		if {[string length $command]} {
634			uplevel #0 $command $oop [list $newlySelected $newlyDeselected]
635		}
636	}
637
638	return
639}
640
641# NSCanvist::Selection --
642#
643#	Return a list of row indexes of all currently selected rows.
644#
645# Arguments:
646#	arg1					about arg1
647#
648# Results:
649#	Returns list of indexes or empty list if no rows are
650#	selected.
651
652proc NSCanvist::Selection {oop} {
653
654	global NSCanvist
655
656	set selection {}
657	set row 0
658	foreach state $NSCanvist($oop,selection) {
659		if {$state} {
660			lappend selection $row
661		}
662		incr row
663	}
664
665	return $selection
666}
667
668# NSCanvist::Button1 --
669#
670#	Handle ButtonPress-1 event.
671#
672# Arguments:
673#	arg1					about arg1
674#
675# Results:
676#	What happened.
677
678proc NSCanvist::Button1 {oop x y extend} {
679
680	global NSCanvist
681	variable Priv
682
683	set c $NSCanvist($oop,canvas)
684
685	# Claim the input focus
686	focus $c
687
688	# Get the hit row.
689	set row [PointToRow $oop $x $y]
690
691	# List rows to select/deselect
692	set select {}
693	set deselect {}
694
695	set callClickCmd 0
696
697	# No item was hit
698	if {$row == -1} {
699
700		# Unselect all rows if not extending selection.
701		if {!$extend} {
702			set deselect all
703		}
704
705		# Prepare for drag
706		if {[Info $oop stroke]} {
707			itemMark $c $x $y
708		}
709
710		# Remember no cell was hit
711		set Priv(canvistPrev) -1
712
713	# An item was hit
714	} else {
715
716		# The row is currently selected
717		if {[IsRowSelected $oop $row]} {
718
719			# Control-click toggles selection
720			if {$extend} {
721				set deselect $row
722			} else {
723				set deselect all
724				set select $row
725				set callClickCmd 1
726			}
727
728
729		# Row was not selected
730		} else {
731
732			# Unselect all rows if not extending selection.
733			if {!$extend} {
734				set deselect all
735			}
736
737			# Select the hit row
738			set select $row
739		}
740
741		# Remember the current row
742		set Priv(canvistPrev) $row
743	}
744
745	# Update the selection
746	UpdateSelection $oop $select $deselect
747
748	if {$callClickCmd} {
749		set command [Info $oop clickCmd]
750		if {[string length $command]} {
751			uplevel #0 $command $oop $row
752		}
753	}
754
755	return
756}
757
758# NSCanvist::Release1 --
759#
760#	.
761#
762# Arguments:
763#	arg1					about arg1
764#
765# Results:
766#	What happened.
767
768proc NSCanvist::Release1 {oop x y} {
769
770	global NSCanvist
771	variable Priv
772
773	set canvas $NSCanvist($oop,canvas)
774
775	itemSelect $oop
776	set Priv(stroke) 0
777	$canvas delete area
778
779	CancelRepeat $oop
780
781	return
782}
783
784# NSCanvist::Motion1 --
785#
786#	.
787#
788# Arguments:
789#	arg1					about arg1
790#
791# Results:
792#	What happened.
793
794proc NSCanvist::Motion1 {oop x y} {
795
796	variable Priv
797
798	set canvas [Info $oop canvas]
799
800	# Don't track while UpdateSelection() is in progress
801	if {[Info $oop trackIgnore]} return
802
803	# Don't track if initial click was outside any cell
804	if {$Priv(canvistPrev) == -1} return
805
806	# When mouse tracking (but not the initial click) we find
807	# the cell nearest to the given location, even if the location
808	# is outside any cell, or even the canvas boundary.
809	Info $oop nearest 1
810
811	# Get the hit row.
812	set row [PointToRow $oop $x $y]
813
814	Info $oop nearest 0
815
816	# No item was hit
817	if {($row == -1) || $Priv(stroke)} {
818
819		if {[Info $oop stroke]} {
820
821			# Drag out selection box
822			itemStroke $canvas $x $y
823		}
824
825	# An item was hit
826	} else {
827
828		# Same row as last time
829		if {$row == $Priv(canvistPrev)} return
830
831		if {![Info $oop dragSpecial]} {
832			Info $oop trackIgnore 1
833			UpdateSelection $oop $row all
834			Info $oop trackIgnore 0
835		}
836
837		set Priv(canvistPrev) $row
838	}
839
840	return
841}
842
843# NSCanvist::Leave1 --
844#
845#	.
846#
847# Arguments:
848#	arg1					about arg1
849#
850# Results:
851#	What happened.
852
853proc NSCanvist::Leave1 {oop x y} {
854
855	AutoScan $oop
856
857	return
858}
859
860# NSCanvist::Double1 --
861#
862#	Call client's command when canvas double-clicked.
863#
864# Arguments:
865#	arg1					about arg1
866#
867# Results:
868#	What happened.
869
870proc NSCanvist::Double1 {oop x y} {
871
872	global NSCanvist
873
874	set command $NSCanvist($oop,invokeCmd)
875	if {[string length $command]} {
876		uplevel #0 $command $oop $x $y
877	}
878
879	return
880}
881
882# NSCanvist::AutoScan --
883#
884#	Description.
885#
886# Arguments:
887#	arg1					about arg1
888#
889# Results:
890#	What happened.
891
892proc NSCanvist::AutoScan {oop} {
893
894	global NSCanvist
895	variable Priv
896
897	set canvas $NSCanvist($oop,canvas)
898	if {![winfo exists $canvas]} return
899
900	# Don't track while UpdateSelection() is in progress
901	if {[Info $oop trackIgnore]} return
902
903	set pointerx [winfo pointerx $canvas]
904	set pointery [winfo pointery $canvas]
905	if {[winfo containing $pointerx $pointery] == "$canvas"} return
906
907	set x [expr {$pointerx - [winfo rootx $canvas]}]
908	set y [expr {$pointery - [winfo rooty $canvas]}]
909
910	set scrollRgn [$canvas cget -scrollregion]
911	set scrollWidth [expr {[lindex $scrollRgn 2] - [lindex $scrollRgn 0]}]
912	set scrollHeight [expr {[lindex $scrollRgn 3] - [lindex $scrollRgn 1]}]
913
914	if {[winfo width $canvas] < $scrollWidth} {
915	    if {$x >= [winfo width $canvas]} {
916			$canvas xview scroll 1 units
917	    } elseif {$x < 0} {
918			$canvas xview scroll -1 units
919	    }
920	}
921
922	if {[winfo height $canvas] < $scrollHeight} {
923		if {$y >= [winfo height $canvas]} {
924			$canvas yview scroll 1 units
925	    } elseif {$y < 0} {
926			$canvas yview scroll -1 units
927	    }
928	}
929
930	Motion1 $oop $x $y
931
932	set Priv(scan,afterId) [after 50 NSCanvist::AutoScan $oop]
933
934	return
935}
936
937# NSCanvist::CancelRepeat --
938#
939#	Cancel auto-scrolling "after" command.
940#
941# Arguments:
942#	arg1					about arg1
943#
944# Results:
945#	What happened.
946
947proc NSCanvist::CancelRepeat {oop} {
948
949	variable Priv
950
951	after cancel $Priv(scan,afterId)
952	set Priv(scan,afterId) {}
953
954	return
955}
956
957# NSCanvist::Synch --
958#
959#	Sets the scroll region of the canvas to the row height
960#	multiplied by the number of items in the list.
961#
962# Arguments:
963#	arg1					about arg1
964#
965# Results:
966#	What happened.
967
968proc NSCanvist::Synch {oop} {
969
970	global NSCanvist
971
972	set c $NSCanvist($oop,canvas)
973
974	# The canvist height is (num rows) * (row height)
975	set rowHgt $NSCanvist($oop,rowHgt)
976	set height [expr {$rowHgt * $NSCanvist($oop,count)}]
977
978	# Get the scroll region and change the height
979	set scrollRegion [lreplace [$c cget -scrollregion] 3 3 $height]
980	$c configure -scrollregion $scrollRegion
981
982	return
983}
984
985# NSCanvist::ItemRow --
986#
987#	Return the row index the given item is on
988#
989# Arguments:
990#	arg1					about arg1
991#
992# Results:
993#	What happened.
994
995proc NSCanvist::ItemRow {oop tagOrId} {
996
997	global NSCanvist
998
999	set rowTag [_GetRowTag $oop $tagOrId]
1000	if {$rowTag == {}} {return -1}
1001	return [lsearch -exact $NSCanvist($oop,rowTags) $rowTag]
1002}
1003
1004# NSCanvist::PointToRow --
1005#
1006#	Finds the row containing the given point. If the rowsEnabled option
1007#	is set, returns the row containing the point, or -1 of no row
1008#	contains the point. If the nearest option is also set, returns the
1009#	row closest to the given point, even if the point is outside any
1010#	row.
1011#
1012#	If the rowsEnabled option is not set, returns the row for which an
1013#	enabled canvas item contains the point, otherwise returns -1.
1014#
1015# Arguments:
1016#	arg1					about arg1
1017#
1018# Results:
1019#	What happened.
1020
1021proc NSCanvist::PointToRow {oop x y} {
1022
1023	global NSCanvist
1024
1025	set canvas $NSCanvist($oop,canvas)
1026
1027	# Option: Don't check for enabled items, just hit the row
1028	if {$NSCanvist($oop,rowsEnabled)} {
1029		set rows [Info $oop count]
1030		set rowHeight [Info $oop rowHgt]
1031		set row [expr {int([$canvas canvasy $y] / $rowHeight)}]
1032
1033		# Option: Find nearest hit row (used for mouse tracking)
1034		if {[Info $oop nearest]} {
1035			if {$row < 0} {
1036				set row 0
1037			} elseif {$row >= $rows} {
1038				set row [expr {$rows - 1}]
1039			}
1040
1041			# Restrict to visible rows only
1042			set rowTop [expr {int([$canvas canvasy 0 $rowHeight] / $rowHeight)}]
1043			set rowBottom [expr {int([$canvas canvasy [winfo height $canvas] $rowHeight] / $rowHeight - 1)}]
1044			if {$row < $rowTop} {
1045				set row $rowTop
1046			} elseif {$row > $rowBottom} {
1047				set row $rowBottom
1048			}
1049		}
1050
1051		if {$row < $rows && $row >= 0} {
1052			return $row
1053		}
1054		return -1
1055	}
1056
1057	set x [$canvas canvasx $x]
1058	set y [$canvas canvasy $y]
1059
1060	# Get the item(s) under the point.
1061	set itemIdList [$canvas find overlapping $x $y [expr {$x + 1}] [expr {$y + 1}]]
1062
1063	# No item is under that point
1064	if {![llength $itemIdList]} {return -1}
1065
1066	# Get the topmost enabled item
1067	foreach itemId $itemIdList {
1068		if {[lsearch -exact [$canvas gettags $itemId] enabled] != -1} {
1069			return [ItemRow $oop $itemId]
1070		}
1071	}
1072
1073	# No enabled item is overlapping the given location
1074	return -1
1075}
1076
1077# NSCanvist::UpDown --
1078#
1079#	Handle KeyPress-Up and KeyPress-Down.
1080#
1081# Arguments:
1082#	arg1					about arg1
1083#
1084# Results:
1085#	What happened.
1086
1087proc NSCanvist::UpDown {oop delta} {
1088
1089	global NSCanvist
1090
1091	set canvas $NSCanvist($oop,canvas)
1092
1093	set selection [Selection $oop]
1094	set max [expr {$NSCanvist($oop,count) - 1}]
1095	if {$max < 0} return
1096
1097	if {[llength $selection]} {
1098		set row [expr {[lindex $selection 0] + $delta}]
1099		if {$row < 0} {
1100			set row $max
1101		} elseif {$row > $max} {
1102			set row 0
1103		}
1104	} else {
1105		if {$delta > 0} {
1106			set row 0
1107		} else {
1108			set row $max
1109		}
1110	}
1111
1112	UpdateSelection $oop $row $selection
1113	See $oop $row
1114
1115	return
1116}
1117
1118# NSCanvist::See --
1119#
1120#	Scroll the given row into view. If it is the row above the currently-
1121#	visible top row, then scroll up one row. If it is the row below the
1122#	currently-visible bottom row, then scroll down one row. Otherwise
1123#	attempt to center the row.
1124#
1125# Arguments:
1126#	arg1					about arg1
1127#
1128# Results:
1129#	What happened.
1130
1131proc NSCanvist::See {oop row} {
1132
1133	global NSCanvist
1134
1135	set canvas $NSCanvist($oop,canvas)
1136	set rowHeight $NSCanvist($oop,rowHgt)
1137	set scrollRgn [$canvas cget -scrollregion]
1138	set height [lindex $scrollRgn 3]
1139
1140	set rowTop [expr {int([$canvas canvasy 0 $rowHeight] / $rowHeight)}]
1141	set rowBottom [expr {int($rowTop + [winfo height $canvas] / $rowHeight - 1)}]
1142
1143	if {($row >= $rowTop) && ($row <= $rowBottom)} {
1144
1145	} elseif {$row == $rowTop - 1} {
1146		$canvas yview scroll -1 units
1147
1148	} elseif {$row == $rowBottom + 1} {
1149		$canvas yview scroll +1 units
1150
1151	} else {
1152		set top [expr {($row * $rowHeight - [winfo height $canvas] / 2) \
1153			/ double($height)}]
1154		$canvas yview moveto $top
1155	}
1156
1157	return
1158}
1159
1160
1161
1162# Utility procedures for stroking out a rectangle
1163# Adopted from Tk "Widget Demo"
1164
1165proc NSCanvist::itemMark {c x y} {
1166
1167    variable Priv
1168
1169    set Priv(areaX1) [$c canvasx $x]
1170    set Priv(areaY1) [$c canvasy $y]
1171	set Priv(areaX2) $Priv(areaX1)
1172	set Priv(areaY2) $Priv(areaY1)
1173    $c delete area
1174	set Priv(stroke) 1
1175
1176	return
1177}
1178
1179proc NSCanvist::itemStroke {c x y} {
1180
1181    variable Priv
1182
1183	if {!$Priv(stroke)} return
1184    set x [$c canvasx $x]
1185    set y [$c canvasy $y]
1186    if {($Priv(areaX1) != $x) && ($Priv(areaY1) != $y)} {
1187		$c delete area
1188		$c addtag area withtag [$c create rect $Priv(areaX1) \
1189			 $Priv(areaY1) $x $y -outline Grey]
1190		set Priv(areaX2) $x
1191		set Priv(areaY2) $y
1192    }
1193
1194	return
1195}
1196
1197proc NSCanvist::itemSelect {oop} {
1198
1199	global NSCanvist
1200	variable Priv
1201
1202	if {!$Priv(stroke)} return
1203
1204	# Gotta delete it or its included in the list!
1205	$NSCanvist($oop,canvas) delete area
1206
1207    if {($Priv(areaX1) == $Priv(areaX2)) || \
1208		($Priv(areaY1) == $Priv(areaY2))} return
1209
1210	# Find all items overlapping the selection rectangle
1211	set list [$NSCanvist($oop,canvas) find overlapping \
1212		$Priv(areaX1) $Priv(areaY1) \
1213		$Priv(areaX2) $Priv(areaY2)]
1214
1215	set doneRows {}
1216	set select {}
1217	set deselect {}
1218
1219	foreach index $list {
1220
1221		# Some items are not "enabled"
1222		if {[_GetRowTag $oop $index] == {}} continue
1223
1224		# Get the row this item is on
1225		set row [ItemRow $oop $index]
1226
1227		# Already processed this row
1228		if {[lsearch -exact $doneRows $row] != -1} continue
1229
1230		# Select this row
1231		lappend select $row
1232
1233		# Remember we did this row
1234		lappend doneRows $row
1235	}
1236
1237	# Update the selection
1238	UpdateSelection $oop $select $deselect
1239
1240	return
1241}
1242
1243# NSCanvist::Activate --
1244#
1245#	Called when the focus enters or leaves the canvas. Calls the
1246#	client highlight routine for each selected row. This is so
1247#	the client can highlight differently depending on whether the
1248#	canvas has the focus or not.
1249#
1250# Arguments:
1251#	arg1					about arg1
1252#
1253# Results:
1254#	What happened.
1255
1256proc NSCanvist::Activate {oop activate} {
1257
1258	foreach row [Selection $oop] {
1259		SelectRow $oop $row
1260	}
1261
1262	return
1263}
1264
1265# FindItemByTag --
1266#
1267#	Return a list of canvas itemIds from the given list of item ids
1268#	which are tagged with the given tag.
1269#
1270# Arguments:
1271#	arg1					about arg1
1272#
1273# Results:
1274#	What happened.
1275
1276proc FindItemByTag {canvas itemIdList tag} {
1277
1278	set result {}
1279	foreach itemId $itemIdList {
1280		set tagList [$canvas gettags $itemId]
1281		if {[lsearch -exact $tagList $tag] != -1} {
1282			lappend result $itemId
1283		}
1284	}
1285
1286	return $result
1287}
1288