-
Notifications
You must be signed in to change notification settings - Fork 0
/
tc-picking.adb
357 lines (335 loc) · 12.3 KB
/
tc-picking.adb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
package body TC.Picking is
-- Behaviour changed 12-Aug-2007:
-- For single pick (resp. unpick), only the unselected
-- (resp. selected) object nearest to M1 and under the max distance,
-- if any, is being selected (resp. unselected) at a time.
-- Previously, all objects under the max distance were
-- selected at the same time, it was nasty (required zooming a lot).
-- Now, when you click on overlapping objects, they are selected
-- one per click.
procedure PicPic
(p : in out Picture;
op : Pick_operation;
M1, M2 : Point := (0.0, 0.0))
is
dist_max, dist_max_2, txt_dist2 : Real;
a : ptr_Obj_Type := p.root;
last_near_dist : Real; -- 2007
function Near_Point return Boolean is
use REF, TC.Graphics;
function Near_Text (T : Point) return Boolean is
x_adj : Natural;
dist2 : Real;
begin
x_adj := Integer'Min (Integer'Max (1, Length (a.inhalt)), TC.Graphics.text_cutting);
dist2 := ((M1.x - T.x) / Real (x_adj))**2 + (M1.y - T.y)**2;
if dist2 <= txt_dist2 then
last_near_dist := Sqrt (dist2);
return True;
else
return False;
end if;
end Near_Text;
-- 25-Apr-2003
-- Change from TeXCAD 3 ergonomy: we check the distance to the
-- frame instead of distance to the full box.
-- Reason: with no [y/n] choice for picking, then a box is always
-- selected with the old method while selecting object inside of it.
function Near_Box (P1, P2 : Point; frame : Boolean) return Boolean is
dist : Real;
begin
if frame then -- Distance to frame:
-- vertical range
if M1.y >= P1.y - dist_max and then M1.y <= P2.y + dist_max then
if abs (M1.x - P1.x) <= dist_max then -- left side
last_near_dist := abs (M1.x - P1.x);
return True;
elsif abs (M1.x - P2.x) <= dist_max then -- right side
last_near_dist := abs (M1.x - P2.x);
return True;
end if;
end if;
-- horizontal range
if M1.x >= P1.x - dist_max and then M1.x <= P2.x + dist_max then
if abs (M1.y - P1.y) <= dist_max then -- top side
last_near_dist := abs (M1.y - P1.y);
return True;
elsif abs (M1.y - P2.y) <= dist_max then -- bottom side
last_near_dist := abs (M1.y - P2.y);
return True;
end if;
end if;
return a.art = box and then Near_Text (Position_of_Text (a.all));
end if;
-- Distance to full box:
dist := Real'Max (P1.x - M1.x, M1.x - P2.x);
dist := Real'Max (dist, P1.y - M1.y);
dist := Real'Max (dist, M1.y - P2.y);
if dist <= dist_max then
last_near_dist := dist;
return True;
end if;
return False;
end Near_Box;
function Near_Line return Boolean is
slope, h_slope, v_slope, dx, dy, temp : Real;
P, A1, A2 : Point;
begin
if a.any_slope then
h_slope := a.P2.x - a.P1.x;
v_slope := a.P2.y - a.P1.y;
else
h_slope := Real (a.line_slope (h));
v_slope := Real (a.line_slope (v));
end if;
if abs (h_slope) < 1.0 then
if (abs (M1.x - a.P1.x) <= dist_max) and then
(M1.y > Real'Min (a.P1.y, a.P2.y)) and then
(M1.y < Real'Max (a.P1.y, a.P2.y))
then
last_near_dist := abs (M1.x - a.P1.x);
return True;
end if;
else
slope := v_slope / h_slope;
A1 := a.P1;
A2 := a.P2;
P := M1;
if abs (slope) > 1.0 then
temp := A1.x; A1.x := A1.y; A1.y := temp;
temp := A2.x; A2.x := A2.y; A2.y := temp;
temp := P.x; P.x := P.y; P.y := temp;
slope := 1.0 / slope;
end if;
if (P.x > Real'Min (A1.x, A2.x)) and (P.x < Real'Max (A1.x, A2.x)) then
dx := P.x - A1.x;
dy := dx * slope;
if abs (P.y - A1.y - dy) <= dist_max then
last_near_dist := abs (P.y - A1.y - dy);
return True;
end if;
end if;
end if;
return False;
end Near_Line;
near : Boolean := False;
procedure Near_to_target_Point (P : Point) is
dist : Real;
begin
if Norm2 (M1 - P) <= dist_max_2 then
dist := Sqrt (Norm2 (M1 - P));
if near then
last_near_dist := Real'Min (dist, last_near_dist);
else
last_near_dist := dist;
end if;
near := True;
end if;
end Near_to_target_Point;
procedure Scout_Bezier is new Bezier_Curve (Near_to_target_Point);
procedure Do_Nothing is null;
procedure Scout_Param_2D is new Parametric_Curve_2D (Near_to_target_Point, Do_Nothing);
begin -- Near_Point
case a.art is
when txt | putaux =>
return Near_Text (a.P1);
when box =>
return Near_Box (a.P1, a.P1 + a.size, frame => not a.solid);
when line =>
return Near_Line;
when disc =>
if Norm (M1 - a.P1) - a.rad <= dist_max then
last_near_dist := Real'Max (0.0, Norm (M1 - a.P1) - a.rad);
return True;
end if;
when circ =>
if abs (Norm (M1 - a.P1) - a.rad) <= dist_max then
last_near_dist := abs (Norm (M1 - a.P1) - a.rad);
return True;
end if;
when oval =>
return Near_Box (a.LL, a.LL + a.osize, frame => True);
when bezier =>
Scout_Bezier (a.all, p.ul_in_pt);
return near;
when paramcurve2d =>
Scout_Param_2D (a.all, p.ul_in_pt);
return near;
when others =>
return False; -- incl.: aux
end case;
return False;
end Near_Point;
function In_Area return Boolean is
MI, MA : Point;
begin
MI.x := Real'Min (M1.x, M2.x) - dist_max;
MI.y := Real'Min (M1.y, M2.y) - dist_max;
MA.x := Real'Max (M1.x, M2.x) + dist_max;
MA.y := Real'Max (M1.y, M2.y) + dist_max;
case a.art is
when txt | putaux =>
return a.P1.x >= MI.x and then a.P1.x <= MA.x and then
a.P1.y >= MI.y and then a.P1.y <= MA.y;
when box =>
return a.P1.x >= MI.x and then a.P1.x + a.size.x <= MA.x and then
a.P1.y >= MI.y and then a.P1.y + a.size.y <= MA.y;
when line =>
return a.P1.x >= MI.x and then a.P1.x <= MA.x and then
a.P1.y >= MI.y and then a.P1.y <= MA.y and then
a.P2.x >= MI.x and then a.P2.x <= MA.x and then
a.P2.y >= MI.y and then a.P2.y <= MA.y;
when circ | disc =>
return a.P1.x - a.rad >= MI.x and then a.P1.x + a.rad <= MA.x and then
a.P1.y - a.rad >= MI.y and then a.P1.y + a.rad <= MA.y;
when oval =>
return a.LL.x >= MI.x and then a.LL.x + a.osize.x <= MA.x and then
a.LL.y >= MI.y and then a.LL.y + a.osize.y <= MA.y;
when bezier =>
return a.P1.x >= MI.x and then -- xc>=P1.x and then
a.PE.x >= MI.x and then
a.P1.x <= MA.x and then -- xc<=xx and then
a.PE.x <= MA.x and then
a.P1.y >= MI.y and then -- yc>=P1.y and then
a.PE.y >= MI.y and then
a.P1.y <= MA.y and then -- yc<=yy and then
a.PE.y <= MA.y; -- xc,yc kann ausserhalb Screen liegen
when paramcurve2d =>
return a.P1.x >= MI.x and then a.P1.x <= MA.x and then
a.P1.y >= MI.y and then a.P1.y <= MA.y;
-- !! Cheap solution (catch only first point)...
-- We should compute a bounding box !!
when others =>
return False; -- includes: aux
end case;
end In_Area;
found : Boolean;
has_modifiable_info : constant array (Obj_Art_Type) of Boolean :=
(txt | putaux | box | oval | bezier | paramcurve2d => True,
others => False);
min_dist : Real := Real'Last;
begin -- PicPic
if op = pick_text then
PicPic (p, unpick_all);
p.memo := null;
end if;
-- 27-Apr-2003
-- Change from TeXCAD 3 ergonomy: distance is pixel-based
-- -> Adds precision with zooming
-- -> Better chance to pick something on large pictures
dist_max := px_dist / p.opt.zoom_fac;
dist_max_2 := dist_max ** 2;
txt_dist2 := px_txt_dist2 / (p.opt.zoom_fac**2);
-- 1/ Nobody is candidate a priori
a := p.root;
while a /= null loop
a.pick_swap_candidate := False;
a := a.next;
end loop;
-- 2/ Identify the candidates for being picked / unpicked
a := p.root;
while a /= null loop
case op is
when pick | unpick =>
found := Near_Point;
when pick_area | unpick_area =>
found := In_Area;
when pick_all | unpick_all =>
found := True;
when pick_text =>
if has_modifiable_info (a.art) then
if a.art = box and then a.solid then
found := False;
else
found := Near_Point;
end if;
else
found := False;
end if;
end case;
case op is
when pick | pick_area | pick_all | pick_text =>
if found and not a.picked then
a.pick_swap_candidate := True;
if op = pick then
a.pick_distance := last_near_dist;
min_dist := Real'Min (min_dist, last_near_dist);
end if;
exit when op = pick_text;
end if;
when unpick | unpick_area | unpick_all =>
if found and a.picked then
a.pick_swap_candidate := True;
if op = unpick then
a.pick_distance := last_near_dist;
min_dist := Real'Min (min_dist, last_near_dist);
end if;
end if;
end case;
a := a.next;
end loop;
-- 3/ Re-scan the picture to set/cancel the picked status definitively
a := p.root;
while a /= null loop
if a.pick_swap_candidate then
case op is
when pick | pick_area | pick_all | pick_text =>
if op /= pick or else Almost_Zero (a.pick_distance - min_dist) then
a.picked := True;
p.picked := p.picked + 1;
if hidden (a.art) then
p.picked_hidden := p.picked_hidden + 1;
end if;
p.memo := a;
exit when op = pick_text or op = pick;
end if;
when unpick | unpick_area | unpick_all =>
if op /= unpick or else Almost_Zero (a.pick_distance - min_dist) then
a.picked := False;
p.picked := p.picked - 1;
if hidden (a.art) then
p.picked_hidden := p.picked_hidden - 1;
end if;
exit when op = pick_text or op = unpick;
end if;
end case;
end if;
a := a.next;
end loop;
end PicPic;
procedure Del_picked (p : in out Picture) is
procedure Stat_and_del (o : in out ptr_Obj_Type) is
begin
p.saved := False;
p.picked := p.picked - 1;
p.total := p.total - 1;
if hidden (o.art) then
p.picked_hidden := p.picked_hidden - 1;
p.total_hidden := p.total_hidden - 1;
end if;
Dispose (o);
end Stat_and_del;
a, b : ptr_Obj_Type;
begin
while p.root /= null and then p.root.picked loop
a := p.root;
p.root := p.root.next;
Stat_and_del (a);
end loop;
if p.root /= null then
a := p.root;
b := a.next;
while b /= null loop
-- Invariant here:
-- a.all exists, a.picked=False, b=a.next, b.all exists
if b.picked then
a.next := b.next; -- b skipped
Stat_and_del (b); -- b deleted
else
a := b;
end if;
b := a.next;
end loop;
end if;
end Del_picked;
end TC.Picking;