1 /*
2 * part-item.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 *
12 * Web page: https://ahoi.io/project/oregano
13 *
14 * Copyright (C) 1999-2001 Richard Hult
15 * Copyright (C) 2003,2006 Ricardo Markiewicz
16 * Copyright (C) 2009-2012 Marc Lorber
17 * Copyright (C) 2013-2014 Bernhard Schuster
18 * Copyright (C) 2018 Guido Trentalancia
19 *
20 * This program is free software; you can redistribute it and/or
21 * modify it under the terms of the GNU General Public License as
22 * published by the Free Software Foundation; either version 2 of the
23 * License, or (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
28 * General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public
31 * License along with this program; if not, write to the
32 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
33 * Boston, MA 02110-1301, USA.
34 */
35
36 #include <glib/gi18n.h>
37 #include <string.h>
38 #include <goocanvas.h>
39 #include <math.h>
40
41 #include "oregano.h"
42 #include "sheet-item.h"
43 #include "part-item.h"
44 #include "part-private.h"
45 #include "part-property.h"
46 #include "load-library.h"
47 #include "load-common.h"
48 #include "part-label.h"
49 #include "stock.h"
50 #include "dialogs.h"
51 #include "sheet.h"
52 #include "oregano-utils.h"
53 #include "options.h"
54
55 #define NORMAL_COLOR "red"
56 #define LABEL_COLOR "dark cyan"
57 #define SELECTED_COLOR "green"
58
59 #include "debug.h"
60
61 static void part_item_class_init (PartItemClass *klass);
62 static void part_item_init (PartItem *gspart);
63 static void part_item_finalize (GObject *object);
64 static void part_item_moved (SheetItem *sheet_item);
65 static void edit_properties (SheetItem *object);
66 static void selection_changed (PartItem *item, gboolean select, gpointer user_data);
67 static int select_idle_callback (PartItem *item);
68 static int deselect_idle_callback (PartItem *item);
69 static void update_canvas_labels (PartItem *part_item);
70 static gboolean is_in_area (SheetItem *object, Coords *p1, Coords *p2);
71 inline static void get_cached_bounds (PartItem *item, Coords *p1, Coords *p2);
72 static void show_labels (SheetItem *sheet_item, gboolean show);
73 static void part_item_paste (Sheet *sheet, ItemData *data);
74 static void part_rotated_callback (ItemData *data, int angle, SheetItem *item);
75 static void part_flipped_callback (ItemData *data, IDFlip direction, SheetItem *sheet_item);
76 static void part_moved_callback (ItemData *data, Coords *pos, SheetItem *item);
77 static void part_changed_callback (ItemData *data, SheetItem *sheet_item);
78
79 static void part_item_place (SheetItem *item, Sheet *sheet);
80 static void part_item_place_ghost (SheetItem *item, Sheet *sheet);
81 static void create_canvas_items (GooCanvasGroup *group, LibraryPart *library_part);
82 static void create_canvas_labels (PartItem *item, Part *part);
83 static void create_canvas_label_nodes (PartItem *item, Part *part);
84 static PartItem *part_item_canvas_new (Sheet *sheet, Part *part);
85 static void part_item_get_property (GObject *object, guint prop_id, GValue *value,
86 GParamSpec *spec);
87 static void part_item_set_property (GObject *object, guint prop_id, const GValue *value,
88 GParamSpec *spec);
89 static void part_item_dispose (GObject *object);
90 static GooCanvasAnchorType part_item_get_anchor_from_part (Part *part);
91
92 enum {
93 ARG_0,
94 ARG_DATA,
95 ARG_SHEET,
96 ARG_ACTION_GROUP,
97 ARG_NAME,
98 ARG_SYMNAME,
99 ARG_LIBNAME,
100 ARG_REFDES,
101 ARG_TEMPLATE,
102 ARG_MODEL
103 };
104
105 struct _PartItemPriv
106 {
107 guint cache_valid : 1;
108 GooCanvasItem *label_group;
109 GSList *label_items;
110 GooCanvasItem *node_group;
111 GSList *label_nodes;
112
113 GooCanvasItem *rect;
114 // Cached bounding box. This is used to make
115 // the rubberband selection a bit faster.
116 Coords bbox_start;
117 Coords bbox_end;
118 };
119
120 typedef struct
121 {
122 GtkDialog *dialog;
123 PartItem *part_item;
124 // List of GtkEntry's
125 GList *widgets;
126 } PartPropDialog;
127
128 static PartPropDialog *prop_dialog = NULL;
129 static SheetItemClass *parent_class = NULL;
130
131 static const char *part_item_context_menu = "<ui>"
132 " <popup name='ItemMenu'>"
133 " <menuitem action='ObjectProperties'/>"
134 " </popup>"
135 "</ui>";
136
137 static GtkActionEntry action_entries[] = {{"ObjectProperties", GTK_STOCK_PROPERTIES,
138 N_ ("_Object Properties..."), NULL,
139 N_ ("Modify object properties"), NULL}};
140
141 enum { ANCHOR_NORTH, ANCHOR_SOUTH, ANCHOR_WEST, ANCHOR_EAST };
142
G_DEFINE_TYPE(PartItem,part_item,TYPE_SHEET_ITEM)143 G_DEFINE_TYPE (PartItem, part_item, TYPE_SHEET_ITEM)
144
145 static void part_item_class_init (PartItemClass *part_item_class)
146 {
147 GObjectClass *object_class;
148 SheetItemClass *sheet_item_class;
149
150 object_class = G_OBJECT_CLASS (part_item_class);
151 sheet_item_class = SHEET_ITEM_CLASS (part_item_class);
152 parent_class = g_type_class_peek_parent (part_item_class);
153
154 object_class->finalize = part_item_finalize;
155 object_class->dispose = part_item_dispose;
156 object_class->set_property = part_item_set_property;
157 object_class->get_property = part_item_get_property;
158
159 sheet_item_class->moved = part_item_moved;
160 sheet_item_class->is_in_area = is_in_area;
161 sheet_item_class->show_labels = show_labels;
162 sheet_item_class->paste = part_item_paste;
163 sheet_item_class->edit_properties = edit_properties;
164 sheet_item_class->selection_changed = (gpointer)selection_changed;
165
166 sheet_item_class->place = part_item_place;
167 sheet_item_class->place_ghost = part_item_place_ghost;
168 }
169
part_item_init(PartItem * item)170 static void part_item_init (PartItem *item)
171 {
172 PartItemPriv *priv;
173
174 priv = g_slice_new0 (PartItemPriv);
175 priv->rect = NULL;
176 priv->cache_valid = FALSE;
177
178 item->priv = priv;
179
180 sheet_item_add_menu (SHEET_ITEM (item), part_item_context_menu, action_entries,
181 G_N_ELEMENTS (action_entries));
182 }
183
part_item_set_property(GObject * object,guint propety_id,const GValue * value,GParamSpec * pspec)184 static void part_item_set_property (GObject *object, guint propety_id, const GValue *value,
185 GParamSpec *pspec)
186 {
187 g_return_if_fail (object != NULL);
188 g_return_if_fail (IS_PART_ITEM (object));
189
190 switch (propety_id) {
191 default:
192 g_warning ("PartItem: Invalid argument.\n");
193 }
194 }
195
part_item_get_property(GObject * object,guint propety_id,GValue * value,GParamSpec * pspec)196 static void part_item_get_property (GObject *object, guint propety_id, GValue *value,
197 GParamSpec *pspec)
198 {
199 g_return_if_fail (object != NULL);
200 g_return_if_fail (IS_PART_ITEM (object));
201
202 switch (propety_id) {
203 default:
204 pspec->value_type = G_TYPE_INVALID;
205 break;
206 }
207 }
208
part_item_dispose(GObject * object)209 static void part_item_dispose (GObject *object) { G_OBJECT_CLASS (parent_class)->dispose (object); }
210
part_item_finalize(GObject * object)211 static void part_item_finalize (GObject *object)
212 {
213 PartItemPriv *priv;
214
215 priv = PART_ITEM (object)->priv;
216
217 g_slist_free (priv->label_nodes);
218 g_slist_free (priv->label_items);
219 g_slice_free (PartItemPriv, priv);
220 priv = NULL;
221 G_OBJECT_CLASS (parent_class)->finalize (object);
222 }
223
224 ////////////////////////////////////////////////////////////////////////////////
225 // END BOILER PLATE
226 ////////////////////////////////////////////////////////////////////////////////
227
part_item_set_label_items(PartItem * item,GSList * item_list)228 static void part_item_set_label_items (PartItem *item, GSList *item_list)
229 {
230 PartItemPriv *priv;
231
232 g_return_if_fail (item != NULL);
233 g_return_if_fail (IS_PART_ITEM (item));
234
235 priv = item->priv;
236
237 if (priv->label_items)
238 g_slist_free (priv->label_items);
239
240 priv->label_items = item_list;
241 }
242
part_item_moved(SheetItem * sheet_item)243 static void part_item_moved (SheetItem *sheet_item)
244 {
245 // g_warning ("part MOVED callback called - LEGACY");
246 }
247
part_item_canvas_new(Sheet * sheet,Part * part)248 PartItem *part_item_canvas_new (Sheet *sheet, Part *part)
249 {
250 PartItem *part_item;
251 PartItemPriv *priv;
252 GooCanvasItem *goo_item;
253 ItemData *item_data;
254
255 g_return_val_if_fail (sheet != NULL, NULL);
256 g_return_val_if_fail (IS_SHEET (sheet), NULL);
257 g_return_val_if_fail (part != NULL, NULL);
258 g_return_val_if_fail (IS_PART (part), NULL);
259
260 part_item = g_object_new (TYPE_PART_ITEM, NULL);
261 goo_item = GOO_CANVAS_ITEM (part_item);
262
263 g_object_set (part_item, "parent", sheet->object_group, NULL);
264
265 g_object_set (part_item, "data", part, NULL);
266
267 priv = part_item->priv;
268
269 Coords b1, b2;
270 item_data_get_relative_bbox (ITEM_DATA (part), &b1, &b2);
271
272 priv->rect = goo_canvas_rect_new (
273 goo_item, b1.x, b1.y, b2.x - b1.x, b2.y - b1.y, "stroke-color", "green", "line-width", .0,
274 "fill-color-rgba", 0x7733aa66, "radius-x", 1.0, "radius-y", 1.0, "visibility",
275 oregano_options_debug_boxes () ? GOO_CANVAS_ITEM_VISIBLE : GOO_CANVAS_ITEM_INVISIBLE, NULL);
276
277 priv->label_group = goo_canvas_group_new (goo_item, "width", -1.0, "height", -1.0, NULL);
278
279 priv->node_group = goo_canvas_group_new (goo_item, NULL);
280
281 g_object_set (priv->node_group, "visibility", GOO_CANVAS_ITEM_INVISIBLE, NULL);
282
283 item_data = ITEM_DATA (part);
284 item_data->rotated_handler_id =
285 g_signal_connect_object (part, "rotated", G_CALLBACK (part_rotated_callback), part_item, 0);
286 item_data->flipped_handler_id =
287 g_signal_connect_object (part, "flipped", G_CALLBACK (part_flipped_callback), part_item, 0);
288 item_data->moved_handler_id =
289 g_signal_connect_object (part, "moved", G_CALLBACK (part_moved_callback), part_item, 0);
290 item_data->changed_handler_id =
291 g_signal_connect_object (part, "changed", G_CALLBACK (part_changed_callback), part_item, 0);
292
293 return part_item;
294 }
295
update_canvas_labels(PartItem * item)296 static void update_canvas_labels (PartItem *item)
297 {
298 PartItemPriv *priv;
299 Part *part;
300 GSList *labels, *label_items;
301 GooCanvasItem *canvas_item;
302
303 g_return_if_fail (item != NULL);
304 g_return_if_fail (IS_PART_ITEM (item));
305
306 priv = item->priv;
307 part = PART (sheet_item_get_data (SHEET_ITEM (item)));
308
309 label_items = priv->label_items;
310
311 // Put the label of each item
312 for (labels = part_get_labels (part); labels;
313 labels = labels->next, label_items = label_items->next) {
314 char *text;
315 PartLabel *label = (PartLabel *)labels->data;
316 g_assert (label_items != NULL);
317 canvas_item = label_items->data;
318
319 text = part_property_expand_macros (part, label->text);
320 g_object_set (canvas_item, "text", text, NULL);
321 g_free (text);
322 }
323 }
324
part_item_update_node_label(PartItem * item)325 void part_item_update_node_label (PartItem *item)
326 {
327 PartItemPriv *priv;
328 Part *part;
329 GSList *labels;
330 GooCanvasItem *canvas_item;
331 Pin *pins;
332 gint num_pins;
333
334 g_return_if_fail (item != NULL);
335 g_return_if_fail (IS_PART_ITEM (item));
336 priv = item->priv;
337 part = PART (sheet_item_get_data (SHEET_ITEM (item)));
338
339 g_return_if_fail (IS_PART (part));
340
341 // Put the label of each node
342 num_pins = part_get_num_pins (part);
343
344 if (num_pins == 1) {
345 pins = part_get_pins (part);
346 labels = priv->label_nodes;
347 for (labels = priv->label_nodes; labels; labels = labels->next) {
348 char *txt;
349
350 txt = g_strdup_printf ("V(%d)", pins[0].node_nr);
351 canvas_item = labels->data;
352 if (pins[0].node_nr != 0)
353 g_object_set (canvas_item, "text", txt, "fill_color", LABEL_COLOR, "font", "Sans 8",
354 NULL);
355 else
356 g_object_set (canvas_item, "text", "", NULL);
357
358 g_free (txt);
359 }
360 }
361 }
362
prop_dialog_destroy(GtkWidget * widget,PartPropDialog * prop_dialog)363 static void prop_dialog_destroy (GtkWidget *widget, PartPropDialog *prop_dialog)
364 {
365 g_free (prop_dialog);
366 }
367
prop_dialog_response(GtkWidget * dialog,gint response,PartPropDialog * prop_dialog)368 static void prop_dialog_response (GtkWidget *dialog, gint response, PartPropDialog *prop_dialog)
369 {
370 GSList *props = NULL;
371 GList *widget;
372 Property *prop;
373 PartItem *item;
374 Part *part;
375 gchar *prop_name;
376 const gchar *prop_value;
377 guint prop_value_length;
378 gboolean valid_entry;
379 GtkWidget *w;
380
381 item = prop_dialog->part_item;
382
383 part = PART (sheet_item_get_data (SHEET_ITEM (item)));
384
385 for (widget = prop_dialog->widgets; widget; widget = widget->next) {
386 w = widget->data;
387
388 prop_name = g_object_get_data (G_OBJECT (w), "user");
389 prop_value = gtk_entry_get_text (GTK_ENTRY (w));
390
391 for (props = part_get_properties (part); props; props = props->next) {
392 prop = props->data;
393 if (g_ascii_strcasecmp (prop->name, prop_name) == 0) {
394 valid_entry = TRUE;
395
396 // Prevent invalid properties name entries
397 if (g_ascii_strcasecmp (prop->name, "Refdes") == 0) {
398 // Prevent invalid voltage source name entries
399 if (prop->value[0] == 'V') {
400 if (prop_value[0] != 'V')
401 valid_entry = FALSE;
402 prop_value_length = strlen (prop_value);
403 for (int i = 1; i < prop_value_length; i++)
404 if (prop_value[i] < '0' || prop_value[i] > '9')
405 valid_entry = FALSE;
406 }
407 }
408
409 if (valid_entry) {
410 g_free (prop->value);
411 prop->value = g_strdup (prop_value);
412 }
413 }
414 }
415 g_free (prop_name);
416 }
417
418 update_canvas_labels (item);
419 }
420
edit_properties_point(PartItem * item)421 static void edit_properties_point (PartItem *item)
422 {
423 GSList *properties;
424 Part *part;
425 GtkBuilder *gui;
426 GError *error = NULL;
427 GtkRadioButton *radio_v, *radio_c;
428 GtkRadioButton *ac_r, *ac_m, *ac_i, *ac_p;
429 GtkCheckButton *chk_db;
430
431 part = PART (sheet_item_get_data (SHEET_ITEM (item)));
432
433 if ((gui = gtk_builder_new ()) == NULL) {
434 oregano_error (_ ("Could not create part properties dialog."));
435 return;
436 }
437 gtk_builder_set_translation_domain (gui, NULL);
438
439 if (gtk_builder_add_from_file (gui, OREGANO_UIDIR "/clamp-properties-dialog.ui", &error) <= 0) {
440 oregano_error_with_title (_ ("Could not create part properties dialog."), error->message);
441 g_error_free (error);
442 return;
443 }
444
445 prop_dialog = g_new0 (PartPropDialog, 1);
446
447 prop_dialog->part_item = item;
448
449 prop_dialog->dialog = GTK_DIALOG (gtk_builder_get_object (gui, "clamp-properties-dialog"));
450
451 radio_v = GTK_RADIO_BUTTON (gtk_builder_get_object (gui, "radio_v"));
452 radio_c = GTK_RADIO_BUTTON (gtk_builder_get_object (gui, "radio_c"));
453
454 gtk_widget_set_sensitive (GTK_WIDGET (radio_c), FALSE);
455
456 ac_r = GTK_RADIO_BUTTON (gtk_builder_get_object (gui, "radio_r"));
457 ac_m = GTK_RADIO_BUTTON (gtk_builder_get_object (gui, "radio_m"));
458 ac_p = GTK_RADIO_BUTTON (gtk_builder_get_object (gui, "radio_p"));
459 ac_i = GTK_RADIO_BUTTON (gtk_builder_get_object (gui, "radio_i"));
460
461 chk_db = GTK_CHECK_BUTTON (gtk_builder_get_object (gui, "check_db"));
462
463 // Setup GUI from properties
464 for (properties = part_get_properties (part); properties; properties = properties->next) {
465 Property *prop;
466 prop = properties->data;
467 if (prop->name) {
468 if (!g_ascii_strcasecmp (prop->name, "internal"))
469 continue;
470
471 if (!g_ascii_strcasecmp (prop->name, "type")) {
472 if (!g_ascii_strcasecmp (prop->value, "v")) {
473 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio_v), TRUE);
474 } else {
475 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio_c), TRUE);
476 }
477 } else if (!g_ascii_strcasecmp (prop->name, "ac_type")) {
478 if (!g_ascii_strcasecmp (prop->value, "m")) {
479 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ac_m), TRUE);
480 } else if (!g_ascii_strcasecmp (prop->value, "i")) {
481 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ac_i), TRUE);
482 } else if (!g_ascii_strcasecmp (prop->value, "p")) {
483 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ac_p), TRUE);
484 } else if (!g_ascii_strcasecmp (prop->value, "r")) {
485 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ac_r), TRUE);
486 }
487 } else if (!g_ascii_strcasecmp (prop->name, "ac_db")) {
488 if (!g_ascii_strcasecmp (prop->value, "true"))
489 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chk_db), TRUE);
490 }
491 }
492 }
493
494 gtk_dialog_run (prop_dialog->dialog);
495
496 // Save properties from GUI
497 for (properties = part_get_properties (part); properties; properties = properties->next) {
498 Property *prop;
499 prop = properties->data;
500
501 if (prop->name) {
502 if (!g_ascii_strcasecmp (prop->name, "internal"))
503 continue;
504
505 if (!g_ascii_strcasecmp (prop->name, "type")) {
506 g_free (prop->value);
507 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (radio_v))) {
508 prop->value = g_strdup ("v");
509 } else {
510 prop->value = g_strdup ("i");
511 }
512 } else if (!g_ascii_strcasecmp (prop->name, "ac_type")) {
513 g_free (prop->value);
514 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ac_m))) {
515 prop->value = g_strdup ("m");
516 } else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ac_i))) {
517 prop->value = g_strdup ("i");
518 } else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ac_p))) {
519 prop->value = g_strdup ("p");
520 } else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ac_r))) {
521 prop->value = g_strdup ("r");
522 }
523 } else if (!g_ascii_strcasecmp (prop->name, "ac_db")) {
524 g_free (prop->value);
525 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (chk_db)))
526 prop->value = g_strdup ("true");
527 else
528 prop->value = g_strdup ("false");
529 }
530 }
531 }
532 gtk_widget_destroy (GTK_WIDGET (prop_dialog->dialog));
533 }
534
edit_properties(SheetItem * object)535 static void edit_properties (SheetItem *object)
536 {
537 GSList *properties;
538 PartItem *item;
539 Part *part;
540 char *internal, *msg;
541 GtkBuilder *gui;
542 GError *error = NULL;
543 GtkGrid *prop_grid;
544 GtkNotebook *notebook;
545 gint response, y = 0;
546 gboolean has_model;
547 gchar *model_name = NULL;
548
549 g_return_if_fail (object != NULL);
550 g_return_if_fail (IS_PART_ITEM (object));
551
552 item = PART_ITEM (object);
553 part = PART (sheet_item_get_data (SHEET_ITEM (item)));
554
555 internal = part_get_property (part, "internal");
556 if (internal) {
557 if (g_ascii_strcasecmp (internal, "ground") == 0) {
558 g_free (internal);
559 return;
560 }
561 if (g_ascii_strcasecmp (internal, "point") == 0) {
562 edit_properties_point (item);
563 return;
564 }
565 }
566
567 g_free (internal);
568
569 if ((gui = gtk_builder_new ()) == NULL) {
570 oregano_error (_ ("Could not create part properties dialog."));
571 return;
572 } else
573 gtk_builder_set_translation_domain (gui, NULL);
574
575 if (gtk_builder_add_from_file (gui, OREGANO_UIDIR "/part-properties-dialog.ui", &error) <= 0) {
576 msg = error->message;
577 oregano_error_with_title (_ ("Could not create part properties dialog."), msg);
578 g_error_free (error);
579 return;
580 }
581
582 prop_dialog = g_new0 (PartPropDialog, 1);
583
584 prop_dialog->part_item = item;
585
586 prop_dialog->dialog = GTK_DIALOG (gtk_builder_get_object (gui, "part-properties-dialog"));
587
588 prop_grid = GTK_GRID (gtk_builder_get_object (gui, "prop_grid"));
589 notebook = GTK_NOTEBOOK (gtk_builder_get_object (gui, "notebook"));
590
591 g_signal_connect (prop_dialog->dialog, "destroy", G_CALLBACK (prop_dialog_destroy),
592 prop_dialog);
593
594 prop_dialog->widgets = NULL;
595 has_model = FALSE;
596
597 for (properties = part_get_properties (part); properties; properties = properties->next) {
598 Property *prop;
599
600 prop = properties->data;
601
602 if (prop->name) {
603 GtkWidget *entry;
604 GtkWidget *label;
605 gchar *temp = NULL;
606
607 if (!g_ascii_strcasecmp (prop->name, "internal"))
608 continue;
609
610 if (!g_ascii_strcasecmp (prop->name, "model")) {
611 has_model = TRUE;
612 model_name = g_strdup (prop->value);
613 }
614
615 // Find the Refdes and replace by their real value
616 temp = prop->name;
617 if (!g_ascii_strcasecmp (temp, "Refdes"))
618 temp = _ ("Designation");
619 if (!g_ascii_strcasecmp (temp, "Template"))
620 temp = _ ("Template");
621 if (!g_ascii_strcasecmp (temp, "Res"))
622 temp = _ ("Resistor");
623 if (!g_ascii_strcasecmp (temp, "Cap"))
624 temp = _ ("Capacitor");
625 if (!g_ascii_strcasecmp (temp, "Ind"))
626 temp = _ ("Inductor");
627 label = gtk_label_new (temp);
628
629 entry = gtk_entry_new ();
630 gtk_entry_set_text (GTK_ENTRY (entry), prop->value);
631 g_object_set_data (G_OBJECT (entry), "user", g_strdup (prop->name));
632
633 gtk_grid_attach (prop_grid, label, 0, y, 1, 1);
634
635 gtk_grid_attach (prop_grid, entry, 1, y, 1, 1);
636
637 y++;
638 gtk_widget_show (label);
639 gtk_widget_show (entry);
640
641 prop_dialog->widgets = g_list_prepend (prop_dialog->widgets, entry);
642 }
643 }
644
645 if (!has_model) {
646 gtk_notebook_remove_page (notebook, 1);
647 } else {
648 GtkTextBuffer *txtbuffer;
649 GtkTextView *txtmodel;
650 gchar *filename, *str;
651 GError *read_error = NULL;
652
653 txtmodel = GTK_TEXT_VIEW (gtk_builder_get_object (gui, "txtmodel"));
654 txtbuffer = gtk_text_buffer_new (NULL);
655
656 filename = g_strdup_printf ("%s/%s.model", OREGANO_MODELDIR, model_name);
657 if (g_file_get_contents (filename, &str, NULL, &read_error)) {
658 gtk_text_buffer_set_text (txtbuffer, str, -1);
659 g_free (str);
660 } else {
661 gtk_text_buffer_set_text (txtbuffer, read_error->message, -1);
662 g_error_free (read_error);
663 }
664
665 g_free (filename);
666 g_free (model_name);
667
668 gtk_text_view_set_buffer (txtmodel, txtbuffer);
669 }
670
671 gtk_dialog_set_default_response (prop_dialog->dialog, 1);
672
673 response = gtk_dialog_run (prop_dialog->dialog);
674
675 prop_dialog_response (GTK_WIDGET (prop_dialog->dialog), response, prop_dialog);
676
677 gtk_widget_destroy (GTK_WIDGET (prop_dialog->dialog));
678 }
679
angle_to_anchor(int angle)680 inline static GooCanvasAnchorType angle_to_anchor (int angle)
681 {
682 GooCanvasAnchorType anchor;
683 // Get the right anchor for the labels. This is needed since the
684 // canvas doesn't know how to rotate text and since we rotate the
685 // label_group instead of the labels directly.
686
687 while (angle < 0)
688 angle += 360;
689 angle %= 360;
690
691 if (90 - 45 < angle && angle < 90 + 45) {
692 anchor = GOO_CANVAS_ANCHOR_NORTH_WEST;
693 } else if (180 - 45 < angle && angle < 180 + 45) {
694 anchor = GOO_CANVAS_ANCHOR_NORTH_EAST;
695 } else if (270 - 45 < angle && angle < 270 + 45) {
696 anchor = GOO_CANVAS_ANCHOR_SOUTH_EAST;
697 } else /* if (360-45 < angle && angle < 0+45) */ {
698 anchor = GOO_CANVAS_ANCHOR_SOUTH_WEST;
699 }
700
701 return anchor;
702 }
703
704 /**
705 * whenever the model changes, this one gets called to update the view
706 * representation
707 * @attention this recalculates the matrix every time, this makes sure no errors
708 * stack up
709 * @attention further reading on matrix manipulations
710 * @attention http://www.cairographics.org/matrix_transform/
711 * @param data the model item, a bare C struct derived from ItemData
712 * @param sheet_item the view item, derived from goo_canvas_group/item
713 */
part_changed_callback(ItemData * data,SheetItem * sheet_item)714 static void part_changed_callback (ItemData *data, SheetItem *sheet_item)
715 {
716 g_return_if_fail (sheet_item != NULL);
717 g_return_if_fail (IS_PART_ITEM (sheet_item));
718
719 // TODO add static vars in order to skip the redraw if nothing changed
720 // TODO may happen once in a while and the check is really cheap
721 PartItem *item = PART_ITEM (sheet_item);
722 PartItemPriv *priv = item->priv;
723
724 // init the states
725
726 cairo_matrix_t morph, inv;
727 cairo_status_t done;
728
729 inv = *(item_data_get_rotate (data)); // copy
730 cairo_matrix_multiply (&morph, &inv, item_data_get_translate (data));
731
732 done = cairo_matrix_invert (&inv);
733 if (done != CAIRO_STATUS_SUCCESS) {
734 g_warning ("Failed to invert matrix. This should never happen. Ever!");
735 return;
736 }
737 // no translations
738 inv.y0 = inv.x0 = 0.;
739
740 goo_canvas_item_set_transform (GOO_CANVAS_ITEM (sheet_item), &(morph));
741
742 priv->cache_valid = FALSE;
743 return; /* FIXME */
744 #if 0
745 GooCanvasGroup *group = GOO_CANVAS_GROUP (item);
746
747 // rotate all items in the canvas group
748 for (int index = 0; index < group->items->len; index++) {
749 GooCanvasItem *canvas_item = GOO_CANVAS_ITEM (group->items->pdata[index]);
750 goo_canvas_item_set_transform (GOO_CANVAS_ITEM (canvas_item), &morph);
751 }
752
753 // revert the rotation of all labels and change their anchor to not overlap too badly
754 // this assures that the text is always horizontal and properly aligned
755 GooCanvasAnchorType anchor = angle_to_anchor (rotation);
756
757 for (GSList *iter = priv->label_items; iter; iter = iter->next) {
758 g_object_set (iter->data,
759 "anchor", anchor,
760 NULL);
761
762 goo_canvas_item_set_transform (iter->data, &inv);
763
764 }
765 // same for label nodes
766 for (GSList *iter = priv->label_nodes; iter; iter = iter->next) {
767 g_object_set (iter->data,
768 "anchor", anchor,
769 NULL);
770
771 goo_canvas_item_set_transform (iter->data, &inv);
772 }
773
774
775 // Invalidate the bounding box cache.
776 priv->cache_valid = FALSE;
777 #endif
778 }
779
780 /**
781 * a part got rotated
782 *
783 * @angle the angle the item is rotated towards the default (0) rotation
784 *
785 */
part_rotated_callback(ItemData * data,int angle,SheetItem * sheet_item)786 static void part_rotated_callback (ItemData *data, int angle, SheetItem *sheet_item)
787 {
788 // g_warning ("ROTATED callback called - LEGACY\n");
789 }
790
791 /**
792 * handles the update of the canvas item when a part gets flipped (within the
793 * backend alias model)
794 * @data the part in form of a ItemData pointer
795 * @direction the new flip state
796 * @sheet_item the corresponding sheet_item to the model item @data
797 */
part_flipped_callback(ItemData * data,IDFlip direction,SheetItem * sheet_item)798 static void part_flipped_callback (ItemData *data, IDFlip direction, SheetItem *sheet_item)
799 {
800 // g_warning ("FLIPPED callback called - LEGACY\n");
801 }
802
part_item_signal_connect_floating(PartItem * item)803 void part_item_signal_connect_floating (PartItem *item)
804 {
805 Sheet *sheet;
806
807 sheet = sheet_item_get_sheet (SHEET_ITEM (item));
808 sheet->state = SHEET_STATE_FLOAT_START;
809
810 g_signal_connect (G_OBJECT (item), "double_clicked", G_CALLBACK (edit_properties), item);
811 }
812
selection_changed(PartItem * item,gboolean select,gpointer user_data)813 static void selection_changed (PartItem *item, gboolean select, gpointer user_data)
814 {
815 g_object_ref (G_OBJECT (item));
816 if (select)
817 g_idle_add ((gpointer)select_idle_callback, item);
818 else
819 g_idle_add ((gpointer)deselect_idle_callback, item);
820 }
821
select_idle_callback(PartItem * item)822 static int select_idle_callback (PartItem *item)
823 {
824 GooCanvasItem *canvas_item = NULL;
825 int index;
826
827 g_return_val_if_fail (item != NULL, FALSE);
828
829 for (index = 0; index < GOO_CANVAS_GROUP (item)->items->len; index++) {
830 canvas_item = GOO_CANVAS_ITEM (GOO_CANVAS_GROUP (item)->items->pdata[index]);
831 g_object_set (canvas_item, "stroke-color", SELECTED_COLOR, NULL);
832 }
833 g_object_unref (G_OBJECT (item));
834 return FALSE;
835 }
836
deselect_idle_callback(PartItem * item)837 static int deselect_idle_callback (PartItem *item)
838 {
839 GooCanvasItem *canvas_item = NULL;
840 int index;
841
842 for (index = 0; index < GOO_CANVAS_GROUP (item)->items->len; index++) {
843 canvas_item = GOO_CANVAS_ITEM (GOO_CANVAS_GROUP (item)->items->pdata[index]);
844
845 if (GOO_IS_CANVAS_TEXT (canvas_item)) {
846 g_object_set (canvas_item, "stroke-color", LABEL_COLOR, NULL);
847 } else {
848 g_object_set (canvas_item, "stroke-color", NORMAL_COLOR, NULL);
849 }
850 }
851 g_object_unref (G_OBJECT (item));
852 return FALSE;
853 }
854
is_in_area(SheetItem * object,Coords * p1,Coords * p2)855 static gboolean is_in_area (SheetItem *object, Coords *p1, Coords *p2)
856 {
857 PartItem *item;
858 Coords bbox_start, bbox_end;
859
860 item = PART_ITEM (object);
861
862 get_cached_bounds (item, &bbox_start, &bbox_end);
863
864 if ((p1->x < bbox_start.x) && (p2->x > bbox_end.x) && (p1->y < bbox_start.y) &&
865 (p2->y > bbox_end.y)) {
866 return TRUE;
867 }
868 return FALSE;
869 }
870
show_labels(SheetItem * sheet_item,gboolean show)871 static void show_labels (SheetItem *sheet_item, gboolean show)
872 {
873 PartItem *item;
874 PartItemPriv *priv;
875
876 g_return_if_fail (sheet_item != NULL);
877 g_return_if_fail (IS_PART_ITEM (sheet_item));
878
879 item = PART_ITEM (sheet_item);
880 priv = item->priv;
881
882 if (show)
883 g_object_set (priv->label_group, "visibility", GOO_CANVAS_ITEM_VISIBLE, NULL);
884 else
885 g_object_set (priv->label_group, "visibility", GOO_CANVAS_ITEM_INVISIBLE, NULL);
886 }
887
888 // Retrieves the bounding box. We use a caching scheme for this
889 // since it's too expensive to calculate it every time we need it.
get_cached_bounds(PartItem * item,Coords * p1,Coords * p2)890 inline static void get_cached_bounds (PartItem *item, Coords *p1, Coords *p2)
891 {
892 PartItemPriv *priv;
893 priv = item->priv;
894
895 if (G_LIKELY (priv->cache_valid)) {
896 *p1 = priv->bbox_start;
897 *p2 = priv->bbox_end;
898 } else {
899 Coords start_pos, end_pos;
900 GooCanvasBounds bounds;
901
902 goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (item), &bounds);
903
904 start_pos.x = bounds.x1;
905 start_pos.y = bounds.y1;
906 end_pos.x = bounds.x2;
907 end_pos.y = bounds.y2;
908
909 *p1 = priv->bbox_start = start_pos;
910 *p2 = priv->bbox_end = end_pos;
911 priv->cache_valid = TRUE;
912 }
913 }
914
part_item_paste(Sheet * sheet,ItemData * data)915 static void part_item_paste (Sheet *sheet, ItemData *data)
916 {
917 g_return_if_fail (sheet != NULL);
918 g_return_if_fail (IS_SHEET (sheet));
919 g_return_if_fail (data != NULL);
920 g_return_if_fail (IS_PART (data));
921
922 sheet_add_ghost_item (sheet, data);
923 }
924
part_item_new(Sheet * sheet,Part * part)925 PartItem *part_item_new (Sheet *sheet, Part *part)
926 {
927 Library *library;
928 LibraryPart *library_part;
929 PartPriv *priv;
930 PartItem *item;
931
932 priv = part->priv;
933
934 library = priv->library;
935 library_part = library_get_part (library, priv->name);
936
937 // Create the PartItem canvas item
938 item = part_item_canvas_new (sheet, part);
939 create_canvas_items (GOO_CANVAS_GROUP (item), library_part);
940 create_canvas_labels (item, part);
941 create_canvas_label_nodes (item, part);
942 goo_canvas_item_ensure_updated (GOO_CANVAS_ITEM (item));
943 return item;
944 }
945
part_item_create_canvas_items_for_preview(GooCanvasGroup * group,LibraryPart * library_part)946 void part_item_create_canvas_items_for_preview (GooCanvasGroup *group, LibraryPart *library_part)
947 {
948 g_return_if_fail (group != NULL);
949 g_return_if_fail (library_part != NULL);
950
951 create_canvas_items (group, library_part);
952 }
953
create_canvas_items(GooCanvasGroup * group,LibraryPart * library_part)954 static void create_canvas_items (GooCanvasGroup *group, LibraryPart *library_part)
955 {
956 GooCanvasItem *item;
957 GooCanvasPoints *points;
958 GSList *objects;
959 LibrarySymbol *symbol;
960 SymbolObject *object;
961 gdouble height, width;
962 GooCanvasBounds bounds, group_bounds = {0, 0, 0, 0};
963
964 g_return_if_fail (group != NULL);
965 g_return_if_fail (library_part != NULL);
966
967 symbol = library_get_symbol (library_part->symbol_name);
968 if (symbol == NULL) {
969 g_warning ("Couldn't find the requested symbol %s for part %s in "
970 "library.\n",
971 library_part->symbol_name, library_part->name);
972 return;
973 }
974
975 for (objects = symbol->symbol_objects; objects; objects = objects->next) {
976 object = (SymbolObject *)(objects->data);
977 switch (object->type) {
978 case SYMBOL_OBJECT_LINE:
979 points = object->u.uline.line;
980 item = goo_canvas_polyline_new (GOO_CANVAS_ITEM (group), FALSE, 0, "points", points,
981 "stroke-color", NORMAL_COLOR, "line-width", 0.5, NULL);
982 if (object->u.uline.spline) {
983 g_object_set (item, "smooth", TRUE, "spline_steps", 5, NULL);
984 }
985 break;
986 case SYMBOL_OBJECT_ARC:
987 item = goo_canvas_ellipse_new (GOO_CANVAS_ITEM (group),
988 (object->u.arc.x2 + object->u.arc.x1) / 2.0,
989 (object->u.arc.y1 + object->u.arc.y2) / 2.0,
990 (object->u.arc.x2 - object->u.arc.x1) / 2.0,
991 (object->u.arc.y1 - object->u.arc.y2) / 2.0,
992 "stroke-color", NORMAL_COLOR, "line_width", 1.0, NULL);
993 break;
994 case SYMBOL_OBJECT_TEXT:
995 item = goo_canvas_text_new (GOO_CANVAS_ITEM (group), object->u.text.str,
996 (double)object->u.text.x, (double)object->u.text.y, -1,
997 GOO_CANVAS_ANCHOR_NORTH_EAST, "fill_color", LABEL_COLOR,
998 "font", "Sans 8", NULL);
999 break;
1000 default:
1001 g_warning ("Unknown symbol object.\n");
1002 continue;
1003 }
1004 goo_canvas_item_get_bounds (item, &bounds);
1005 if (group_bounds.x1 > bounds.x1)
1006 group_bounds.x1 = bounds.x1;
1007 if (group_bounds.x2 < bounds.x2)
1008 group_bounds.x2 = bounds.x2;
1009 if (group_bounds.y1 > bounds.y1)
1010 group_bounds.y1 = bounds.y1;
1011 if (group_bounds.y2 < bounds.y2)
1012 group_bounds.y2 = bounds.y2;
1013 }
1014
1015 g_object_get (group, "width", &width, "height", &height, NULL);
1016 width = group_bounds.x2 - group_bounds.x1;
1017 height = group_bounds.y2 - group_bounds.y1;
1018
1019 g_object_set (group, "width", width, "height", height, NULL);
1020 }
1021
create_canvas_labels(PartItem * item,Part * part)1022 static void create_canvas_labels (PartItem *item, Part *part)
1023 {
1024 GooCanvasItem *canvas_item;
1025 GSList *list, *item_list;
1026 GooCanvasGroup *group;
1027
1028 g_return_if_fail (item != NULL);
1029 g_return_if_fail (IS_PART_ITEM (item));
1030 g_return_if_fail (part != NULL);
1031 g_return_if_fail (IS_PART (part));
1032
1033 group = GOO_CANVAS_GROUP (item->priv->label_group);
1034 item_list = NULL;
1035
1036 for (list = part_get_labels (part); list; list = list->next) {
1037 PartLabel *label = list->data;
1038 char *text;
1039
1040 text = part_property_expand_macros (part, label->text);
1041
1042 canvas_item = goo_canvas_text_new (GOO_CANVAS_ITEM (group), text, (double)label->pos.x,
1043 (double)label->pos.y, 0, GOO_CANVAS_ANCHOR_SOUTH_WEST,
1044 "fill_color", LABEL_COLOR, "font", "Sans 8", NULL);
1045
1046 item_list = g_slist_prepend (item_list, canvas_item);
1047 g_free (text);
1048 }
1049
1050 item_list = g_slist_reverse (item_list);
1051 part_item_set_label_items (item, item_list);
1052 }
1053
create_canvas_label_nodes(PartItem * item,Part * part)1054 static void create_canvas_label_nodes (PartItem *item, Part *part)
1055 {
1056 GooCanvasItem *canvas_item;
1057 GSList *item_list;
1058 GooCanvasItem *group;
1059 Pin *pins;
1060 int num_pins, i;
1061 Coords p1, p2;
1062 GooCanvasAnchorType anchor;
1063
1064 g_return_if_fail (item != NULL);
1065 g_return_if_fail (IS_PART_ITEM (item));
1066 g_return_if_fail (part != NULL);
1067 g_return_if_fail (IS_PART (part));
1068
1069 num_pins = part_get_num_pins (part);
1070 pins = part_get_pins (part);
1071 group = item->priv->node_group;
1072 item_list = NULL;
1073
1074 get_cached_bounds (item, &p1, &p2);
1075
1076 switch (part_get_rotation (part)) {
1077 case 0:
1078 anchor = GOO_CANVAS_ANCHOR_SOUTH_WEST;
1079 break;
1080 case 90:
1081 anchor = GOO_CANVAS_ANCHOR_NORTH_WEST;
1082 break;
1083 case 180:
1084 anchor = GOO_CANVAS_ANCHOR_NORTH_EAST;
1085 break;
1086 case 270:
1087 anchor = GOO_CANVAS_ANCHOR_SOUTH_EAST;
1088 break;
1089 default:
1090 anchor = GOO_CANVAS_ANCHOR_SOUTH_WEST;
1091 }
1092
1093 for (i = 0; i < num_pins; i++) {
1094 int x, y;
1095 char *text;
1096 x = pins[i].offset.x;
1097 y = pins[i].offset.y;
1098
1099 text = g_strdup_printf ("%d", pins[i].node_nr);
1100 canvas_item = goo_canvas_text_new (GOO_CANVAS_ITEM (group), text, (double)x, (double)y, 0,
1101 anchor, "fill_color", "black", "font", "Sans 8", NULL);
1102 // Shift slightly the label for a Voltmeter
1103 if (i == 0)
1104 goo_canvas_item_translate (canvas_item, -15.0, -10.0);
1105
1106 item_list = g_slist_prepend (item_list, canvas_item);
1107 g_free (text);
1108 }
1109 item_list = g_slist_reverse (item_list);
1110 item->priv->label_nodes = item_list;
1111 }
1112
1113 // This is called when the part data was moved. Update the view accordingly.
part_moved_callback(ItemData * data,Coords * pos,SheetItem * item)1114 static void part_moved_callback (ItemData *data, Coords *pos, SheetItem *item) {}
1115
part_item_place(SheetItem * item,Sheet * sheet)1116 static void part_item_place (SheetItem *item, Sheet *sheet)
1117 {
1118 g_signal_connect (G_OBJECT (item), "button_press_event", G_CALLBACK (sheet_item_event), sheet);
1119
1120 g_signal_connect (G_OBJECT (item), "button_release_event", G_CALLBACK (sheet_item_event),
1121 sheet);
1122
1123 g_signal_connect (G_OBJECT (item), "motion_notify_event", G_CALLBACK (sheet_item_event), sheet);
1124
1125 g_signal_connect (G_OBJECT (item), "key_press_event", G_CALLBACK (sheet_item_event), sheet);
1126
1127 g_signal_connect (G_OBJECT (item), "double_clicked", G_CALLBACK (edit_properties), item);
1128 }
1129
part_item_place_ghost(SheetItem * item,Sheet * sheet)1130 static void part_item_place_ghost (SheetItem *item, Sheet *sheet)
1131 {
1132 // part_item_signal_connect_placed (PART_ITEM (item));
1133 }
1134
part_item_show_node_labels(PartItem * part,gboolean show)1135 void part_item_show_node_labels (PartItem *part, gboolean show)
1136 {
1137 PartItemPriv *priv;
1138
1139 priv = part->priv;
1140
1141 if (show)
1142 g_object_set (priv->node_group, "visibility", GOO_CANVAS_ITEM_VISIBLE, NULL);
1143 else
1144 g_object_set (priv->node_group, "visibility", GOO_CANVAS_ITEM_INVISIBLE, NULL);
1145 }
1146
part_item_get_anchor_from_part(Part * part)1147 static GooCanvasAnchorType part_item_get_anchor_from_part (Part *part)
1148 {
1149 int anchor_h, anchor_v;
1150 int angle;
1151 IDFlip flip;
1152
1153 flip = part_get_flip (part);
1154 angle = part_get_rotation (part);
1155
1156 switch (angle) {
1157 case 0:
1158 anchor_h = ANCHOR_SOUTH;
1159 anchor_v = ANCHOR_WEST;
1160 break;
1161 case 90:
1162 anchor_h = ANCHOR_NORTH;
1163 anchor_v = ANCHOR_WEST;
1164 // Invert Rotation
1165 if (flip & ID_FLIP_HORIZ)
1166 flip = ID_FLIP_VERT;
1167 else if (flip & ID_FLIP_VERT)
1168 flip = ID_FLIP_HORIZ;
1169 break;
1170 }
1171
1172 if (flip & ID_FLIP_HORIZ) {
1173 anchor_v = ANCHOR_EAST;
1174 }
1175 if (flip & ID_FLIP_VERT) {
1176 anchor_h = ANCHOR_NORTH;
1177 }
1178
1179 if ((anchor_v == ANCHOR_EAST) && (anchor_h == ANCHOR_NORTH))
1180 return GOO_CANVAS_ANCHOR_NORTH_EAST;
1181 if ((anchor_v == ANCHOR_WEST) && (anchor_h == ANCHOR_NORTH))
1182 return GOO_CANVAS_ANCHOR_NORTH_WEST;
1183 if ((anchor_v == ANCHOR_WEST) && (anchor_h == ANCHOR_SOUTH))
1184 return GOO_CANVAS_ANCHOR_SOUTH_WEST;
1185
1186 return GOO_CANVAS_ANCHOR_SOUTH_EAST;
1187 }
1188