1 /*
2 * sheet.c
3 *
4 *
5 * Authors:
6 * Richard Hult <rhult@hem.passagen.se>
7 * Ricardo Markiewicz <rmarkie@fi.uba.ar>
8 * Andres de Barbara <adebarbara@fi.uba.ar>
9 * Marc Lorber <lorber.marc@wanadoo.fr>
10 * Bernhard Schuster <bernhard@ahoi.io>
11 * Guido Trentalancia <guido@trentalancia.com>
12 *
13 * Web page: https://ahoi.io/project/oregano
14 *
15 * Copyright (C) 1999-2001 Richard Hult
16 * Copyright (C) 2003,2006 Ricardo Markiewicz
17 * Copyright (C) 2009-2012 Marc Lorber
18 * Copyright (C) 2013-2019 Bernhard Schuster
19 * Copyright (C) 2017 Guido Trentalancia
20 *
21 * This program is free software; you can redistribute it and/or
22 * modify it under the terms of the GNU General Public License as
23 * published by the Free Software Foundation; either version 2 of the
24 * License, or (at your option) any later version.
25 *
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
29 * General Public License for more details.
30 *
31 * You should have received a copy of the GNU General Public
32 * License along with this program; if not, write to the
33 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
34 * Boston, MA 02110-1301, USA.
35 */
36
37 #include <gdk/gdk.h>
38 #include <gdk/gdkkeysyms.h>
39 #include <math.h>
40 #include <goocanvas.h>
41 #include <goocanvasutils.h>
42
43 #include "sheet-private.h"
44 #include "sheet-item.h"
45 #include "node-store.h"
46 #include "node-item.h"
47 #include "wire-item.h"
48 #include "part-item.h"
49 #include "grid.h"
50 #include "sheet-item-factory.h"
51 #include "schematic-view.h"
52 #include "options.h"
53 #include "rubberband.h"
54 #include "create-wire.h"
55
56 static void sheet_class_init (SheetClass *klass);
57 static void sheet_init (Sheet *sheet);
58 static void sheet_set_property (GObject *object, guint prop_id, const GValue *value,
59 GParamSpec *spec);
60 static void sheet_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *spec);
61 static void sheet_set_zoom (Sheet *sheet, const double zoom);
62 static GList *sheet_preserve_selection (Sheet *sheet);
63 static void rotate_items (Sheet *sheet, GList *items, gint angle);
64 static void move_items (Sheet *sheet, GList *items, const Coords *delta);
65 static void flip_items (Sheet *sheet, GList *items, IDFlip direction);
66 static void node_dot_added_callback (Schematic *schematic, Coords *pos, Sheet *sheet);
67 static void node_dot_removed_callback (Schematic *schematic, Coords *pos, Sheet *sheet);
68 static void sheet_finalize (GObject *object);
69 static int dot_equal (gconstpointer a, gconstpointer b);
70 static guint dot_hash (gconstpointer key);
71
72 #define ZOOM_MIN 0.35
73 #define ZOOM_MAX 3
74
75 #include "debug.h"
76
77 enum { SELECTION_CHANGED, BUTTON_PRESS, CONTEXT_CLICK, CANCEL, LAST_SIGNAL };
78 static guint signals[LAST_SIGNAL] = {0};
79
80 enum { ARG_0, ARG_ZOOM };
81
G_DEFINE_TYPE(Sheet,sheet,GOO_TYPE_CANVAS)82 G_DEFINE_TYPE (Sheet, sheet, GOO_TYPE_CANVAS)
83
84 static void sheet_class_init (SheetClass *sheet_class)
85 {
86 GObjectClass *object_class;
87
88 object_class = G_OBJECT_CLASS (sheet_class);
89
90 object_class->set_property = sheet_set_property;
91 object_class->get_property = sheet_get_property;
92 object_class->finalize = sheet_finalize;
93 sheet_parent_class = g_type_class_peek (GOO_TYPE_CANVAS);
94
95 g_object_class_install_property (object_class, ARG_ZOOM,
96 g_param_spec_double ("zoom", "Sheet::zoom", "the zoom factor",
97 0.01f, 10.0f, 1.0f, G_PARAM_READWRITE));
98
99 // Signals.
100 signals[SELECTION_CHANGED] =
101 g_signal_new ("selection_changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST,
102 G_STRUCT_OFFSET (SheetClass, selection_changed), NULL, NULL,
103 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
104
105 signals[BUTTON_PRESS] =
106 g_signal_new ("button_press", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST,
107 G_STRUCT_OFFSET (SheetClass, button_press), NULL, NULL,
108 g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, GDK_TYPE_EVENT);
109
110 signals[CONTEXT_CLICK] = g_signal_new (
111 "context_click", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST,
112 G_STRUCT_OFFSET (SheetClass, context_click), NULL, NULL, g_cclosure_marshal_VOID__POINTER,
113 G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_POINTER);
114
115 signals[CANCEL] =
116 g_signal_new ("cancel", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST,
117 G_STRUCT_OFFSET (SheetClass, cancel), NULL, NULL,
118 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
119 }
120
sheet_init(Sheet * sheet)121 static void sheet_init (Sheet *sheet)
122 {
123 sheet->priv = g_new0 (SheetPriv, 1);
124 sheet->priv->zoom = 1.0;
125 sheet->priv->selected_group = NULL;
126 sheet->priv->floating_group = NULL;
127 sheet->priv->floating_objects = NULL;
128 sheet->priv->selected_objects = NULL;
129 sheet->priv->wire_handler_id = 0;
130 sheet->priv->float_handler_id = 0;
131
132 sheet->priv->items = NULL;
133 sheet->priv->rubberband_info = NULL;
134 sheet->priv->create_wire_info = NULL;
135 sheet->priv->preserve_selection_items = NULL;
136 sheet->priv->sheet_parent_class = g_type_class_ref (GOO_TYPE_CANVAS);
137 sheet->priv->voltmeter_nodes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
138
139 sheet->state = SHEET_STATE_NONE;
140
141 // track these to cancel wires if the cursor leaves the window
142 gtk_widget_add_events(GTK_WIDGET(sheet), GDK_LEAVE_NOTIFY | GDK_ENTER_NOTIFY);
143 }
144
sheet_finalize(GObject * object)145 static void sheet_finalize (GObject *object)
146 {
147 Sheet *sheet = SHEET (object);
148
149 if (sheet->priv) {
150 g_list_free (sheet->priv->selected_objects);
151 g_list_free (sheet->priv->floating_objects);
152 g_list_free (sheet->priv->items);
153 g_list_free (sheet->priv->preserve_selection_items);
154
155 if (sheet->priv->voltmeter_nodes)
156 g_hash_table_destroy (sheet->priv->voltmeter_nodes);
157
158 if (sheet->priv->node_dots)
159 g_hash_table_destroy (sheet->priv->node_dots);
160
161 g_free (sheet->priv->rubberband_info);
162 g_free (sheet->priv->create_wire_info);
163
164 g_free (sheet->priv);
165 }
166
167 if (sheet->grid)
168 g_object_unref (G_OBJECT (sheet->grid));
169
170 if (G_OBJECT_CLASS (sheet_parent_class)->finalize)
171 (*G_OBJECT_CLASS (sheet_parent_class)->finalize)(object);
172 }
173
174 /**
175 * position within the sheet in pixel coordinates
176 *
177 * coordinates are clamped to grid if grid is enabled
178 * see snap_to_grid
179 * zero point : top left corner of the window (not widget!)
180 *
181 * @param x horizontal, left to right
182 * @param y vertical, top to bottom
183 * @returns wether the position could be detected properly
184 *
185 * @attention never call in event handlers!
186 */
sheet_get_pointer_pixel(Sheet * sheet,gdouble * x,gdouble * y)187 gboolean sheet_get_pointer_pixel (Sheet *sheet, gdouble *x, gdouble *y)
188 {
189 GtkAdjustment *hadj = NULL, *vadj = NULL;
190 gdouble x1, y1;
191 gint _x, _y;
192 GdkDevice *device_pointer;
193 GdkRectangle allocation;
194 GdkDisplay *display;
195 GdkWindow *window;
196 #if GTK_CHECK_VERSION (3,20,0)
197 GdkSeat *seat;
198 #else
199 GdkDeviceManager *device_manager;
200 #endif
201
202 // deprecated gtk_widget_get_pointer (GTK_WIDGET (sheet), &_x, &_y);
203 // replaced by a code copied from evince
204
205 if (G_UNLIKELY (!sheet || !gtk_widget_get_realized (GTK_WIDGET (sheet)))) {
206 NG_DEBUG ("Widget is not realized.");
207 return FALSE;
208 }
209
210 display = gtk_widget_get_display (GTK_WIDGET (sheet));
211
212 #if GTK_CHECK_VERSION (3,20,0)
213 seat = gdk_display_get_default_seat (display);
214 device_pointer = gdk_seat_get_pointer (seat);
215 #else
216 device_manager = gdk_display_get_device_manager (display);
217
218 // gdk_device_manager_get_client_pointer
219 // shall not be used within events
220 device_pointer = gdk_device_manager_get_client_pointer (device_manager);
221 #endif
222
223 window = gtk_widget_get_window (GTK_WIDGET (sheet));
224
225 if (!window) {
226 NG_DEBUG ("Window is not realized.");
227 return FALSE;
228 }
229 // even though above is all defined the below will always return NUL for
230 // unknown reason and _x and _y are populated as expected
231 gdk_window_get_device_position (window, device_pointer, &_x, &_y, NULL);
232 #if 0
233 if (!window) { //fails always
234 NG_DEBUG ("Window does not seem to be realized yet?");
235 return FALSE;
236 }
237 #else
238 #if GTK_CHECK_VERSION (3,20,0)
239 NG_DEBUG ("\n%p %p %p %p %i %i\n\n", display, seat, device_pointer, window, _x, _y);
240 #else
241 NG_DEBUG ("\n%p %p %p %p %i %i\n\n", display, device_manager, device_pointer, window, _x, _y);
242 #endif
243 #endif
244
245 gtk_widget_get_allocation (GTK_WIDGET (sheet), &allocation);
246
247 _x -= allocation.x;
248 _y -= allocation.y;
249
250 x1 = (gdouble)_x;
251 y1 = (gdouble)_y;
252
253 if (!sheet_get_adjustments (sheet, &hadj, &vadj)) {
254 return FALSE;
255 }
256
257 x1 += gtk_adjustment_get_value (hadj);
258 y1 += gtk_adjustment_get_value (vadj);
259
260 *x = x1;
261 *y = y1;
262 return TRUE;
263 }
264
265 /**
266 * get the pointer position in goocanvas coordinates
267 *
268 * @attention shall not be called in event callbacks,
269 * except for GDK_MOTION_... where it is useless since
270 * the event itself contains the cursor position
271 */
sheet_get_pointer(Sheet * sheet,gdouble * x,gdouble * y)272 gboolean sheet_get_pointer (Sheet *sheet, gdouble *x, gdouble *y)
273 {
274 if (!sheet_get_pointer_pixel (sheet, x, y)) {
275 return FALSE;
276 }
277 goo_canvas_convert_from_pixels (GOO_CANVAS (sheet), x, y);
278 return TRUE;
279 }
280
sheet_get_pointer_snapped(Sheet * sheet,gdouble * x,gdouble * y)281 gboolean sheet_get_pointer_snapped (Sheet *sheet, gdouble *x, gdouble *y)
282 {
283 if (!sheet_get_pointer_pixel (sheet, x, y)) {
284 return FALSE;
285 }
286 goo_canvas_convert_from_pixels (GOO_CANVAS (sheet), x, y);
287 snap_to_grid (sheet->grid, x, y);
288 return TRUE;
289 }
290
sheet_get_zoom(const Sheet * sheet,gdouble * zoom)291 void sheet_get_zoom (const Sheet *sheet, gdouble *zoom)
292 {
293 g_return_if_fail (sheet);
294 g_return_if_fail (IS_SHEET (sheet));
295
296 *zoom = sheet->priv->zoom;
297 }
298
sheet_set_zoom(Sheet * sheet,const double zoom)299 static void sheet_set_zoom (Sheet *sheet, const double zoom)
300 {
301 g_return_if_fail (sheet);
302 g_return_if_fail (IS_SHEET (sheet));
303
304 sheet->priv->zoom = zoom;
305 }
306
307 /*
308 * \brief gets the sheets parent adjustments
309 *
310 * @returns TRUE on success
311 */
sheet_get_adjustments(const Sheet * sheet,GtkAdjustment ** hadj,GtkAdjustment ** vadj)312 gboolean sheet_get_adjustments (const Sheet *sheet, GtkAdjustment **hadj, GtkAdjustment **vadj)
313 {
314 GtkWidget *parent;
315 GtkScrolledWindow *scrolled;
316
317 if (G_UNLIKELY (!sheet))
318 return FALSE;
319 if (G_UNLIKELY (!vadj || !hadj))
320 return FALSE;
321
322 parent = gtk_widget_get_parent (GTK_WIDGET (sheet));
323 if (G_UNLIKELY (!parent || !GTK_IS_SCROLLED_WINDOW (parent)))
324 return FALSE;
325 scrolled = GTK_SCROLLED_WINDOW (parent);
326
327 *hadj = gtk_scrolled_window_get_hadjustment (scrolled);
328 if (G_UNLIKELY (!*hadj || !GTK_IS_ADJUSTMENT (*hadj)))
329 return FALSE;
330
331 *vadj = gtk_scrolled_window_get_vadjustment (scrolled);
332 if (G_UNLIKELY (!*vadj || !GTK_IS_ADJUSTMENT (*vadj)))
333 return FALSE;
334
335 return TRUE;
336 }
337
338 /**
339 * \brief change the zoom by factor (zoom step)
340 *
341 * zoom origin when zooming in is the cursor
342 * zoom origin when zooming out is the center of the current viewport
343 *
344 * @param sheet
345 * @param factor values should be in the range of [0.5 .. 2]
346 */
sheet_zoom_step(Sheet * sheet,const gdouble factor)347 void sheet_zoom_step (Sheet *sheet, const gdouble factor)
348 {
349 double zoom;
350
351 g_return_if_fail (sheet);
352 g_return_if_fail (IS_SHEET (sheet));
353
354 sheet_get_zoom (sheet, &zoom);
355 sheet_set_zoom (sheet, zoom * factor);
356
357 Coords adju, r, pointer, delta, center, pagesize;
358 GtkAdjustment *hadj = NULL, *vadj = NULL;
359 GooCanvas *canvas;
360 gboolean b = FALSE;
361
362 canvas = GOO_CANVAS (sheet);
363
364 // if we scroll out, just use the center as focus
365 // mouse curser centered scroll out "feels" awkward
366 if (factor < 1.) {
367 goo_canvas_set_scale (canvas, factor * goo_canvas_get_scale (canvas));
368 return;
369 }
370
371 // get pointer position in pixels
372 // just skip the correction if we can not get the pointer
373 if (!sheet_get_pointer_pixel (sheet, &pointer.x, &pointer.y)) {
374 goo_canvas_set_scale (canvas, factor * goo_canvas_get_scale (canvas));
375 g_warning ("Failed to get cursor position.");
376 return;
377 }
378
379 // top left corner in pixels
380 b = sheet_get_adjustments (sheet, &hadj, &vadj);
381 if (b) {
382 adju.x = gtk_adjustment_get_value (hadj);
383 adju.y = gtk_adjustment_get_value (vadj);
384 // get the page size in pixels
385 pagesize.x = gtk_adjustment_get_page_size (hadj);
386 pagesize.y = gtk_adjustment_get_page_size (vadj);
387 } else {
388 // FIXME untested codepath, check for variable space conversion
389 // FIXME Pixel vs GooUnits
390 gdouble left, right, top, bottom;
391 goo_canvas_get_bounds (canvas, &left, &top, &right, &bottom);
392 pagesize.x = bottom - top;
393 pagesize.y = right - left;
394 adju.x = adju.y = 0.;
395 }
396
397 // calculate the center of the widget in pixels
398 center.x = adju.x + pagesize.x / 2.;
399 center.y = adju.y + pagesize.y / 2.;
400
401 // calculate the delta between the center and the pointer in pixels
402 // this is required as the center is the zoom target
403 delta.x = pointer.x - center.x;
404 delta.y = pointer.y - center.y;
405
406 // increase the top left position in pixels by our calculated delta
407 adju.x += delta.x;
408 adju.y += delta.y;
409
410 // convert to canvas coords
411 goo_canvas_convert_from_pixels (canvas, &adju.x, &adju.y);
412
413 // the center of the canvas is now our cursor position
414 goo_canvas_scroll_to (canvas, adju.x, adju.y);
415
416 // calculate a correction term
417 // for the case that we can not scroll the pane far enough to
418 // compensate the whole off-due-to-wrong-center-error
419
420 if (b) {
421 r.x = gtk_adjustment_get_value (hadj);
422 r.y = gtk_adjustment_get_value (vadj);
423 goo_canvas_convert_from_pixels (canvas, &r.x, &r.y);
424 // the correction term in goo coordinates, to be subtracted from the
425 // backscroll distance
426 r.x -= adju.x;
427 r.y -= adju.y;
428 } else {
429 r.x = r.y = 0.;
430 }
431
432 // no the center is our cursor position and we can safely call scale
433 goo_canvas_set_scale (canvas, factor * goo_canvas_get_scale (canvas));
434
435 // top left corner in pixels after scaling
436 if (b) {
437 adju.x = gtk_adjustment_get_value (hadj);
438 adju.y = gtk_adjustment_get_value (vadj);
439 } else {
440 adju.x = adju.y = 0.;
441 }
442
443 // gtk_adjustment_get_page_size is constant before and after scale
444 adju.x -= (delta.x) / sheet->priv->zoom;
445 adju.y -= (delta.y) / sheet->priv->zoom;
446 goo_canvas_convert_from_pixels (canvas, &adju.x, &adju.y);
447
448 goo_canvas_scroll_to (canvas, adju.x - r.x, adju.y - r.y);
449
450 gtk_widget_queue_draw (GTK_WIDGET (canvas));
451 }
452
453 /**
454 * \brief defines the drawing widget on which the actual schematic will be drawn
455 *
456 * @param width width of the content area
457 * @param height height of the content area
458 */
sheet_new(const gdouble width,const gdouble height)459 GtkWidget *sheet_new (const gdouble width, const gdouble height)
460 {
461 GooCanvas *sheet_canvas;
462 GooCanvasGroup *sheet_group;
463 GooCanvasPoints *points;
464 Sheet *sheet;
465 GtkWidget *sheet_widget;
466 GooCanvasItem *root;
467
468 // Creation of the Canvas
469 sheet = SHEET (g_object_new (TYPE_SHEET, NULL));
470
471 sheet_canvas = GOO_CANVAS (sheet);
472 g_object_set (G_OBJECT (sheet_canvas), "bounds-from-origin", FALSE, "bounds-padding", 4.0,
473 "background-color-rgb", 0xFFFFFF, NULL);
474
475 root = goo_canvas_get_root_item (sheet_canvas);
476
477 sheet_group = GOO_CANVAS_GROUP (goo_canvas_group_new (root, NULL));
478 sheet_widget = GTK_WIDGET (sheet);
479
480 goo_canvas_set_bounds (GOO_CANVAS (sheet_canvas), 0., 0., width + 20., height + 20.);
481
482 sheet->priv->width = width;
483 sheet->priv->height = height;
484
485 // Create the dot grid.
486 sheet->grid = grid_new (GOO_CANVAS_ITEM (sheet_group), width, height);
487
488 // Everything outside the sheet should be gray.
489 // top //
490 goo_canvas_rect_new (GOO_CANVAS_ITEM (sheet_group), 0.0, 0.0, width + 20.0, 20.0, "fill_color",
491 "gray", "line-width", 0.0, NULL);
492
493 goo_canvas_rect_new (GOO_CANVAS_ITEM (sheet_group), 0.0, height, width + 20.0, height + 20.0,
494 "fill_color", "gray", "line-width", 0.0, NULL);
495
496 // right //
497 goo_canvas_rect_new (GOO_CANVAS_ITEM (sheet_group), 0.0, 0.0, 20.0, height + 20.0, "fill_color",
498 "gray", "line-width", 0.0, NULL);
499
500 goo_canvas_rect_new (GOO_CANVAS_ITEM (sheet_group), width, 0.0, width + 20.0, height + 20.0,
501 "fill_color", "gray", "line-width", 0.0, NULL);
502
503 if (oregano_options_debug_directions ()) {
504 goo_canvas_polyline_new_line (GOO_CANVAS_ITEM (sheet_group), 10.0, 10.0, 50.0, 10.0,
505 "stroke-color", "green", "line-width", 2.0, "end-arrow", TRUE,
506 NULL);
507 goo_canvas_text_new (GOO_CANVAS_ITEM (sheet_group), "x", 90.0, 10.0, -1.0,
508 GOO_CANVAS_ANCHOR_WEST, "fill-color", "green", NULL);
509
510 goo_canvas_polyline_new_line (GOO_CANVAS_ITEM (sheet_group), 10.0, 10.0, 10.0, 50.0,
511 "stroke-color", "red", "line-width", 2.0, "end-arrow", TRUE,
512 NULL);
513 goo_canvas_text_new (GOO_CANVAS_ITEM (sheet_group), "y", 10.0, 90.0, -1.0,
514 GOO_CANVAS_ANCHOR_CENTER, "fill-color", "red", NULL);
515 }
516 // Draw a thin black border around the sheet.
517 points = goo_canvas_points_new (5);
518 points->coords[0] = 20.0;
519 points->coords[1] = 20.0;
520 points->coords[2] = width;
521 points->coords[3] = 20.0;
522 points->coords[4] = width;
523 points->coords[5] = height;
524 points->coords[6] = 20.0;
525 points->coords[7] = height;
526 points->coords[8] = 20.0;
527 points->coords[9] = 20.0;
528
529 goo_canvas_polyline_new (GOO_CANVAS_ITEM (sheet_group), FALSE, 0, "line-width", 1.0, "points",
530 points, NULL);
531
532 goo_canvas_points_unref (points);
533
534 // Finally, create the object group that holds all objects.
535 sheet->object_group = GOO_CANVAS_GROUP (goo_canvas_group_new (root, "x", 0.0, "y", 0.0, NULL));
536 NG_DEBUG ("root group %p", sheet->object_group);
537
538 sheet->priv->selected_group = GOO_CANVAS_GROUP (
539 goo_canvas_group_new (GOO_CANVAS_ITEM (sheet->object_group), "x", 0.0, "y", 0.0, NULL));
540 NG_DEBUG ("selected group %p", sheet->priv->selected_group);
541
542 sheet->priv->floating_group = GOO_CANVAS_GROUP (
543 goo_canvas_group_new (GOO_CANVAS_ITEM (sheet->object_group), "x", 0.0, "y", 0.0, NULL));
544 NG_DEBUG ("floating group %p", sheet->priv->floating_group);
545
546 // Hash table that maps coordinates to a specific dot.
547 sheet->priv->node_dots = g_hash_table_new_full (dot_hash, dot_equal, g_free, NULL);
548
549 // this requires object_group to be setup properly
550 sheet->priv->rubberband_info = rubberband_info_new (sheet);
551 sheet->priv->create_wire_info = create_wire_info_new (sheet);
552
553 return sheet_widget;
554 }
555
556 /*
557 * Replace the current sheet with a new one (eventually
558 * with a different size, i.e. with a different width
559 * and/or height).
560 */
sheet_replace(SchematicView * sv)561 gboolean sheet_replace (SchematicView *sv)
562 {
563 g_return_val_if_fail (sv != NULL, FALSE);
564 g_return_val_if_fail (IS_SCHEMATIC_VIEW (sv), FALSE);
565
566 Schematic *sm = schematic_view_get_schematic (sv);
567 Sheet *old_sheet = schematic_view_get_sheet (sv);
568 Sheet *sheet;
569 GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (old_sheet));
570 gdouble zoom;
571
572 g_return_val_if_fail (old_sheet != NULL, FALSE);
573 g_return_val_if_fail (IS_SHEET (old_sheet), FALSE);
574
575 sheet_get_zoom (old_sheet, &zoom);
576 sheet = SHEET (sheet_new ((double) schematic_get_width (sm) + SHEET_BORDER, (double) schematic_get_height (sm) + SHEET_BORDER));
577 if (!sheet)
578 return FALSE;
579
580 sheet_set_zoom (sheet, zoom);
581 goo_canvas_set_scale (GOO_CANVAS (sheet), zoom);
582
583 g_signal_connect (G_OBJECT (sheet), "event", G_CALLBACK (sheet_event_callback),
584 sheet);
585
586 schematic_view_set_sheet (sv, sheet);
587
588 rubberband_info_destroy (old_sheet->priv->rubberband_info);
589 old_sheet->priv->rubberband_info = NULL;
590
591 create_wire_info_destroy (old_sheet->priv->create_wire_info);
592 old_sheet->priv->create_wire_info = NULL;
593
594 gtk_widget_destroy (GTK_WIDGET (old_sheet));
595
596 gtk_container_add (GTK_CONTAINER (parent), GTK_WIDGET (sheet));
597
598 gtk_widget_grab_focus (GTK_WIDGET (sheet));
599
600 return TRUE;
601 }
602
sheet_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * spec)603 static void sheet_set_property (GObject *object, guint prop_id, const GValue *value,
604 GParamSpec *spec)
605 {
606 Sheet *sheet = SHEET (object);
607
608 switch (prop_id) {
609 case ARG_ZOOM:
610 sheet_set_zoom (sheet, (const double) g_value_get_double (value));
611 break;
612 }
613 }
614
sheet_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * spec)615 static void sheet_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *spec)
616 {
617 const Sheet *sheet = SHEET (object);
618
619 switch (prop_id) {
620 case ARG_ZOOM:
621 g_value_set_double (value, sheet->priv->zoom);
622 break;
623
624 default:
625 G_OBJECT_WARN_INVALID_PROPERTY_ID (sheet, prop_id, spec);
626 break;
627 }
628 }
629
630 /*
631 * scroll to <dx,dy> in pixels relative to the current coords
632 * note that pixels are _not_ affected by zoom
633 */
sheet_scroll_pixel(const Sheet * sheet,int delta_x,int delta_y)634 void sheet_scroll_pixel (const Sheet *sheet, int delta_x, int delta_y)
635 {
636 GtkAdjustment *hadj = NULL, *vadj = NULL;
637 GtkAllocation allocation;
638 gfloat vnew, hnew;
639 gfloat hmax, vmax;
640 gfloat x1, y1;
641 const SheetPriv *priv = sheet->priv;
642
643 if (sheet_get_adjustments (sheet, &hadj, &vadj)) {
644 x1 = gtk_adjustment_get_value (hadj);
645 y1 = gtk_adjustment_get_value (vadj);
646 } else {
647 x1 = y1 = 0.f;
648 }
649
650 gtk_widget_get_allocation (GTK_WIDGET (sheet), &allocation);
651
652 if (priv->width > allocation.width)
653 hmax = (gfloat)(priv->width - allocation.width);
654 else
655 hmax = 0.f;
656
657 if (priv->height > allocation.height)
658 vmax = (gfloat)(priv->height - allocation.height);
659 else
660 vmax = 0.f;
661
662 hnew = CLAMP (x1 + (gfloat)delta_x, 0.f, hmax);
663 vnew = CLAMP (y1 + (gfloat)delta_y, 0.f, vmax);
664
665 if (hadj && hnew != x1) {
666 gtk_adjustment_set_value (hadj, hnew);
667 g_signal_emit_by_name (G_OBJECT (hadj), "value_changed");
668 }
669 if (vadj && vnew != y1) {
670 gtk_adjustment_set_value (vadj, vnew);
671 g_signal_emit_by_name (G_OBJECT (vadj), "value_changed");
672 }
673 }
674
sheet_get_size_pixels(const Sheet * sheet,guint * width,guint * height)675 void sheet_get_size_pixels (const Sheet *sheet, guint *width, guint *height)
676 {
677 *width = sheet->priv->width * sheet->priv->zoom;
678 *height = sheet->priv->height * sheet->priv->zoom;
679 }
680
sheet_remove_selected_object(const Sheet * sheet,SheetItem * item)681 void sheet_remove_selected_object (const Sheet *sheet, SheetItem *item)
682 {
683 sheet->priv->selected_objects = g_list_remove (sheet->priv->selected_objects, item);
684 }
685
sheet_prepend_selected_object(Sheet * sheet,SheetItem * item)686 void sheet_prepend_selected_object (Sheet *sheet, SheetItem *item)
687 {
688 sheet->priv->selected_objects = g_list_prepend (sheet->priv->selected_objects, item);
689 }
690
sheet_remove_floating_object(const Sheet * sheet,SheetItem * item)691 void sheet_remove_floating_object (const Sheet *sheet, SheetItem *item)
692 {
693 sheet->priv->floating_objects = g_list_remove (sheet->priv->floating_objects, item);
694 }
695
sheet_prepend_floating_object(Sheet * sheet,SheetItem * item)696 void sheet_prepend_floating_object (Sheet *sheet, SheetItem *item)
697 {
698 sheet->priv->floating_objects = g_list_prepend (sheet->priv->floating_objects, item);
699 }
700
sheet_connect_part_item_to_floating_group(Sheet * sheet,gpointer * sv)701 void sheet_connect_part_item_to_floating_group (Sheet *sheet, gpointer *sv)
702 {
703 g_return_if_fail (sheet);
704 g_return_if_fail (IS_SHEET (sheet));
705 g_return_if_fail (sv);
706 g_return_if_fail (IS_SCHEMATIC_VIEW (sv));
707
708 sheet->state = SHEET_STATE_FLOAT_START;
709
710 if (sheet->priv->float_handler_id != 0)
711 return;
712
713 sheet->priv->float_handler_id =
714 g_signal_connect (G_OBJECT (sheet), "event", G_CALLBACK (sheet_item_floating_event), sv);
715 }
716
sheet_show_node_labels(Sheet * sheet,gboolean show)717 void sheet_show_node_labels (Sheet *sheet, gboolean show)
718 {
719 g_return_if_fail (sheet != NULL);
720 g_return_if_fail (IS_SHEET (sheet));
721
722 GList *item = NULL;
723
724 for (item = sheet->priv->items; item; item = item->next) {
725 if (IS_PART_ITEM (item->data)) {
726 if (part_get_num_pins (PART (sheet_item_get_data (SHEET_ITEM (item->data)))) == 1) {
727 part_item_show_node_labels (PART_ITEM (item->data), show);
728 }
729 }
730 }
731 }
732
sheet_add_item(Sheet * sheet,SheetItem * item)733 void sheet_add_item (Sheet *sheet, SheetItem *item)
734 {
735 g_return_if_fail (sheet != NULL);
736 g_return_if_fail (IS_SHEET (sheet));
737 g_return_if_fail (item != NULL);
738 g_return_if_fail (IS_SHEET_ITEM (item));
739
740 sheet->priv->items = g_list_prepend (sheet->priv->items, item);
741 }
742
743 /**
744 * save the selection
745 * @attention not stackable
746 */
sheet_preserve_selection(Sheet * sheet)747 GList *sheet_preserve_selection (Sheet *sheet)
748 {
749 g_return_val_if_fail (sheet != NULL, FALSE);
750 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
751
752 GList *list = NULL;
753 for (list = sheet->priv->selected_objects; list; list = list->next) {
754 sheet_item_set_preserve_selection (SHEET_ITEM (list->data), TRUE);
755 }
756 // Return the list so that we can remove the preserve_selection
757 // flags later.
758 return sheet->priv->selected_objects;
759 }
760
sheet_event_callback(GtkWidget * widget,GdkEvent * event,Sheet * sheet)761 int sheet_event_callback (GtkWidget *widget, GdkEvent *event, Sheet *sheet)
762 {
763 GtkWidgetClass *wklass = GTK_WIDGET_CLASS (sheet->priv->sheet_parent_class);
764 switch (event->type) {
765
766 case GDK_3BUTTON_PRESS:
767 // We do not care about triple clicks on the sheet.
768 return FALSE;
769
770 case GDK_2BUTTON_PRESS:
771 // The sheet does not care about double clicks, but invoke the
772 // canvas event handler and see if an item picks up the event.
773 return wklass->button_press_event (widget, (GdkEventButton *)event);
774
775 case GDK_BUTTON_PRESS:
776 // If we are in the middle of something else, don't interfere
777 // with that.
778 if (sheet->state != SHEET_STATE_NONE) {
779 return FALSE;
780 }
781
782 if (wklass->button_press_event (widget, (GdkEventButton *)event)) {
783 return TRUE;
784 }
785
786 if (event->button.button == 3) {
787 run_context_menu (schematic_view_get_schematicview_from_sheet (sheet),
788 (GdkEventButton *)event);
789 return TRUE;
790 }
791
792 if (event->button.button == 1) {
793 if (!(event->button.state & GDK_SHIFT_MASK)) {
794 sheet_select_all (sheet, FALSE);
795 }
796
797 rubberband_start (sheet, event);
798 return TRUE;
799 }
800 break;
801
802 case GDK_BUTTON_RELEASE:
803 if (event->button.button == 4 || event->button.button == 5) {
804 return TRUE;
805 }
806
807 if (event->button.button == 1 && sheet->priv->rubberband_info->state == RUBBERBAND_ACTIVE) {
808 rubberband_finish (sheet, event);
809 return TRUE;
810 }
811 if (wklass->button_release_event != NULL) {
812 return wklass->button_release_event (widget, (GdkEventButton *)event);
813 }
814 break;
815
816 case GDK_SCROLL: {
817 GdkEventScroll *scr_event = (GdkEventScroll *)event;
818 if (scr_event->state & GDK_SHIFT_MASK) {
819 // Scroll horizontally
820 if (scr_event->direction == GDK_SCROLL_UP) {
821 sheet_scroll_pixel (sheet, -30, 0);
822 }
823 else if (scr_event->direction == GDK_SCROLL_DOWN) {
824 sheet_scroll_pixel (sheet, 30, 0);
825 }
826
827 } else if (scr_event->state & GDK_CONTROL_MASK) {
828 // Scroll vertically
829 if (scr_event->direction == GDK_SCROLL_UP) {
830 sheet_scroll_pixel (sheet, 0, -30);
831 }
832 else if (scr_event->direction == GDK_SCROLL_DOWN) {
833 sheet_scroll_pixel (sheet, 0, 30);
834 }
835
836 } else {
837 // Zoom
838 if (scr_event->direction == GDK_SCROLL_UP) {
839 double zoom;
840 sheet_get_zoom (sheet, &zoom);
841 if (zoom < ZOOM_MAX)
842 sheet_zoom_step (sheet, 1.1);
843 } else if (scr_event->direction == GDK_SCROLL_DOWN) {
844 double zoom;
845 sheet_get_zoom (sheet, &zoom);
846 if (zoom > ZOOM_MIN)
847 sheet_zoom_step (sheet, 0.9);
848 }
849 }
850 } break;
851
852 case GDK_MOTION_NOTIFY:
853 if (sheet->priv->rubberband_info->state == RUBBERBAND_ACTIVE) {
854 rubberband_update (sheet, event);
855 return TRUE;
856 }
857 if (wklass->motion_notify_event != NULL) {
858 return wklass->motion_notify_event (widget, (GdkEventMotion *)event);
859 }
860 break;
861
862 case GDK_ENTER_NOTIFY:
863 return wklass->enter_notify_event (widget, (GdkEventCrossing *)event);
864
865 case GDK_LEAVE_NOTIFY:
866 return wklass->leave_notify_event (widget, (GdkEventCrossing *)event);
867
868 case GDK_KEY_PRESS:
869 switch (event->key.keyval) {
870 case GDK_KEY_R:
871 case GDK_KEY_r:
872 if (sheet->state == SHEET_STATE_NONE)
873 sheet_rotate_selection (sheet, 90);
874 break;
875 case GDK_KEY_L:
876 case GDK_KEY_l:
877 if (sheet->state == SHEET_STATE_NONE)
878 sheet_rotate_selection (sheet, -90);
879 break;
880 case GDK_KEY_Home:
881 case GDK_KEY_End:
882 break;
883 case GDK_KEY_Left:
884 if (event->key.state & GDK_MOD1_MASK) {
885 sheet_scroll_pixel (sheet, -20, 0);
886 } else {
887 sheet_move_selection (sheet, -20., 0.);
888 }
889 break;
890 case GDK_KEY_Up:
891 if (event->key.state & GDK_MOD1_MASK) {
892 sheet_scroll_pixel (sheet, 0, -20);
893 } else {
894 sheet_move_selection (sheet, 0., -20.);
895 }
896 break;
897 case GDK_KEY_Right:
898 if (event->key.state & GDK_MOD1_MASK) {
899 sheet_scroll_pixel (sheet, 20, 0);
900 } else {
901 sheet_move_selection (sheet, +20., 0.);
902 }
903 break;
904 case GDK_KEY_Down:
905 if (event->key.state & GDK_MOD1_MASK) {
906 sheet_scroll_pixel (sheet, 0, 20);
907 } else {
908 sheet_move_selection (sheet, 0., +20.);
909 }
910 break;
911 case GDK_KEY_Escape:
912 g_signal_emit_by_name (G_OBJECT (sheet), "cancel");
913 break;
914 case GDK_KEY_Delete:
915 sheet_delete_selection (sheet);
916 break;
917 default:
918 return FALSE;
919 }
920 default:
921 return FALSE;
922 }
923
924 return TRUE;
925 }
926
927 /**
928 * select all items on the sheet
929 */
sheet_select_all(Sheet * sheet,gboolean select)930 void sheet_select_all (Sheet *sheet, gboolean select)
931 {
932 GList *list;
933
934 g_return_if_fail (sheet != NULL);
935 g_return_if_fail (IS_SHEET (sheet));
936
937 for (list = sheet->priv->items; list; list = list->next)
938 sheet_item_select (SHEET_ITEM (list->data), select);
939
940 if (!select)
941 sheet_release_selected_objects (sheet);
942 }
943
944 /**
945 * rotate the currently selected on the sheet
946 */
sheet_rotate_selection(Sheet * sheet,gint angle)947 void sheet_rotate_selection (Sheet *sheet, gint angle)
948 {
949 g_return_if_fail (sheet != NULL);
950 g_return_if_fail (IS_SHEET (sheet));
951
952 if (sheet->priv->selected_objects != NULL)
953 rotate_items (sheet, sheet->priv->selected_objects, angle);
954 }
955
956 /**
957 * move the currently selected on the sheet
958 */
sheet_move_selection(Sheet * sheet,gdouble x,gdouble y)959 void sheet_move_selection (Sheet *sheet, gdouble x, gdouble y)
960 {
961 g_return_if_fail (sheet != NULL);
962 g_return_if_fail (IS_SHEET (sheet));
963
964 const Coords delta = {x, y};
965 if (sheet->priv->selected_objects != NULL)
966 move_items (sheet, sheet->priv->selected_objects, &delta);
967 }
968
969 /**
970 * rotate floating items
971 */
sheet_rotate_ghosts(Sheet * sheet)972 void sheet_rotate_ghosts (Sheet *sheet)
973 {
974 g_return_if_fail (sheet != NULL);
975 g_return_if_fail (IS_SHEET (sheet));
976
977 if (sheet->priv->floating_objects != NULL)
978 rotate_items (sheet, sheet->priv->floating_objects, 90);
979 }
980
rotate_items(Sheet * sheet,GList * items,gint angle)981 static void rotate_items (Sheet *sheet, GList *items, gint angle)
982 {
983 GList *list, *item_data_list;
984 Coords center, b1, b2;
985
986 item_data_list = NULL;
987 for (list = items; list; list = list->next) {
988 item_data_list = g_list_prepend (item_data_list, sheet_item_get_data (list->data));
989 }
990
991 item_data_list_get_absolute_bbox (item_data_list, &b1, &b2);
992
993 center = coords_average (&b1, &b2);
994
995 snap_to_grid (sheet->grid, ¢er.x, ¢er.y);
996
997 for (list = item_data_list; list; list = list->next) {
998 ItemData *item_data = list->data;
999 if (item_data == NULL)
1000 continue;
1001
1002 if (sheet->state == SHEET_STATE_NONE)
1003 item_data_unregister (item_data);
1004
1005 item_data_rotate (item_data, angle, ¢er);
1006
1007 if (sheet->state == SHEET_STATE_NONE)
1008 item_data_register (item_data);
1009 }
1010
1011 g_list_free (item_data_list);
1012 }
1013
move_items(Sheet * sheet,GList * items,const Coords * trans)1014 static void move_items (Sheet *sheet, GList *items, const Coords *trans)
1015 {
1016 GList *list;
1017
1018 for (list = items; list; list = list->next) {
1019 g_assert (list->data != NULL);
1020 ItemData *item_data = sheet_item_get_data (list->data);
1021 g_assert (item_data != NULL);
1022
1023 if (sheet->state == SHEET_STATE_NONE)
1024 item_data_unregister (item_data);
1025
1026 item_data_move (item_data, trans);
1027
1028 if (sheet->state == SHEET_STATE_NONE)
1029 item_data_register (item_data);
1030 }
1031 }
1032
1033 /**
1034 * remove the currently selected items from the sheet
1035 * (especially their goocanvas representators)
1036 */
sheet_delete_selection(Sheet * sheet)1037 void sheet_delete_selection (Sheet *sheet)
1038 {
1039 GList *copy, *iter;
1040
1041 g_return_if_fail (sheet != NULL);
1042 g_return_if_fail (IS_SHEET (sheet));
1043
1044 if (sheet->state != SHEET_STATE_NONE)
1045 return;
1046
1047 copy = g_list_copy (sheet->priv->selected_objects);
1048
1049 for (iter = copy; iter; iter = iter->next) {
1050 sheet_remove_item_in_sheet (SHEET_ITEM (iter->data), sheet);
1051 goo_canvas_item_remove (GOO_CANVAS_ITEM (iter->data));
1052 g_object_unref (iter->data);
1053 }
1054 g_list_free (copy);
1055
1056 // function <sheet_remove_item_in_sheet>
1057 // requires selected_objects, items, floating_objects
1058 // to be not NULL!
1059 g_list_free (sheet->priv->selected_objects);
1060 sheet->priv->selected_objects = NULL;
1061 }
1062
1063 /**
1064 * removes all canvas items in the selected canvas group from their parents
1065 * but does NOT delete them
1066 */
sheet_release_selected_objects(Sheet * sheet)1067 void sheet_release_selected_objects (Sheet *sheet)
1068 {
1069 GList *list, *copy;
1070 GooCanvasGroup *group;
1071 gint item_nbr;
1072
1073 g_return_if_fail (sheet != NULL);
1074 g_return_if_fail (IS_SHEET (sheet));
1075
1076 group = sheet->priv->selected_group;
1077 copy = g_list_copy (sheet->priv->selected_objects);
1078
1079 // Remove all the selected_objects into selected_group
1080 for (list = copy; list; list = list->next) {
1081 item_nbr =
1082 goo_canvas_item_find_child (GOO_CANVAS_ITEM (group), GOO_CANVAS_ITEM (list->data));
1083 goo_canvas_item_remove_child (GOO_CANVAS_ITEM (group), item_nbr);
1084 }
1085 g_list_free (copy);
1086
1087 g_list_free (sheet->priv->selected_objects);
1088 sheet->priv->selected_objects = NULL;
1089 }
1090
1091 /**
1092 * @returns [transfer-none] the list of selected objects
1093 */
sheet_get_selection(Sheet * sheet)1094 GList *sheet_get_selection (Sheet *sheet)
1095 {
1096 g_return_val_if_fail (sheet != NULL, NULL);
1097 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1098
1099 return sheet->priv->selected_objects;
1100 }
1101
1102 /**
1103 * update the node lables of all parts in the current sheet
1104 */
sheet_update_parts(Sheet * sheet)1105 void sheet_update_parts (Sheet *sheet)
1106 {
1107 GList *list;
1108
1109 g_return_if_fail (sheet != NULL);
1110 g_return_if_fail (IS_SHEET (sheet));
1111
1112 for (list = sheet->priv->items; list; list = list->next) {
1113 if (IS_PART_ITEM (list->data))
1114 part_item_update_node_label (PART_ITEM (list->data));
1115 }
1116 }
1117
1118 /**
1119 * flip currently selected items
1120 */
sheet_flip_selection(Sheet * sheet,IDFlip direction)1121 void sheet_flip_selection (Sheet *sheet, IDFlip direction)
1122 {
1123 g_return_if_fail (sheet != NULL);
1124 g_return_if_fail (IS_SHEET (sheet));
1125
1126 if (sheet->priv->selected_objects != NULL)
1127 flip_items (sheet, sheet->priv->selected_objects, direction);
1128 }
1129
1130 /**
1131 * flip currently floating items
1132 */
sheet_flip_ghosts(Sheet * sheet,IDFlip direction)1133 void sheet_flip_ghosts (Sheet *sheet, IDFlip direction)
1134 {
1135 g_return_if_fail (sheet != NULL);
1136 g_return_if_fail (IS_SHEET (sheet));
1137
1138 if (sheet->priv->floating_objects != NULL)
1139 flip_items (sheet, sheet->priv->floating_objects, direction);
1140 }
1141
flip_items(Sheet * sheet,GList * items,IDFlip direction)1142 static void flip_items (Sheet *sheet, GList *items, IDFlip direction)
1143 {
1144 GList *iter, *item_data_list;
1145 Coords center, b1, b2;
1146 Coords after;
1147
1148 item_data_list = NULL;
1149 for (iter = items; iter; iter = iter->next) {
1150 item_data_list = g_list_prepend (item_data_list, sheet_item_get_data (iter->data));
1151 }
1152
1153 item_data_list_get_absolute_bbox (item_data_list, &b1, &b2);
1154
1155 // FIXME center is currently not used by item_data_flip (part.c implentation)
1156 center.x = b2.x / 2 + b1.x / 2;
1157 center.y = b2.y / 2 + b1.y / 2;
1158
1159 // FIXME - registering an item after flipping it still creates an offset as
1160 // the position is still 0
1161 for (iter = item_data_list; iter; iter = iter->next) {
1162 ItemData *item_data = iter->data;
1163
1164 if (sheet->state == SHEET_STATE_NONE)
1165 item_data_unregister (item_data);
1166
1167 item_data_flip (item_data, direction, ¢er);
1168
1169 // Make sure we snap to grid.
1170 item_data_get_pos (item_data, &after);
1171
1172 snap_to_grid (sheet->grid, &after.x, &after.y);
1173
1174 item_data_set_pos (item_data, &after);
1175
1176 if (sheet->state == SHEET_STATE_NONE)
1177 item_data_register (item_data);
1178 }
1179
1180 g_list_free (item_data_list);
1181 }
1182
sheet_clear_op_values(Sheet * sheet)1183 void sheet_clear_op_values (Sheet *sheet)
1184 {
1185 g_return_if_fail (sheet != NULL);
1186 g_return_if_fail (IS_SHEET (sheet));
1187
1188 g_hash_table_destroy (sheet->priv->voltmeter_nodes);
1189 sheet->priv->voltmeter_nodes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1190 }
1191
sheet_provide_object_properties(Sheet * sheet)1192 void sheet_provide_object_properties (Sheet *sheet)
1193 {
1194 g_return_if_fail (sheet != NULL);
1195 g_return_if_fail (IS_SHEET (sheet));
1196
1197 if (g_list_length (sheet->priv->selected_objects) == 1)
1198 sheet_item_edit_properties (sheet->priv->selected_objects->data);
1199 }
1200
1201 /**
1202 * get rid of floating items (delete them)
1203 */
sheet_clear_ghosts(Sheet * sheet)1204 void sheet_clear_ghosts (Sheet *sheet)
1205 {
1206 GList *copy, *list;
1207
1208 g_return_if_fail (sheet != NULL);
1209 g_return_if_fail (IS_SHEET (sheet));
1210
1211 if (sheet->priv->floating_objects == NULL)
1212 return;
1213
1214 g_assert (sheet->state != SHEET_STATE_FLOAT);
1215 copy = g_list_copy (sheet->priv->floating_objects);
1216
1217 for (list = copy; list; list = list->next) {
1218 g_object_force_floating (G_OBJECT (list->data));
1219 g_object_unref (G_OBJECT (list->data));
1220 }
1221
1222 g_list_free (copy);
1223
1224 sheet->priv->floating_objects = NULL;
1225 }
1226
1227 /**
1228 * count selected objects O(n)
1229 */
sheet_get_selected_objects_length(Sheet * sheet)1230 guint sheet_get_selected_objects_length (Sheet *sheet)
1231 {
1232 g_return_val_if_fail ((sheet != NULL), 0);
1233 g_return_val_if_fail (IS_SHEET (sheet), 0);
1234
1235 return g_list_length (sheet->priv->selected_objects);
1236 }
1237
1238 /**
1239 * @returns a shallow copy of the floating items list, free with `g_list_free`
1240 */
sheet_get_floating_objects(Sheet * sheet)1241 GList *sheet_get_floating_objects (Sheet *sheet)
1242 {
1243 g_return_val_if_fail (sheet != NULL, NULL);
1244 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1245
1246 return g_list_copy (sheet->priv->floating_objects);
1247 }
1248
1249 /**
1250 * add a recently created item (no view representation yet!)
1251 * to the group of floating items and add a SheetItem via
1252 * `sheet_item_factory_create_sheet_item()`
1253 */
sheet_add_ghost_item(Sheet * sheet,ItemData * data)1254 void sheet_add_ghost_item (Sheet *sheet, ItemData *data)
1255 {
1256 SheetItem *item;
1257
1258 g_return_if_fail (sheet != NULL);
1259 g_return_if_fail (IS_SHEET (sheet));
1260
1261 item = sheet_item_factory_create_sheet_item (sheet, data);
1262
1263 g_object_set (G_OBJECT (item), "visibility", GOO_CANVAS_ITEM_INVISIBLE, NULL);
1264
1265 sheet_prepend_floating_object (sheet, item);
1266 }
1267
sheet_stop_create_wire(Sheet * sheet)1268 void sheet_stop_create_wire (Sheet *sheet)
1269 {
1270 g_return_if_fail (sheet != NULL);
1271 g_return_if_fail (IS_SHEET (sheet));
1272
1273 create_wire_cleanup (sheet);
1274 }
1275
sheet_initiate_create_wire(Sheet * sheet)1276 void sheet_initiate_create_wire (Sheet *sheet)
1277 {
1278 g_return_if_fail (sheet != NULL);
1279 g_return_if_fail (IS_SHEET (sheet));
1280
1281 create_wire_setup (sheet);
1282 }
1283
node_dot_added_callback(Schematic * schematic,Coords * pos,Sheet * sheet)1284 static void node_dot_added_callback (Schematic *schematic, Coords *pos, Sheet *sheet)
1285 {
1286 NodeItem *node_item;
1287 Coords *key;
1288
1289 g_return_if_fail (sheet != NULL);
1290 g_return_if_fail (IS_SHEET (sheet));
1291
1292 node_item = g_hash_table_lookup (sheet->priv->node_dots, pos);
1293 if (node_item == NULL) {
1294 node_item = NODE_ITEM (g_object_new (TYPE_NODE_ITEM, NULL));
1295 g_object_set (node_item, "parent", goo_canvas_get_root_item (GOO_CANVAS (sheet)), "x",
1296 pos->x, "y", pos->y, NULL);
1297 }
1298
1299 node_item_show_dot (node_item, TRUE);
1300 key = g_new0 (Coords, 1);
1301 key->x = pos->x;
1302 key->y = pos->y;
1303
1304 g_hash_table_insert (sheet->priv->node_dots, key, node_item);
1305 }
1306
node_dot_removed_callback(Schematic * schematic,Coords * pos,Sheet * sheet)1307 static void node_dot_removed_callback (Schematic *schematic, Coords *pos, Sheet *sheet)
1308 {
1309 GooCanvasItem *node_item;
1310 Coords *orig_key;
1311 gboolean found;
1312
1313 g_return_if_fail (sheet != NULL);
1314 g_return_if_fail (IS_SHEET (sheet));
1315
1316 found = g_hash_table_lookup_extended (sheet->priv->node_dots, pos, (gpointer)&orig_key,
1317 (gpointer)&node_item);
1318
1319 if (found) {
1320 goo_canvas_item_remove (GOO_CANVAS_ITEM (node_item));
1321 g_hash_table_remove (sheet->priv->node_dots, pos);
1322 } else {
1323 g_warning ("No dot to remove!");
1324 }
1325 }
1326
1327 /**
1328 * hash function for dots (for their position actually)
1329 * good enough to get some spread and very lightweight
1330 */
dot_hash(gconstpointer key)1331 static guint dot_hash (gconstpointer key)
1332 {
1333 Coords *sp = (Coords *)key;
1334 int x, y;
1335
1336 x = (int)rint (sp->x) % 256;
1337 y = (int)rint (sp->y) % 256;
1338
1339 return (y << 8) | x;
1340 }
1341
1342 #define HASH_EPSILON 1e-2
1343
dot_equal(gconstpointer a,gconstpointer b)1344 static int dot_equal (gconstpointer a, gconstpointer b)
1345 {
1346 Coords *spa, *spb;
1347
1348 g_return_val_if_fail (a != NULL, 0);
1349 g_return_val_if_fail (b != NULL, 0);
1350
1351 spa = (Coords *)a;
1352 spb = (Coords *)b;
1353
1354 if (fabs (spa->y - spb->y) > HASH_EPSILON)
1355 return 0;
1356
1357 if (fabs (spa->x - spb->x) > HASH_EPSILON)
1358 return 0;
1359
1360 return 1;
1361 }
1362
sheet_connect_node_dots_to_signals(Sheet * sheet)1363 void sheet_connect_node_dots_to_signals (Sheet *sheet)
1364 {
1365 g_return_if_fail (sheet != NULL);
1366 g_return_if_fail (IS_SHEET (sheet));
1367
1368 GList *iter, *list;
1369 Schematic *sm;
1370
1371 sm = schematic_view_get_schematic_from_sheet (sheet);
1372
1373 g_signal_connect_object (G_OBJECT (sm), "node_dot_added", G_CALLBACK (node_dot_added_callback),
1374 G_OBJECT (sheet), 0);
1375 g_signal_connect_object (G_OBJECT (sm), "node_dot_removed",
1376 G_CALLBACK (node_dot_removed_callback), G_OBJECT (sheet), 0);
1377
1378 list = node_store_get_node_positions (schematic_get_store (sm));
1379 for (iter = list; iter; iter = iter->next)
1380 node_dot_added_callback (sm, iter->data, sheet);
1381 }
1382
1383 /**
1384 * remove a single item from the sheet
1385 */
sheet_remove_item_in_sheet(SheetItem * item,Sheet * sheet)1386 void sheet_remove_item_in_sheet (SheetItem *item, Sheet *sheet)
1387 {
1388 g_return_if_fail (sheet != NULL);
1389 g_return_if_fail (IS_SHEET (sheet));
1390 g_return_if_fail (item != NULL);
1391 g_return_if_fail (IS_SHEET_ITEM (item));
1392
1393 sheet->priv->items = g_list_remove (sheet->priv->items, item);
1394
1395 // Remove the object from the selected-list before destroying.
1396 sheet_remove_selected_object (sheet, item);
1397 sheet_remove_floating_object (sheet, item);
1398
1399 ItemData *data = sheet_item_get_data (item);
1400 g_return_if_fail (data != NULL);
1401 g_return_if_fail (IS_ITEM_DATA (data));
1402
1403 // properly unregister the itemdata from the sheet
1404 item_data_unregister (data);
1405 // Destroy the item-data (model) associated to the sheet-item
1406 g_object_unref (data);
1407 }
1408
extract_time(GdkEvent * event)1409 inline static guint32 extract_time (GdkEvent *event)
1410 {
1411 if (event) {
1412 switch (event->type) {
1413 /* only added relevant events */
1414 case GDK_MOTION_NOTIFY:
1415 return ((GdkEventMotion *)event)->time;
1416 case GDK_3BUTTON_PRESS:
1417 case GDK_2BUTTON_PRESS:
1418 case GDK_BUTTON_RELEASE:
1419 case GDK_BUTTON_PRESS:
1420 return ((GdkEventButton *)event)->time;
1421 case GDK_KEY_PRESS:
1422 return ((GdkEventKey *)event)->time;
1423 case GDK_ENTER_NOTIFY:
1424 case GDK_LEAVE_NOTIFY:
1425 return ((GdkEventCrossing *)event)->time;
1426 case GDK_PROPERTY_NOTIFY:
1427 return ((GdkEventProperty *)event)->time;
1428 case GDK_DRAG_ENTER:
1429 case GDK_DRAG_LEAVE:
1430 case GDK_DRAG_MOTION:
1431 case GDK_DRAG_STATUS:
1432 case GDK_DROP_START:
1433 case GDK_DROP_FINISHED:
1434 return ((GdkEventDND *)event)->time;
1435 default:
1436 return 0;
1437 }
1438 }
1439 return 0;
1440 }
1441
1442 /**
1443 * helpful for debugging to not grab all input forever
1444 * if oregano segfaults while running inside
1445 * a gdb session
1446 */
1447 #ifndef DEBUG_DISABLE_GRABBING
1448 #define DEBUG_DISABLE_GRABBING 0
1449 #endif
1450
sheet_pointer_grab(Sheet * sheet,GdkEvent * event)1451 gboolean sheet_pointer_grab (Sheet *sheet, GdkEvent *event)
1452 {
1453 g_return_val_if_fail (sheet, FALSE);
1454 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
1455 #if !DEBUG_DISABLE_GRABBING
1456 if (sheet->priv->pointer_grabbed == 0 &&
1457 goo_canvas_pointer_grab (GOO_CANVAS (sheet), GOO_CANVAS_ITEM (sheet->grid),
1458 GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK |
1459 GDK_BUTTON_RELEASE_MASK,
1460 NULL, extract_time (event)) == GDK_GRAB_SUCCESS) {
1461 sheet->priv->pointer_grabbed = 1;
1462 }
1463 return (sheet->priv->pointer_grabbed == 1);
1464 #else
1465 return TRUE;
1466 #endif
1467 }
1468
sheet_pointer_ungrab(Sheet * sheet,GdkEvent * event)1469 void sheet_pointer_ungrab (Sheet *sheet, GdkEvent *event)
1470 {
1471 g_return_if_fail (sheet);
1472 g_return_if_fail (IS_SHEET (sheet));
1473 #if !DEBUG_DISABLE_GRABBING
1474 if (sheet->priv->pointer_grabbed) {
1475 sheet->priv->pointer_grabbed = FALSE;
1476 goo_canvas_pointer_ungrab (GOO_CANVAS (sheet), GOO_CANVAS_ITEM (sheet->grid),
1477 extract_time (event));
1478 }
1479 #endif
1480 }
1481
sheet_keyboard_grab(Sheet * sheet,GdkEvent * event)1482 gboolean sheet_keyboard_grab (Sheet *sheet, GdkEvent *event)
1483 {
1484 g_return_val_if_fail (sheet, FALSE);
1485 g_return_val_if_fail (IS_SHEET (sheet), FALSE);
1486 #if !DEBUG_DISABLE_GRABBING
1487 if (sheet->priv->keyboard_grabbed == FALSE &&
1488 goo_canvas_keyboard_grab (GOO_CANVAS (sheet), GOO_CANVAS_ITEM (sheet->grid),
1489 TRUE, /*do not reroute signals through sheet->grid*/
1490 extract_time (event)) == GDK_GRAB_SUCCESS) {
1491 sheet->priv->keyboard_grabbed = TRUE;
1492 }
1493 return (sheet->priv->keyboard_grabbed == TRUE);
1494 #else
1495 return TRUE;
1496 #endif
1497 }
1498
sheet_keyboard_ungrab(Sheet * sheet,GdkEvent * event)1499 void sheet_keyboard_ungrab (Sheet *sheet, GdkEvent *event)
1500 {
1501 g_return_if_fail (sheet);
1502 g_return_if_fail (IS_SHEET (sheet));
1503 #if !DEBUG_DISABLE_GRABBING
1504 if (sheet->priv->keyboard_grabbed) {
1505 sheet->priv->keyboard_grabbed = FALSE;
1506 goo_canvas_keyboard_ungrab (GOO_CANVAS (sheet), GOO_CANVAS_ITEM (sheet->grid),
1507 extract_time (event));
1508 }
1509 #endif
1510 }
1511