1# File: widget.tcl
2
3# Purpose: commands for manipulating Widgets
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 NSWidget {
14
15# namespace eval NSWidget
16}
17
18# NSWidget::InitModule --
19#
20#	One-time-only-ever initialization.
21#
22# Arguments:
23#	arg1					about arg1
24#
25# Results:
26#	What happened.
27
28proc NSWidget::InitModule {} {
29}
30
31# NSWidget::NSWidget --
32#
33#	Object constructor called by NSObject::New().
34#
35# Arguments:
36#	arg1					about arg1
37#
38# Results:
39#	What happened.
40
41proc NSWidget::NSWidget {oop parent width height gwidth gheight} {
42
43	set widget $parent.widget$oop
44
45	widget $widget -width $width -height $height \
46		-gwidth $gwidth -gheight $gheight
47
48	bind $widget <Enter> "NSWidget::Motion $oop %x %y"
49	bind $widget <Motion> "NSWidget::Motion $oop %x %y"
50	bind $widget <Leave> "NSWidget::Leave $oop"
51
52	# Hack -- When we point to a location, the Recall Window may be
53	# set with information, and we may want to interact with the
54	# Recall Window to see the information. But if the mouse moves
55	# over another grid (on the way to the Recall Window) the
56	# information in the Recall Window may change. So we don't
57	# examine cave locations when the Shift key is down.
58	bind $widget <Shift-Enter> break
59	bind $widget <Shift-Motion> break
60
61	# Shift-drag does nothing
62	bind $widget <Shift-Button1-Motion> break
63
64	bind $widget <ButtonPress-1> "NSWidget::TrackPress $oop %x %y"
65	bind $widget <Button1-Motion> "NSWidget::TrackOnce $oop %x %y"
66
67	# Disable tracking when dragging
68	bind $widget <Button1-Enter> break
69	bind $widget <Button1-Leave> break
70
71	bind $widget <MouseWheel> {
72		%W yview scroll [expr {- (%D / 120) * 4}] units
73	}
74
75	Info $oop widget $widget
76	Info $oop examined ""
77	Info $oop examineCmd ""
78	Info $oop leaveCmd ""
79	Info $oop scaleCmd ""
80	Info $oop xviewCmd ""
81	Info $oop yviewCmd ""
82	Info $oop track,mouseMoved 0
83	Info $oop caveyx 0
84
85	# Set the checkmark for the current scale
86	Info $oop scale $gwidth
87
88	return
89}
90
91# NSWidget::Info --
92#
93#	Query and modify info.
94#
95# Arguments:
96#	arg1					about arg1
97#
98# Results:
99#	What happened.
100
101proc NSWidget::Info {oop info args} {
102
103	global NSWidget
104
105	# Verify the object
106	NSObject::CheckObject NSWidget $oop
107
108	# Set info
109	if {[llength $args]} {
110		set NSWidget($oop,$info) [lindex $args 0]
111	# Get info
112	} else {
113		return $NSWidget($oop,$info)
114	}
115
116	return
117}
118
119# NSWidget::Motion --
120#
121#	Call the client's command when the mouse moves over a grid.
122#
123# Arguments:
124#	oop					OOP ID.
125#	x					x location in widget.
126#	y					y location in widget.
127#
128# Results:
129#	What happened.
130
131proc NSWidget::Motion {oop x y} {
132
133	set pos [PointToCave $oop $x $y]
134	if {![string length $pos]} return
135	if {[string equal [Info $oop examined] $pos]} return
136	Info $oop examined $pos
137
138	set command [Info $oop examineCmd]
139	if {[string length $command]} {
140		uplevel #0 $command $oop $pos
141	}
142
143	return
144}
145
146# NSWidget::Leave --
147#
148#	Handle the <Leave> event.
149#
150# Arguments:
151#	oop					OOP ID.
152#
153# Results:
154#	What happened.
155
156proc NSWidget::Leave {oop} {
157
158	Info $oop examined ""
159
160	set command [Info $oop leaveCmd]
161	if {[string length $command]} {
162		uplevel #0 $command $oop
163	}
164}
165
166# NSWidget::PointToCave --
167#
168#	Determine the cave y,x location based on the given
169#	coordinates inside the given widget.
170#
171# Arguments:
172#	oop					OOP ID.
173#	x					x coordinate in Widget.
174#	y					y coordinate in Widget.
175#
176# Results:
177#	Return "y x".
178
179proc NSWidget::PointToCave {oop x y} {
180
181	set widget [Info $oop widget]
182
183	# Normally, we want to know which grid the point is over,
184	# and for isometric view this requires accurate hittesting
185	# of the actual icons near the point, instead of just the
186	# floor tile.
187	if {![Info $oop caveyx]} {
188		set str [$widget hittest $x $y]
189		if {[string length $str]} {
190			scan $str "%d %d" cy cx
191			set str "$cy $cx"
192		}
193		return $str
194	}
195
196	# Vault editor wants floor tile.
197	return [$widget caveyx $x $y]
198}
199
200# NSWidget::SetScale --
201#
202#	Sets the resolution of the Widget, but doesn't let the Widget
203#	get any larger than its original dimensions.
204#
205# Arguments:
206#	arg1					about arg1
207#
208# Results:
209#	What happened.
210
211proc NSWidget::SetScale {oop scale} {
212
213	set widget [Info $oop widget]
214
215	if {$scale == [$widget cget -gwidth]} return
216
217	$widget configure -gwidth $scale -gheight $scale
218
219	# Context menu
220	Info $oop scale $scale
221
222	# Hack -- Fully update the widget
223	$widget wipe
224	eval $widget center [$widget center]
225
226	set command [Info $oop scaleCmd]
227	if {[string length $command]} {
228		uplevel #0 $command
229	}
230
231	return
232}
233
234# NSWidget::Resize --
235#
236#	Change the size of the widget.
237#
238# Arguments:
239#	arg1					about arg1
240#
241# Results:
242#	What happened.
243
244proc NSWidget::Resize {oop width height} {
245
246	set widget [Info $oop widget]
247
248if 0 {
249	if {($width == [$widget cget -width]) && \
250		($height == [$widget cget -height])} {
251		return 0
252	}
253}
254
255	$widget configure -width $width -height $height
256
257if 0 {
258	# Hack -- Fully update the widget
259	$widget wipe
260	eval $widget center [$widget center]
261}
262	return 1
263}
264
265proc NSWidget::Size {oop _height _width} {
266
267	upvar $_height height
268	upvar $_width width
269
270	set widget [Info $oop widget]
271
272	scan [$widget bounds] "%d %d %d %d" y_min x_min y_max x_max
273	set height [expr {$y_max - $y_min + 1}]
274	set width [expr {$x_max - $x_min + 1}]
275
276	return
277}
278
279proc NSWidget::CaveSize {oop _height _width} {
280
281	upvar $_height height
282	upvar $_width width
283
284	set widget [Info $oop widget]
285
286	set h [angband cave height]
287	set w [angband cave width]
288
289	Size $oop h2 w2
290	if {$h > $h2} {
291		incr h 2
292	}
293	if {$w > $w2} {
294		incr w 2
295	}
296
297	set height $h
298	set width $w
299
300	return
301}
302
303
304# NSWidget::yview --
305#
306#	Typical yview command
307#
308# Arguments:
309#	arg1					about arg1
310#
311# Results:
312#	What happened.
313
314proc NSWidget::yview {oop cmd args} {
315
316	set widget [Info $oop widget]
317
318	scan [$widget center] "%d %d" oy ox
319
320	scan [$widget bounds] "%d %d %d %d" y_min x_min y_max x_max
321	set height [expr {$y_max - $y_min + 1}]
322
323	set caveHgt [angband cave height]
324
325	if {$caveHgt > $height} {
326		incr caveHgt 2
327		set fiddle -1
328	} else {
329		set fiddle 0
330	}
331
332	switch $cmd {
333
334		moveto {
335			set fraction [lindex $args 0]
336			if {$fraction > 1.0} {
337				set fraction 1.0
338			} elseif {$fraction < 0} {
339				set fraction 0
340			}
341			set top [expr {int($fraction * double($caveHgt) + 0.5)}]
342			incr top $fiddle
343			set ny [expr {$top + $height / 2}]
344		}
345
346		scroll {
347
348			set number [lindex $args 0]
349			set what [lindex $args 1]
350
351			switch $what {
352
353				units {
354					set ny [expr {$oy + $number}]
355				}
356
357				pages {
358					set pageSize [expr {$height - 10}]
359					set ny [expr {$oy + $number * $pageSize}]
360				}
361			}
362		}
363	}
364
365	set ny [ConstrainCenter $ny $caveHgt $height]
366
367	# Do nothing if position unchanged
368	if {$oy == $ny} return
369
370	$widget center $ny $ox
371
372	set command [Info $oop yviewCmd]
373	if {[string length $command]} {
374		uplevel #0 $command
375	}
376
377	return
378}
379
380# NSWidget::xview --
381#
382#	Typical xview command
383#
384# Arguments:
385#	arg1					about arg1
386#
387# Results:
388#	What happened.
389
390proc NSWidget::xview {oop cmd args} {
391
392	set widget [Info $oop widget]
393
394	scan [$widget center] "%d %d" oy ox
395
396	scan [$widget bounds] "%d %d %d %d" y_min x_min y_max x_max
397	set width [expr {$x_max - $x_min + 1}]
398
399	set caveWid [angband cave width]
400
401	if {$caveWid > $width} {
402		incr caveWid 2
403		set fiddle -1
404	} else {
405		set fiddle 0
406	}
407
408	switch $cmd {
409
410		moveto {
411			set fraction [lindex $args 0]
412			if {$fraction > 1.0} {
413				set fraction 1.0
414			} elseif {$fraction < 0} {
415				set fraction 0
416			}
417			set left [expr {int($fraction * double($caveWid) + 0.5)}]
418			incr left $fiddle
419			set nx [expr {$left + $width / 2}]
420		}
421
422		scroll {
423
424			set number [lindex $args 0]
425			set what [lindex $args 1]
426
427			switch $what {
428
429				units {
430					set nx [expr {$ox + $number}]
431				}
432
433				pages {
434					set pageSize [expr {$width - 10}]
435					set nx [expr {$ox + $number * $pageSize}]
436				}
437			}
438		}
439	}
440
441	set nx [ConstrainCenter $nx $caveWid $width]
442
443	# Do nothing if position unchanged
444	if {$ox == $nx} return
445
446	$widget center $oy $nx
447
448	set command [Info $oop xviewCmd]
449	if {[string length $command]} {
450		uplevel #0 $command
451	}
452
453	return
454}
455
456
457# NSWidget::TrackPress --
458#
459#	Handles <ButtonPress-1> events
460#
461# Arguments:
462#	arg1					about arg1
463#
464# Results:
465#	What happened.
466
467proc NSWidget::TrackPress {oop x y} {
468
469	Info $oop track,x $x
470	Info $oop track,y $y
471	Info $oop track,mouseMoved 0
472
473	return
474}
475
476# NSWidget::TrackOnce --
477#
478#	Handles <Button1-Motion> events
479#
480# Arguments:
481#	arg1					about arg1
482#
483# Results:
484#	What happened.
485
486proc NSWidget::TrackOnce {oop x y} {
487
488	# Get the widget
489	set widget [Info $oop widget]
490
491	# Get the scale
492	set scale [$widget cget -gwidth]
493
494	# Calculate the distance the pointer moved
495	set dx [expr {[Info $oop track,x] - $x}]
496	set dy [expr {[Info $oop track,y] - $y}]
497
498	# Require minimum movement
499	if {abs($dx) < $scale} {
500		set dx 0
501	}
502	if {abs($dy) < $scale} {
503		set dy 0
504	}
505
506	# If the pointer didn't move, do nothing
507	if {!$dx && !$dy} {
508		return
509	}
510
511	# Remember the pointer moved
512	Info $oop track,mouseMoved 1
513
514	# Remember the current center
515	scan [$widget center] "%d %d" oy ox
516
517	# We should scroll horizontally
518	if {$dx} {
519
520		# Convert from pixels to grid size
521		set dx [expr {$dx / $scale}]
522
523		# Scroll the Widget
524		xview $oop scroll $dx units
525	}
526
527	# We should scroll vertically
528	if {$dy} {
529
530		# Convert from pixels to grid size
531		set dy [expr {$dy / $scale}]
532
533		# Scroll the Widget
534		yview $oop scroll $dy units
535	}
536
537	# Get the new center
538	scan [$widget center] "%d %d" ny nx
539
540	# Remember the current pointer position
541	if {$nx != $ox} {
542		Info $oop track,x $x
543	}
544
545	# Remember the current pointer position
546	if {$ny != $oy} {
547		Info $oop track,y $y
548	}
549
550	return
551}
552
553# WidgetCenter --
554#
555#	When the character goes to a new level (or WOR back to a level) this
556#	routine sets the center of the given widget. The widget is centered
557#   on the character position.
558#
559# Arguments:
560#	widget					Widget to center
561#
562# Results:
563#	What happened.
564
565proc WidgetCenter {widget} {
566
567	scan [angband player position] "%d %d" y x
568
569	$widget center $y $x
570
571	return "$y $x"
572}
573
574# ClipCenter --
575#
576#	Helper command used control scrolling of a widget when updating the
577#	character's position.
578#
579# Arguments:
580#	_coord					Name of variable holding
581#	center					Current widget center.
582#	units					Cave height or width.
583#	units2					Widget height or width.
584#
585# Results:
586#	What happened.
587
588proc ClipCenter {_coord center units units2} {
589
590	upvar $_coord coord
591
592	set min [expr {$center - $units2 / 2}]
593	set max [expr {$min + $units2 - 1}]
594	set bord [expr {$units2 / 8}]
595	set pad [expr {$units2 / 4}]
596	if {$coord < $min + $bord} {
597		set coord [expr {($coord + $pad) - $units2 / 2}]
598		if {$units2 % 2 == 0} {incr coord}
599		set scroll 1
600	} elseif {$coord > $max - $bord} {
601		set coord [expr {($coord - $pad) + $units2 / 2}]
602		set scroll 1
603	} else {
604		set coord $center
605		set scroll 0
606	}
607
608	if {$scroll} {
609		if {$units > $units2} {
610			set centerMin [expr {$units2 / 2 - 1}]
611			set centerMax [expr {$units - $units2 / 2 + 1}]
612			if {$units2 & 1} {incr centerMax -1}
613			if {$coord < $centerMin} {
614				set coord $centerMin
615			} elseif {$coord > $centerMax} {
616				set coord $centerMax
617			} elseif {$coord == $centerMin + 1} {
618				set coord $centerMin
619			} elseif {$coord == $centerMax - 1} {
620				set coord $centerMax
621			}
622		} else {
623			set coord [expr {($units - $units2) / 2 + $units2 / 2}]
624		}
625	}
626
627	return $scroll
628}
629
630# ConstrainCenter --
631#
632#	Call this when you want to set the x/y center of a widget but do
633#	not want the widget to scroll "too far". This calculation adds a
634#	1-grid border around the edge of the cave.
635#
636# Arguments:
637#	arg1					about arg1
638#
639# Results:
640#	What happened.
641
642proc ConstrainCenter {coord units units2} {
643
644	if {$units > $units2} {
645		set centerMin [expr {$units2 / 2 - 1}]
646		set centerMax [expr {$units - $units2 / 2 - 1}]
647		if {$units2 & 1} {incr centerMax -1}
648		if {$coord < $centerMin} {
649			set coord $centerMin
650		} elseif {$coord > $centerMax} {
651			set coord $centerMax
652		}
653	} else {
654		set coord [expr {($units - $units2) / 2 + $units2 / 2}]
655	}
656
657	return $coord
658}
659
660