1 /*
2 * COPYRIGHT
3 *
4 * gEDA - GNU Electronic Design Automation
5 * This is a part of gerbv
6 *
7 * Copyright (C) 2008 Dan McMahill
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 */
25
26 /** \file attribute.c
27 \brief Dynamic GUI window creation functions
28 \ingroup gerbv
29 */
30
31 #ifdef HAVE_CONFIG_H
32 #include "config.h"
33 #endif
34
35 #ifdef HAVE_STDLIB_H
36 #include <stdlib.h>
37 #endif
38
39 #ifdef HAVE_STRING_H
40 #include <string.h>
41 #endif
42
43 #ifdef HAVE_SYS_STAT_H
44 #include <sys/stat.h>
45 #endif
46
47 #include <gtk/gtk.h>
48
49 #include "common.h"
50 #include "gerbv.h"
51 #include "attribute.h"
52 #include "main.h"
53
54 #define dprintf if(DEBUG) printf
55
56 static int auto_uncheck_needed = 0;
57 static GtkWidget * auto_uncheck_widget = NULL;
58 static int * auto_uncheck_attr = NULL;
59 static GtkWidget ** all_widgets = NULL;
60 static int n_widgets;
61
clear_auto()62 static void clear_auto()
63 {
64 if( auto_uncheck_needed && auto_uncheck_widget != NULL && auto_uncheck_attr != NULL) {
65 /* disable this bit of code so we don't enter an endless loop */
66 auto_uncheck_needed = 0;
67
68 /* uncheck the "auto" toggle button */
69 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (auto_uncheck_widget), 0);
70
71 /* store that we have unchecked the "auto" toggle button */
72 *auto_uncheck_attr = 0;
73
74 /* re-enable this bit of code */
75 auto_uncheck_needed = 1;
76 }
77 }
78
79 /* Callback for toggling a boolean attribute */
80 static void
set_flag_cb(GtkToggleButton * button,gboolean * flag)81 set_flag_cb (GtkToggleButton * button, gboolean * flag)
82 {
83 int i, f;
84
85 *flag = gtk_toggle_button_get_active (button);
86
87 /*
88 * if this is the "auto" button then set/clear the sensitivity of
89 * everything else. Otherwise call the clear_auto() function
90 */
91 if (auto_uncheck_widget == GTK_WIDGET (button)) {
92 f = *flag ? 0 : 1;
93 for (i = 1 ; i < n_widgets ; i++) {
94 gtk_widget_set_sensitive (all_widgets[i], f);
95 }
96 } else {
97 clear_auto ();
98 }
99 }
100
101 /* Callback for setting an integer value */
102 static void
intspinner_changed_cb(GtkWidget * spin_button,gpointer data)103 intspinner_changed_cb (GtkWidget * spin_button, gpointer data)
104 {
105 int *ival = data;
106
107 *ival = gtk_spin_button_get_value (GTK_SPIN_BUTTON (spin_button));
108 clear_auto ();
109 }
110
111 /* Callback for setting a floating point value */
112 static void
dblspinner_changed_cb(GtkWidget * spin_button,gpointer data)113 dblspinner_changed_cb (GtkWidget * spin_button, gpointer data)
114 {
115 double *dval = data;
116
117 *dval = gtk_spin_button_get_value (GTK_SPIN_BUTTON (spin_button));
118 clear_auto ();
119 }
120
121 /* Callback for setting an string value */
122 static void
entry_changed_cb(GtkEntry * entry,char ** str)123 entry_changed_cb (GtkEntry * entry, char **str)
124 {
125 const gchar *s;
126
127 s = gtk_entry_get_text (entry);
128
129 if (*str)
130 free (*str);
131 *str = strdup (s);
132
133 clear_auto ();
134 }
135
136 /* Callback for setting an enum value */
137 static void
enum_changed_cb(GtkWidget * combo_box,int * val)138 enum_changed_cb (GtkWidget * combo_box, int *val)
139 {
140 gint active;
141
142 active = gtk_combo_box_get_active (GTK_COMBO_BOX (combo_box));
143 *val = active;
144
145 clear_auto ();
146 }
147
148 /* Utility function for building a vbox with a text label */
149 /* Written by Bill Wilson for PCB */
150 static GtkWidget *
ghid_category_vbox(GtkWidget * box,const gchar * category_header,gint header_pad,gint box_pad,gboolean pack_start,gboolean bottom_pad)151 ghid_category_vbox (GtkWidget * box, const gchar * category_header,
152 gint header_pad,
153 gint box_pad, gboolean pack_start, gboolean bottom_pad)
154 {
155 GtkWidget *vbox, *vbox1, *hbox, *label;
156 gchar *s;
157
158 vbox = gtk_vbox_new (FALSE, 0);
159 if (pack_start)
160 gtk_box_pack_start (GTK_BOX (box), vbox, FALSE, FALSE, 0);
161 else
162 gtk_box_pack_end (GTK_BOX (box), vbox, FALSE, FALSE, 0);
163
164 if (category_header)
165 {
166 label = gtk_label_new (NULL);
167 s = g_strconcat ("<span weight=\"bold\">", category_header,
168 "</span>", NULL);
169 gtk_label_set_markup (GTK_LABEL (label), s);
170 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
171 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, header_pad);
172 g_free (s);
173 }
174
175 hbox = gtk_hbox_new (FALSE, 0);
176 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
177 label = gtk_label_new (" ");
178 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
179 vbox1 = gtk_vbox_new (FALSE, box_pad);
180 gtk_box_pack_start (GTK_BOX (hbox), vbox1, TRUE, TRUE, 0);
181
182 if (bottom_pad)
183 {
184 label = gtk_label_new ("");
185 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
186 }
187 return vbox1;
188 }
189
190 /* Utility function for creating a spin button */
191 /* Written by Bill Wilson for PCB */
192 static void
ghid_spin_button(GtkWidget * box,GtkWidget ** spin_button,gfloat value,gfloat low,gfloat high,gfloat step0,gfloat step1,gint digits,gint width,void (* cb_func)(),gpointer data,gboolean right_align,gchar * string)193 ghid_spin_button (GtkWidget * box, GtkWidget ** spin_button, gfloat value,
194 gfloat low, gfloat high, gfloat step0, gfloat step1,
195 gint digits, gint width,
196 void (*cb_func) (), gpointer data, gboolean right_align,
197 gchar * string)
198 {
199 GtkWidget *hbox = NULL, *label, *spin_but;
200 GtkSpinButton *spin;
201 GtkAdjustment *adj;
202
203 if (string && box)
204 {
205 hbox = gtk_hbox_new (FALSE, 0);
206 gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, FALSE, 2);
207 box = hbox;
208 }
209 adj = (GtkAdjustment *) gtk_adjustment_new (value,
210 low, high, step0, step1, 0.0);
211 spin_but = gtk_spin_button_new (adj, 0.5, digits);
212 if (spin_button)
213 *spin_button = spin_but;
214 if (width > 0)
215 gtk_widget_set_size_request (spin_but, width, -1);
216 spin = GTK_SPIN_BUTTON (spin_but);
217 gtk_spin_button_set_numeric (spin, TRUE);
218 if (data == NULL)
219 data = (gpointer) spin;
220 if (cb_func)
221 g_signal_connect (G_OBJECT (spin_but), "value_changed",
222 G_CALLBACK (cb_func), data);
223 if (box)
224 {
225 if (right_align && string)
226 {
227 label = gtk_label_new (string);
228 gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
229 gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 2);
230 }
231 gtk_box_pack_start (GTK_BOX (box), spin_but, FALSE, FALSE, 2);
232 if (!right_align && string)
233 {
234 label = gtk_label_new (string);
235 gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
236 gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 2);
237 }
238 }
239 }
240
241 /* Utility function for creating a check button */
242 /* Written by Bill Wilson for PCB */
243 static void
ghid_check_button_connected(GtkWidget * box,GtkWidget ** button,gboolean active,gboolean pack_start,gboolean expand,gboolean fill,gint pad,void (* cb_func)(),gpointer data,gchar * string)244 ghid_check_button_connected (GtkWidget * box,
245 GtkWidget ** button,
246 gboolean active,
247 gboolean pack_start,
248 gboolean expand,
249 gboolean fill,
250 gint pad,
251 void (*cb_func) (),
252 gpointer data, gchar * string)
253 {
254 GtkWidget *b;
255
256 if (!string)
257 return;
258 b = gtk_check_button_new_with_label (string);
259 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b), active);
260 if (box && pack_start)
261 gtk_box_pack_start (GTK_BOX (box), b, expand, fill, pad);
262 else if (box && !pack_start)
263 gtk_box_pack_end (GTK_BOX (box), b, expand, fill, pad);
264
265 if (cb_func)
266 gtk_signal_connect (GTK_OBJECT (b), "clicked",
267 GTK_SIGNAL_FUNC (cb_func), data);
268 if (button)
269 *button = b;
270 }
271
272 /*
273 * The following function is taken almost directly from
274 * ghid_attribte_dialog() from pcb. It is a generic attribute editor
275 * gui where the dialog is built on the fly based on a passed in
276 * attribute list.
277 *
278 * Written by Dan McMahill
279 */
280 int
attribute_interface_dialog(gerbv_HID_Attribute * attrs,int n_attrs,gerbv_HID_Attr_Val * results,const char * title,const char * descr)281 attribute_interface_dialog (gerbv_HID_Attribute * attrs,
282 int n_attrs, gerbv_HID_Attr_Val * results,
283 const char * title,
284 const char * descr)
285 {
286 GtkWidget *dialog, *main_vbox, *vbox, *vbox1, *hbox, *entry;
287 GtkWidget *combo;
288 GtkWidget *widget;
289 int i, j;
290 GtkTooltips *tips;
291 int rc = 0;
292 int set_auto_uncheck = 0;
293 int sen = TRUE;
294
295 /*
296 * Store how many widgets we'll have in our dialog and keep track of
297 * them. Be sure to free up our list if one existed already.
298 */
299 n_widgets = n_attrs;
300 if (all_widgets != NULL)
301 free (all_widgets);
302
303 all_widgets = (GtkWidget **) malloc (n_widgets * sizeof(GtkWidget *));
304 if (all_widgets == NULL) {
305 fprintf (stderr, "%s(): malloc failed for an array of size %d\n", __FUNCTION__, n_widgets);
306 exit (1);
307 }
308
309 dprintf ("%s(%p, %d, %p, \"%s\", \"%s\")\n", __FUNCTION__, attrs, n_attrs, results, title, descr);
310
311 auto_uncheck_needed = 0;
312 auto_uncheck_widget = NULL;
313 auto_uncheck_attr = NULL;
314
315 tips = gtk_tooltips_new ();
316
317 dialog = gtk_dialog_new_with_buttons (title,
318 GTK_WINDOW (screen.win.topLevelWindow),
319 GTK_DIALOG_MODAL
320 | GTK_DIALOG_DESTROY_WITH_PARENT,
321 GTK_STOCK_CANCEL, GTK_RESPONSE_NONE,
322 GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
323 gtk_window_set_wmclass (GTK_WINDOW (dialog), "gerbv_attribute_editor", _("gerbv"));
324
325 main_vbox = gtk_vbox_new (FALSE, 6);
326 gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 6);
327 gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), main_vbox);
328
329 vbox = ghid_category_vbox (main_vbox, descr != NULL ? descr : "",
330 4, 2, TRUE, TRUE);
331
332 /*
333 * Iterate over all the attributes and build up a dialog box
334 * that lets us control all of the options. By doing things this
335 * way, any changes to the attributes or if there is a new list of
336 * attributes, everything will automatically be reflected in this
337 * dialog box.
338 */
339 for (j = 0; j < n_attrs; j++)
340 {
341 dprintf ("%s(): adding attribute #%d\n", __func__, j);
342 switch (attrs[j].type)
343 {
344 case HID_Label:
345 widget = gtk_label_new (_(attrs[j].name));
346 gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
347 gtk_tooltips_set_tip (tips, widget, _(attrs[j].help_text), NULL);
348 break;
349
350 case HID_Integer:
351 hbox = gtk_hbox_new (FALSE, 4);
352 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
353
354 /*
355 * FIXME
356 * need to pick the "digits" argument based on min/max
357 * values
358 */
359 ghid_spin_button (hbox, &widget, attrs[j].default_val.int_value,
360 attrs[j].min_val, attrs[j].max_val, 1.0, 1.0, 0, 0,
361 intspinner_changed_cb,
362 &(attrs[j].default_val.int_value), FALSE, NULL);
363
364 gtk_tooltips_set_tip (tips, widget, _(attrs[j].help_text), NULL);
365 all_widgets[j] = widget;
366
367 widget = gtk_label_new (_(attrs[j].name));
368 gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
369 break;
370
371 case HID_Real:
372 hbox = gtk_hbox_new (FALSE, 4);
373 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
374
375 /*
376 * FIXME
377 * need to pick the "digits" and step size argument more
378 * intelligently
379 */
380 ghid_spin_button (hbox, &widget, attrs[j].default_val.real_value,
381 attrs[j].min_val, attrs[j].max_val, 0.01, 0.01, 3,
382 0,
383 dblspinner_changed_cb,
384 &(attrs[j].default_val.real_value), FALSE, NULL);
385
386 gtk_tooltips_set_tip (tips, widget, _(attrs[j].help_text), NULL);
387 all_widgets[j] = widget;
388
389 widget = gtk_label_new (_(attrs[j].name));
390 gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
391 break;
392
393 case HID_String:
394 hbox = gtk_hbox_new (FALSE, 4);
395 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
396
397 entry = gtk_entry_new ();
398 gtk_box_pack_start (GTK_BOX (hbox), entry, FALSE, FALSE, 0);
399 gtk_entry_set_text (GTK_ENTRY (entry),
400 attrs[j].default_val.str_value);
401 gtk_tooltips_set_tip (tips, entry, _(attrs[j].help_text), NULL);
402 g_signal_connect (G_OBJECT (entry), "changed",
403 G_CALLBACK (entry_changed_cb),
404 &(attrs[j].default_val.str_value));
405 all_widgets[j] = entry;
406
407 widget = gtk_label_new (_(attrs[j].name));
408 gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
409 break;
410
411 case HID_Boolean:
412 /* put this in a check button */
413 ghid_check_button_connected (vbox, &widget,
414 attrs[j].default_val.int_value,
415 TRUE, FALSE, FALSE, 0, set_flag_cb,
416 &(attrs[j].default_val.int_value),
417 _(attrs[j].name));
418 gtk_tooltips_set_tip (tips, widget, _(attrs[j].help_text), NULL);
419
420 /*
421 * This is an ugly ugly ugly hack.... If this is
422 * the first in our list of attributes *and* it has a
423 * magic name of "autodetect" then we'll remember it and
424 * all of the other callbacks will cause this button to
425 * come unchecked. Among the other nastiness
426 * involved here, this dialog is now *required* to
427 * be modal since we are using a static variable.
428 * To avoid that, we need a new data type that can hold
429 * more state information. Ideally we need a better
430 * way to capture dependencies between attributes to
431 * allow arbitrary relationships instead of just this
432 * one single "magic" one.
433 */
434 if (j == 0 && strcmp(attrs[j].name, "autodetect") == 0) {
435 set_auto_uncheck = 1;
436 auto_uncheck_widget = widget;
437 auto_uncheck_attr = &(attrs[j].default_val.int_value);
438
439 /* if the "auto" button in checked then don't let
440 * anything else be sensitive.
441 */
442
443 if (attrs[j].default_val.int_value)
444 sen = FALSE;
445 }
446 all_widgets[j] = widget;
447
448 break;
449
450 case HID_Enum:
451 hbox = gtk_hbox_new (FALSE, 4);
452 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
453
454 /*
455 * We have to put the combo_box inside of an event_box in
456 * order for tooltips to work.
457 */
458 widget = gtk_event_box_new ();
459 gtk_tooltips_set_tip (tips, widget, _(attrs[j].help_text), NULL);
460 gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
461
462 combo = gtk_combo_box_new_text ();
463 gtk_container_add (GTK_CONTAINER (widget), combo);
464 g_signal_connect (G_OBJECT (combo), "changed",
465 G_CALLBACK (enum_changed_cb),
466 &(attrs[j].default_val.int_value));
467
468
469 /*
470 * Iterate through each value and add them to the
471 * combo box
472 */
473 i = 0;
474 while (attrs[j].enumerations[i])
475 {
476 gtk_combo_box_append_text (GTK_COMBO_BOX (combo),
477 _(attrs[j].enumerations[i]));
478 i++;
479 }
480 gtk_combo_box_set_active (GTK_COMBO_BOX (combo),
481 attrs[j].default_val.int_value);
482 all_widgets[j] = combo;
483
484 widget = gtk_label_new (_(attrs[j].name));
485 gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
486 break;
487
488 case HID_Mixed:
489 dprintf ("HID_Mixed\n");
490 break;
491
492 case HID_Path:
493 vbox1 = ghid_category_vbox (vbox, _(attrs[j].name), 4, 2, TRUE, TRUE);
494 entry = gtk_entry_new ();
495 gtk_box_pack_start (GTK_BOX (vbox1), entry, FALSE, FALSE, 0);
496 gtk_entry_set_text (GTK_ENTRY (entry),
497 attrs[j].default_val.str_value);
498 g_signal_connect (G_OBJECT (entry), "changed",
499 G_CALLBACK (entry_changed_cb),
500 &(attrs[j].default_val.str_value));
501
502 gtk_tooltips_set_tip (tips, entry, _(attrs[j].help_text), NULL);
503 all_widgets[j] = entry;
504 break;
505
506 default:
507 fprintf (stderr, _("%s: unknown type of HID attribute\n"), __FUNCTION__);
508 break;
509 }
510 }
511
512
513 gtk_widget_show_all (dialog);
514 auto_uncheck_needed = set_auto_uncheck;
515
516 /*
517 * part of the "auto" hack. Make everything sensitive or
518 * insensitive based on the state of the "auto" toggle button (if it
519 * exists)
520 */
521 for (j = 1; j < n_widgets ; j++) {
522 gtk_widget_set_sensitive (all_widgets[j], sen);
523 }
524 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
525 {
526 /* copy over the results */
527 for (i = 0; i < n_attrs; i++)
528 {
529 results[i] = attrs[i].default_val;
530 if (results[i].str_value)
531 results[i].str_value = strdup (results[i].str_value);
532 }
533 rc = 0;
534 }
535 else
536 rc = 1;
537
538 gtk_widget_destroy (dialog);
539
540 return rc;
541 }
542
543