1 /*
2 * node-store.c
3 *
4 *
5 * Store nodes, wires to hashtables.
6 * Generate new wires, merge new wire create requests where possible with
7 *already existing ones
8 *
9 *
10 * Authors:
11 * Richard Hult <rhult@hem.passagen.se>
12 * Ricardo Markiewicz <rmarkie@fi.uba.ar>
13 * Andres de Barbara <adebarbara@fi.uba.ar>
14 * Marc Lorber <lorber.marc@wanadoo.fr>
15 * Bernhard Schuster <bernhard@ahoi.io>
16 * Guido Trentalancia <guido@trentalancia.com>
17 *
18 * Web page: https://ahoi.io/project/oregano
19 *
20 * Copyright (C) 1999-2001 Richard Hult
21 * Copyright (C) 2003,2006 Ricardo Markiewicz
22 * Copyright (C) 2009-2012 Marc Lorber
23 * Copyright (C) 2012-2013 Bernhard Schuster
24 * Copyright (C) 2017 Guido Trentalancia
25 *
26 * This program is free software; you can redistribute it and/or
27 * modify it under the terms of the GNU General Public License as
28 * published by the Free Software Foundation; either version 2 of the
29 * License, or (at your option) any later version.
30 *
31 * This program is distributed in the hope that it will be useful,
32 * but WITHOUT ANY WARRANTY; without even the implied warranty of
33 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
34 * General Public License for more details.
35 *
36 * You should have received a copy of the GNU General Public
37 * License along with this program; if not, write to the
38 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
39 * Boston, MA 02110-1301, USA.
40 */
41
42 #include <glib.h>
43 #include <glib-object.h>
44 #include <gtk/gtk.h>
45 #include <math.h>
46
47 #define NODE_EPSILON 1e-10
48 #define HASH_EPSILON 1e-3
49 #include "node-store.h"
50 #include "node-store-private.h"
51 #include "node.h"
52 #include "part.h"
53 #include "wire.h"
54 #include "wire-private.h"
55
56 #include "debug.h"
57
58 /* NODE_EPSILON is used to check for intersection. */
59 /* HASH_EPSILON is used in the hash equality check function. */
60
61 /* Share an endpoint? */
62 #define SEP(p1x, p1y, p2x, p2y) (IS_EQ (p1x, p2x) && IS_EQ (p1y, p2y))
63
64 /* Equals? */
65 #define IS_EQ(a, b) (fabs ((a) - (b)) < NODE_EPSILON)
66
67 #define ON_THE_WIRE(p1, start, end) \
68 (fabs ((end.y - start.y) * (p1.x - start.x) - (end.x - start.x) * (p1.y - start.y)) < \
69 NODE_EPSILON)
70
71 static void node_store_class_init (NodeStoreClass *klass);
72 static void node_store_init (NodeStore *store);
73 static void node_store_finalize (GObject *self);
74 static void node_store_dispose (GObject *self);
75
76 enum { NODE_DOT_ADDED, NODE_DOT_REMOVED, LAST_SIGNAL };
77
78 G_DEFINE_TYPE (NodeStore, node_store, G_TYPE_OBJECT);
79
80 static guint node_store_signals[LAST_SIGNAL] = {0};
81
node_store_dispose(GObject * self)82 static void node_store_dispose (GObject *self)
83 {
84 G_OBJECT_CLASS (node_store_parent_class)->dispose (self);
85 }
86
node_store_finalize(GObject * object)87 static void node_store_finalize (GObject *object)
88 {
89 NodeStore *self = NODE_STORE (object);
90
91 if (self->nodes) {
92 g_hash_table_destroy (self->nodes);
93 self->nodes = NULL;
94 }
95
96 if (self->wires) {
97 g_list_free (self->wires);
98 self->wires = NULL;
99 }
100 if (self->parts) {
101 g_list_free (self->parts);
102 self->parts = NULL;
103 }
104 if (self->items) {
105 g_list_free (self->items);
106 self->items = NULL;
107 }
108 if (self->textbox) {
109 g_list_free (self->textbox);
110 self->textbox = NULL;
111 }
112
113 G_OBJECT_CLASS (node_store_parent_class)->finalize (object);
114 }
115
node_store_class_init(NodeStoreClass * klass)116 static void node_store_class_init (NodeStoreClass *klass)
117 {
118 GObjectClass *gobject_class;
119
120 gobject_class = G_OBJECT_CLASS (klass);
121 node_store_parent_class = g_type_class_peek_parent (klass);
122
123 gobject_class->finalize = node_store_finalize;
124 gobject_class->dispose = node_store_dispose;
125
126 node_store_signals[NODE_DOT_ADDED] =
127 g_signal_new ("node_dot_added", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_FIRST,
128 G_STRUCT_OFFSET (NodeStoreClass, node_dot_added), NULL, NULL,
129 g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
130
131 node_store_signals[NODE_DOT_REMOVED] =
132 g_signal_new ("node_dot_removed", TYPE_NODE_STORE, G_SIGNAL_RUN_FIRST,
133 G_STRUCT_OFFSET (NodeStoreClass, node_dot_removed), NULL, NULL,
134 g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
135 }
136
node_store_init(NodeStore * self)137 static void node_store_init (NodeStore *self)
138 {
139 self->nodes = g_hash_table_new (node_hash, node_equal);
140 self->wires = NULL;
141 self->parts = NULL;
142 self->items = NULL;
143 self->textbox = NULL;
144 }
145
146 ////////////////////////////////////////////////////////////////////////////////
147 // END BOILERPLATE
148 ////////////////////////////////////////////////////////////////////////////////
149
node_store_new(void)150 NodeStore *node_store_new (void) { return NODE_STORE (g_object_new (TYPE_NODE_STORE, NULL)); }
151
node_dot_added_callback(Node * node,Coords * pos,NodeStore * store)152 static void node_dot_added_callback (Node *node, Coords *pos, NodeStore *store)
153 {
154 g_return_if_fail (store != NULL);
155 g_return_if_fail (IS_NODE_STORE (store));
156
157 g_signal_emit_by_name (G_OBJECT (store), "node_dot_added", pos);
158 }
159
node_dot_removed_callback(Node * node,Coords * pos,NodeStore * store)160 static void node_dot_removed_callback (Node *node, Coords *pos, NodeStore *store)
161 {
162 g_return_if_fail (store);
163 g_return_if_fail (IS_NODE_STORE (store));
164
165 g_signal_emit_by_name (G_OBJECT (store), "node_dot_removed", pos);
166 }
167
168 /**
169 * lookup if a node at @pos exists, if so return it, otherwise
170 * create one, add it to the nodestore and return it
171 */
node_store_get_or_create_node(NodeStore * self,Coords pos)172 Node *node_store_get_or_create_node (NodeStore *self, Coords pos)
173 {
174 Node *node;
175
176 g_return_val_if_fail (self, NULL);
177 g_return_val_if_fail (IS_NODE_STORE (self), NULL);
178
179 node = g_hash_table_lookup (self->nodes, &pos);
180
181 if (!node) {
182 // Create a node at (x, y) and return it.
183 node = node_new (pos);
184
185 g_signal_connect_object (G_OBJECT (node), "node_dot_added",
186 G_CALLBACK (node_dot_added_callback), G_OBJECT (self), 0);
187
188 g_signal_connect_object (G_OBJECT (node), "node_dot_removed",
189 G_CALLBACK (node_dot_removed_callback), G_OBJECT (self), 0);
190
191 g_hash_table_insert (self->nodes, &node->key, node);
192 }
193
194 return node;
195 }
196
197 /**
198 * register a part to the nodestore
199 */
node_store_add_part(NodeStore * self,Part * part)200 gboolean node_store_add_part (NodeStore *self, Part *part)
201 {
202 NG_DEBUG ("-0-");
203 g_return_val_if_fail (self, FALSE);
204 g_return_val_if_fail (IS_NODE_STORE (self), FALSE);
205 g_return_val_if_fail (part, FALSE);
206 g_return_val_if_fail (IS_PART (part), FALSE);
207
208 GSList *iter, *copy;
209 Node *node;
210 Coords pin_pos;
211 Coords part_pos;
212 int i, num_pins;
213 Pin *pins;
214
215 num_pins = part_get_num_pins (part);
216 pins = part_get_pins (part);
217
218 item_data_get_pos (ITEM_DATA (part), &part_pos);
219
220 for (i = 0; i < num_pins; i++) {
221 // Use the position of the pin as hash key.
222 pin_pos.x = part_pos.x + pins[i].offset.x;
223 pin_pos.y = part_pos.y + pins[i].offset.y;
224
225 // Retrieve a node for this position.
226 node = node_store_get_or_create_node (self, pin_pos);
227
228 // Add all the wires that intersect this pin to the node store.
229 copy = get_wires_at_pos (self, pin_pos);
230 for (iter = copy; iter; iter = iter->next) {
231 Wire *wire = copy->data;
232
233 node_add_wire (node, wire);
234 wire_add_node (wire, node);
235 }
236
237 g_slist_free (copy);
238
239 node_add_pin (node, &pins[i]);
240 }
241
242 g_object_set (G_OBJECT (part), "store", self, NULL);
243 self->parts = g_list_prepend (self->parts, part);
244 self->items = g_list_prepend (self->items, part);
245
246 return TRUE;
247 }
248
249 /**
250 * remove/unregister a part from the nodestore
251 * this does _not_ free the part!
252 */
node_store_remove_part(NodeStore * self,Part * part)253 gboolean node_store_remove_part (NodeStore *self, Part *part)
254 {
255 Node *node;
256 Coords pin_pos;
257 Coords part_pos;
258 int i, num_pins;
259 Pin *pins;
260
261 g_return_val_if_fail (self, FALSE);
262 g_return_val_if_fail (IS_NODE_STORE (self), FALSE);
263 g_return_val_if_fail (part, FALSE);
264 g_return_val_if_fail (IS_PART (part), FALSE);
265
266 self->parts = g_list_remove (self->parts, part);
267 self->items = g_list_remove (self->items, part);
268
269 num_pins = part_get_num_pins (part);
270 item_data_get_pos (ITEM_DATA (part), &part_pos);
271
272 pins = part_get_pins (part);
273 for (i = 0; i < num_pins; i++) {
274 pin_pos.x = part_pos.x + pins[i].offset.x;
275 pin_pos.y = part_pos.y + pins[i].offset.y;
276
277 node = g_hash_table_lookup (self->nodes, &pin_pos);
278 if (node) {
279 if (!node_remove_pin (node, &pins[i])) {
280 g_warning ("Could not remove pin[%i] from node %p.", i, node);
281 return FALSE;
282 }
283
284 // If the node is empty after removing the pin,
285 // remove the node as well.
286 if (node_is_empty (node)) {
287 g_hash_table_remove (self->nodes, &pin_pos);
288 g_object_unref (G_OBJECT (node));
289 }
290 } else {
291 return FALSE;
292 }
293 }
294
295 return TRUE;
296 }
297
node_store_add_textbox(NodeStore * self,Textbox * text)298 gboolean node_store_add_textbox (NodeStore *self, Textbox *text)
299 {
300 g_object_set (G_OBJECT (text), "store", self, NULL);
301 self->textbox = g_list_prepend (self->textbox, text);
302
303 return TRUE;
304 }
305
node_store_remove_textbox(NodeStore * self,Textbox * text)306 gboolean node_store_remove_textbox (NodeStore *self, Textbox *text)
307 {
308 self->textbox = g_list_remove (self->textbox, text);
309
310 return TRUE;
311 }
312
313 /**
314 * Remove all wires in the nodestore that overlap with a given
315 * wire (preserve the longest of the two overlapping wires).
316 * This is basically an optimization of the nodestore.
317 */
node_store_remove_overlapping_wires(NodeStore * store,Wire * wire)318 void node_store_remove_overlapping_wires (NodeStore *store, Wire *wire)
319 {
320 gdouble a, b;
321 Coords start_pos, end_pos, start_pos_other, end_pos_other;
322 Coords length, length_other;
323 Coords so, eo;
324 GList *list, *to_be_removed = NULL;
325 Node *sn, *en;
326 Wire *other;
327 gboolean overlap;
328
329 g_return_if_fail (store != NULL);
330 g_return_if_fail (IS_NODE_STORE (store));
331 g_return_if_fail (wire != NULL);
332 g_return_if_fail (IS_WIRE (wire));
333
334 // Check for overlapping with other wires.
335 for (list = store->wires; list && list->data != wire; list = list->next) {
336 g_assert (list->data != NULL);
337 g_assert (IS_WIRE (list->data));
338 other = list->data;
339 overlap = do_wires_overlap (wire, other, &so, &eo);
340 NG_DEBUG ("overlap [ %p] and [ %p ] -- %s", wire, other,
341 overlap == TRUE ? "YES" : "NO");
342 if (overlap) {
343 sn = node_store_get_node (store, eo);
344 en = node_store_get_node (store, so);
345 if (!sn && !en)
346 wire = vulcanize_wire (store, wire, other);
347 wire_get_pos_and_length (wire, &start_pos, &length);
348 wire_get_pos_and_length (other, &start_pos_other, &length_other);
349 wire_get_start_and_end_pos (wire, &start_pos, &end_pos);
350 wire_get_start_and_end_pos (other, &start_pos_other, &end_pos_other);
351 if (length.x < 0) {
352 a = start_pos.x;
353 b = end_pos.x;
354 start_pos.x = b;
355 end_pos.x = a;
356 length.x = -length.x;
357 }
358 if (length.y < 0) {
359 a = start_pos.y;
360 b = end_pos.y;
361 start_pos.y = b;
362 end_pos.y = a;
363 length.y = -length.y;
364 }
365 if (length_other.x < 0) {
366 a = start_pos_other.x;
367 b = end_pos_other.x;
368 start_pos_other.x = b;
369 end_pos_other.x = a;
370 length_other.x = -length_other.x;
371 }
372 if (length_other.y < 0) {
373 a = start_pos_other.y;
374 b = end_pos_other.y;
375 start_pos_other.y = b;
376 end_pos_other.y = a;
377 length_other.y = -length_other.y;
378 }
379 if (length.x > 0 && length_other.x > 0 && length.y == 0 && length_other.y == 0) {
380 if (start_pos.x >= start_pos_other.x && end_pos.x <= end_pos_other.x)
381 to_be_removed = g_list_append (to_be_removed, wire);
382 if (start_pos_other.x >= start_pos.x && end_pos_other.x <= end_pos.x)
383 to_be_removed = g_list_append (to_be_removed, other);
384 } else if (length.y > 0 && length_other.y > 0 && length.x == 0 && length_other.x == 0) {
385 if (start_pos.y >= start_pos_other.y && end_pos.y <= end_pos_other.y)
386 to_be_removed = g_list_append (to_be_removed, wire);
387 if (start_pos_other.y >= start_pos.y && end_pos_other.y <= end_pos.y)
388 to_be_removed = g_list_append (to_be_removed, other);
389 }
390 }
391 }
392
393 // Remove all the wires that overlap with a given wire
394 for (list = to_be_removed; list; list = list->next) {
395 if (node_store_remove_wire (store, list->data))
396 wire_delete (list->data);
397 }
398 g_list_free (to_be_removed);
399 }
400
401 /**
402 * add/register the wire to the nodestore
403 *
404 * @param store
405 * @param wire
406 * @returns TRUE if the wire was added or merged, else FALSE
407 */
node_store_add_wire(NodeStore * store,Wire * wire)408 gboolean node_store_add_wire (NodeStore *store, Wire *wire)
409 {
410 GList *list;
411 Node *node;
412 Coords where;
413 int i = 0;
414
415 g_return_val_if_fail (store, FALSE);
416 g_return_val_if_fail (IS_NODE_STORE (store), FALSE);
417 g_return_val_if_fail (wire, FALSE);
418 g_return_val_if_fail (IS_WIRE (wire), FALSE);
419
420 // Check for intersection with other wires.
421 for (list = store->wires; list; list = list->next) {
422 g_assert (list->data != NULL);
423 g_assert (IS_WIRE (list->data));
424
425 Wire *other = list->data;
426 if (do_wires_intersect (wire, other, &where)) {
427 if (is_t_crossing (wire, other, &where) || is_t_crossing (other, wire, &where)) {
428
429 node = node_store_get_or_create_node (store, where);
430
431 node_add_wire (node, wire);
432 node_add_wire (node, other);
433
434 wire_add_node (wire, node);
435 wire_add_node (other, node);
436
437 NG_DEBUG ("Add wire %p to wire %p @ %lf,%lf.\n", wire, other, where.x, where.y);
438 } else {
439 // magic node removal if a x crossing is overlapped with another wire
440 node = node_store_get_node (store, where);
441 NG_DEBUG ("Nuke that node [ %p ] at coords inbetween", node);
442 if (node) {
443 Coords c[4];
444 wire_get_start_and_end_pos (other, c + 0, c + 1);
445 wire_get_start_and_end_pos (wire, c + 2, c + 3);
446 if (!coords_equal (&where, c + 0) && !coords_equal (&where, c + 1) &&
447 !coords_equal (&where, c + 2) && !coords_equal (&where, c + 3)) {
448
449 wire_remove_node (wire, node);
450 wire_remove_node (other, node);
451 node_remove_wire (node, wire);
452 node_remove_wire (node, other);
453 }
454 }
455 }
456 }
457 }
458
459 node_store_remove_overlapping_wires (store, wire);
460
461 // Check for intersection with parts (pins).
462 for (list = store->parts; list; list = list->next) {
463 g_assert (list->data != NULL);
464 g_assert (IS_PART (list->data));
465
466 Coords part_pos;
467 gint num_pins = -1;
468 Part *part = list->data;
469
470 num_pins = part_get_num_pins (part);
471 item_data_get_pos (ITEM_DATA (part), &part_pos);
472
473 // Go through all the parts and see which of their
474 // pins that intersect the wire.
475 for (i = 0; i < num_pins; i++) {
476 Pin *pins;
477 Coords lookup_pos;
478
479 pins = part_get_pins (part);
480 lookup_pos.x = part_pos.x + pins[i].offset.x;
481 lookup_pos.y = part_pos.y + pins[i].offset.y;
482
483 // If there is a wire at this pin's position,
484 // add it to the return list.
485 if (is_point_on_wire (wire, &lookup_pos)) {
486 Node *node;
487 node = node_store_get_node (store, lookup_pos);
488
489 if (node != NULL) {
490 // Add the wire to the node (pin) that it intersected.
491 node_add_wire (node, wire);
492 wire_add_node (wire, node);
493 NG_DEBUG ("Add wire %p to pin (node) %p.\n", wire, node);
494 } else {
495 g_warning ("Bug: Found no node at pin at (%g %g).\n", lookup_pos.x,
496 lookup_pos.y);
497 }
498 }
499 }
500 }
501
502 g_object_set (G_OBJECT (wire), "store", store, NULL);
503 store->wires = g_list_prepend (store->wires, wire);
504 store->items = g_list_prepend (store->items, wire);
505
506 return TRUE;
507 }
508
509 /**
510 * removes/unregisters a wire from the nodestore
511 * this does _not_ free the wire itself!
512 */
node_store_remove_wire(NodeStore * store,Wire * wire)513 gboolean node_store_remove_wire (NodeStore *store, Wire *wire)
514 {
515 GSList *copy, *iter;
516 Coords lookup_key;
517
518 g_return_val_if_fail (store, FALSE);
519 g_return_val_if_fail (IS_NODE_STORE (store), FALSE);
520 g_return_val_if_fail (wire, FALSE);
521 g_return_val_if_fail (IS_WIRE (wire), FALSE);
522
523 if (item_data_get_store (ITEM_DATA (wire)) == NULL) {
524 g_warning ("Trying to remove not-stored wire %p.", wire);
525 return FALSE;
526 }
527
528 store->wires = g_list_remove (store->wires, wire);
529 store->items = g_list_remove (store->items, wire);
530
531 // If the nodes that this wire passes through will be
532 // empty when the wire is removed, remove the node as well.
533
534 // FIXME if done properly, a list copy is _not_ necessary
535 copy = g_slist_copy (wire_get_nodes (wire));
536 for (iter = copy; iter; iter = iter->next) {
537 Node *node = iter->data;
538
539 lookup_key = node->key;
540
541 node_remove_wire (node, wire);
542 wire_remove_node (wire, node);
543
544 if (node_is_empty (node))
545 g_hash_table_remove (store->nodes, &lookup_key);
546 }
547
548 g_slist_free (copy);
549
550 return TRUE;
551 }
552
553 /**
554 * check if there is at least one wire at the specified position
555 *
556 * @param store which NodeStore to check
557 * @param pos the position
558 * @returns TRUE or FALSE
559 */
node_store_is_wire_at_pos(NodeStore * store,Coords pos)560 gboolean node_store_is_wire_at_pos (NodeStore *store, Coords pos)
561 {
562 GList *iter;
563
564 g_return_val_if_fail (store, FALSE);
565 g_return_val_if_fail (IS_NODE_STORE (store), FALSE);
566
567 for (iter = store->wires; iter; iter = iter->next) {
568 Wire *wire = iter->data;
569
570 if (is_point_on_wire (wire, &pos))
571 return TRUE;
572 }
573 return FALSE;
574 }
575
576 /**
577 * lookup node at specified position
578 *
579 * @param store which store to check
580 * @param pos where to check in that store
581 * @returns the node pointer if there is a node, else NULL
582 */
node_store_get_node(NodeStore * store,Coords pos)583 Node *node_store_get_node (NodeStore *store, Coords pos)
584 {
585 Node *node;
586
587 g_return_val_if_fail (store != NULL, NULL);
588 g_return_val_if_fail (IS_NODE_STORE (store), NULL);
589
590 node = g_hash_table_lookup (store->nodes, &pos);
591
592 if (!node) {
593 NG_DEBUG ("No node at (%g, %g)", pos.x, pos.y);
594 } else {
595 NG_DEBUG ("Found node at (%g, %g)", pos.x, pos.y);
596 }
597 return node;
598 }
599
600 /**
601 * Call GHFunc for each node in the nodestore
602 */
node_store_node_foreach(NodeStore * store,GHFunc * func,gpointer user_data)603 void node_store_node_foreach (NodeStore *store, GHFunc *func, gpointer user_data)
604 {
605 g_return_if_fail (store != NULL);
606 g_return_if_fail (IS_NODE_STORE (store));
607
608 g_hash_table_foreach (store->nodes, (gpointer)func, user_data);
609 }
610
611 /**
612 * [transfer-none]
613 */
node_store_get_parts(NodeStore * store)614 GList *node_store_get_parts (NodeStore *store)
615 {
616 g_return_val_if_fail (store != NULL, NULL);
617 g_return_val_if_fail (IS_NODE_STORE (store), NULL);
618
619 return store->parts;
620 }
621
622 /**
623 * [transfer-none]
624 */
node_store_get_wires(NodeStore * store)625 GList *node_store_get_wires (NodeStore *store)
626 {
627 g_return_val_if_fail (store != NULL, NULL);
628 g_return_val_if_fail (IS_NODE_STORE (store), NULL);
629
630 return store->wires;
631 }
632
633 /**
634 * [transfer-none]
635 */
node_store_get_items(NodeStore * store)636 GList *node_store_get_items (NodeStore *store)
637 {
638 g_return_val_if_fail (store != NULL, NULL);
639 g_return_val_if_fail (IS_NODE_STORE (store), NULL);
640
641 return store->items;
642 }
643
644 /**
645 * the caller has to free the list himself, but not the actual data items!
646 */
node_store_get_node_positions(NodeStore * store)647 GList *node_store_get_node_positions (NodeStore *store)
648 {
649 GList *result;
650
651 g_return_val_if_fail (store != NULL, NULL);
652 g_return_val_if_fail (IS_NODE_STORE (store), NULL);
653
654 result = NULL;
655 g_hash_table_foreach (store->nodes, (GHFunc)add_node_position, &result);
656
657 return result;
658 }
659
660 /**
661 * the caller has to free the list himself, but not the actual data items!
662 */
node_store_get_nodes(NodeStore * store)663 GList *node_store_get_nodes (NodeStore *store)
664 {
665 GList *result;
666
667 g_return_val_if_fail (store != NULL, NULL);
668 g_return_val_if_fail (IS_NODE_STORE (store), NULL);
669
670 result = NULL;
671 g_hash_table_foreach (store->nodes, (GHFunc)add_node, &result);
672
673 return result;
674 }
675
node_store_get_bounds(NodeStore * store,NodeRect * rect)676 void node_store_get_bounds (NodeStore *store, NodeRect *rect)
677 {
678 GList *list;
679 Coords p1, p2;
680
681 g_return_if_fail (store != NULL);
682 g_return_if_fail (IS_NODE_STORE (store));
683 g_return_if_fail (rect != NULL);
684
685 rect->x0 = G_MAXDOUBLE;
686 rect->y0 = G_MAXDOUBLE;
687 rect->x1 = -G_MAXDOUBLE;
688 rect->y1 = -G_MAXDOUBLE;
689
690 for (list = store->items; list; list = list->next) {
691 item_data_get_absolute_bbox (ITEM_DATA (list->data), &p1, &p2);
692
693 rect->x0 = MIN (rect->x0, p1.x);
694 rect->y0 = MIN (rect->y0, p1.y);
695 rect->x1 = MAX (rect->x1, p2.x);
696 rect->y1 = MAX (rect->y1, p2.y);
697 }
698 }
699
node_store_count_items(NodeStore * store,NodeRect * rect)700 gint node_store_count_items (NodeStore *store, NodeRect *rect)
701 {
702 GList *list;
703 Coords p1, p2;
704 ItemData *data;
705 gint n;
706
707 g_return_val_if_fail (store != NULL, 0);
708 g_return_val_if_fail (IS_NODE_STORE (store), 0);
709
710 if (rect == NULL)
711 return g_list_length (store->items);
712
713 for (list = store->items, n = 0; list; list = list->next) {
714 data = ITEM_DATA (list->data);
715 item_data_get_absolute_bbox (data, &p1, &p2);
716 if (p1.x <= rect->x1 && p1.y <= rect->y1 && p2.x >= rect->x0 && p2.y >= rect->y0) {
717 n++;
718 }
719 }
720
721 return n;
722 }
723
draw_dot(Coords * pos,Node * value,cairo_t * cr)724 static void draw_dot (Coords *pos, Node *value, cairo_t *cr)
725 {
726 if (node_needs_dot (value)) {
727 cairo_save (cr);
728 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
729 cairo_translate (cr, pos->x, pos->y);
730 cairo_scale (cr, 1.0, 1.0);
731 cairo_arc (cr, 0.0, 0.0, 1.0, 0.0, 2 * M_PI);
732 cairo_fill (cr);
733 cairo_restore (cr);
734 }
735 }
736
node_store_print_items(NodeStore * store,cairo_t * cr,SchematicPrintContext * ctx)737 void node_store_print_items (NodeStore *store, cairo_t *cr, SchematicPrintContext *ctx)
738 {
739 GList *list;
740 ItemData *data;
741
742 g_return_if_fail (store != NULL);
743 g_return_if_fail (IS_NODE_STORE (store));
744
745 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
746 for (list = store->items; list; list = list->next) {
747 data = ITEM_DATA (list->data);
748 item_data_print (data, cr, ctx);
749 }
750
751 g_hash_table_foreach (store->nodes, (GHFunc)draw_dot, cr);
752 }
753
node_store_is_pin_at_pos(NodeStore * store,Coords pos)754 gboolean node_store_is_pin_at_pos (NodeStore *store, Coords pos)
755 {
756 int num_pins;
757 Coords part_pos;
758 GList *p;
759 Part *part;
760 Pin *pins;
761 int i;
762 gdouble x, y;
763
764 for (p = store->parts; p; p = p->next) {
765 part = PART (p->data);
766
767 num_pins = part_get_num_pins (part);
768
769 item_data_get_pos (ITEM_DATA (part), &part_pos);
770
771 pins = part_get_pins (part);
772 for (i = 0; i < num_pins; i++) {
773 x = part_pos.x + pins[i].offset.x;
774 y = part_pos.y + pins[i].offset.y;
775
776 if (fabs (x - pos.x) < NODE_EPSILON && fabs (y - pos.y) < NODE_EPSILON)
777 return TRUE;
778 }
779 }
780 return FALSE;
781 }
782