1 /* Lepton EDA Schematic Capture
2  * Copyright (C) 1998-2010 Ales Hvezda
3  * Copyright (C) 1998-2015 gEDA Contributors
4  * Copyright (C) 2017-2021 Lepton EDA Contributors
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 #include <config.h>
21 
22 #include <stdio.h>
23 #include <math.h>
24 #ifdef HAVE_STDLIB_H
25 #include <stdlib.h>
26 #endif
27 
28 #include "gschem.h"
29 #include <gdk/gdkkeysyms.h>
30 
31 
32 /* used for the stroke stuff */
33 #ifdef HAVE_LIBSTROKE
34 static int DOING_STROKE = FALSE;
35 #endif /* HAVE_LIBSTROKE */
36 
37 #ifdef ENABLE_GTK3
38 /*! \brief Redraws the view when widget is exposed.
39  *
40  *  \param [in] view      The GschemPageView.
41  *  \param [in] cr        The cairo context.
42  *  \param [in] w_current The GschemToplevel.
43  *  \returns FALSE to propagate the event further.
44  */
45 gint
x_event_draw(GschemPageView * view,cairo_t * cr,GschemToplevel * w_current)46 x_event_draw (GschemPageView *view,
47               cairo_t *cr,
48               GschemToplevel *w_current)
49 {
50   gschem_page_view_redraw (view, cr, w_current);
51 
52   return(0);
53 }
54 
55 #else /* GTK2 */
56 
57 /*! \brief Redraws the view when widget is exposed.
58  *
59  *  \param [in] view      The GschemPageView.
60  *  \param [in] event     The event structure.
61  *  \param [in] w_current The GschemToplevel.
62  *  \returns FALSE to propagate the event further.
63  */
64 gint
x_event_expose(GschemPageView * view,GdkEventExpose * event,GschemToplevel * w_current)65 x_event_expose (GschemPageView *view,
66                 GdkEventExpose *event,
67                 GschemToplevel *w_current)
68 {
69   gschem_page_view_redraw (view, event, w_current);
70 
71   return(0);
72 }
73 #endif
74 
75 
76 /*! \todo Finish function documentation!!!
77  *  \brief
78  *  \par Function Description
79  *
80  */
81 gint
x_event_button_pressed(GschemPageView * page_view,GdkEventButton * event,GschemToplevel * w_current)82 x_event_button_pressed(GschemPageView *page_view, GdkEventButton *event, GschemToplevel *w_current)
83 {
84   LeptonPage *page = gschem_page_view_get_page (page_view);
85   int w_x, w_y;
86   int unsnapped_wx, unsnapped_wy;
87 
88   g_return_val_if_fail ((w_current != NULL), 0);
89 
90   if (page == NULL) {
91     return TRUE; /* terminate event */
92   }
93 
94   if (!gtk_widget_has_focus (GTK_WIDGET (page_view))) {
95     gtk_widget_grab_focus (GTK_WIDGET (page_view));
96   }
97 
98   scm_dynwind_begin ((scm_t_dynwind_flags) 0);
99   g_dynwind_window (w_current);
100 
101 #if DEBUG
102   printf("pressed button %d! \n", event->button);
103   printf("event state: %d \n", event->state);
104   printf("w_current state: %d \n", w_current->event_state);
105   printf("Selection is:\n");
106   o_selection_print_all(&(page->selection_list));
107   printf("\n");
108 #endif
109 
110   gschem_page_view_SCREENtoWORLD (page_view, (int) event->x, (int) event->y,
111                                   &unsnapped_wx, &unsnapped_wy);
112   w_x = snap_grid (w_current, unsnapped_wx);
113   w_y = snap_grid (w_current, unsnapped_wy);
114 
115   if (event->type == GDK_2BUTTON_PRESS &&
116       w_current->event_state == SELECT) {
117     /* Don't re-select an object (lp-912978) */
118     /* o_find_object(w_current, w_x, w_y, TRUE); */
119 
120     /* GDK_BUTTON_EVENT is emitted before GDK_2BUTTON_EVENT, which
121      * leads to setting of the inside_action flag.  If o_edit()
122      * brings up a modal window (e.g., the edit attribute dialog),
123      * it intercepts the release button event and thus doesn't
124      * allow resetting of the inside_action flag so we do it
125      * manually here before processing the double-click event. */
126     i_action_stop (w_current);
127     o_edit(w_current, lepton_list_get_glist( page->selection_list ));
128     scm_dynwind_end ();
129     return(0);
130   }
131 
132   w_current->SHIFTKEY   = (event->state & GDK_SHIFT_MASK  ) ? 1 : 0;
133   w_current->CONTROLKEY = (event->state & GDK_CONTROL_MASK) ? 1 : 0;
134   w_current->ALTKEY     = (event->state & GDK_MOD1_MASK) ? 1 : 0;
135 
136   /* Huge switch statement to evaluate state transitions. Jump to
137    * end_button_pressed label to escape the state evaluation rather than
138    * returning from the function directly. */
139 
140   if (event->button == 1) {
141     if (w_current->inside_action) {
142       /* End action */
143       if (page->place_list != NULL) {
144         switch(w_current->event_state) {
145           case (COMPMODE)   : o_place_end(w_current, w_x, w_y, w_current->continue_component_place,
146                                 "%add-objects-hook"); break;
147           case (TEXTMODE)   : o_place_end(w_current, w_x, w_y, FALSE,
148                                 "%add-objects-hook"); break;
149           case (PASTEMODE)  : o_place_end(w_current, w_x, w_y, FALSE,
150                                 "%paste-objects-hook"); break;
151           default: break;
152         }
153       } else {
154         switch(w_current->event_state) {
155           case (ARCMODE)    : o_arc_end1(w_current, w_x, w_y); break;
156           case (BOXMODE)    : o_box_end(w_current, w_x, w_y); break;
157           case (BUSMODE)    : o_bus_end(w_current, w_x, w_y); break;
158           case (CIRCLEMODE) : o_circle_end(w_current, w_x, w_y); break;
159           case (LINEMODE)   : o_line_end(w_current, w_x, w_y); break;
160           case (NETMODE)    : o_net_end(w_current, w_x, w_y); break;
161           case (PATHMODE)   : o_path_continue (w_current, w_x, w_y); break;
162           case (PICTUREMODE): o_picture_end(w_current, w_x, w_y); break;
163           case (PINMODE)    : o_pin_end (w_current, w_x, w_y); break;
164           default: break;
165         }
166       }
167     } else {
168       /* Start action */
169       switch(w_current->event_state) {
170         case (ARCMODE)    : o_arc_start(w_current, w_x, w_y); break;
171         case (BOXMODE)    : o_box_start(w_current, w_x, w_y); break;
172         case (BUSMODE)    : o_bus_start(w_current, w_x, w_y); break;
173         case (CIRCLEMODE) : o_circle_start(w_current, w_x, w_y); break;
174         case (LINEMODE)   : o_line_start(w_current, w_x, w_y); break;
175         case (NETMODE)    : o_net_start(w_current, w_x, w_y); break;
176         case (PATHMODE)   : o_path_start (w_current, w_x, w_y); break;
177         case (PICTUREMODE): o_picture_start(w_current, w_x, w_y); break;
178         case (PINMODE)    : o_pin_start (w_current, w_x, w_y); break;
179         case (ZOOMBOX)    : a_zoom_box_start(w_current, unsnapped_wx, unsnapped_wy); break;
180         case (SELECT)     : o_select_start(w_current, w_x, w_y); break;
181 
182         case (COPYMODE)   :
183         case (MCOPYMODE)  : o_copy_start(w_current, w_x, w_y); break;
184         case (MOVEMODE)   : o_move_start(w_current, w_x, w_y); break;
185         default: break;
186       }
187     }
188 
189     switch(w_current->event_state) {
190       case(ROTATEMODE):   o_rotate_world_update(w_current, w_x, w_y, 90,
191                             lepton_list_get_glist(page->selection_list)); break;
192       case(MIRRORMODE):   o_mirror_world_update(w_current, w_x, w_y,
193                             lepton_list_get_glist(page->selection_list)); break;
194 
195       case(PAN):
196         gschem_page_view_pan (page_view, w_x, w_y);
197         i_set_state(w_current, SELECT);
198         break;
199     }
200   } else if (event->button == 2) {
201 
202     /* try this out and see how it behaves */
203     if (w_current->inside_action) {
204       if (!(w_current->event_state == COMPMODE||
205             w_current->event_state == TEXTMODE||
206             w_current->event_state == MOVEMODE||
207             w_current->event_state == COPYMODE  ||
208             w_current->event_state == MCOPYMODE ||
209             w_current->event_state == PASTEMODE )) {
210         i_callback_cancel (NULL, w_current);
211       }
212       goto end_button_pressed;
213     }
214 
215     switch(w_current->middle_button) {
216 
217       case(MOUSEBTN_DO_ACTION):
218 
219       /* don't want to search if shift */
220       /* key is pressed */
221       if (!w_current->SHIFTKEY) {
222         o_find_object(w_current, unsnapped_wx, unsnapped_wy, TRUE);
223       }
224 
225       /* make sure the list is not empty */
226       if (!o_select_selected(w_current)) {
227         /* this means the above find did not
228          * find anything */
229         i_action_stop (w_current);
230         i_set_state(w_current, SELECT);
231         goto end_button_pressed;
232       }
233 
234       /* determine here if copy or move */
235       if (w_current->ALTKEY) {
236         i_set_state(w_current, COPYMODE);
237         o_copy_start(w_current, w_x, w_y);
238       } else {
239         o_move_start(w_current, w_x, w_y);
240       }
241       break;
242 
243       case(MOUSEBTN_DO_REPEAT):
244         g_scm_c_eval_string_protected
245         (
246           "( use-modules (schematic action) )"
247           "( &repeat-last-action )"
248         );
249       break;
250 #ifdef HAVE_LIBSTROKE
251       case(MOUSEBTN_DO_STROKE):
252       DOING_STROKE=TRUE;
253       break;
254 #endif /* HAVE_LIBSTROKE */
255 
256       case(MOUSEBTN_DO_PAN):
257       gschem_page_view_pan_start (page_view, (int) event->x, (int) event->y);
258       break;
259 
260       case (MOUSEBTN_DO_POPUP):
261         i_update_menus(w_current);
262         do_popup(w_current, event);
263         break;
264 
265     } /* switch w_current->middle_button */
266 
267   } else if (event->button == 3) {
268     if (!w_current->inside_action) {
269       if (w_current->third_button == MOUSEBTN_DO_POPUP) {
270         /* (third-button "popup") */
271         i_update_menus(w_current);  /* update menus before popup  */
272         do_popup(w_current, event);
273       } else {
274         /* (third-button "mousepan") */
275         gschem_page_view_pan_start (page_view, (int) event->x, (int) event->y);
276       }
277     } else {
278       if ((w_current->third_button == MOUSEBTN_DO_PAN) &&
279           (!w_current->third_button_cancel)) {
280         gschem_page_view_pan_start (page_view, (int) event->x, (int) event->y);
281       } else { /* this is the default cancel */
282 
283         /* reset all draw and place actions */
284 
285         switch (w_current->event_state) {
286 
287           case (ARCMODE)    : o_arc_invalidate_rubber     (w_current); break;
288           case (BOXMODE)    : o_box_invalidate_rubber     (w_current); break;
289           case (BUSMODE)    : o_bus_reset                 (w_current); break;
290           case (CIRCLEMODE) : o_circle_invalidate_rubber  (w_current); break;
291           case (LINEMODE)   : o_line_invalidate_rubber    (w_current); break;
292           case (NETMODE)    : o_net_reset                 (w_current); break;
293           case (PATHMODE)   : o_path_invalidate_rubber    (w_current); break;
294           case (PICTUREMODE): o_picture_invalidate_rubber (w_current); break;
295           case (PINMODE)    : o_pin_invalidate_rubber     (w_current); break;
296 
297           default:
298             i_callback_cancel (NULL, w_current);
299             break;
300         }
301       }
302     }
303   }
304 
305  end_button_pressed:
306   scm_dynwind_end ();
307 
308   return(0);
309 }
310 
311 /*! \todo Finish function documentation!!!
312  *  \brief
313  *  \par Function Description
314  *
315  */
316 gint
x_event_button_released(GschemPageView * page_view,GdkEventButton * event,GschemToplevel * w_current)317 x_event_button_released (GschemPageView *page_view, GdkEventButton *event, GschemToplevel *w_current)
318 {
319   LeptonPage *page = gschem_page_view_get_page (page_view);
320   int unsnapped_wx, unsnapped_wy;
321   int w_x, w_y;
322 
323   g_return_val_if_fail ((page_view != NULL), 0);
324   g_return_val_if_fail ((w_current != NULL), 0);
325 
326   if (page == NULL) {
327     return TRUE; /* terminate event */
328   }
329 
330 #if DEBUG
331   printf("released! %d \n", w_current->event_state);
332 #endif
333 
334   w_current->SHIFTKEY   = (event->state & GDK_SHIFT_MASK  ) ? 1 : 0;
335   w_current->CONTROLKEY = (event->state & GDK_CONTROL_MASK) ? 1 : 0;
336   w_current->ALTKEY     = (event->state & GDK_MOD1_MASK) ? 1 : 0;
337 
338   gschem_page_view_SCREENtoWORLD (page_view, (int) event->x, (int) event->y,
339                                   &unsnapped_wx, &unsnapped_wy);
340   w_x = snap_grid (w_current, unsnapped_wx);
341   w_y = snap_grid (w_current, unsnapped_wy);
342 
343   /* Huge switch statement to evaluate state transitions. Jump to
344    * end_button_released label to escape the state evaluation rather
345    * than returning from the function directly. */
346   scm_dynwind_begin ((scm_t_dynwind_flags) 0);
347   g_dynwind_window (w_current);
348 
349   if (event->button == 1) {
350 
351     if (w_current->inside_action) {
352       if (page->place_list != NULL) {
353         switch(w_current->event_state) {
354           case (COPYMODE)  :
355           case (MCOPYMODE) : o_copy_end(w_current); break;
356           case (MOVEMODE)  : o_move_end(w_current); break;
357           default: break;
358         }
359       } else {
360         switch(w_current->event_state) {
361           case (GRIPS)     : o_grips_end(w_current); break;
362           case (PATHMODE)  : o_path_end (w_current, w_x, w_y); break;
363           case (SBOX)      : o_select_box_end(w_current, unsnapped_wx, unsnapped_wy); break;
364           case (SELECT)    : o_select_end(w_current, unsnapped_wx, unsnapped_wy); break;
365           case (ZOOMBOX)   : a_zoom_box_end(w_current, unsnapped_wx, unsnapped_wy); break;
366           default: break;
367         }
368       }
369     }
370   } else if (event->button == 2) {
371 
372     if (w_current->inside_action) {
373       if (w_current->event_state == COMPMODE||
374           w_current->event_state == TEXTMODE||
375           w_current->event_state == MOVEMODE||
376           w_current->event_state == COPYMODE  ||
377           w_current->event_state == MCOPYMODE ||
378           w_current->event_state == PASTEMODE ) {
379 
380         if (w_current->event_state == MOVEMODE) {
381           o_move_invalidate_rubber (w_current, FALSE);
382         } else {
383           o_place_invalidate_rubber (w_current, FALSE);
384         }
385         w_current->rubber_visible = 0;
386 
387         o_place_rotate(w_current);
388 
389         if (w_current->event_state == COMPMODE) {
390           o_component_place_changed_run_hook (w_current);
391         }
392 
393         if (w_current->event_state == MOVEMODE) {
394           o_move_invalidate_rubber (w_current, TRUE);
395         } else {
396           o_place_invalidate_rubber (w_current, TRUE);
397         }
398         w_current->rubber_visible = 1;
399         goto end_button_released;
400       }
401     }
402 
403     switch(w_current->middle_button) {
404       case(MOUSEBTN_DO_ACTION):
405         if (w_current->inside_action && (page->place_list != NULL)) {
406           switch(w_current->event_state) {
407             case (COPYMODE): o_copy_end(w_current); break;
408             case (MOVEMODE): o_move_end(w_current); break;
409           }
410         }
411       break;
412 
413 #ifdef HAVE_LIBSTROKE
414       case(MOUSEBTN_DO_STROKE):
415       DOING_STROKE = FALSE;
416       x_stroke_translate_and_execute (w_current);
417       break;
418 #endif /* HAVE_LIBSTROKE */
419 
420       case(MOUSEBTN_DO_PAN):
421         if (gschem_page_view_pan_end (page_view) && w_current->undo_panzoom) {
422           o_undo_savestate_old(w_current, UNDO_VIEWPORT_ONLY);
423         }
424       break;
425     }
426 
427   } else if (event->button == 3) {
428       /* just for ending a mouse pan */
429       if (gschem_page_view_pan_end (page_view) && w_current->undo_panzoom) {
430         o_undo_savestate_old(w_current, UNDO_VIEWPORT_ONLY);
431       }
432   }
433  end_button_released:
434   scm_dynwind_end ();
435 
436   return(0);
437 }
438 
439 /*! \todo Finish function documentation!!!
440  *  \brief
441  *  \par Function Description
442  *
443  */
444 gint
x_event_motion(GschemPageView * page_view,GdkEventMotion * event,GschemToplevel * w_current)445 x_event_motion (GschemPageView *page_view, GdkEventMotion *event, GschemToplevel *w_current)
446 {
447   LeptonPage *page = gschem_page_view_get_page (page_view);
448   int w_x, w_y;
449   int unsnapped_wx, unsnapped_wy;
450   int skip_event=0;
451   GdkEvent *test_event;
452 
453   g_return_val_if_fail ((w_current != NULL), 0);
454 
455   if (page == NULL) {
456     return TRUE; /* terminate event */
457   }
458 
459   w_current->SHIFTKEY   = (event->state & GDK_SHIFT_MASK  ) ? 1 : 0;
460   w_current->CONTROLKEY = (event->state & GDK_CONTROL_MASK) ? 1 : 0;
461   w_current->ALTKEY     = (event->state & GDK_MOD1_MASK) ? 1 : 0;
462 
463 #if DEBUG
464   /*  printf("MOTION!\n");*/
465 #endif
466 
467 #ifdef HAVE_LIBSTROKE
468   if (DOING_STROKE == TRUE) {
469     x_stroke_record (w_current, event->x, event->y);
470     return(0);
471   }
472 #endif /* HAVE_LIBSTROKE */
473 
474   /* skip the moving event if there are other moving events in the
475      gdk event queue (Werner)
476      Only skip the event if is the same event and no buttons or modifier
477      keys changed*/
478   if ((test_event = gdk_event_get()) != NULL) {
479     if (test_event->type == GDK_MOTION_NOTIFY
480         && ((GdkEventMotion *) test_event)->state == event->state) {
481       skip_event= 1;
482     }
483     gdk_event_put(test_event); /* put it back in front of the queue */
484     gdk_event_free(test_event);
485     if (skip_event == 1)
486       return 0;
487   }
488 
489   gschem_page_view_SCREENtoWORLD (page_view, (int) event->x, (int) event->y,
490                                   &unsnapped_wx, &unsnapped_wy);
491   w_x = snap_grid (w_current, unsnapped_wx);
492   w_y = snap_grid (w_current, unsnapped_wy);
493 
494   if (w_current->cowindow) {
495     coord_display_update(w_current, (int) event->x, (int) event->y);
496   }
497 
498   gschem_page_view_pan_motion (page_view, w_current->mousepan_gain, (int) event->x, (int) event->y);
499 
500   /* Huge switch statement to evaluate state transitions. Jump to
501    * end_motion label to escape the state evaluation rather
502    * than returning from the function directly. */
503   scm_dynwind_begin ((scm_t_dynwind_flags) 0);
504   g_dynwind_window (w_current);
505 
506   if (w_current->inside_action) {
507     if (page->place_list != NULL) {
508       switch(w_current->event_state) {
509         case (COPYMODE)   :
510         case (MCOPYMODE)  :
511         case (COMPMODE)   :
512         case (PASTEMODE)  :
513         case (TEXTMODE)   : o_place_motion (w_current, w_x, w_y); break;
514         case (MOVEMODE)   : o_move_motion (w_current, w_x, w_y); break;
515         default: break;
516       }
517     } else {
518       switch(w_current->event_state) {
519         case (ARCMODE)    : o_arc_motion (w_current, w_x, w_y, ARC_RADIUS); break;
520         case (BOXMODE)    : o_box_motion  (w_current, w_x, w_y); break;
521         case (BUSMODE)    : o_bus_motion (w_current, w_x, w_y); break;
522         case (CIRCLEMODE) : o_circle_motion (w_current, w_x, w_y); break;
523         case (LINEMODE)   : o_line_motion (w_current, w_x, w_y); break;
524         case (NETMODE)    : o_net_motion (w_current, w_x, w_y); break;
525         case (PATHMODE)   : o_path_motion (w_current, w_x, w_y); break;
526         case (PICTUREMODE): o_picture_motion (w_current, w_x, w_y); break;
527         case (PINMODE)    : o_pin_motion (w_current, w_x, w_y); break;
528         case (GRIPS)      : o_grips_motion(w_current, w_x, w_y); break;
529         case (SBOX)       : o_select_box_motion (w_current, unsnapped_wx, unsnapped_wy); break;
530         case (ZOOMBOX)    : a_zoom_box_motion (w_current, unsnapped_wx, unsnapped_wy); break;
531         case (SELECT)     : o_select_motion (w_current, w_x, w_y); break;
532         default: break;
533       }
534     }
535   } else {
536     switch(w_current->event_state) {
537       case(NETMODE)    :   o_net_start_magnetic(w_current, w_x, w_y); break;
538       default: break;
539     }
540   }
541 
542   scm_dynwind_end ();
543 
544   return(0);
545 }
546 
547 /*! \brief Updates the display when drawing area is configured.
548  *  \par Function Description
549  *  This is the callback function connected to the configure event of
550  *  the GschemPageView of the main window.
551  *
552  *  It re-pans each of its pages to keep their contents centered in the
553  *  GschemPageView.
554  *
555  *  When the window is maximised, the zoom of every page is changed to
556  *  best fit the previously displayed area of the page in the new
557  *  area. Otherwise the current zoom level is left unchanged.
558  *
559  *  \param [in] widget    The GschemPageView which received the signal.
560  *  \param [in] event     The event structure of signal configure-event.
561  *  \param [in] unused
562  *  \returns FALSE to propagate the event further.
563  */
564 gboolean
x_event_configure(GschemPageView * page_view,GdkEventConfigure * event,gpointer unused)565 x_event_configure (GschemPageView    *page_view,
566                    GdkEventConfigure *event,
567                    gpointer           unused)
568 {
569   GtkAllocation current_allocation;
570   GList *iter;
571   LeptonPage *p_current = gschem_page_view_get_page (page_view);
572 
573   if (p_current == NULL) {
574     /* don't want to call this if the current page isn't setup yet */
575     return FALSE;
576   }
577 
578   g_return_val_if_fail (p_current->toplevel != NULL, FALSE);
579 
580   gtk_widget_get_allocation (GTK_WIDGET(page_view), &current_allocation);
581 
582   if ((current_allocation.width == page_view->previous_allocation.width) &&
583       (current_allocation.height == page_view->previous_allocation.height)) {
584     /* the size of the drawing area has not changed -- nothing to do here */
585     return FALSE;
586   }
587 
588   page_view->previous_allocation = current_allocation;
589 
590 
591   /* tabbed GUI: zoom/pan, mark page_view as configured and return:
592    * there is only one page per page view.
593   */
594   if (x_tabs_enabled())
595   {
596     if (page_view->configured)
597     {
598       gschem_page_view_pan_mouse (page_view, 0, 0);
599     }
600     else
601     {
602       gschem_page_view_zoom_extents (page_view, NULL);
603     }
604 
605     page_view->configured = TRUE;
606     return FALSE;
607   }
608 
609 
610   /* re-pan each page of the LeptonToplevel */
611   for ( iter = lepton_list_get_glist (p_current->toplevel->pages);
612         iter != NULL;
613         iter = g_list_next (iter) ) {
614 
615     gschem_page_view_set_page (page_view, (LeptonPage *)iter->data);
616 
617     if (page_view->configured) {
618       gschem_page_view_pan_mouse (page_view, 0, 0);
619     } else {
620       gschem_page_view_zoom_extents (page_view, NULL);
621     }
622   }
623 
624   page_view->configured = TRUE;
625 
626   gschem_page_view_set_page (page_view, p_current);
627 
628   return FALSE;
629 }
630 
631 /*! \todo Finish function documentation!!!
632  *  \brief
633  *  \par Function Description
634  *
635  */
x_event_enter(GtkWidget * widget,GdkEventCrossing * event,GschemToplevel * w_current)636 gint x_event_enter(GtkWidget *widget, GdkEventCrossing *event,
637                    GschemToplevel *w_current)
638 {
639   g_return_val_if_fail ((w_current != NULL), 0);
640   /* do nothing or now */
641   return(0);
642 }
643 
644 /*! \brief Callback to handle key events in the drawing area.
645  *  \par Function Description
646  *
647  *  GTK+ callback function (registered in x_window_setup_draw_events() ) which
648  *  handles key press and release events from the GTK+ system.
649  *
650  * \param [in] widget     the widget that generated the event
651  * \param [in] event      the event itself
652  * \param      w_current  the toplevel environment
653  * \returns TRUE if the event has been handled.
654  */
655 gboolean
x_event_key(GschemPageView * page_view,GdkEventKey * event,GschemToplevel * w_current)656 x_event_key (GschemPageView *page_view, GdkEventKey *event, GschemToplevel *w_current)
657 {
658   gboolean retval = FALSE;
659   int pressed;
660   gboolean special = FALSE;
661 
662   g_return_val_if_fail (page_view != NULL, FALSE);
663 
664 #if DEBUG
665   printf("x_event_key_pressed: Pressed key %i.\n", event->keyval);
666 #endif
667 
668   /* update the state of the modifiers */
669   w_current->ALTKEY     = (event->state & GDK_MOD1_MASK)    ? 1 : 0;
670   w_current->SHIFTKEY   = (event->state & GDK_SHIFT_MASK)   ? 1 : 0;
671   w_current->CONTROLKEY = (event->state & GDK_CONTROL_MASK) ? 1 : 0;
672 
673   pressed = (event->type == GDK_KEY_PRESS) ? 1 : 0;
674 
675   switch (event->keyval) {
676     case GDK_KEY_Alt_L:
677     case GDK_KEY_Alt_R:
678       w_current->ALTKEY = pressed;
679       break;
680 
681     case GDK_KEY_Shift_L:
682     case GDK_KEY_Shift_R:
683       w_current->SHIFTKEY = pressed;
684       special = TRUE;
685       break;
686 
687     case GDK_KEY_Control_L:
688     case GDK_KEY_Control_R:
689       w_current->CONTROLKEY = pressed;
690       special = TRUE;
691       break;
692   }
693 
694   scm_dynwind_begin ((scm_t_dynwind_flags) 0);
695   g_dynwind_window (w_current);
696 
697   /* Special case to update the object being drawn or placed after
698    * scrolling when Shift or Control were pressed */
699   if (special) {
700     x_event_faked_motion (page_view, event);
701   }
702 
703   if (pressed)
704     retval = g_keys_execute (w_current, event) ? TRUE : FALSE;
705 
706   scm_dynwind_end ();
707 
708   return retval;
709 }
710 
711 
712 /*! \todo Finish function documentation!!!
713  *  \brief
714  *  \par Function Description
715  *
716  *  \param [in] widget The GschemPageView with the scroll event.
717  *  \param [in] event
718  *  \param [in] w_current
719  */
x_event_scroll(GtkWidget * widget,GdkEventScroll * event,GschemToplevel * w_current)720 gint x_event_scroll (GtkWidget *widget, GdkEventScroll *event,
721                      GschemToplevel *w_current)
722 {
723   GtkAdjustment *adj;
724   gboolean pan_xaxis = FALSE;
725   gboolean pan_yaxis = FALSE;
726   gboolean zoom = FALSE;
727   int pan_direction = 1;
728   int zoom_direction = ZOOM_IN;
729   GschemPageView *view = NULL;
730   LeptonPage *page = NULL;
731 
732   g_return_val_if_fail ((w_current != NULL), 0);
733 
734   view = GSCHEM_PAGE_VIEW (widget);
735   g_return_val_if_fail ((view != NULL), 0);
736 
737   page = gschem_page_view_get_page (view);
738 
739   if (page == NULL) {
740     return FALSE; /* we cannot zoom page if it doesn't exist :) */
741   }
742 
743   /* update the state of the modifiers */
744   w_current->SHIFTKEY   = (event->state & GDK_SHIFT_MASK  ) ? 1 : 0;
745   w_current->CONTROLKEY = (event->state & GDK_CONTROL_MASK) ? 1 : 0;
746   w_current->ALTKEY     = (event->state & GDK_MOD1_MASK) ? 1 : 0;
747 
748   if (w_current->scroll_wheel == SCROLL_WHEEL_CLASSIC) {
749     /* Classic gschem behaviour */
750     zoom =      !w_current->CONTROLKEY && !w_current->SHIFTKEY;
751     pan_yaxis = !w_current->CONTROLKEY &&  w_current->SHIFTKEY;
752     pan_xaxis =  w_current->CONTROLKEY && !w_current->SHIFTKEY;
753   } else {
754     /* GTK style behaviour */
755     zoom =       w_current->CONTROLKEY && !w_current->SHIFTKEY;
756     pan_yaxis = !w_current->CONTROLKEY && !w_current->SHIFTKEY;
757     pan_xaxis = !w_current->CONTROLKEY &&  w_current->SHIFTKEY;
758   }
759 
760   /* If the user has a left/right scroll wheel, always scroll the y-axis */
761   if (event->direction == GDK_SCROLL_LEFT ||
762       event->direction == GDK_SCROLL_RIGHT) {
763     zoom = FALSE;
764     pan_yaxis = FALSE;
765     pan_xaxis = TRUE;
766   }
767 
768   /* You must have scrollbars enabled if you want to use the scroll wheel to pan */
769   if (!w_current->scrollbars_flag) {
770     pan_xaxis = FALSE;
771     pan_yaxis = FALSE;
772   }
773 
774 #ifdef ENABLE_GTK3
775   static guint last_scroll_event_time = GDK_CURRENT_TIME;
776   /* check for duplicate legacy scroll event, see GNOME bug 726878 */
777   if (event->direction != GDK_SCROLL_SMOOTH &&
778       last_scroll_event_time == event->time) {
779     g_debug ("[%d] duplicate legacy scroll event %d\n",
780              event->time,
781              event->direction);
782     return FALSE;
783   }
784 
785   switch (event->direction) {
786   case GDK_SCROLL_SMOOTH:
787     /* As of GTK 3.4, all directional scroll events are provided by */
788     /* the GDK_SCROLL_SMOOTH direction on XInput2 and Wayland devices. */
789     last_scroll_event_time = event->time;
790 
791     /* event->delta_x seems to be unused on not touch devices. */
792     pan_direction = event->delta_y;
793     zoom_direction = (event->delta_y > 0) ? ZOOM_OUT : ZOOM_IN;
794     break;
795   case GDK_SCROLL_UP:
796   case GDK_SCROLL_LEFT:
797     pan_direction = -1;
798     zoom_direction = ZOOM_IN;
799     break;
800   case GDK_SCROLL_DOWN:
801   case GDK_SCROLL_RIGHT:
802     pan_direction =  1;
803     zoom_direction = ZOOM_OUT;
804     break;
805   }
806 #else
807   switch (event->direction) {
808     case GDK_SCROLL_UP:
809     case GDK_SCROLL_LEFT:
810       pan_direction = -1;
811       zoom_direction = ZOOM_IN;
812       break;
813     case GDK_SCROLL_DOWN:
814     case GDK_SCROLL_RIGHT:
815       pan_direction =  1;
816       zoom_direction = ZOOM_OUT;
817       break;
818   }
819 #endif
820 
821   if (zoom) {
822     /*! \todo Change "HOTKEY" TO new "MOUSE" specifier? */
823     a_zoom(w_current, GSCHEM_PAGE_VIEW (widget), zoom_direction, HOTKEY);
824   }
825 
826   if (pan_xaxis) {
827     adj = gschem_page_view_get_hadjustment (GSCHEM_PAGE_VIEW (widget));
828     g_return_val_if_fail (adj != NULL, TRUE);
829     gtk_adjustment_set_value (adj,
830                               MIN (gtk_adjustment_get_value (adj) + pan_direction *
831                                    (gtk_adjustment_get_page_increment (adj) /
832                                     w_current->scrollpan_steps),
833                                    gtk_adjustment_get_upper (adj) -
834                                    gtk_adjustment_get_page_size (adj)));
835   }
836 
837   if (pan_yaxis) {
838     adj = gschem_page_view_get_vadjustment (GSCHEM_PAGE_VIEW (widget));
839     g_return_val_if_fail (adj != NULL, TRUE);
840     gtk_adjustment_set_value (adj,
841                               MIN (gtk_adjustment_get_value (adj) + pan_direction *
842                                    (gtk_adjustment_get_page_increment (adj) /
843                                     w_current->scrollpan_steps),
844                                    gtk_adjustment_get_upper (adj) -
845                                    gtk_adjustment_get_page_size (adj)));
846   }
847 
848   if (w_current->undo_panzoom && (zoom || pan_xaxis || pan_yaxis)) {
849     o_undo_savestate_old(w_current, UNDO_VIEWPORT_ONLY);
850   }
851 
852   x_event_faked_motion (view, NULL);
853   /* Stop further processing of this signal */
854   return TRUE;
855 }
856 
857 
858 /*! \brief get the pointer position of a given GschemToplevel
859  *  \par Function Description
860  *  This function gets the pointer position of the drawing area of the
861  *  current workspace <b>GschemToplevel</b>. The flag <b>snapped</b> specifies
862  *  whether the pointer position should be snapped to the current grid.
863  *
864  *  \param [in] w_current  The GschemToplevel object.
865  *  \param [in] snapped    An option flag to specify the wished coords
866  *  \param [out] wx        snapped/unsnapped world x coordinate
867  *  \param [out] wy        snapped/unsnapped world y coordinate
868  *
869  *  \return Returns TRUE if the pointer position is inside the drawing area.
870  *
871  */
872 gboolean
x_event_get_pointer_position(GschemToplevel * w_current,gboolean snapped,gint * wx,gint * wy)873 x_event_get_pointer_position (GschemToplevel *w_current, gboolean snapped, gint *wx, gint *wy)
874 {
875   int width;
876   int height;
877   int sx;
878   int sy;
879   int x;
880   int y;
881 
882   GschemPageView *page_view = gschem_toplevel_get_current_page_view (w_current);
883   g_return_val_if_fail (page_view != NULL, FALSE);
884 
885   GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (page_view));
886   g_return_val_if_fail (window != NULL, FALSE);
887 
888   width = gdk_window_get_width (window);
889   height = gdk_window_get_height (window);
890 
891 #ifdef ENABLE_GTK3
892   GdkDisplay *display = gdk_window_get_display (window);
893   GdkSeat *seat = gdk_display_get_default_seat (display);
894   GdkDevice *pointer = gdk_seat_get_pointer (seat);
895 
896   gdk_window_get_device_position (window, pointer, &sx, &sy, NULL);
897 #else
898   gtk_widget_get_pointer(GTK_WIDGET (page_view), &sx, &sy);
899 #endif
900 
901   /* check if we are inside the drawing area */
902   if ((sx < 0) || (sx >= width) || (sy < 0) || (sy >= height)) {
903     return FALSE;
904   }
905 
906   gschem_page_view_SCREENtoWORLD (page_view, sx, sy, &x, &y);
907 
908   if (snapped) {
909     x = snap_grid (w_current, x);
910     y = snap_grid (w_current, y);
911   }
912 
913   *wx = x;
914   *wy = y;
915 
916   return TRUE;
917 }
918 
919 /*! \brief Emits a faked motion event to update objects being drawn or placed
920  *  \par Function Description
921  *  This function emits an additional "motion-notify-event" to
922  *  update objects being drawn or placed while zooming, scrolling, or
923  *  panning.
924  *
925  *  If its event parameter is not NULL, the current state of Shift
926  *  and Control is preserved to correctly deal with special cases.
927  *
928  *  \param [in] view      The GschemPageView object which received the signal.
929  *  \param [in] event     The event structure of the signal or NULL.
930  *  \returns FALSE to propagate the event further.
931  */
932 gboolean
x_event_faked_motion(GschemPageView * view,GdkEventKey * event)933 x_event_faked_motion (GschemPageView *view, GdkEventKey *event) {
934   gint x, y;
935   gboolean ret;
936   GdkEventMotion *newevent;
937 
938 #ifdef ENABLE_GTK3
939   GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (view));
940   g_return_val_if_fail (window != NULL, FALSE);
941 
942   GdkDisplay *display = gdk_window_get_display (window);
943   GdkSeat *seat = gdk_display_get_default_seat (display);
944   GdkDevice *pointer = gdk_seat_get_pointer (seat);
945 
946   gdk_window_get_device_position (window, pointer, &x, &y, NULL);
947 #else
948   gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
949 #endif
950   newevent = (GdkEventMotion*)gdk_event_new(GDK_MOTION_NOTIFY);
951   newevent->x = x;
952   newevent->y = y;
953 
954   if (event != NULL ) {
955     switch (event->keyval) {
956       case GDK_KEY_Control_L:
957       case GDK_KEY_Control_R:
958         if (event->type == GDK_KEY_PRESS) {
959           newevent->state |= GDK_CONTROL_MASK;
960         } else {
961           newevent->state &= ~GDK_CONTROL_MASK;
962         }
963         break;
964 
965       case GDK_KEY_Shift_L:
966       case GDK_KEY_Shift_R:
967         if (event->type == GDK_KEY_PRESS) {
968           newevent->state |= GDK_SHIFT_MASK;
969         } else {
970           newevent->state &= ~GDK_SHIFT_MASK;
971         }
972         break;
973     }
974   }
975 
976   g_signal_emit_by_name (view, "motion-notify-event", newevent, &ret);
977 
978   gdk_event_free((GdkEvent*)newevent);
979 
980   return FALSE;
981 }
982