1 /* Lepton EDA attribute editor
2 * Copyright (C) 2003-2010 Stuart D. Brorson.
3 * Copyright (C) 2003-2015 gEDA Contributors
4 * Copyright (C) 2017-2021 Lepton EDA Contributors
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 /*------------------------------------------------------------------*/
22 /*! \file
23 * \brief Functions to interface to the spreadsheet widget.
24 *
25 * This file holds functions used to handle the spreadsheet widget.
26 * Much of this was hacked from testgtksheet.c starting in Jan 2004
27 * by SDB.
28 */
29
30 #ifdef HAVE_CONFIG_H
31 #include "config.h"
32 #endif
33
34 /*------------------------------------------------------------------
35 * Includes required to run graphical widgets.
36 *------------------------------------------------------------------*/
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <gtk/gtk.h>
40 #include <gdk/gdk.h>
41 #include <gdk/gdkkeysyms.h>
42
43 #include <glib.h>
44 #include <glib-object.h>
45
46 #ifdef HAVE_STRING_H
47 #include <string.h>
48 #endif
49
50
51 /*------------------------------------------------------------------
52 * Gattrib specific includes
53 *------------------------------------------------------------------*/
54 #include <liblepton/liblepton.h>
55 #include "../include/struct.h" /* typdef and struct declarations */
56 #include "../include/prototype.h" /* function prototypes */
57 #include "../include/globals.h"
58 #include "../include/gettext.h"
59
60 static void show_entry(GtkWidget *widget, gpointer data);
61
62
63 static gchar* current_cell_text = NULL;
64
65
66 static gboolean
on_activate(GtkSheet * sheet,gint row,gint column,gpointer data)67 on_activate (GtkSheet* sheet,
68 gint row,
69 gint column,
70 gpointer data)
71 {
72 current_cell_text = gtk_sheet_get_entry_text (sheet);
73
74 return FALSE; /* ignored */
75 }
76
77
78 static gboolean
on_deactivate(GtkSheet * sheet,gint row,gint column,gpointer data)79 on_deactivate (GtkSheet* sheet,
80 gint row,
81 gint column,
82 gpointer data)
83 {
84 gchar* str = gtk_sheet_get_entry_text (sheet);
85
86 if (strcmp (str, current_cell_text) != 0)
87 {
88 s_sheet_data_set_changed (sheet_head, TRUE);
89 }
90
91 return TRUE; /* TRUE => allow deactivation */
92 }
93
94
95 /*! \brief Call it just after the sheet has been saved.
96 *
97 * \par Function Description
98 *
99 * Update the current_cell_text global variable, so that
100 * on_deactivate() handler won't mark the sheet as modified
101 * when the current cell is deactivated.
102 *
103 * We need this to handle a particular use case:
104 * while editing text in a cell, instead of pressing Enter
105 * to commit the changes, the user presses Ctrl+S (Save).
106 * If we do not update current_cell_text after that, the
107 * consequent cell deactivation will mark the document as
108 * dirty, while it is, in fact, just has been saved.
109 */
110 void
x_gtksheet_set_saved()111 x_gtksheet_set_saved()
112 {
113 current_cell_text = gtk_sheet_get_entry_text (sheets[0]);
114 }
115
116
117 /*! \brief Create the GtkSheet
118 *
119 * Creates and initializes the GtkSheet widget, which is the
120 * spreadsheet widget used for displaying the data.
121 */
122 void
x_gtksheet_init()123 x_gtksheet_init()
124 {
125 gint i;
126 const gchar *folder[]= {_("Components"),
127 _("Nets"),
128 _("Pins")};
129
130 /* --- Create three new sheets. were malloc'ed in x_window_init --- */
131
132 /* ----- Components ----- */
133 if ((sheet_head->comp_count > 0) && (sheet_head->comp_attrib_count >0)) {
134 sheets[0] = (GtkSheet *) gtk_sheet_new((guint) sheet_head->comp_count, (guint) sheet_head->comp_attrib_count, _("Components"));
135 } else {
136 x_dialog_fatal_error(_("No components found in design. Please check your schematic and try again!\n"), 1);
137 }
138
139
140 #ifdef UNIMPLEMENTED_FEATURES
141 /* ----- Nets ----- */
142 if ((sheet_head->net_count > 0) && (sheet_head->net_attrib_count >0)) {
143 sheets[1] = (GtkSheet *) gtk_sheet_new(sheet_head->net_count, sheet_head->net_attrib_count, _("Nets"));
144 gtk_sheet_set_locked(GTK_SHEET(sheets[1]), TRUE); /* disallow editing of attribs for now */
145 } else {
146 sheets[1] = (GtkSheet *) gtk_sheet_new(1, 1, _("Nets"));
147 gtk_sheet_row_button_add_label(sheets[1], 0, _("TBD"));
148 gtk_sheet_row_button_justify(sheets[1], 0, GTK_JUSTIFY_LEFT);
149 gtk_sheet_column_button_add_label(sheets[1], 0, _("TBD"));
150 gtk_sheet_column_button_justify(sheets[1], 0, GTK_JUSTIFY_LEFT);
151 gtk_sheet_set_locked(GTK_SHEET(sheets[1]), TRUE); /* disallow editing of attribs for now */
152 }
153 #endif
154
155
156 #ifdef UNIMPLEMENTED_FEATURES
157 /* ----- Pins ----- */
158 if ((sheet_head->pin_count > 0) && (sheet_head->pin_attrib_count >0)) {
159 sheets[2] = (GtkSheet *) gtk_sheet_new(sheet_head->pin_count, sheet_head->pin_attrib_count, _("Pins"));
160 gtk_sheet_set_locked(GTK_SHEET(sheets[2]), TRUE); /* disallow editing of attribs for now */
161 } else {
162 sheets[2] = (GtkSheet *) gtk_sheet_new(1, 1, _("Pins"));
163 gtk_sheet_set_locked(GTK_SHEET(sheets[2]), TRUE); /* disallow editing of attribs for now */
164 }
165 #endif
166
167
168
169 /* --- Finally stick labels on the notebooks holding the two sheets. --- */
170 for(i=0; i<NUM_SHEETS; i++){
171 if (sheets[i] != NULL) { /* is this check needed?
172 * Yes, it prevents us from segfaulting on empty nets sheet. */
173
174 GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
175
176 gtk_container_add( GTK_CONTAINER(scrolled_window), GTK_WIDGET(sheets[i]) );
177
178 /* First remove old notebook page. I should probably do some checking here. */
179 if (notebook != NULL)
180 gtk_notebook_remove_page(GTK_NOTEBOOK(notebook), i);
181
182
183 /* Then add new, updated notebook page */
184 label= gtk_label_new(folder[i]);
185
186 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scrolled_window,
187 GTK_WIDGET(label) );
188
189 gtk_widget_show( GTK_WIDGET(sheets[i]) );
190 gtk_widget_show( scrolled_window );
191 gtk_widget_show( GTK_WIDGET(notebook) ); /* show updated notebook */
192
193
194 /* "changed" signal raised when user changes anything in entry cell */
195 /* Note that the entry cell is the text entry field at the top of the
196 * sheet's working area (like in MS E*cel). I have removed this from
197 * gattrib, but leave the code in just in case I want to put it back. */
198 g_signal_connect (gtk_sheet_get_entry (GTK_SHEET (sheets[i])),
199 "changed", (GCallback) show_entry, NULL);
200
201
202 g_signal_connect (sheets[i],
203 "activate",
204 G_CALLBACK (&on_activate),
205 NULL);
206
207 g_signal_connect (sheets[i],
208 "deactivate",
209 G_CALLBACK (&on_deactivate),
210 NULL);
211
212 }
213 }
214 }
215
216
217
218 /*------------------------------------------------------------------*/
219 /*! \brief Add row labels to GtkSheet
220 *
221 * Add row labels to GtkSheet
222 * \param sheet Pointer to the GtkSheet object
223 * \param count Number of row labels to add
224 * \param list_head Top of the string list
225 */
226 void
x_gtksheet_add_row_labels(GtkSheet * sheet,int count,STRING_LIST * list_head)227 x_gtksheet_add_row_labels(GtkSheet *sheet, int count, STRING_LIST *list_head)
228 {
229 STRING_LIST *string_list_item;
230 gchar *text;
231 int j;
232 int width = 0;
233 int new_width = 0;
234 int char_width;
235
236 /* Leave if no items to add are available */
237 if ((count == 0) || (list_head == NULL)) return;
238
239 #ifndef ENABLE_GTK3
240 /* Get character width based upon "X", which is a large char.
241 * font_combo is a global. Where is it set? */
242 GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (sheet));
243
244 if (style->private_font)
245 char_width = gdk_char_width (style->private_font, (gchar) 'X');
246 else
247 #endif
248 char_width = 12;
249
250 string_list_item = list_head;
251 for (j = 0; j < count; j++) {
252 text = (gchar *) g_strdup(string_list_item->data);
253 new_width = char_width * strlen(text);
254 if (new_width > width)
255 width = new_width;
256
257 gtk_sheet_row_button_add_label(sheet, j, text);
258 gtk_sheet_row_button_justify(sheet, j, GTK_JUSTIFY_LEFT);
259 g_free(text);
260 string_list_item = string_list_item->next;
261 }
262
263
264 gtk_sheet_set_row_titles_width(sheet, width+8);
265 }
266
267
268 /*------------------------------------------------------------------*/
269 /*! \brief Add column labels to GtkSheet
270 *
271 * Add column labels to GtkSheet.
272 * \param sheet GtkSheet to add columns to
273 * \param count
274 * \param list_head pointer to top of STRING_LIST
275 */
276 void
x_gtksheet_add_col_labels(GtkSheet * sheet,int count,STRING_LIST * list_head)277 x_gtksheet_add_col_labels(GtkSheet *sheet, int count, STRING_LIST *list_head)
278 {
279 STRING_LIST *string_list_item;
280 gchar *text;
281 int j;
282
283 /* Leave if no items to add are available */
284 if ((count == 0) || (list_head == NULL)) return;
285
286 string_list_item = list_head;
287 for (j = 0; j < count; j++) {
288 text = (gchar *) g_strdup(string_list_item->data);
289 gtk_sheet_column_button_add_label(sheet, j, text);
290 gtk_sheet_column_button_justify(sheet, j, GTK_JUSTIFY_LEFT);
291 /* need to resize the column width here . . . */
292 g_free(text);
293 string_list_item = string_list_item->next;
294 }
295 }
296
297
298 /*------------------------------------------------------------------*/
299 /*! \brief Add a cell item to the GtkSheet
300 *
301 * Add a cell item to the GtkSheet
302 * \param sheet GtkSheet to add the cell item to
303 * \param i
304 * \param j
305 * \param text
306 * \param visibility
307 * \param show_name_value
308 */
309 void
x_gtksheet_add_cell_item(GtkSheet * sheet,gint i,gint j,gchar * text,gint visibility,gint show_name_value)310 x_gtksheet_add_cell_item(GtkSheet *sheet,gint i, gint j,
311 gchar *text,
312 gint visibility,
313 gint show_name_value)
314 {
315
316 /* Should I do some sanity checking here? */
317
318 gtk_sheet_set_cell(sheet, i, j, GTK_JUSTIFY_LEFT, text);
319 if (visibility == INVISIBLE) {
320 x_gtksheet_set_cell_text_color(sheet, i, j, GREY);
321 } else {
322 switch(show_name_value) {
323
324 case(SHOW_NAME_VALUE):
325 x_gtksheet_set_cell_text_color(sheet, i, j, BLUE);
326 break;
327
328 case(SHOW_NAME):
329 x_gtksheet_set_cell_text_color(sheet, i, j, RED);
330 break;
331
332 case(SHOW_VALUE):
333 x_gtksheet_set_cell_text_color(sheet, i, j, BLACK);
334 break;
335 }
336 } /* if (visibility == INVISIBLE) */
337
338
339
340 /* Need to find a way to ensure that the text in a cell is clipped.
341 * Otherwise, long attribs overwrite adjacent cells. */
342 }
343
344
345 /*! \brief Get the first column selected in the GtkSheet
346 *
347 * Get the first column selected in the GtkSheet
348 * Returns the index of the first column selected, or -1 if
349 * no column is selected.
350 * \param sheet GtkSheet to query
351 * \returns index of the first column selected, or -1 if
352 * no column is selected.
353 */
x_gtksheet_get_min_col(GtkSheet * sheet)354 int x_gtksheet_get_min_col(GtkSheet *sheet) {
355 if (sheet->state == GTK_SHEET_COLUMN_SELECTED) {
356 return sheet->range.col0;
357 } else {
358 return -1;
359 }
360 }
361
362
363 /*! \brief Get the last column selected in the GtkSheet
364 *
365 * Get the last column selected in the GtkSheet
366 * \param GtkSheet to query
367 * \returns the index of the last column selected, or -1 if
368 * no column is selected.
369 */
x_gtksheet_get_max_col(GtkSheet * sheet)370 int x_gtksheet_get_max_col(GtkSheet *sheet) {
371 if (sheet->state == GTK_SHEET_COLUMN_SELECTED) {
372 return sheet->range.coli;
373 } else {
374 return -1;
375 }
376 }
377
378
379 #ifndef ENABLE_GTK3
380 /*! \brief Set the text color of a cell (GTK2) */
381 static void
_set_cell_text_color(GtkSheet * sheet,gint row,gint col,gint color_name)382 _set_cell_text_color (GtkSheet *sheet,
383 gint row,
384 gint col,
385 gint color_name)
386 {
387 GtkSheetRange *range;
388 GdkColormap *cmap;
389 GdkColor *color;
390
391 /* First get the system color map and allocate the color */
392 cmap = gdk_colormap_get_system ();
393 color = g_new (GdkColor, 1);
394 switch(color_name) {
395 case RED:
396 color->red = 0xffff;
397 color->green = 0x0;
398 color->blue = 0x0;
399 break;
400
401 case BLUE:
402 color->red = 0x0;
403 color->green = 0x0;
404 color->blue = 0xffff;
405 break;
406
407 case BLACK:
408 color->red = 0x0;
409 color->green = 0x0;
410 color->blue = 0x0;
411 break;
412
413 case GREY:
414 color->red = 0x9999;
415 color->green = 0x9999;
416 color->blue = 0x9999;
417 break;
418 }
419
420 if (!gdk_colormap_alloc_color (cmap, color, FALSE, FALSE)) {
421 g_error (_("couldn't allocate color"));
422 return;
423 }
424 /* g_free(cmap); */
425
426 /* XXXXX Attempt to set cell color */
427 range = g_new (GtkSheetRange, 1);
428 range->row0 = row;
429 range->rowi = row;
430 range->col0 = col;
431 range->coli = col;
432
433 /* Now set color */
434 gtk_sheet_range_set_foreground(sheet, range, color);
435 g_free(color);
436 g_free(range);
437 }
438
439 #else
440
441 /*! \brief Set the text color of a cell (GTK3) */
442 static void
_set_cell_text_color_gtk3(GtkSheet * sheet,gint row,gint col,gint color_name)443 _set_cell_text_color_gtk3 (GtkSheet *sheet,
444 gint row,
445 gint col,
446 gint color_name)
447 {
448 GtkSheetRange *range;
449 GdkRGBA color;
450
451 switch(color_name) {
452 case RED:
453 gdk_rgba_parse (&color, "red");
454 break;
455
456 case BLUE:
457 gdk_rgba_parse (&color, "blue");
458 break;
459
460 case BLACK:
461 gdk_rgba_parse (&color, "black");
462 break;
463
464 case GREY:
465 gdk_rgba_parse (&color, "grey");
466 break;
467 }
468
469 /* XXXXX Attempt to set cell color */
470 range = g_new (GtkSheetRange, 1);
471 range->row0 = row;
472 range->rowi = row;
473 range->col0 = col;
474 range->coli = col;
475
476 /* Now set color */
477 gtk_sheet_range_set_foreground(sheet, range, &color);
478 g_free(range);
479 }
480 #endif
481
482 void
x_gtksheet_set_cell_text_color(GtkSheet * sheet,gint row,gint col,gint color_name)483 x_gtksheet_set_cell_text_color (GtkSheet *sheet,
484 gint row,
485 gint col,
486 gint color_name)
487 {
488 #ifdef ENABLE_GTK3
489 _set_cell_text_color_gtk3 (sheet, row, col, color_name);
490 #else
491 _set_cell_text_color (sheet, row, col, color_name);
492 #endif
493 }
494
495
496 /*! \brief Show text entry box
497 *
498 * Displays a text entry box at the top of the working area.
499 * It is removed since it is not needed now, but may come in
500 * handy later. Therefore I keep the code around.
501 * \param widget
502 * \param data
503 */
504 static void
show_entry(GtkWidget * widget,gpointer data)505 show_entry(GtkWidget *widget, gpointer data)
506 {
507 gchar *text;
508 GtkSheet *sheet;
509 GtkWidget *sheet_entry = NULL;
510 gint cur_page;
511
512 if (!gtk_widget_has_focus (widget)) {
513 return;
514 }
515
516 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook));
517
518 sheet = GTK_SHEET(sheets[cur_page]);
519 if (sheet != NULL) {
520 sheet_entry = gtk_sheet_get_entry( GTK_SHEET(sheet) );
521 }
522
523 /* Here's another place where we mix entry and sheet_entry */
524 if (entry != NULL) {
525 text = (gchar *) gtk_entry_get_text (GTK_ENTRY(sheet_entry));
526 if( text != NULL ) {
527 gtk_entry_set_text(GTK_ENTRY(entry), text);
528 }
529 else {
530 gtk_entry_set_text(GTK_ENTRY(entry), (const gchar *) "");
531 /* gtk_entry_set_text(GTK_ENTRY(entry), NULL); */
532 }
533 }
534 }
535