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