1 /*
2  *                            COPYRIGHT
3  *
4  *  PCB, interactive printed circuit board design
5  *  Copyright (C) 1994,1995,1996 Thomas Nau
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License along
18  *  with this program; if not, write to the Free Software Foundation, Inc.,
19  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  *  Contact addresses for paper mail and Email:
22  *  Thomas Nau, Schlehenweg 15, 88471 Baustetten, Germany
23  *  Thomas.Nau@rz.uni-ulm.de
24  *
25  */
26 
27 /* This file originally from the PCB Gtk port by Bill Wilson. It has
28  * since been combined with modified code from the gEDA project:
29  *
30  * gschem/src/ghid_library_window.c, checked out by Peter Clifton
31  * from gEDA/gaf commit 72581a91da08c9d69593c24756144fc18940992e
32  * on 3rd Jan, 2008.
33  *
34  * gEDA - GPL Electronic Design Automation
35  * gschem - gEDA Schematic Capture
36  * Copyright (C) 1998-2007 Ales Hvezda
37  * Copyright (C) 1998-2007 gEDA Contributors (see ChangeLog for details)
38  *
39  * This program is free software; you can redistribute it and/or modify
40  * it under the terms of the GNU General Public License as published by
41  * the Free Software Foundation; either version 2 of the License, or
42  * (at your option) any later version.
43  *
44  * This program is distributed in the hope that it will be useful,
45  * but WITHOUT ANY WARRANTY; without even the implied warranty of
46  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
47  * GNU General Public License for more details.
48  *
49  * You should have received a copy of the GNU General Public License
50  * along with this program; if not, write to the Free Software
51  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
52  * MA 02110-1301 USA.
53  */
54 
55 #ifdef HAVE_CONFIG_H
56 #include "config.h"
57 #endif
58 
59 #include "gui.h"
60 #include "global.h"
61 #include "buffer.h"
62 #include "data.h"
63 #include "set.h"
64 
65 #include <gdk/gdkkeysyms.h>
66 
67 #ifdef HAVE_LIBDMALLOC
68 #include <dmalloc.h>
69 #endif
70 
71 static GtkWidget *library_window;
72 
73 #include "gui-pinout-preview.h"
74 #include "gui-library-window.h"
75 
76 /*! \def LIBRARY_FILTER_INTERVAL
77  *  \brief The time interval between request and actual filtering
78  *
79  *  This constant is the time-lag between user modifications in the
80  *  filter entry and the actual evaluation of the filter which
81  *  ultimately update the display. It helps reduce the frequency of
82  *  evaluation of the filter as user types.
83  *
84  *  Unit is milliseconds.
85  */
86 #define LIBRARY_FILTER_INTERVAL 200
87 
88 
89 static gint
library_window_configure_event_cb(GtkWidget * widget,GdkEventConfigure * ev,gpointer data)90 library_window_configure_event_cb (GtkWidget * widget, GdkEventConfigure * ev,
91 				   gpointer data)
92 {
93   GtkAllocation allocation;
94 
95   gtk_widget_get_allocation (widget, &allocation);
96   ghidgui->library_window_width = allocation.width;
97   ghidgui->library_window_height = allocation.height;
98   ghidgui->config_modified = TRUE;
99   return FALSE;
100 }
101 
102 
103 enum
104 {
105   MENU_TOPPATH_COLUMN,		/* Top path of the library         */
106   MENU_SUBPATH_COLUMN,		/* Relative path to the top        */
107   MENU_NAME_COLUMN,		/* Text to display in the tree     */
108   MENU_LIBRARY_COLUMN,		/* Pointer to the LibraryMenuType  */
109   MENU_ENTRY_COLUMN,		/* Pointer to the LibraryEntryType */
110   N_MENU_COLUMNS
111 };
112 
113 
114 /*! \brief Process the response returned by the library dialog.
115  *  \par Function Description
116  *  This function handles the response <B>arg1</B> of the library
117  *  dialog <B>dialog</B>.
118  *
119  *  Parameter <B>user_data</B> is a pointer on the relevant toplevel
120  *  structure.
121  *
122  *  \param [in] dialog    The library dialog.
123  *  \param [in] arg1      The response ID.
124  *  \param [in] user_data
125  */
126 static void
library_window_callback_response(GtkDialog * dialog,gint arg1,gpointer user_data)127 library_window_callback_response (GtkDialog * dialog,
128 				  gint arg1, gpointer user_data)
129 {
130   switch (arg1)
131     {
132     case GTK_RESPONSE_CLOSE:
133     case GTK_RESPONSE_DELETE_EVENT:
134       gtk_widget_destroy (GTK_WIDGET (library_window));
135       library_window = NULL;
136       break;
137 
138     default:
139       /* Do nothing, in case there's another handler function which
140          can handle the response ID received. */
141       break;
142     }
143 }
144 
145 
146 /*! \brief Creates a library dialog.
147  *  \par Function Description
148  *  This function create the library dialog if it is not already created.
149  *  It does not show the dialog, use ghid_library_window_show for that.
150  *
151  */
152 void
ghid_library_window_create(GHidPort * out)153 ghid_library_window_create (GHidPort * out)
154 {
155   GtkWidget *current_tab, *entry_filter;
156   GtkNotebook *notebook;
157 
158   if (library_window)
159     return;
160 
161   library_window = (GtkWidget *)g_object_new (GHID_TYPE_LIBRARY_WINDOW, NULL);
162 
163   g_signal_connect (library_window,
164                     "response",
165                     G_CALLBACK (library_window_callback_response), NULL);
166   g_signal_connect (G_OBJECT (library_window), "configure_event",
167                     G_CALLBACK (library_window_configure_event_cb), NULL);
168   gtk_window_resize (GTK_WINDOW (library_window),
169                      ghidgui->library_window_width,
170                      ghidgui->library_window_height);
171 
172   gtk_window_set_title (GTK_WINDOW (library_window), _("PCB Library"));
173   gtk_window_set_wmclass (GTK_WINDOW (library_window), "PCB_Library",
174                           "PCB");
175 
176   gtk_widget_realize (library_window);
177   if (Settings.AutoPlace)
178     gtk_window_move (GTK_WINDOW (library_window), 10, 10);
179 
180   gtk_editable_select_region (GTK_EDITABLE
181 			      (GHID_LIBRARY_WINDOW (library_window)->
182 			       entry_filter), 0, -1);
183 
184   /* Set the focus to the filter entry only if it is in the current
185      displayed tab */
186   notebook = GTK_NOTEBOOK (GHID_LIBRARY_WINDOW (library_window)->viewtabs);
187   current_tab = gtk_notebook_get_nth_page (notebook,
188 					   gtk_notebook_get_current_page
189 					   (notebook));
190   entry_filter =
191     GTK_WIDGET (GHID_LIBRARY_WINDOW (library_window)->entry_filter);
192   if (gtk_widget_is_ancestor (entry_filter, current_tab))
193     {
194       gtk_widget_grab_focus (entry_filter);
195     }
196 }
197 
198 /*! \brief Show the library dialog.
199  *  \par Function Description
200  *  This function show the library dialog, creating it if it is not
201  *  already created, and presents it to the user (brings it to the
202  *  front with focus).
203  */
204 void
ghid_library_window_show(GHidPort * out,gboolean raise)205 ghid_library_window_show (GHidPort * out, gboolean raise)
206 {
207   ghid_library_window_create (out);
208   gtk_widget_show_all (library_window);
209   if (raise)
210     gtk_window_present (GTK_WINDOW(library_window));
211 }
212 
213 static GObjectClass *library_window_parent_class = NULL;
214 
215 
216 /*! \brief Determines visibility of items of the library treeview.
217  *  \par Function Description
218  *  This is the function used to filter entries of the footprint
219  *  selection tree.
220  *
221  *  \param [in] model The current selection in the treeview.
222  *  \param [in] iter  An iterator on a footprint or folder in the tree.
223  *  \param [in] data  The library dialog.
224  *  \returns TRUE if item should be visible, FALSE otherwise.
225  */
226 static gboolean
lib_model_filter_visible_func(GtkTreeModel * model,GtkTreeIter * iter,gpointer data)227 lib_model_filter_visible_func (GtkTreeModel * model,
228 			       GtkTreeIter * iter, gpointer data)
229 {
230   GhidLibraryWindow *library_window = (GhidLibraryWindow *) data;
231   const gchar *compname;
232   gchar *compname_upper, *text_upper, *pattern;
233   const gchar *text;
234   gboolean ret;
235 
236   g_assert (GHID_IS_LIBRARY_WINDOW (data));
237 
238   text = gtk_entry_get_text (library_window->entry_filter);
239   if (g_ascii_strcasecmp (text, "") == 0)
240     {
241       return TRUE;
242     }
243 
244   /* If this is a source, only display it if it has children that
245    * match */
246   if (gtk_tree_model_iter_has_child (model, iter))
247     {
248       GtkTreeIter iter2;
249 
250       gtk_tree_model_iter_children (model, &iter2, iter);
251       ret = FALSE;
252       do
253 	{
254 	  if (lib_model_filter_visible_func (model, &iter2, data))
255 	    {
256 	      ret = TRUE;
257 	      break;
258 	    }
259 	}
260       while (gtk_tree_model_iter_next (model, &iter2));
261     }
262   else
263     {
264       gtk_tree_model_get (model, iter, MENU_NAME_COLUMN, &compname, -1);
265       /* Do a case insensitive comparison, converting the strings
266          to uppercase */
267       compname_upper = g_ascii_strup (compname, -1);
268       text_upper = g_ascii_strup (text, -1);
269       pattern = g_strconcat ("*", text_upper, "*", NULL);
270       ret = g_pattern_match_simple (pattern, compname_upper);
271       g_free (compname_upper);
272       g_free (text_upper);
273       g_free (pattern);
274     }
275 
276   return ret;
277 }
278 
279 
280 /*! \brief Handles activation (e.g. double-clicking) of a component row
281  *  \par Function Description
282  *  Component row activated handler:
283  *  As a convenince to the user, expand / contract any node with children.
284  *
285  *  \param [in] tree_view The component treeview.
286  *  \param [in] path      The GtkTreePath to the activated row.
287  *  \param [in] column    The GtkTreeViewColumn in which the activation occurre
288  *  \param [in] user_data The component selection dialog.
289  */
290 static void
tree_row_activated(GtkTreeView * tree_view,GtkTreePath * path,GtkTreeViewColumn * column,gpointer user_data)291 tree_row_activated (GtkTreeView       *tree_view,
292                     GtkTreePath       *path,
293                     GtkTreeViewColumn *column,
294                     gpointer           user_data)
295 {
296   GtkTreeModel *model;
297   GtkTreeIter iter;
298 
299   model = gtk_tree_view_get_model (tree_view);
300   gtk_tree_model_get_iter (model, &iter, path);
301 
302   if (!gtk_tree_model_iter_has_child (model, &iter))
303     return;
304 
305   if (gtk_tree_view_row_expanded (tree_view, path))
306     gtk_tree_view_collapse_row (tree_view, path);
307   else
308     gtk_tree_view_expand_row (tree_view, path, FALSE);
309 }
310 
311 /*! \brief Handles CTRL-C keypress in the TreeView
312  *  \par Function Description
313  *  Keypress activation handler:
314  *  If CTRL-C is pressed, copy footprint name into the clipboard.
315  *
316  *  \param [in] tree_view The component treeview.
317  *  \param [in] event     The GdkEventKey with keypress info.
318  *  \param [in] user_data Not used.
319  *  \return TRUE if CTRL-C event was handled, FALSE otherwise.
320  */
321 static gboolean
tree_row_key_pressed(GtkTreeView * tree_view,GdkEventKey * event,gpointer user_data)322 tree_row_key_pressed (GtkTreeView *tree_view,
323                       GdkEventKey *event,
324                       gpointer     user_data)
325 {
326   GtkTreeSelection *selection;
327   GtkTreeModel *model;
328   GtkTreeIter iter;
329   GtkClipboard *clipboard;
330   const gchar *compname;
331   guint default_mod_mask = gtk_accelerator_get_default_mod_mask();
332 
333   /* Handle both lower- and uppercase `c' */
334   if (((event->state & default_mod_mask) != GDK_CONTROL_MASK)
335       || ((event->keyval != GDK_c) && (event->keyval != GDK_C)))
336     return FALSE;
337 
338   selection = gtk_tree_view_get_selection (tree_view);
339   g_return_val_if_fail (selection != NULL, TRUE);
340 
341   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
342     return TRUE;
343 
344   gtk_tree_model_get (model, &iter, MENU_NAME_COLUMN, &compname, -1);
345 
346   clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
347   g_return_val_if_fail (clipboard != NULL, TRUE);
348 
349   gtk_clipboard_set_text (clipboard, compname, -1);
350 
351   return TRUE;
352 }
353 
354 /*! \brief Handles changes in the treeview selection.
355  *  \par Function Description
356  *  This is the callback function that is called every time the user
357  *  select a row the library treeview of the dialog.
358  *
359  *  If the selection is not a selection of a footprint, it does
360  *  nothing. Otherwise it updates the preview and Element data.
361  *
362  *  \param [in] selection The current selection in the treeview.
363  *  \param [in] user_data The library dialog.
364  */
365 static void
library_window_callback_tree_selection_changed(GtkTreeSelection * selection,gpointer user_data)366 library_window_callback_tree_selection_changed (GtkTreeSelection * selection,
367 						gpointer user_data)
368 {
369   GtkTreeModel *model;
370   GtkTreeIter iter;
371   GhidLibraryWindow *library_window = (GhidLibraryWindow *) user_data;
372   LibraryEntryType *entry = NULL;
373   gchar *m4_args;
374 
375   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
376     return;
377 
378   gtk_tree_model_get (model, &iter, MENU_ENTRY_COLUMN, &entry, -1);
379 
380   if (entry == NULL)
381     return;
382 
383   /* -1 flags this is an element file part and the file path is in
384      |  entry->AllocateMemory.
385    */
386   if (entry->Template == (char *) -1)
387     {
388       if (LoadElementToBuffer (PASTEBUFFER, entry->AllocatedMemory, true))
389         {
390           SetMode (PASTEBUFFER_MODE);
391           goto out;
392         }
393       return;
394     }
395 
396   /* Otherwise, it's a m4 element and we need to create a string of
397      |  macro arguments to be passed to the library command in
398      |  LoadElementToBuffer()
399    */
400   m4_args = g_strdup_printf ("'%s' '%s' '%s'", EMPTY (entry->Template),
401 			     EMPTY (entry->Value), EMPTY (entry->Package));
402 
403   if (LoadElementToBuffer (PASTEBUFFER, m4_args, false))
404     {
405       SetMode (PASTEBUFFER_MODE);
406       g_free (m4_args);
407       goto out;
408     }
409 
410   g_free (m4_args);
411   return;
412 
413 out:
414 
415   /* update the preview with new symbol data */
416   g_object_set (library_window->preview,
417 		"element-data", PASTEBUFFER->Data->Element->data, NULL);
418 }
419 
420 /*! \brief If there is only one toplevel node, expand it. */
421 
422 static void
maybe_expand_toplevel_node(GtkTreeView * tree_view)423 maybe_expand_toplevel_node (GtkTreeView *tree_view)
424 {
425   GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
426   if (gtk_tree_model_iter_n_children (model, NULL) == 1)
427     {
428       GtkTreePath *path = gtk_tree_path_new_first ();
429       if (path != NULL)
430         {
431           gtk_tree_view_expand_row (tree_view, path, FALSE);
432           gtk_tree_path_free(path);
433         }
434     }
435 }
436 
437 /*! \brief Requests re-evaluation of the filter.
438  *  \par Function Description
439  *  This is the timeout function for the filtering of footprint
440  *  in the tree of the dialog.
441  *
442  *  The timeout this callback is attached to is removed after the
443  *  function.
444  *
445  *  \param [in] data The library dialog.
446  *  \returns FALSE to remove the timeout.
447  */
448 static gboolean
library_window_filter_timeout(gpointer data)449 library_window_filter_timeout (gpointer data)
450 {
451   GhidLibraryWindow *library_window = GHID_LIBRARY_WINDOW (data);
452   GtkTreeModel *model;
453 
454   /* resets the source id in library_window */
455   library_window->filter_timeout = 0;
456 
457   model = gtk_tree_view_get_model (library_window->libtreeview);
458 
459   if (model != NULL)
460     {
461       const gchar *text = gtk_entry_get_text (library_window->entry_filter);
462       gtk_tree_model_filter_refilter ((GtkTreeModelFilter *) model);
463       if (strcmp (text, "") != 0)
464         {
465           /* filter text not-empty */
466           gtk_tree_view_expand_all (library_window->libtreeview);
467         } else {
468           /* filter text is empty, collapse expanded tree */
469           gtk_tree_view_collapse_all (library_window->libtreeview);
470           maybe_expand_toplevel_node (library_window->libtreeview);
471         }
472     }
473 
474   /* return FALSE to remove the source */
475   return FALSE;
476 }
477 
478 /*! \brief Callback function for the changed signal of the filter entry.
479  *  \par Function Description
480  *  This function monitors changes in the entry filter of the dialog.
481  *
482  *  It specifically manages the sensitivity of the clear button of the
483  *  entry depending on its contents. It also requests an update of the
484  *  footprint list by re-evaluating filter at every changes.
485  *
486  *  \param [in] editable  The filter text entry.
487  *  \param [in] user_data The library dialog.
488  */
489 static void
library_window_callback_filter_entry_changed(GtkEditable * editable,gpointer user_data)490 library_window_callback_filter_entry_changed (GtkEditable * editable,
491 					      gpointer user_data)
492 {
493   GhidLibraryWindow *library_window = GHID_LIBRARY_WINDOW (user_data);
494   GtkWidget *button;
495   gboolean sensitive;
496 
497   /* turns button off if filter entry is empty */
498   /* turns it on otherwise */
499   button = GTK_WIDGET (library_window->button_clear);
500   sensitive =
501     (g_ascii_strcasecmp (gtk_entry_get_text (library_window->entry_filter),
502 			 "") != 0);
503   gtk_widget_set_sensitive (button, sensitive);
504 
505   /* Cancel any pending update of the footprint list filter */
506   if (library_window->filter_timeout != 0)
507     g_source_remove (library_window->filter_timeout);
508 
509   /* Schedule an update of the footprint list filter in
510    * LIBRARY_FILTER_INTERVAL milliseconds */
511   library_window->filter_timeout = g_timeout_add (LIBRARY_FILTER_INTERVAL,
512 						  library_window_filter_timeout,
513 						  library_window);
514 
515 }
516 
517 /*! \brief Handles a click on the clear button.
518  *  \par Function Description
519  *  This is the callback function called every time the user press the
520  *  clear button associated with the filter.
521  *
522  *  It resets the filter entry, indirectly causing re-evaluation
523  *  of the filter on the list of symbols to update the display.
524  *
525  *  \param [in] editable  The filter text entry.
526  *  \param [in] user_data The library dialog.
527  */
528 static void
library_window_callback_filter_button_clicked(GtkButton * button,gpointer user_data)529 library_window_callback_filter_button_clicked (GtkButton * button,
530 					       gpointer user_data)
531 {
532   GhidLibraryWindow *library_window = GHID_LIBRARY_WINDOW (user_data);
533 
534   /* clears text in text entry for filter */
535   gtk_entry_set_text (library_window->entry_filter, "");
536 
537 }
538 
539 /* \brief Create the tree model for the "Library" view.
540  * \par Function Description
541  * Creates a tree where the branches are the available library
542  * sources and the leaves are the footprints.
543  */
544 static GtkTreeModel *
create_lib_tree_model(GhidLibraryWindow * library_window)545 create_lib_tree_model (GhidLibraryWindow * library_window)
546 {
547   GtkTreeStore *tree;
548   char *rel_path, empty_string[] = ""; /* writable */
549   GtkTreeIter *iter, p_iter, e_iter, c_iter;
550   char *tok_start, *tok_end;
551   gchar *name;
552   gboolean exists;
553 
554   tree = gtk_tree_store_new (N_MENU_COLUMNS,
555 			     G_TYPE_STRING, G_TYPE_STRING,
556 			     G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER);
557 
558   MENU_LOOP (&Library);
559   {
560     /* Watch for directory changes of library parts and create new
561        |  parent iter at each change.
562      */
563     if (!menu->directory)	/* Shouldn't happen */
564       menu->directory = g_strdup ("???");
565 
566     rel_path = menu->Name;
567 
568     if (strncmp(rel_path, menu->directory, strlen(menu->directory)) == 0)
569       {
570 	if (rel_path[strlen(menu->directory)] == '\0')
571 	  rel_path = empty_string;
572 	else if (rel_path[strlen(menu->directory)] == PCB_DIR_SEPARATOR_C)
573 	  rel_path += strlen(menu->directory) + 1;
574       }
575 
576     iter = NULL;
577     tok_start = tok_end = rel_path;
578 
579     do
580       {
581 	char saved_ch = *tok_end;
582 	*tok_end = '\0';
583 
584 	exists = FALSE;
585 	if (gtk_tree_model_iter_children (GTK_TREE_MODEL (tree), &e_iter, iter))
586 	  do
587 	    {
588 	      gtk_tree_model_get (GTK_TREE_MODEL (tree), &e_iter,
589 				  MENU_TOPPATH_COLUMN, &name, -1);
590 	      if (strcmp (name, menu->directory) != 0)
591 		continue;
592 
593 	      gtk_tree_model_get (GTK_TREE_MODEL (tree), &e_iter,
594 				  MENU_SUBPATH_COLUMN, &name, -1);
595 	      if (strcmp (name, rel_path) != 0)
596 		continue;
597 
598 	      exists = TRUE;
599 	      break;
600 	    }
601 	  while (gtk_tree_model_iter_next (GTK_TREE_MODEL (tree), &e_iter));
602 
603 	if (exists)
604 	  p_iter = e_iter;
605 	else
606 	  {
607 	    gtk_tree_store_append (tree, &p_iter, iter);
608 	    gtk_tree_store_set (tree, &p_iter,
609 				MENU_TOPPATH_COLUMN, menu->directory,
610 				MENU_SUBPATH_COLUMN, rel_path,
611 				MENU_NAME_COLUMN,
612 				  tok_end == rel_path ?
613 				    g_path_get_basename(menu->directory) : tok_start,
614 				MENU_LIBRARY_COLUMN,
615 				  saved_ch == '\0' ? menu : NULL,
616 				MENU_ENTRY_COLUMN, NULL, -1);
617 	  }
618 	iter = &p_iter;
619 
620 	*tok_end = saved_ch;
621 
622 	tok_start = tok_end;
623 	if (*tok_start == PCB_DIR_SEPARATOR_C)
624 	  tok_start++;
625 	tok_end = strchr(tok_start, PCB_DIR_SEPARATOR_C);
626     if (!tok_end) tok_end = tok_start + strlen(tok_start);
627       }
628     while (*tok_start != '\0');
629 
630     ENTRY_LOOP (menu);
631     {
632       gtk_tree_store_append (tree, &c_iter, iter);
633       gtk_tree_store_set (tree, &c_iter,
634 			  MENU_TOPPATH_COLUMN, menu->directory,
635 			  MENU_SUBPATH_COLUMN, rel_path,
636 			  MENU_NAME_COLUMN, entry->ListEntry,
637 			  MENU_LIBRARY_COLUMN, menu,
638 			  MENU_ENTRY_COLUMN, entry, -1);
639     }
640     END_LOOP;
641 
642   }
643   END_LOOP;
644 
645   return (GtkTreeModel *) tree;
646 }
647 
648 
649 #if 0
650 /* \brief On-demand refresh of the footprint library.
651  * \par Function Description
652  * Requests a rescan of the footprint library in order to pick up any
653  * new signals, and then updates the library window.
654  */
655 static void
656 library_window_callback_refresh_library (GtkButton * button,
657 					 gpointer user_data)
658 {
659   GhidLibraryWindow *library_window = GHID_LIBRARY_WINDOW (user_data);
660   GtkTreeModel *model;
661 
662   /* Rescan the libraries for symbols */
663   /*  TODO: How do we do this in PCB?  */
664 
665   /* Refresh the "Library" view */
666   model = (GtkTreeModel *)
667     g_object_new (GTK_TYPE_TREE_MODEL_FILTER,
668 		  "child-model", create_lib_tree_model (library_window),
669 		  "virtual-root", NULL, NULL);
670 
671   gtk_tree_model_filter_set_visible_func ((GtkTreeModelFilter *) model,
672 					  lib_model_filter_visible_func,
673 					  library_window, NULL);
674 
675   gtk_tree_view_set_model (library_window->libtreeview, model);
676   maybe_expand_toplevel_node (library_window->libtreeview);
677 }
678 #endif
679 
680 
681 /*! \brief Creates the treeview for the "Library" view */
682 static GtkWidget *
create_lib_treeview(GhidLibraryWindow * library_window)683 create_lib_treeview (GhidLibraryWindow * library_window)
684 {
685   GtkWidget *libtreeview, *vbox, *scrolled_win, *label,
686     *hbox, *entry, *button;
687   GtkTreeModel *child_model, *model;
688   GtkTreeSelection *selection;
689   GtkCellRenderer *renderer;
690   GtkTreeViewColumn *column;
691 
692   /* -- library selection view -- */
693 
694   /* vertical box for footprint selection and search entry */
695   vbox = GTK_WIDGET (g_object_new (GTK_TYPE_VBOX,
696 				   /* GtkContainer */
697 				   "border-width", 5,
698 				   /* GtkBox */
699 				   "homogeneous", FALSE, "spacing", 5, NULL));
700 
701   child_model = create_lib_tree_model (library_window);
702   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (child_model),
703 					MENU_NAME_COLUMN, GTK_SORT_ASCENDING);
704   model = (GtkTreeModel *) g_object_new (GTK_TYPE_TREE_MODEL_FILTER,
705 					 "child-model", child_model,
706 					 "virtual-root", NULL, NULL);
707 
708   scrolled_win = GTK_WIDGET (g_object_new (GTK_TYPE_SCROLLED_WINDOW,
709 					   /* GtkScrolledWindow */
710 					   "hscrollbar-policy",
711 					   GTK_POLICY_AUTOMATIC,
712 					   "vscrollbar-policy",
713 					   GTK_POLICY_ALWAYS, "shadow-type",
714 					   GTK_SHADOW_ETCHED_IN, NULL));
715   /* create the treeview */
716   libtreeview = GTK_WIDGET (g_object_new (GTK_TYPE_TREE_VIEW,
717 					  /* GtkTreeView */
718 					  "model", model,
719 					  "rules-hint", TRUE,
720 					  "headers-visible", FALSE, NULL));
721 
722   g_signal_connect (libtreeview,
723                     "row-activated",
724                     G_CALLBACK (tree_row_activated),
725                     NULL);
726 
727   g_signal_connect (libtreeview,
728                     "key-press-event",
729                     G_CALLBACK (tree_row_key_pressed),
730                     NULL);
731 
732   maybe_expand_toplevel_node (GTK_TREE_VIEW (libtreeview));
733 
734   /* connect callback to selection */
735   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (libtreeview));
736   gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
737   g_signal_connect (selection,
738 		    "changed",
739 		    G_CALLBACK
740 		    (library_window_callback_tree_selection_changed),
741 		    library_window);
742 
743   /* insert a column to treeview for library/symbol name */
744   renderer = GTK_CELL_RENDERER (g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
745 					      /* GtkCellRendererText */
746 					      "editable", FALSE, NULL));
747   column = GTK_TREE_VIEW_COLUMN (g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
748 					       /* GtkTreeViewColumn */
749 					       "title", _("Components"),
750 					       NULL));
751   gtk_tree_view_column_pack_start (column, renderer, TRUE);
752   gtk_tree_view_column_set_attributes (column, renderer,
753 				       "text", MENU_NAME_COLUMN, NULL);
754   gtk_tree_view_append_column (GTK_TREE_VIEW (libtreeview), column);
755 
756   /* add the treeview to the scrolled window */
757   gtk_container_add (GTK_CONTAINER (scrolled_win), libtreeview);
758 
759   /* add the scrolled window for directories to the vertical box */
760   gtk_box_pack_start (GTK_BOX (vbox), scrolled_win, TRUE, TRUE, 0);
761 
762 
763   /* -- filter area -- */
764   hbox = GTK_WIDGET (g_object_new (GTK_TYPE_HBOX,
765 				   /* GtkBox */
766 				   "homogeneous", FALSE, "spacing", 3, NULL));
767 
768   /* create the entry label */
769   label = GTK_WIDGET (g_object_new (GTK_TYPE_LABEL,
770 				    /* GtkMisc */
771 				    "xalign", 0.0,
772 				    /* GtkLabel */
773 				    "label", _("Filter:"), NULL));
774   /* add the search label to the filter area */
775   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
776 
777   /* create the text entry for filter in footprints */
778   entry = GTK_WIDGET (g_object_new (GTK_TYPE_ENTRY,
779 				    /* GtkEntry */
780 				    "text", "", NULL));
781   g_signal_connect (entry,
782 		    "changed",
783 		    G_CALLBACK (library_window_callback_filter_entry_changed),
784 		    library_window);
785 
786   /* now that that we have an entry, set the filter func of model */
787   gtk_tree_model_filter_set_visible_func ((GtkTreeModelFilter *) model,
788 					  lib_model_filter_visible_func,
789 					  library_window, NULL);
790 
791   /* add the filter entry to the filter area */
792   gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
793   /* set filter entry of library_window */
794   library_window->entry_filter = GTK_ENTRY (entry);
795   /* and init the event source for footprint filter */
796   library_window->filter_timeout = 0;
797 
798   /* create the erase button for filter entry */
799   button = GTK_WIDGET (g_object_new (GTK_TYPE_BUTTON,
800 				     /* GtkWidget */
801 				     "sensitive", FALSE,
802 				     /* GtkButton */
803 				     "relief", GTK_RELIEF_NONE, NULL));
804 
805   gtk_container_add (GTK_CONTAINER (button),
806 		     gtk_image_new_from_stock (GTK_STOCK_CLEAR,
807 					       GTK_ICON_SIZE_SMALL_TOOLBAR));
808   g_signal_connect (button,
809 		    "clicked",
810 		    G_CALLBACK
811 		    (library_window_callback_filter_button_clicked),
812 		    library_window);
813   /* add the clear button to the filter area */
814   gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
815   /* set clear button of library_window */
816   library_window->button_clear = GTK_BUTTON (button);
817 
818 #if 0
819   /* create the refresh button */
820   button = GTK_WIDGET (g_object_new (GTK_TYPE_BUTTON,
821 				     /* GtkWidget */
822 				     "sensitive", TRUE,
823 				     /* GtkButton */
824 				     "relief", GTK_RELIEF_NONE, NULL));
825   gtk_container_add (GTK_CONTAINER (button),
826 		     gtk_image_new_from_stock (GTK_STOCK_REFRESH,
827 					       GTK_ICON_SIZE_SMALL_TOOLBAR));
828   /* add the refresh button to the filter area */
829   gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
830   g_signal_connect (button,
831 		    "clicked",
832 		    G_CALLBACK (library_window_callback_refresh_library),
833 		    library_window);
834 #endif
835 
836   /* add the filter area to the vertical box */
837   gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
838 
839   /* save pointer to libtreeview in library_window */
840   library_window->libtreeview = GTK_TREE_VIEW (libtreeview);
841 
842   return vbox;
843 }
844 
845 
846 static GObject *
library_window_constructor(GType type,guint n_construct_properties,GObjectConstructParam * construct_params)847 library_window_constructor (GType type,
848 			    guint n_construct_properties,
849 			    GObjectConstructParam * construct_params)
850 {
851   GObject *object;
852   GhidLibraryWindow *library_window;
853   GtkWidget *content_area;
854   GtkWidget *hpaned, *notebook;
855   GtkWidget *libview;
856   GtkWidget *preview;
857   GtkWidget *alignment, *frame;
858 
859   /* chain up to constructor of parent class */
860   object = G_OBJECT_CLASS (library_window_parent_class)->
861     constructor (type, n_construct_properties, construct_params);
862   library_window = GHID_LIBRARY_WINDOW (object);
863 
864   /* dialog initialization */
865   g_object_set (object,
866 		/* GtkWindow */
867 		"type", GTK_WINDOW_TOPLEVEL,
868 		"title", _("Select Footprint..."),
869 		"default-height", 300,
870 		"default-width", 400,
871 		"modal", FALSE, "window-position", GTK_WIN_POS_NONE,
872 		/* GtkDialog */
873 		"has-separator", TRUE, NULL);
874   g_object_set (gtk_dialog_get_content_area (GTK_DIALOG (library_window)),
875 		"homogeneous", FALSE, NULL);
876 
877   /* horizontal pane containing selection and preview */
878   hpaned = GTK_WIDGET (g_object_new (GTK_TYPE_HPANED,
879 				     /* GtkContainer */
880 				     "border-width", 5, NULL));
881   library_window->hpaned = hpaned;
882 
883   /* notebook for library views */
884   notebook = GTK_WIDGET (g_object_new (GTK_TYPE_NOTEBOOK,
885 				       "show-tabs", FALSE, NULL));
886   library_window->viewtabs = GTK_NOTEBOOK (notebook);
887 
888   libview = create_lib_treeview (library_window);
889   gtk_notebook_append_page (GTK_NOTEBOOK (notebook), libview,
890 			    gtk_label_new (_("Libraries")));
891 
892   /* include the vertical box in horizontal box */
893   gtk_paned_pack1 (GTK_PANED (hpaned), notebook, TRUE, FALSE);
894 
895 
896   /* -- preview area -- */
897   frame = GTK_WIDGET (g_object_new (GTK_TYPE_FRAME,
898 				    /* GtkFrame */
899 				    "label", _("Preview"), NULL));
900   alignment = GTK_WIDGET (g_object_new (GTK_TYPE_ALIGNMENT,
901 					/* GtkAlignment */
902 					"left-padding", 5,
903 					"right-padding", 5,
904 					"top-padding", 5,
905 					"bottom-padding", 5,
906 					"xscale", 1.0,
907 					"yscale", 1.0,
908 					"xalign", 0.5, "yalign", 0.5, NULL));
909   preview = (GtkWidget *)g_object_new (GHID_TYPE_PINOUT_PREVIEW,
910 			  /* GhidPinoutPreview */
911 			  "element-data", NULL,
912 			  /* GtkWidget */
913 			  "width-request", 150, "height-request", 150, NULL);
914   gtk_container_add (GTK_CONTAINER (alignment), preview);
915   gtk_container_add (GTK_CONTAINER (frame), alignment);
916   /* set preview of library_window */
917   library_window->preview = preview;
918 
919   gtk_paned_pack2 (GTK_PANED (hpaned), frame, FALSE, FALSE);
920 
921   /* add the hpaned to the dialog content area */
922   content_area = gtk_dialog_get_content_area (GTK_DIALOG (library_window));
923   gtk_box_pack_start (GTK_BOX (content_area), hpaned, TRUE, TRUE, 0);
924   gtk_widget_show_all (hpaned);
925 
926 
927   /* now add buttons in the action area */
928   gtk_dialog_add_buttons (GTK_DIALOG (library_window),
929 			  /*  - close button */
930 			  GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
931 
932   return object;
933 }
934 
935 static void
library_window_finalize(GObject * object)936 library_window_finalize (GObject * object)
937 {
938   GhidLibraryWindow *library_window = GHID_LIBRARY_WINDOW (object);
939 
940   if (library_window->filter_timeout != 0)
941     {
942       g_source_remove (library_window->filter_timeout);
943       library_window->filter_timeout = 0;
944     }
945 
946   G_OBJECT_CLASS (library_window_parent_class)->finalize (object);
947 }
948 
949 
950 static void
library_window_class_init(GhidLibraryWindowClass * klass)951 library_window_class_init (GhidLibraryWindowClass * klass)
952 {
953   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
954 
955   gobject_class->constructor = library_window_constructor;
956   gobject_class->finalize = library_window_finalize;
957 
958   library_window_parent_class = (GObjectClass *)g_type_class_peek_parent (klass);
959 }
960 
961 
962 GType
ghid_library_window_get_type()963 ghid_library_window_get_type ()
964 {
965   static GType library_window_type = 0;
966 
967   if (!library_window_type)
968     {
969       static const GTypeInfo library_window_info = {
970 	sizeof (GhidLibraryWindowClass),
971 	NULL,			/* base_init */
972 	NULL,			/* base_finalize */
973 	(GClassInitFunc) library_window_class_init,
974 	NULL,			/* class_finalize */
975 	NULL,			/* class_data */
976 	sizeof (GhidLibraryWindow),
977 	0,			/* n_preallocs */
978 	NULL			/* instance_init */
979       };
980 
981       library_window_type = g_type_register_static (GTK_TYPE_DIALOG,
982 						    "GhidLibraryWindow",
983 						    &library_window_info, (GTypeFlags)0);
984     }
985 
986   return library_window_type;
987 }
988