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), ¤t_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