1
2 /******************************************************************************
3 * MODULE : edit_mouse.cpp
4 * DESCRIPTION: Mouse handling
5 * COPYRIGHT : (C) 1999 Joris van der Hoeven
6 *******************************************************************************
7 * This software falls under the GNU general public license version 3 or later.
8 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
9 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
10 ******************************************************************************/
11
12 #include "edit_interface.hpp"
13 #include "tm_buffer.hpp"
14 #include "timer.hpp"
15 #include "link.hpp"
16 #include "analyze.hpp"
17 #include "drd_mode.hpp"
18 #include "message.hpp"
19 #include "window.hpp"
20
21 // These are tm-defined in graphics-utils.scm (looks like they shouldn't)
22 #define ShiftMask 256
23 #define LockMask 512
24 #define ControlMask 1024
25 #define Mod1Mask 2048
26 #define Mod2Mask 4096
27 #define Mod3Mask 8192
28 #define Mod4Mask 16384
29 #define Mod5Mask 32768
30
31 void disable_double_clicks ();
32
33 /******************************************************************************
34 * Routines for the mouse
35 ******************************************************************************/
36
37 void
mouse_click(SI x,SI y)38 edit_interface_rep::mouse_click (SI x, SI y) {
39 if (eb->action ("click", x, y, 0) != "") return;
40 start_x= x;
41 start_y= y;
42 send_mouse_grab (this, true);
43 }
44
45 bool
mouse_extra_click(SI x,SI y)46 edit_interface_rep::mouse_extra_click (SI x, SI y) {
47 go_to (x, y);
48 if (eb->action ("double-click", x, y, 0) != "") return true;
49 go_to (x, y);
50 path p1, p2;
51 get_selection (p1, p2);
52 if ((p1==p2) || path_less (tp, p1) || path_less (p2, tp)) select (tp, tp);
53 select_enlarge ();
54 if (selection_active_any ())
55 selection_set ("mouse", selection_get (), true);
56 return false;
57 }
58
59 void
mouse_adjust_selection(SI x,SI y,int mods)60 edit_interface_rep::mouse_adjust_selection (SI x, SI y, int mods) {
61 if (inside_graphics () || mods <=1) return;
62 if (eb->action ("drag", x, y, 0) != "") return;
63 go_to (x, y);
64 end_x= x;
65 end_y= y;
66 path sp= find_innermost_scroll (eb, tp);
67 path p1= tree_path (sp, start_x, start_y, 0);
68 path p2= tree_path (sp, end_x , end_y , 0);
69 path p3= tree_path (sp, x , y , 0);
70
71 bool p1_p2= path_inf (p1, p2);
72 bool p1_p3= path_inf (p1, p3);
73 bool p2_p3= path_inf (p2, p3);
74
75 if (mods & ShiftMask) { // Holding shift: enlarge in direction start_ -> end_
76 if (!p1_p2 && p1_p3) { // p2<p1<p3
77 start_x= end_x;
78 start_y= end_y;
79 end_x = x;
80 end_y = y;
81 p1 = p2;
82 p2 = p3;
83 } else if (!p1_p3 && p1_p2) { // p3<p1<p2
84 start_x= end_x;
85 start_y= end_y;
86 end_x = x;
87 end_y = y;
88 p1 = p3;
89 } else if ((p2_p3 && !p1_p3) || (!p1_p2 && !p2_p3)) { // p2<p3<p1, p3<p2<p1
90 end_x= x;
91 end_y= y;
92 p2 = p1;
93 p1 = p3;
94 } else if ((p1_p2 && p2_p3) || (p1_p3 && !p2_p3)) { // p1<p2<p3, p1<p3<p2
95 end_x= x;
96 end_y= y;
97 p2 = p3;
98 }
99 selection_visible ();
100 set_selection (p1, p2);
101 notify_change (THE_SELECTION);
102 selection_set ("mouse", selection_get (), true);
103 }
104 }
105
106 void
mouse_drag(SI x,SI y)107 edit_interface_rep::mouse_drag (SI x, SI y) {
108 if (inside_graphics ()) return;
109 if (eb->action ("drag", x, y, 0) != "") return;
110 go_to (x, y);
111 end_x = x;
112 end_y = y;
113 selection_visible ();
114 path sp= find_innermost_scroll (eb, tp);
115 path p1= tree_path (sp, start_x, start_y, 0);
116 path p2= tree_path (sp, end_x , end_y , 0);
117 if (path_inf (p2, p1)) {
118 path temp= p1;
119 p1= p2;
120 p2= temp;
121 }
122 set_selection (p1, p2);
123 notify_change (THE_SELECTION);
124 }
125
126 void
mouse_select(SI x,SI y,int mods,bool drag)127 edit_interface_rep::mouse_select (SI x, SI y, int mods, bool drag) {
128 if (eb->action ("select" , x, y, 0) != "") return;
129 if (!is_nil (mouse_ids) && (mods & ShiftMask) == 0 && !drag) {
130 call ("link-follow-ids", object (mouse_ids), object ("click"));
131 disable_double_clicks ();
132 return;
133 }
134 tree g;
135 bool b0= inside_graphics (false);
136 bool b= inside_graphics ();
137 if (b) g= get_graphics ();
138 go_to (x, y);
139 if ((!b0 && inside_graphics (false)) || (b0 && !inside_graphics (false)))
140 drag= false;
141 if (!b && inside_graphics ())
142 eval ("(graphics-reset-context 'begin)");
143 tree g2= get_graphics ();
144 if (b && (!inside_graphics () || obtain_ip (g) != obtain_ip (g2))) {
145 invalidate_graphical_object ();
146 eval ("(graphics-reset-context 'exit)");
147 }
148 if (!drag) {
149 path sp= find_innermost_scroll (eb, tp);
150 path p0= tree_path (sp, x, y, 0);
151 set_selection (p0, p0);
152 notify_change (THE_SELECTION);
153 }
154 if (selection_active_any ())
155 selection_set ("mouse", selection_get (), true);
156 }
157
158 void
mouse_paste(SI x,SI y)159 edit_interface_rep::mouse_paste (SI x, SI y) { (void) x; (void) y;
160 if (eb->action ("paste", x, y, 0) != "") return;
161 go_to (x, y);
162 selection_paste ("mouse");
163 }
164
165 void
mouse_adjust(SI x,SI y,int mods)166 edit_interface_rep::mouse_adjust (SI x, SI y, int mods) {
167 if (eb->action ("adjust", x, y, 0) != "") return;
168 x= (SI) (x * magf);
169 y= (SI) (y * magf);
170 abs_round (x, y);
171 if (is_nil (popup_win)) {
172 SI wx, wy;
173 ::get_position (get_window (this), wx, wy);
174 widget wid;
175 string menu= "texmacs-popup-menu";
176 if ((mods & (ShiftMask + ControlMask)) != 0)
177 menu= "texmacs-alternative-popup-menu";
178 SERVER (menu_widget ("(vertical (link " * menu * "))", wid));
179 widget popup_wid= ::popup_widget (wid);
180 popup_win= ::popup_window_widget (popup_wid, "Popup menu");
181 #if defined (QTTEXMACS) || defined(AQUATEXMACS)
182 SI ox, oy, sx, sy;
183 get_position (this, ox, oy);
184 get_scroll_position(this, sx, sy);
185 ox -= sx; oy -= sy;
186 #endif
187 set_position (popup_win, wx+ ox+ x, wy+ oy+ y);
188 set_visibility (popup_win, true);
189 send_keyboard_focus (this);
190 send_mouse_grab (popup_wid, true);
191 }
192 }
193
194 void
mouse_scroll(SI x,SI y,bool up)195 edit_interface_rep::mouse_scroll (SI x, SI y, bool up) {
196 string action= up? string ("scroll up"): string ("scroll down");
197 if (eb->action (action , x, y, 0) != "") return;
198 SI dy= 100*PIXEL;
199 if (!up) dy= -dy;
200 path sp= find_innermost_scroll (eb, tp);
201 if (is_nil (sp)) {
202 SERVER (scroll_where (x, y));
203 y += dy;
204 SERVER (scroll_to (x, y));
205 }
206 else {
207 SI x, y, sx, sy;
208 rectangle outer, inner;
209 find_canvas_info (eb, sp, x, y, sx, sy, outer, inner);
210 SI ty= inner->y2 - inner->y1;
211 SI cy= outer->y2 - outer->y1;
212 if (ty > cy) {
213 tree old_yt= eb[path_up (sp)]->get_info ("scroll-y");
214 string old_ys= as_string (old_yt);
215 double old_p = 0.0;
216 if (ends (old_ys, "%")) old_p= as_double (old_ys (0, N(old_ys)-1));
217 double new_p= old_p + 100.0 * ((double) dy) / ((double) (ty - cy));
218 new_p= max (min (new_p, 100.0), 0.0);
219 tree new_yt= as_string (new_p) * "%";
220 if (new_yt != old_yt && is_accessible (obtain_ip (old_yt))) {
221 object fun= symbol_object ("tree-set");
222 object cmd= list_object (fun, old_yt, new_yt);
223 exec_delayed (scheme_cmd (cmd));
224 temp_invalid_cursor= true;
225 }
226 }
227 }
228 }
229
230 /******************************************************************************
231 * getting the cursor (both for text and graphics)
232 ******************************************************************************/
233
234 cursor
get_cursor()235 edit_interface_rep::get_cursor () {
236 if (inside_graphics ()) {
237 frame f= find_frame ();
238 if (!is_nil (f)) {
239 point p= f [point (last_x, last_y)];
240 p= f (adjust (p));
241 SI x= (SI) p[0];
242 SI y= (SI) p[1];
243 return cursor (x, y, 0, -5*pixel, 5*pixel, 1.0);
244 }
245 }
246 return copy (the_cursor ());
247 }
248
249 array<SI>
get_mouse_position()250 edit_interface_rep::get_mouse_position () {
251 rectangle wr= get_window_extents ();
252 SI sz= get_pixel_size ();
253 double sf= ((double) sz) / 256.0;
254 SI mx= ((SI) (last_x / sf)) + wr->x1;
255 SI my= ((SI) (last_y / sf)) + wr->y2;
256 return array<SI> (mx, my);
257 }
258
259 void
set_pointer(string name)260 edit_interface_rep::set_pointer (string name) {
261 send_mouse_pointer (this, name);
262 }
263
264 void
set_pointer(string curs_name,string mask_name)265 edit_interface_rep::set_pointer (
266 string curs_name, string mask_name)
267 {
268 send_mouse_pointer (this, curs_name, mask_name);
269 }
270
271 /******************************************************************************
272 * Active loci
273 ******************************************************************************/
274
275 void
update_mouse_loci()276 edit_interface_rep::update_mouse_loci () {
277 if (is_nil (eb)) {
278 locus_new_rects= rectangles ();
279 mouse_ids= list<string> ();
280 return;
281 }
282
283 int old_mode= set_access_mode (DRD_ACCESS_SOURCE);
284 path cp= path_up (tree_path (path (), last_x, last_y, 0));
285 set_access_mode (old_mode);
286 tree mt= subtree (et, cp);
287 path p = cp;
288 list<string> ids1, ids2;
289 rectangles rs1, rs2;
290 eb->loci (last_x, last_y, 0, ids1, rs1);
291 while (rp <= p) {
292 ids2 << get_ids (subtree (et, p));
293 p= path_up (p);
294 }
295
296 locus_new_rects= rectangles ();
297 mouse_ids= list<string> ();
298 if (!is_nil (ids1 * ids2) && !has_changed (THE_FOCUS)) {
299 list<tree> l= as_list_tree (call ("link-active-upwards", object (mt)));
300 while (!is_nil (l)) {
301 tree lt= l->item;
302 path lp= reverse (obtain_ip (lt));
303 selection sel= eb->find_check_selection (lp * start(lt), lp * end(lt));
304 rs2 << outline (sel->rs, pixel);
305 l= l->next;
306 }
307 ids1= as_list_string (call ("link-active-ids", object (ids1)));
308 ids2= as_list_string (call ("link-active-ids", object (ids2)));
309 if (is_nil (ids1)) rs1= rectangles ();
310 // FIXME: we should keep track which id corresponds to which rectangle
311 locus_new_rects= rs1 * rs2;
312 mouse_ids= ids1 * ids2;
313 }
314 if (locus_new_rects != locus_rects) notify_change (THE_LOCUS);
315 }
316
317 void
update_focus_loci()318 edit_interface_rep::update_focus_loci () {
319 path p= path_up (tp);
320 list<string> ids;
321 while (rp <= p) {
322 ids << get_ids (subtree (et, p));
323 p= path_up (p);
324 }
325 focus_ids= list<string> ();
326 if (!is_nil (ids) && !has_changed (THE_FOCUS)) {
327 ids= as_list_string (call ("link-active-ids", object (ids)));
328 focus_ids= ids;
329 }
330 }
331
332 /******************************************************************************
333 * drag and double click detection for left button
334 ******************************************************************************/
335
336 static void* left_handle = NULL;
337 static bool left_started = false;
338 static bool left_dragging= false;
339 static SI left_x= 0;
340 static SI left_y= 0;
341 static time_t left_last= 0;
342 static int double_click_delay= 500;
343
344 void
drag_left_reset()345 drag_left_reset () {
346 left_started = false;
347 left_dragging= false;
348 left_x = 0;
349 left_y = 0;
350 }
351
352 void
disable_double_clicks()353 disable_double_clicks () {
354 left_last -= (double_click_delay + 1);
355 }
356
357 static string
detect_left_drag(void * handle,string type,SI x,SI y,time_t t,SI d)358 detect_left_drag (void* handle, string type, SI x, SI y, time_t t, SI d) {
359 if (left_handle != handle) drag_left_reset ();
360 left_handle= handle;
361 if (type == "press-left") {
362 left_dragging= true;
363 left_started = true;
364 left_x = x;
365 left_y = y;
366 }
367 else if (type == "move") {
368 if (left_started) {
369 if (norm (point (x - left_x, y - left_y)) < d) return "wait-left";
370 left_started= false;
371 return "start-drag-left";
372 }
373 if (left_dragging) return "dragging-left";
374 }
375 else if (type == "release-left") {
376 if (left_started) drag_left_reset ();
377 if (left_dragging) {
378 drag_left_reset ();
379 return "end-drag-left";
380 }
381 if ((t >= left_last) && ((t - left_last) <= double_click_delay)) {
382 left_last= t;
383 return "double-left";
384 }
385 left_last= t;
386 }
387 return type;
388 }
389
390 /******************************************************************************
391 * drag and double click detection for right button
392 ******************************************************************************/
393
394 static void* right_handle = NULL;
395 static bool right_started = false;
396 static bool right_dragging= false;
397 static SI right_x= 0;
398 static SI right_y= 0;
399 static time_t right_last= 0;
400
401 void
drag_right_reset()402 drag_right_reset () {
403 right_started = false;
404 right_dragging= false;
405 right_x = 0;
406 right_y = 0;
407 right_last = 0;
408 }
409
410 static string
detect_right_drag(void * handle,string type,SI x,SI y,time_t t,SI d)411 detect_right_drag (void* handle, string type, SI x, SI y, time_t t, SI d) {
412 if (right_handle != handle) drag_right_reset ();
413 right_handle= handle;
414 if (type == "press-right") {
415 right_dragging= true;
416 right_started = true;
417 right_x = x;
418 right_y = y;
419 }
420 else if (type == "move") {
421 if (right_started) {
422 if (norm (point (x - right_x, y - right_y)) < d) return "wait-right";
423 right_started= false;
424 return "start-drag-right";
425 }
426 if (right_dragging) return "dragging-right";
427 }
428 else if (type == "release-right") {
429 if (right_started) drag_right_reset ();
430 if (right_dragging) {
431 drag_right_reset ();
432 return "end-drag-right";
433 }
434 if ((t >= right_last) && ((t - right_last) <= 500)) {
435 right_last= t;
436 return "double-right";
437 }
438 right_last= t;
439 }
440 return type;
441 }
442
443 /******************************************************************************
444 * dispatching
445 ******************************************************************************/
446
447 void
mouse_any(string type,SI x,SI y,int mods,time_t t)448 edit_interface_rep::mouse_any (string type, SI x, SI y, int mods, time_t t) {
449 //cout << "Mouse any " << type << ", " << x << ", " << y << "; " << mods << ", " << t << "\n";
450 if (t < last_t && (last_x != 0 || last_y != 0 || last_t != 0)) {
451 //cout << "Ignored " << type << ", " << x << ", " << y << "; " << mods << ", " << t << "\n";
452 return;
453 }
454 last_x= x; last_y= y; last_t= t;
455 bool move_like=
456 (type == "move" || type == "dragging-left" || type == "dragging-right");
457 if ((!move_like) || (is_attached (this) && !check_event (MOTION_EVENT)))
458 update_mouse_loci ();
459 if (!is_nil (mouse_ids) && type == "move")
460 call ("link-follow-ids", object (mouse_ids), object ("mouse-over"));
461
462 if (type == "leave")
463 set_pointer ("XC_top_left_arrow");
464 if ((!move_like) && (type != "enter") && (type != "leave"))
465 set_input_normal ();
466 if (!is_nil (popup_win) && (type != "leave")) {
467 set_visibility (popup_win, false);
468 destroy_window_widget (popup_win);
469 popup_win= widget ();
470 }
471
472 if (inside_graphics (false)) {
473 if (mouse_graphics (type, x, y, mods, t)) return;
474 if (!over_graphics (x, y))
475 eval ("(graphics-reset-context 'text-cursor)");
476 }
477
478 if (type == "press-left" || type == "start-drag-left") {
479 if (mods > 1) {
480 mouse_adjusting = mods;
481 mouse_adjust_selection(x, y, mods);
482 } else
483 mouse_click (x, y);
484 }
485 if (type == "dragging-left") {
486 if (mouse_adjusting && mods > 1) {
487 mouse_adjusting = mods;
488 mouse_adjust_selection(x, y, mods);
489 } else if (is_attached (this) && check_event (DRAG_EVENT)) return;
490 else mouse_drag (x, y);
491 }
492 if ((type == "release-left" || type == "end-drag-left")) {
493 if (!(mouse_adjusting & ShiftMask))
494 mouse_select (x, y, mods, type == "end-drag-left");
495 send_mouse_grab (this, false);
496 mouse_adjusting &= ~mouse_adjusting;
497 }
498
499 if (type == "double-left") {
500 send_mouse_grab (this, false);
501 if (mouse_extra_click (x, y))
502 drag_left_reset ();
503 }
504 if (type == "press-middle") mouse_paste (x, y);
505 if (type == "press-right") mouse_adjust (x, y, mods);
506 if (type == "press-up") mouse_scroll (x, y, true);
507 if (type == "press-down") mouse_scroll (x, y, false);
508
509 if ((type == "press-left") ||
510 (type == "release-left") ||
511 (type == "end-drag-left") ||
512 (type == "press-middle") ||
513 (type == "press-right"))
514 notify_change (THE_DECORATIONS);
515 }
516
517 /******************************************************************************
518 * Event handlers
519 ******************************************************************************/
520
521 static void
call_mouse_event(string kind,SI x,SI y,SI m,time_t t)522 call_mouse_event (string kind, SI x, SI y, SI m, time_t t) {
523 array<object> args;
524 args << object (kind) << object (x) << object (y)
525 << object (m) << object ((double) t);
526 call ("mouse-event", args);
527 }
528
529 static void
delayed_call_mouse_event(string kind,SI x,SI y,SI m,time_t t)530 delayed_call_mouse_event (string kind, SI x, SI y, SI m, time_t t) {
531 // NOTE: interestingly, the (:idle 1) is not necessary for the Qt port
532 // but is required for appropriate updating when using the X11 port
533 string cmd=
534 "(delayed (:idle 1) (mouse-event " * scm_quote (kind) * " " *
535 as_string (x) * " " * as_string (y) * " " *
536 as_string (m) * " " * as_string ((long int) t) * "))";
537 eval (cmd);
538 }
539
540 void
handle_mouse(string kind,SI x,SI y,int m,time_t t)541 edit_interface_rep::handle_mouse (string kind, SI x, SI y, int m, time_t t) {
542 bool started= false;
543 #ifdef USE_EXCEPTIONS
544 try {
545 #endif
546 if (is_nil (eb)) apply_changes ();
547 start_editing ();
548 started= true;
549 x= ((SI) (x / magf));
550 y= ((SI) (y / magf));
551 //cout << kind << " (" << x << ", " << y << "; " << m << ")"
552 // << " at " << t << "\n";
553
554 string rew= kind;
555 SI dist= (SI) (5 * PIXEL / magf);
556 rew= detect_left_drag ((void*) this, rew, x, y, t, dist);
557 if (rew == "start-drag-left") {
558 call_mouse_event (rew, left_x, left_y, m, t);
559 delayed_call_mouse_event ("dragging-left", x, y, m, t);
560 }
561 else {
562 rew= detect_right_drag ((void*) this, rew, x, y, t, dist);
563 if (rew == "start-drag-right") {
564 call_mouse_event (rew, right_x, right_y, m, t);
565 delayed_call_mouse_event ("dragging-right", x, y, m, t);
566 }
567 else call_mouse_event (rew, x, y, m, t);
568 }
569 end_editing ();
570 #ifdef USE_EXCEPTIONS
571 }
572 catch (string msg) {
573 if (started) cancel_editing ();
574 }
575 handle_exceptions ();
576 #endif
577 }
578