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, &center.x, &center.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, &center);
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, &center);
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