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