Bump gEDA version
[geda-gaf.git] / gschem / src / gschem_compselect_dockable.c
blob39326de47bf1db040e6b4f5f056122ee680fcb57
1 /* gEDA - GPL Electronic Design Automation
2 * gschem - gEDA Schematic Capture
3 * Copyright (C) 1998-2010 Ales Hvezda
4 * Copyright (C) 1998-2020 gEDA Contributors (see ChangeLog for details)
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.
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.
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
20 #include <config.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <sys/types.h>
25 #ifdef HAVE_SYS_PARAM_H
26 #include <sys/param.h>
27 #endif
28 #include <sys/stat.h>
29 #ifdef HAVE_UNISTD_H
30 #include <unistd.h>
31 #endif
32 #ifdef HAVE_STRING_H
33 #include <string.h>
34 #endif
36 #include "gschem.h"
37 #include "actions.decl.x"
38 #include <gdk/gdkkeysyms.h>
40 #include "../include/gschem_compselect_dockable.h"
42 /*! \def COMPSELECT_FILTER_INTERVAL
43 * \brief The time interval between request and actual filtering
45 * This constant is the time-lag between user modifications in the
46 * filter entry and the actual evaluation of the filter which
47 * ultimately update the display. It helps reduce the frequency of
48 * evaluation of the filter as user types.
50 * Unit is milliseconds.
52 #define COMPSELECT_FILTER_INTERVAL 200
55 enum compselect_behavior {
56 BEHAVIOR_REFERENCE,
57 BEHAVIOR_EMBED,
58 BEHAVIOR_INCLUDE
61 enum {
62 INUSE_COLUMN_SYMBOL,
63 N_INUSE_COLUMNS
66 enum {
67 LIB_COLUMN_SYMBOL_OR_SOURCE,
68 LIB_COLUMN_NAME,
69 LIB_COLUMN_IS_SYMBOL,
70 N_LIB_COLUMNS
73 /* Both the inuse model and symbol rows of the lib model store the
74 symbol pointer in column 0. Define a special constant which is
75 used whenever we depend on this fact. */
76 enum {
77 COMMON_COLUMN_SYMBOL
80 enum {
81 ATTRIBUTE_COLUMN_NAME = 0,
82 ATTRIBUTE_COLUMN_VALUE,
83 NUM_ATTRIBUTE_COLUMNS
87 static GObjectClass *compselect_parent_class = NULL;
90 static void compselect_class_init (GschemCompselectDockableClass *class);
91 static void compselect_constructed (GObject *object);
92 static void compselect_dispose (GObject *object);
93 static void compselect_finalize (GObject *object);
95 static GtkWidget *compselect_create_widget (GschemDockable *dockable);
98 static void
99 update_attributes_model (GschemCompselectDockable *compselect, gchar *filename);
100 static void
101 compselect_callback_tree_selection_changed (GtkTreeSelection *selection,
102 gpointer user_data);
105 static void
106 compselect_place (GschemCompselectDockable *compselect)
108 GschemToplevel *w_current = GSCHEM_DOCKABLE (compselect)->w_current;
109 TOPLEVEL *toplevel = gschem_toplevel_get_toplevel (w_current);
111 enum compselect_behavior behavior =
112 gtk_combo_box_get_active (compselect->combobox_behaviors);
114 w_current->include_complex = w_current->embed_complex = 0;
115 switch (behavior) {
116 case BEHAVIOR_REFERENCE:
117 break;
118 case BEHAVIOR_EMBED:
119 w_current->embed_complex = 1;
120 break;
121 case BEHAVIOR_INCLUDE:
122 w_current->include_complex = 1;
123 break;
124 default:
125 g_assert_not_reached ();
128 if (w_current->event_state == COMPMODE) {
129 /* Delete the component which was being placed */
130 if (w_current->rubber_visible)
131 o_place_invalidate_rubber (w_current, FALSE);
132 w_current->rubber_visible = 0;
133 s_delete_object_glist (toplevel,
134 toplevel->page_current->place_list);
135 toplevel->page_current->place_list = NULL;
136 } else {
137 /* Cancel whatever other action is currently in progress */
138 o_redraw_cleanstates (w_current);
141 if (compselect->selected_symbol == NULL) {
142 /* If there is no symbol selected, switch to SELECT mode */
143 i_set_state (w_current, SELECT);
144 i_action_stop (w_current);
145 } else {
146 /* Otherwise set the new symbol to place */
147 o_complex_prepare_place (w_current, compselect->selected_symbol);
152 static void
153 select_symbol (GschemCompselectDockable *compselect, CLibSymbol *symbol)
155 if (symbol == compselect->selected_symbol)
156 return;
157 compselect->selected_symbol = symbol;
159 /* update in-use and library selections */
160 GtkTreeSelection *selection;
161 GtkTreeModel *model;
162 GtkTreeIter iter;
164 selection = gtk_tree_view_get_selection (compselect->inusetreeview);
165 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
166 CLibSymbol *sym = NULL;
167 gtk_tree_model_get (model, &iter, INUSE_COLUMN_SYMBOL, &sym, -1);
168 if (sym != symbol) {
169 g_signal_handlers_block_by_func (
170 selection, compselect_callback_tree_selection_changed, compselect);
171 gtk_tree_selection_unselect_all (selection);
172 g_signal_handlers_unblock_by_func (
173 selection, compselect_callback_tree_selection_changed, compselect);
177 selection = gtk_tree_view_get_selection (compselect->libtreeview);
178 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
179 CLibSymbol *sym = NULL;
180 gboolean is_sym = FALSE;
181 gtk_tree_model_get (model, &iter, LIB_COLUMN_SYMBOL_OR_SOURCE, &sym,
182 LIB_COLUMN_IS_SYMBOL, &is_sym, -1);
183 if (is_sym && sym != symbol) {
184 g_signal_handlers_block_by_func (
185 selection, compselect_callback_tree_selection_changed, compselect);
186 gtk_tree_selection_unselect_all (selection);
187 g_signal_handlers_unblock_by_func (
188 selection, compselect_callback_tree_selection_changed, compselect);
192 /* update the preview with new symbol data */
193 gchar *buffer = symbol ? s_clib_symbol_get_data (symbol) : NULL;
194 g_object_set (compselect->preview,
195 "buffer", buffer,
196 "active", buffer != NULL,
197 NULL);
198 g_free (buffer);
200 /* update the attributes with the toplevel of the preview widget*/
201 if (symbol == NULL) {
202 compselect->is_selected = FALSE;
203 update_attributes_model (compselect, NULL);
204 } else {
205 g_free (compselect->selected_filename);
206 compselect->selected_filename = s_clib_symbol_get_filename (symbol);
207 compselect->is_selected = TRUE;
208 update_attributes_model (compselect, compselect->selected_filename);
210 gschem_action_set_sensitive (action_add_last_component,
211 compselect->selected_filename != NULL,
212 GSCHEM_DOCKABLE (compselect)->w_current);
215 /* signal a component has been selected to parent of dockable */
216 compselect_place (compselect);
220 static void
221 compselect_cancel (GschemDockable *dockable)
223 GschemToplevel *w_current = dockable->w_current;
225 if (w_current->event_state == COMPMODE) {
226 /* Cancel the place operation currently in progress */
227 o_redraw_cleanstates (w_current);
229 /* return to the default state */
230 i_set_state (w_current, SELECT);
231 i_action_stop (w_current);
236 static void
237 compselect_post_present (GschemDockable *dockable)
239 GschemCompselectDockable *compselect = GSCHEM_COMPSELECT_DOCKABLE (dockable);
241 GtkWidget *current_tab, *entry_filter;
242 GtkNotebook *compselect_notebook;
244 gtk_editable_select_region (GTK_EDITABLE (compselect->entry_filter), 0, -1);
246 /* Set the focus to the filter entry only if it is in the current
247 displayed tab */
248 compselect_notebook = GTK_NOTEBOOK (compselect->viewtabs);
249 current_tab = gtk_notebook_get_nth_page (compselect_notebook,
250 gtk_notebook_get_current_page (compselect_notebook));
251 entry_filter = GTK_WIDGET (compselect->entry_filter);
252 if (gtk_widget_is_ancestor (entry_filter, current_tab)) {
253 gtk_widget_grab_focus (entry_filter);
258 void
259 x_compselect_deselect (GschemToplevel *w_current)
261 GschemCompselectDockable *compselect =
262 GSCHEM_COMPSELECT_DOCKABLE (w_current->compselect_dockable);
264 select_symbol (compselect, NULL);
268 /*! \brief Sets data for a particular cell of the in use treeview.
269 * \par Function Description
270 * This function determines what data is to be displayed in the
271 * "in use" symbol selection view.
273 * The model is a list of symbols. s_clib_symbol_get_name() is called
274 * to get the text to display.
276 static void
277 inuse_treeview_set_cell_data (GtkTreeViewColumn *tree_column,
278 GtkCellRenderer *cell,
279 GtkTreeModel *tree_model,
280 GtkTreeIter *iter,
281 gpointer data)
283 CLibSymbol *symbol;
285 gtk_tree_model_get (tree_model, iter, INUSE_COLUMN_SYMBOL, &symbol, -1);
286 g_object_set (G_OBJECT (cell), "text", s_clib_symbol_get_name (symbol), NULL);
289 /*! \brief Returns whether a library treeview node represents a symbol. */
291 static gboolean is_symbol(GtkTreeModel *tree_model, GtkTreeIter *iter)
293 gboolean result;
294 gtk_tree_model_get (tree_model, iter, LIB_COLUMN_IS_SYMBOL, &result, -1);
295 return result;
298 /*! \brief Determines visibility of items of the library treeview.
299 * \par Function Description
300 * This is the function used to filter entries of the component
301 * selection tree.
303 * \param [in] model The current selection in the treeview.
304 * \param [in] iter An iterator on a component or folder in the tree.
305 * \param [in] data The component selection dockable.
306 * \returns TRUE if item should be visible, FALSE otherwise.
308 static gboolean
309 lib_model_filter_visible_func (GtkTreeModel *model,
310 GtkTreeIter *iter,
311 gpointer data)
313 GschemCompselectDockable *compselect = GSCHEM_COMPSELECT_DOCKABLE (data);
314 CLibSymbol *sym;
315 const gchar *compname;
316 gchar *compname_upper, *text_upper, *pattern;
317 const gchar *text;
318 gboolean ret;
320 g_assert (GSCHEM_IS_COMPSELECT_DOCKABLE (data));
322 if (compselect->entry_filter == NULL)
323 return TRUE;
324 text = gtk_entry_get_text (compselect->entry_filter);
325 if (g_ascii_strcasecmp (text, "") == 0) {
326 return TRUE;
329 /* If this is a source, only display it if it has children that
330 * match */
331 if (!is_symbol (model, iter)) {
332 GtkTreeIter iter2;
334 ret = FALSE;
335 if (gtk_tree_model_iter_children (model, &iter2, iter))
336 do {
337 if (lib_model_filter_visible_func (model, &iter2, data)) {
338 ret = TRUE;
339 break;
341 } while (gtk_tree_model_iter_next (model, &iter2));
342 } else {
343 gtk_tree_model_get (model, iter,
344 LIB_COLUMN_SYMBOL_OR_SOURCE, &sym,
345 -1);
346 compname = s_clib_symbol_get_name (sym);
347 /* Do a case insensitive comparison, converting the strings
348 to uppercase */
349 compname_upper = g_ascii_strup (compname, -1);
350 text_upper = g_ascii_strup (text, -1);
351 pattern = g_strconcat ("*", text_upper, "*", NULL);
352 ret = g_pattern_match_simple (pattern, compname_upper);
353 g_free (compname_upper);
354 g_free (text_upper);
355 g_free (pattern);
358 return ret;
362 /*! \brief Handles activation (e.g. double-clicking) of a component row
363 * \par Function Description
364 * Component row activated handler:
365 * As a convenience to the user, expand / contract any node with children.
366 * Hide the component selector if a node without children is activated.
368 * \param [in] tree_view The component treeview.
369 * \param [in] path The GtkTreePath to the activated row.
370 * \param [in] column The GtkTreeViewColumn in which the activation occurred.
371 * \param [in] user_data The component selection dockable.
373 static void
374 tree_row_activated (GtkTreeView *tree_view,
375 GtkTreePath *path,
376 GtkTreeViewColumn *column,
377 gpointer user_data)
379 GtkTreeModel *model;
380 GtkTreeIter iter;
381 GschemCompselectDockable *compselect = GSCHEM_COMPSELECT_DOCKABLE (user_data);
383 model = gtk_tree_view_get_model (tree_view);
384 gtk_tree_model_get_iter (model, &iter, path);
386 if (tree_view == compselect->inusetreeview ||
387 /* No special handling required */
388 (tree_view == compselect->libtreeview && is_symbol (model, &iter))) {
389 /* Tree view needs to check that we're at a symbol node */
390 CLibSymbol *symbol = NULL;
391 gtk_tree_model_get (model, &iter, COMMON_COLUMN_SYMBOL, &symbol, -1);
392 select_symbol (compselect, symbol);
394 GschemDockable *dockable = GSCHEM_DOCKABLE (compselect);
395 switch (gschem_dockable_get_state (dockable)) {
396 case GSCHEM_DOCKABLE_STATE_DIALOG:
397 /* if shown as dialog, hide */
398 gschem_dockable_hide (dockable);
399 return;
400 case GSCHEM_DOCKABLE_STATE_WINDOW:
401 /* if shown as detached window, focus main window */
402 gtk_widget_grab_focus (dockable->w_current->drawing_area);
403 x_window_present (dockable->w_current);
404 default:
405 /* if docked, focus drawing area */
406 gtk_widget_grab_focus (dockable->w_current->drawing_area);
408 return;
411 if (gtk_tree_view_row_expanded (tree_view, path))
412 gtk_tree_view_collapse_row (tree_view, path);
413 else
414 gtk_tree_view_expand_row (tree_view, path, FALSE);
417 /*! \brief GCompareFunc to sort an text object list by the object strings
419 static gint
420 sort_object_text (OBJECT *a, OBJECT *b)
422 return strcmp (a->text->string, b->text->string);
425 /*! \brief Update the model of the attributes treeview
426 * \par Function Description
427 * This function takes the toplevel attributes from the preview widget and
428 * puts them into the model of the <b>attrtreeview</b> widget.
429 * \param [in] compselect The dockable compselect
430 * \param [in] preview_toplevel The toplevel of the preview widget
432 static void
433 update_attributes_model (GschemCompselectDockable *compselect, gchar *filename)
435 GtkListStore *model;
436 GtkTreeIter iter;
437 GtkTreeViewColumn *column;
438 GList *o_iter, *o_attrlist;
439 gchar *name, *value;
440 OBJECT *o_current;
441 EdaConfig *cfg;
442 gchar **filter_list;
443 gint i;
444 gsize n;
446 model = GTK_LIST_STORE (gtk_tree_view_get_model (compselect->attrtreeview));
447 gtk_list_store_clear (model);
449 /* Invalidate the column width for the attribute value column, so
450 * the column is re-sized based on the new data being shown. Symbols
451 * with long values are common, and we don't want having viewed those
452 * forcing a h-scroll-bar on symbols with short valued attributes.
454 * We might also consider invalidating the attribute name columns,
455 * however that gives an inconsistent column division when swithing
456 * between symbols, which doesn't look nice. For now, assume that
457 * the name column can keep the max width gained whilst previewing.
459 column = gtk_tree_view_get_column (compselect->attrtreeview,
460 ATTRIBUTE_COLUMN_VALUE);
461 gtk_tree_view_column_queue_resize (column);
463 PAGE *preview_page = gschem_page_view_get_page (
464 GSCHEM_PAGE_VIEW (compselect->preview));
465 if (preview_page == NULL)
466 return;
468 o_attrlist = o_attrib_find_floating_attribs (s_page_objects (preview_page));
470 cfg = filename != NULL ? eda_config_get_context_for_path (filename)
471 : eda_config_get_user_context ();
472 filter_list = eda_config_get_string_list (cfg, "gschem.library",
473 "component-attributes", &n, NULL);
475 if (filter_list == NULL || (n > 0 && strcmp (filter_list[0], "*") == 0)) {
476 /* display all attributes in alphabetical order */
477 o_attrlist = g_list_sort (o_attrlist, (GCompareFunc) sort_object_text);
478 for (o_iter = o_attrlist; o_iter != NULL; o_iter = g_list_next (o_iter)) {
479 o_current = o_iter->data;
480 o_attrib_get_name_value (o_current, &name, &value);
481 gtk_list_store_append (model, &iter);
482 gtk_list_store_set (model, &iter,
483 ATTRIBUTE_COLUMN_NAME, name,
484 ATTRIBUTE_COLUMN_VALUE, value, -1);
485 g_free (name);
486 g_free (value);
488 } else {
489 /* display only attribute that are in the filter list */
490 for (i = 0; i < n; i++) {
491 for (o_iter = o_attrlist; o_iter != NULL; o_iter = g_list_next (o_iter)) {
492 o_current = o_iter->data;
493 if (o_attrib_get_name_value (o_current, &name, &value)) {
494 if (strcmp (name, filter_list[i]) == 0) {
495 gtk_list_store_append (model, &iter);
496 gtk_list_store_set (model, &iter,
497 ATTRIBUTE_COLUMN_NAME, name,
498 ATTRIBUTE_COLUMN_VALUE, value, -1);
500 g_free (name);
501 g_free (value);
507 g_strfreev (filter_list);
508 g_list_free (o_attrlist);
511 /*! \brief Handles changes in the treeview selection.
512 * \par Function Description
513 * This is the callback function that is called every time the user
514 * select a row in either component treeview of the dockable.
516 * If the selection is not a selection of a component (a directory
517 * name), it does nothing. Otherwise it retrieves the #CLibSymbol
518 * from the model.
520 * It then calls compselect_place to let its parent know that a
521 * component has been selected.
523 * \param [in] selection The current selection in the treeview.
524 * \param [in] user_data The component selection dockable.
526 static void
527 compselect_callback_tree_selection_changed (GtkTreeSelection *selection,
528 gpointer user_data)
530 GtkTreeView *view;
531 GtkTreeModel *model;
532 GtkTreeIter iter;
533 GschemCompselectDockable *compselect = GSCHEM_COMPSELECT_DOCKABLE (user_data);
534 CLibSymbol *sym = NULL;
536 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
538 view = gtk_tree_selection_get_tree_view (selection);
539 if (view == compselect->inusetreeview ||
540 /* No special handling required */
541 (view == compselect->libtreeview && is_symbol (model, &iter))) {
542 /* Tree view needs to check that we're at a symbol node */
544 gtk_tree_model_get (model, &iter, COMMON_COLUMN_SYMBOL, &sym, -1);
548 select_symbol (compselect, sym);
551 /*! \brief Requests re-evaluation of the filter.
552 * \par Function Description
553 * This is the timeout function for the filtering of component in the
554 * tree of the dockable.
556 * The timeout this callback is attached to is removed after the
557 * function.
559 * \param [in] data The component selection dockable.
560 * \returns FALSE to remove the timeout.
562 static gboolean
563 compselect_filter_timeout (gpointer data)
565 GschemCompselectDockable *compselect = GSCHEM_COMPSELECT_DOCKABLE (data);
566 GtkTreeModel *model;
568 /* resets the source id in compselect */
569 compselect->filter_timeout = 0;
571 model = gtk_tree_view_get_model (compselect->libtreeview);
573 if (model != NULL) {
574 const gchar *text = gtk_entry_get_text (compselect->entry_filter);
575 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
576 if (strcmp (text, "") != 0) {
577 /* filter text not-empty */
578 gtk_tree_view_expand_all (compselect->libtreeview);
579 } else {
580 /* filter text is empty, collapse expanded tree */
581 gtk_tree_view_collapse_all (compselect->libtreeview);
585 /* return FALSE to remove the source */
586 return FALSE;
589 /*! \brief Callback function for the changed signal of the filter entry.
590 * \par Function Description
591 * This function monitors changes in the entry filter of the dockable.
593 * It specifically manages the sensitivity of the clear button of the
594 * entry depending on its contents. It also requests an update of the
595 * component list by re-evaluating filter at every changes.
597 * \param [in] editable The filter text entry.
598 * \param [in] user_data The component selection dockable.
600 static void
601 compselect_callback_filter_entry_changed (GtkEditable *editable,
602 gpointer user_data)
604 GschemCompselectDockable *compselect = GSCHEM_COMPSELECT_DOCKABLE (user_data);
605 GtkWidget *button;
606 gboolean sensitive;
608 /* turns button off if filter entry is empty */
609 /* turns it on otherwise */
610 button = GTK_WIDGET (compselect->button_clear);
611 sensitive =
612 (g_ascii_strcasecmp (gtk_entry_get_text (compselect->entry_filter),
613 "") != 0);
614 if (gtk_widget_is_sensitive (button) != sensitive) {
615 gtk_widget_set_sensitive (button, sensitive);
618 /* Cancel any pending update of the component list filter */
619 if (compselect->filter_timeout != 0)
620 g_source_remove (compselect->filter_timeout);
622 /* Schedule an update of the component list filter in
623 * COMPSELECT_FILTER_INTERVAL milliseconds */
624 compselect->filter_timeout = g_timeout_add (COMPSELECT_FILTER_INTERVAL,
625 compselect_filter_timeout,
626 compselect);
630 /*! \brief Handles a click on the clear button.
631 * \par Function Description
632 * This is the callback function called every time the user press the
633 * clear button associated with the filter.
635 * It resets the filter entry, indirectly causing re-evaluation
636 * of the filter on the list of symbols to update the display.
638 * \param [in] button The clear button
639 * \param [in] user_data The component selection dockable.
641 static void
642 compselect_callback_filter_button_clicked (GtkButton *button,
643 gpointer user_data)
645 GschemCompselectDockable *compselect = GSCHEM_COMPSELECT_DOCKABLE (user_data);
647 /* clears text in text entry for filter */
648 gtk_entry_set_text (compselect->entry_filter, "");
652 /*! \brief Handles changes of behavior.
653 * \par Function Description
654 * This function is called every time the value of the option menu
655 * for behaviors is modified.
657 * It calls compselect_place to let the parent know that the
658 * requested behavior for the next adding of a component has been
659 * changed.
661 * \param [in] optionmenu The behavior option menu.
662 * \param [in] user_data The component selection dockable.
664 static void
665 compselect_callback_behavior_changed (GtkOptionMenu *optionmenu,
666 gpointer user_data)
668 GschemCompselectDockable *compselect = GSCHEM_COMPSELECT_DOCKABLE (user_data);
670 compselect_place (compselect);
673 /* \brief Create the tree model for the "In Use" view.
674 * \par Function Description
675 * Creates a straightforward list of symbols which are currently in
676 * use, using s_toplevel_get_symbols().
678 static void
679 create_inuse_tree_model (GschemCompselectDockable *compselect)
681 GtkListStore *store;
682 GList *symhead, *symlist;
683 GtkTreeIter iter;
685 store = GTK_LIST_STORE (gtk_list_store_new (N_INUSE_COLUMNS, G_TYPE_POINTER));
687 symhead = s_toplevel_get_symbols (
688 GSCHEM_DOCKABLE (compselect)->w_current->toplevel);
690 for (symlist = symhead;
691 symlist != NULL;
692 symlist = g_list_next (symlist)) {
694 gtk_list_store_append (store, &iter);
696 gtk_list_store_set (store, &iter,
697 INUSE_COLUMN_SYMBOL, symlist->data,
698 -1);
701 g_list_free (symhead);
703 gtk_tree_view_set_model (compselect->inusetreeview, GTK_TREE_MODEL (store));
704 g_object_unref (store); /* release initially owned reference */
707 /* \brief Helper function for create_lib_tree_model. */
709 static void populate_component_store(GtkTreeStore *store, GList **srclist,
710 GtkTreeIter *parent, const char *prefix)
712 CLibSource *source = (CLibSource *)(*srclist)->data;
713 const char *name = s_clib_source_get_name (source);
715 char *text, *new_prefix;
716 GList *new_srclist;
718 if (*name == '\0') {
719 text = NULL;
720 new_prefix = NULL;
721 new_srclist = NULL;
722 } else if (*name != '/') {
723 /* directory added by component-library */
724 text = g_strdup (name);
725 new_prefix = NULL;
726 new_srclist = NULL;
727 } else {
728 /* directory added by component-library-search */
729 size_t prefix_len = strlen (prefix);
730 g_assert (strncmp (name, prefix, prefix_len) == 0);
731 char *p = strchr (name + prefix_len + 1, '/');
733 if (p != NULL) {
734 /* There is a parent directory that was skipped
735 because it doesn't contain symbols. */
736 source = NULL;
737 text = g_strndup (name + prefix_len, p - name - prefix_len);
738 new_prefix = g_strndup (name, p - name + 1);
739 new_srclist = *srclist;
740 } else {
741 size_t name_len = strlen(name);
742 text = g_strndup (name + prefix_len, name_len - prefix_len);
743 new_prefix = g_strndup (name, name_len + 1); /* reserve one extra byte */
744 new_prefix[name_len] = '/';
745 new_prefix[name_len + 1] = '\0';
746 new_srclist = g_list_next (*srclist);
750 GtkTreeIter iter;
751 gtk_tree_store_append (store, &iter, parent);
752 gtk_tree_store_set (store, &iter,
753 LIB_COLUMN_SYMBOL_OR_SOURCE, source,
754 LIB_COLUMN_NAME, text,
755 LIB_COLUMN_IS_SYMBOL, FALSE,
756 -1);
757 g_free (text);
759 /* Look ahead, adding subdirectories. */
760 while (new_srclist != NULL &&
761 strncmp(s_clib_source_get_name ((CLibSource *)new_srclist->data),
762 new_prefix, strlen(new_prefix)) == 0) {
763 *srclist = new_srclist;
764 populate_component_store(store, srclist, &iter, new_prefix);
765 new_srclist = g_list_next (*srclist);
767 g_free (new_prefix);
769 /* populate symbols */
770 GList *symhead, *symlist;
771 GtkTreeIter iter2;
772 symhead = s_clib_source_get_symbols (source);
773 for (symlist = symhead;
774 symlist != NULL;
775 symlist = g_list_next (symlist)) {
777 gtk_tree_store_append (store, &iter2, &iter);
778 gtk_tree_store_set (store, &iter2,
779 LIB_COLUMN_SYMBOL_OR_SOURCE, symlist->data,
780 LIB_COLUMN_NAME,
781 s_clib_symbol_get_name ((CLibSymbol *)symlist->data),
782 LIB_COLUMN_IS_SYMBOL, TRUE,
783 -1);
785 g_list_free (symhead);
788 /* \brief Create the tree model for the "Library" view.
789 * \par Function Description
790 * Creates a tree where the branches are the available component
791 * sources and the leaves are the symbols.
793 static void
794 create_lib_tree_model (GschemCompselectDockable *compselect)
796 GtkTreeStore *store;
797 GList *srchead, *srclist;
798 EdaConfig *cfg = eda_config_get_user_context ();
799 gboolean sort = eda_config_get_boolean (cfg, "gschem.library", "sort", NULL);
801 store = GTK_TREE_STORE (gtk_tree_store_new (N_LIB_COLUMNS, G_TYPE_POINTER,
802 G_TYPE_STRING,
803 G_TYPE_BOOLEAN));
805 /* populate component store */
806 srchead = s_clib_get_sources (sort);
807 for (srclist = srchead;
808 srclist != NULL;
809 srclist = g_list_next (srclist)) {
810 populate_component_store(store, &srclist, NULL, "/");
812 g_list_free (srchead);
815 /* create filtered model */
816 GtkTreeModel *model = GTK_TREE_MODEL (
817 g_object_new (GTK_TYPE_TREE_MODEL_FILTER,
818 "child-model", GTK_TREE_MODEL (store),
819 "virtual-root", NULL,
820 NULL));
821 g_object_unref (store); /* release initially owned reference */
823 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (model),
824 lib_model_filter_visible_func,
825 compselect, NULL);
827 gtk_tree_view_set_model (compselect->libtreeview, model);
828 g_object_unref (model); /* release initially owned reference */
830 /* re-expand library tree if filter text is not-empty */
831 if (compselect->entry_filter != NULL &&
832 gtk_entry_get_text (compselect->entry_filter)[0] != '\0')
833 gtk_tree_view_expand_all (compselect->libtreeview);
836 /*! \brief Helper function for \ref select_symbol_by_filename.
838 static gboolean
839 find_tree_iter_by_filename (GtkTreeModel *tree_model,
840 GtkTreeIter *iter_return,
841 GtkTreeIter *parent,
842 const gchar *filename)
844 GtkTreeIter iter;
846 if (gtk_tree_model_iter_children (tree_model, &iter, parent))
847 do {
848 if (gtk_tree_model_get_n_columns (tree_model) == N_INUSE_COLUMNS ||
849 is_symbol (tree_model, &iter)) {
850 /* node is a symbol */
851 CLibSymbol *symbol;
852 gtk_tree_model_get (tree_model, &iter,
853 COMMON_COLUMN_SYMBOL, &symbol, -1);
854 gchar *fn = s_clib_symbol_get_filename (symbol);
855 if (strcmp (fn, filename) == 0) {
856 *iter_return = iter;
857 g_free (fn);
858 return TRUE;
860 g_free (fn);
861 continue;
864 /* node is a source */
865 CLibSource *source;
866 gtk_tree_model_get (tree_model, &iter,
867 LIB_COLUMN_SYMBOL_OR_SOURCE, &source, -1);
869 gboolean recurse;
870 if (source == NULL)
871 /* this is a virtual node that was added for sub-directory sources */
872 recurse = TRUE;
873 else {
874 const gchar *directory = s_clib_source_get_directory (source);
875 recurse = strncmp (directory, filename, strlen (directory)) == 0 &&
876 filename[strlen (directory)] == '/';
878 if (recurse &&
879 find_tree_iter_by_filename (tree_model, iter_return, &iter, filename))
880 return TRUE;
881 } while (gtk_tree_model_iter_next (tree_model, &iter));
883 return FALSE;
886 static void
887 select_symbol_by_filename (GschemCompselectDockable *compselect,
888 const gchar *filename)
890 g_return_if_fail (filename != NULL);
892 GtkTreeView *tree_view = NULL;
893 switch (gtk_notebook_get_current_page (compselect->viewtabs)) {
894 case 0:
895 tree_view = compselect->inusetreeview;
896 break;
897 case 1:
898 tree_view = compselect->libtreeview;
899 break;
900 default:
901 g_assert_not_reached ();
904 GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
905 GtkTreeIter iter;
906 if (!find_tree_iter_by_filename (model, &iter, NULL, filename))
907 /* no matching symbol found */
908 return;
910 /* expand the path to the node so it can be selected */
911 GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
912 if (gtk_tree_path_up (path))
913 gtk_tree_view_expand_to_path (tree_view, path);
914 gtk_tree_path_free (path);
916 /* now select the node */
917 gtk_tree_selection_select_iter (
918 gtk_tree_view_get_selection (tree_view), &iter);
921 static void
922 library_updated (gpointer user_data)
924 GschemCompselectDockable *compselect = GSCHEM_COMPSELECT_DOCKABLE (user_data);
925 gboolean was_selected = compselect->is_selected;
927 /* Refresh the "Library" view */
928 create_lib_tree_model (compselect);
930 /* Refresh the "In Use" view */
931 create_inuse_tree_model (compselect);
933 /* re-select previously selected symbol */
934 if (was_selected && compselect->selected_filename != NULL)
935 select_symbol_by_filename (compselect, compselect->selected_filename);
938 void
939 x_compselect_select_previous_symbol (GschemToplevel *w_current)
941 GschemCompselectDockable *compselect =
942 GSCHEM_COMPSELECT_DOCKABLE (w_current->compselect_dockable);
944 if (compselect->selected_filename != NULL)
945 select_symbol_by_filename (compselect, compselect->selected_filename);
948 /* \brief On-demand refresh of the component library.
949 * \par Function Description
950 * Requests a rescan of the component library in order to pick up any
951 * new signals, and then updates the component selector.
953 static void
954 compselect_callback_refresh_library (GtkButton *button, gpointer user_data)
956 /* Rescan the libraries for symbols */
957 s_clib_refresh ();
960 /*! \brief Creates the treeview for the "In Use" view. */
961 static GtkWidget*
962 create_inuse_treeview (GschemCompselectDockable *compselect)
964 GtkWidget *scrolled_win, *treeview, *vbox, *hbox, *button;
965 GtkTreeSelection *selection;
966 GtkCellRenderer *renderer;
967 GtkTreeViewColumn *column;
969 vbox = GTK_WIDGET (g_object_new (GTK_TYPE_VBOX,
970 /* GtkContainer */
971 "border-width", 5,
972 /* GtkBox */
973 "homogeneous", FALSE,
974 "spacing", 5,
975 NULL));
977 /* Create a scrolled window to accomodate the treeview */
978 scrolled_win = GTK_WIDGET (
979 g_object_new (GTK_TYPE_SCROLLED_WINDOW,
980 /* GtkContainer */
981 "border-width", 5,
982 /* GtkScrolledWindow */
983 "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
984 "vscrollbar-policy", GTK_POLICY_ALWAYS,
985 "shadow-type", GTK_SHADOW_ETCHED_IN,
986 NULL));
988 /* Create the treeview */
989 treeview = GTK_WIDGET (g_object_new (GTK_TYPE_TREE_VIEW,
990 /* GtkTreeView */
991 "rules-hint", TRUE,
992 "headers-visible", FALSE,
993 NULL));
995 /* set the inuse treeview of compselect */
996 compselect->inusetreeview = GTK_TREE_VIEW (treeview);
998 create_inuse_tree_model (compselect);
1000 g_signal_connect (treeview,
1001 "row-activated",
1002 G_CALLBACK (tree_row_activated),
1003 compselect);
1005 /* Connect callback to selection */
1006 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
1007 gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
1008 g_signal_connect (selection,
1009 "changed",
1010 G_CALLBACK (compselect_callback_tree_selection_changed),
1011 compselect);
1013 /* Insert a column for symbol name */
1014 renderer = GTK_CELL_RENDERER (
1015 g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
1016 /* GtkCellRendererText */
1017 "editable", FALSE,
1018 NULL));
1019 column = GTK_TREE_VIEW_COLUMN (
1020 g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
1021 /* GtkTreeViewColumn */
1022 "title", _("Symbols"),
1023 NULL));
1024 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1025 gtk_tree_view_column_set_cell_data_func (column, renderer,
1026 inuse_treeview_set_cell_data,
1027 NULL, NULL);
1028 gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
1030 /* Add the treeview to the scrolled window */
1031 gtk_container_add (GTK_CONTAINER (scrolled_win), treeview);
1033 /* add the scrolled window for directories to the vertical box */
1034 gtk_box_pack_start (GTK_BOX (vbox), scrolled_win,
1035 TRUE, TRUE, 0);
1037 /* -- refresh button area -- */
1038 hbox = GTK_WIDGET (g_object_new (GTK_TYPE_HBOX,
1039 /* GtkBox */
1040 "homogeneous", FALSE,
1041 "spacing", 3,
1042 NULL));
1043 /* create the refresh button */
1044 button = GTK_WIDGET (g_object_new (GTK_TYPE_BUTTON,
1045 /* GtkWidget */
1046 "sensitive", TRUE,
1047 /* GtkButton */
1048 "relief", GTK_RELIEF_NONE,
1049 NULL));
1050 gtk_container_add (GTK_CONTAINER (button),
1051 gtk_image_new_from_stock (GTK_STOCK_REFRESH,
1052 GTK_ICON_SIZE_SMALL_TOOLBAR));
1053 /* add the refresh button to the horizontal box at the end */
1054 gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
1055 g_signal_connect (button,
1056 "clicked",
1057 G_CALLBACK (compselect_callback_refresh_library),
1058 compselect);
1060 /* add the refresh button area to the vertical box */
1061 gtk_box_pack_start (GTK_BOX (vbox), hbox,
1062 FALSE, FALSE, 0);
1064 return vbox;
1067 /*! \brief Creates the treeview for the "Library" view */
1068 static GtkWidget *
1069 create_lib_treeview (GschemCompselectDockable *compselect)
1071 GtkWidget *libtreeview, *vbox, *scrolled_win, *label,
1072 *hbox, *entry, *button;
1073 GtkTreeSelection *selection;
1074 GtkCellRenderer *renderer;
1075 GtkTreeViewColumn *column;
1077 /* -- library selection view -- */
1079 /* vertical box for component selection and search entry */
1080 vbox = GTK_WIDGET (g_object_new (GTK_TYPE_VBOX,
1081 /* GtkContainer */
1082 "border-width", 5,
1083 /* GtkBox */
1084 "homogeneous", FALSE,
1085 "spacing", 5,
1086 NULL));
1088 scrolled_win = GTK_WIDGET (
1089 g_object_new (GTK_TYPE_SCROLLED_WINDOW,
1090 /* GtkContainer */
1091 "border-width", 5,
1092 /* GtkScrolledWindow */
1093 "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
1094 "vscrollbar-policy", GTK_POLICY_ALWAYS,
1095 "shadow-type", GTK_SHADOW_ETCHED_IN,
1096 NULL));
1097 /* create the treeview */
1098 libtreeview = GTK_WIDGET (g_object_new (GTK_TYPE_TREE_VIEW,
1099 /* GtkTreeView */
1100 "rules-hint", TRUE,
1101 "headers-visible", FALSE,
1102 NULL));
1104 /* set directory/component treeview of compselect */
1105 compselect->libtreeview = GTK_TREE_VIEW (libtreeview);
1107 create_lib_tree_model (compselect);
1109 g_signal_connect (libtreeview,
1110 "row-activated",
1111 G_CALLBACK (tree_row_activated),
1112 compselect);
1114 /* connect callback to selection */
1115 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (libtreeview));
1116 gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
1117 g_signal_connect (selection,
1118 "changed",
1119 G_CALLBACK (compselect_callback_tree_selection_changed),
1120 compselect);
1122 /* insert a column to treeview for library/symbol name */
1123 renderer = GTK_CELL_RENDERER (
1124 g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
1125 /* GtkCellRendererText */
1126 "editable", FALSE,
1127 NULL));
1128 column = GTK_TREE_VIEW_COLUMN (
1129 g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
1130 /* GtkTreeViewColumn */
1131 "title", _("Symbols"),
1132 NULL));
1133 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1134 gtk_tree_view_column_add_attribute (column, renderer, "text",
1135 LIB_COLUMN_NAME);
1136 gtk_tree_view_append_column (GTK_TREE_VIEW (libtreeview), column);
1138 /* add the treeview to the scrolled window */
1139 gtk_container_add (GTK_CONTAINER (scrolled_win), libtreeview);
1141 /* add the scrolled window for directories to the vertical box */
1142 gtk_box_pack_start (GTK_BOX (vbox), scrolled_win,
1143 TRUE, TRUE, 0);
1146 /* -- filter area -- */
1147 hbox = GTK_WIDGET (g_object_new (GTK_TYPE_HBOX,
1148 /* GtkBox */
1149 "homogeneous", FALSE,
1150 "spacing", 3,
1151 NULL));
1153 /* create the entry label */
1154 label = GTK_WIDGET (g_object_new (GTK_TYPE_LABEL,
1155 /* GtkMisc */
1156 "xalign", 0.0,
1157 /* GtkLabel */
1158 "label", _("Filter:"),
1159 NULL));
1160 /* add the search label to the filter area */
1161 gtk_box_pack_start (GTK_BOX (hbox), label,
1162 FALSE, FALSE, 0);
1164 /* create the text entry for filter in components */
1165 entry = GTK_WIDGET (g_object_new (GTK_TYPE_ENTRY,
1166 /* GtkEntry */
1167 "text", "",
1168 NULL));
1169 gtk_widget_set_size_request (entry, 10, -1);
1170 g_signal_connect (entry,
1171 "changed",
1172 G_CALLBACK (compselect_callback_filter_entry_changed),
1173 compselect);
1175 /* add the filter entry to the filter area */
1176 gtk_box_pack_start (GTK_BOX (hbox), entry,
1177 TRUE, TRUE, 0);
1178 /* set filter entry of compselect */
1179 compselect->entry_filter = GTK_ENTRY (entry);
1180 /* and init the event source for component filter */
1181 compselect->filter_timeout = 0;
1183 /* create the erase button for filter entry */
1184 button = GTK_WIDGET (g_object_new (GTK_TYPE_BUTTON,
1185 /* GtkWidget */
1186 "sensitive", FALSE,
1187 /* GtkButton */
1188 "relief", GTK_RELIEF_NONE,
1189 NULL));
1191 gtk_container_add (GTK_CONTAINER (button),
1192 gtk_image_new_from_stock (GTK_STOCK_CLEAR,
1193 GTK_ICON_SIZE_SMALL_TOOLBAR));
1194 g_signal_connect (button,
1195 "clicked",
1196 G_CALLBACK (compselect_callback_filter_button_clicked),
1197 compselect);
1198 /* add the clear button to the filter area */
1199 gtk_box_pack_start (GTK_BOX (hbox), button,
1200 FALSE, FALSE, 0);
1201 /* set clear button of compselect */
1202 compselect->button_clear = GTK_BUTTON (button);
1204 /* create the refresh button */
1205 button = GTK_WIDGET (g_object_new (GTK_TYPE_BUTTON,
1206 /* GtkWidget */
1207 "sensitive", TRUE,
1208 /* GtkButton */
1209 "relief", GTK_RELIEF_NONE,
1210 NULL));
1211 gtk_container_add (GTK_CONTAINER (button),
1212 gtk_image_new_from_stock (GTK_STOCK_REFRESH,
1213 GTK_ICON_SIZE_SMALL_TOOLBAR));
1214 /* add the refresh button to the filter area */
1215 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
1216 g_signal_connect (button,
1217 "clicked",
1218 G_CALLBACK (compselect_callback_refresh_library),
1219 compselect);
1221 /* add the filter area to the vertical box */
1222 gtk_box_pack_start (GTK_BOX (vbox), hbox,
1223 FALSE, FALSE, 0);
1225 compselect->libtreeview = GTK_TREE_VIEW (libtreeview);
1227 return vbox;
1230 /*! \brief Creates the treeview widget for the attributes
1232 static GtkWidget*
1233 create_attributes_treeview (GschemCompselectDockable *compselect)
1235 GtkWidget *attrtreeview, *scrolled_win;
1236 GtkListStore *model;
1237 GtkCellRenderer *renderer;
1238 GtkTreeViewColumn *column;
1240 model = gtk_list_store_new (NUM_ATTRIBUTE_COLUMNS,
1241 G_TYPE_STRING, G_TYPE_STRING);
1243 attrtreeview = GTK_WIDGET (g_object_new (GTK_TYPE_TREE_VIEW,
1244 /* GtkTreeView */
1245 "model", model,
1246 "headers-visible", FALSE,
1247 "rules-hint", TRUE,
1248 "tooltip-column",
1249 ATTRIBUTE_COLUMN_VALUE,
1250 NULL));
1252 /* two columns for name and value of the attributes */
1253 renderer = GTK_CELL_RENDERER (g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
1254 "editable", FALSE,
1255 NULL));
1257 column = GTK_TREE_VIEW_COLUMN (g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
1258 "title", _("Name"),
1259 "resizable", TRUE,
1260 NULL));
1261 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1262 gtk_tree_view_column_add_attribute (column, renderer, "text",
1263 ATTRIBUTE_COLUMN_NAME);
1264 gtk_tree_view_append_column (GTK_TREE_VIEW (attrtreeview), column);
1266 column = GTK_TREE_VIEW_COLUMN (g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
1267 "title", _("Value"),
1268 "resizable", TRUE,
1269 NULL));
1270 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1271 gtk_tree_view_column_add_attribute (column, renderer, "text",
1272 ATTRIBUTE_COLUMN_VALUE);
1273 gtk_tree_view_append_column (GTK_TREE_VIEW (attrtreeview), column);
1275 scrolled_win = GTK_WIDGET (g_object_new (GTK_TYPE_SCROLLED_WINDOW,
1276 /* GtkScrolledWindow */
1277 "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
1278 "vscrollbar-policy", GTK_POLICY_AUTOMATIC,
1279 "shadow-type", GTK_SHADOW_ETCHED_IN,
1280 NULL));
1282 gtk_container_add (GTK_CONTAINER (scrolled_win), attrtreeview);
1284 compselect->attrtreeview = GTK_TREE_VIEW (attrtreeview);
1286 return scrolled_win;
1289 /*! \brief Create the combo box for behaviors.
1290 * \par Function Description
1291 * This function creates and returns a <B>GtkComboBox</B> for
1292 * selecting the behavior when a component is added to the sheet.
1294 static GtkWidget*
1295 create_behaviors_combo_box (void)
1297 GtkWidget *combobox;
1299 combobox = gtk_combo_box_new_text ();
1301 /* Note: order of items in menu is important */
1302 /* BEHAVIOR_REFERENCE */
1303 gtk_combo_box_append_text (GTK_COMBO_BOX (combobox),
1304 _("Reference symbol (default)"));
1305 /* BEHAVIOR_EMBED */
1306 gtk_combo_box_append_text (GTK_COMBO_BOX (combobox),
1307 _("Embed symbol in schematic"));
1308 /* BEHAVIOR_INCLUDE */
1309 gtk_combo_box_append_text (GTK_COMBO_BOX (combobox),
1310 _("Include symbol as individual objects"));
1312 gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), 0);
1314 return combobox;
1317 GType
1318 gschem_compselect_dockable_get_type ()
1320 static GType compselect_type = 0;
1322 if (!compselect_type) {
1323 static const GTypeInfo compselect_info = {
1324 sizeof (GschemCompselectDockableClass),
1325 NULL, /* base_init */
1326 NULL, /* base_finalize */
1327 (GClassInitFunc) compselect_class_init,
1328 NULL, /* class_finalize */
1329 NULL, /* class_data */
1330 sizeof (GschemCompselectDockable),
1331 0, /* n_preallocs */
1332 NULL /* instance_init */
1335 compselect_type = g_type_register_static (GSCHEM_TYPE_DOCKABLE,
1336 "GschemCompselectDockable",
1337 &compselect_info, 0);
1340 return compselect_type;
1344 /*! \brief GschemDockable "save_internal_geometry" class method handler
1346 * \par Function Description
1347 * Save the dockable's current internal geometry.
1349 * \param [in] dockable The GschemCompselectDockable to save the geometry of.
1350 * \param [in] key_file The GKeyFile to save the geometry data to.
1351 * \param [in] group_name The group name in the key file to store the data under.
1353 static void
1354 compselect_save_internal_geometry (GschemDockable *dockable,
1355 EdaConfig *cfg,
1356 gchar *group_name)
1358 GschemCompselectDockable *compselect = GSCHEM_COMPSELECT_DOCKABLE (dockable);
1359 gint position;
1361 position = gtk_paned_get_position (GTK_PANED (compselect->hpaned));
1362 eda_config_set_int (cfg, group_name, "hpaned", position);
1364 position = gtk_paned_get_position (GTK_PANED (compselect->vpaned));
1365 eda_config_set_int (cfg, group_name, "vpaned", position);
1367 position = gtk_notebook_get_current_page (compselect->viewtabs);
1368 eda_config_set_int (cfg, group_name, "source-tab", position);
1370 eda_config_set_boolean (cfg, group_name, "preview-expanded",
1371 gtk_expander_get_expanded (
1372 GTK_EXPANDER (compselect->preview_expander)));
1374 eda_config_set_boolean (cfg, group_name, "attribs-expanded",
1375 gtk_expander_get_expanded (
1376 GTK_EXPANDER (compselect->attribs_expander)));
1380 /*! \brief GschemDockable "restore_internal_geometry" class method handler
1382 * \par Function Description
1383 * Restore the dockable's current internal geometry.
1385 * \param [in] dockable The GschemCompselectDockable to restore the geometry of.
1386 * \param [in] key_file The GKeyFile to save the geometry data to.
1387 * \param [in] group_name The group name in the key file to store the data under.
1389 static void
1390 compselect_restore_internal_geometry (GschemDockable *dockable,
1391 EdaConfig *cfg,
1392 gchar *group_name)
1394 GschemCompselectDockable *compselect = GSCHEM_COMPSELECT_DOCKABLE (dockable);
1395 gint position;
1396 gboolean expanded;
1397 GError *error = NULL;
1399 position = eda_config_get_int (cfg, group_name, "hpaned", NULL);
1400 if (position == 0)
1401 position = 300;
1402 gtk_paned_set_position (GTK_PANED (compselect->hpaned), position);
1404 position = eda_config_get_int (cfg, group_name, "vpaned", NULL);
1405 if (position != 0)
1406 gtk_paned_set_position (GTK_PANED (compselect->vpaned), position);
1408 position = eda_config_get_int (cfg, group_name, "source-tab", &error);
1409 if (error != NULL) {
1410 position = 1;
1411 g_clear_error (&error);
1413 gtk_notebook_set_current_page (compselect->viewtabs, position);
1415 expanded = eda_config_get_boolean (cfg, group_name, "preview-expanded", &error);
1416 if (error != NULL) {
1417 expanded = TRUE;
1418 g_clear_error (&error);
1420 gtk_expander_set_expanded (GTK_EXPANDER (compselect->preview_expander),
1421 expanded);
1423 expanded = eda_config_get_boolean (cfg, group_name, "attribs-expanded", &error);
1424 if (error != NULL) {
1425 expanded = TRUE;
1426 g_clear_error (&error);
1428 gtk_expander_set_expanded (GTK_EXPANDER (compselect->attribs_expander),
1429 expanded);
1433 static void
1434 compselect_class_init (GschemCompselectDockableClass *klass)
1436 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1437 GschemDockableClass *gschem_dockable_class = GSCHEM_DOCKABLE_CLASS (klass);
1439 gschem_dockable_class->create_widget = compselect_create_widget;
1440 gschem_dockable_class->post_present = compselect_post_present;
1441 gschem_dockable_class->cancel = compselect_cancel;
1443 gschem_dockable_class->save_internal_geometry =
1444 compselect_save_internal_geometry;
1445 gschem_dockable_class->restore_internal_geometry =
1446 compselect_restore_internal_geometry;
1448 gobject_class->constructed = compselect_constructed;
1449 gobject_class->dispose = compselect_dispose;
1450 gobject_class->finalize = compselect_finalize;
1452 compselect_parent_class = g_type_class_peek_parent (klass);
1455 /*! \brief Make <widget> the child of <container>, removing other
1456 * parent-child relations if necessary.
1458 static void
1459 reparent (GtkWidget *container, GtkWidget *widget)
1461 GtkWidget *old_child = gtk_bin_get_child (GTK_BIN (container));
1462 GtkWidget *old_parent = widget ? gtk_widget_get_parent (widget) : NULL;
1463 if (old_child == widget && old_parent == container)
1464 return;
1466 if (old_child != NULL)
1467 gtk_container_remove (GTK_CONTAINER (container), old_child);
1468 if (old_parent != NULL)
1469 gtk_container_remove (GTK_CONTAINER (old_parent), widget);
1471 if (container != NULL && widget != NULL)
1472 gtk_container_add (GTK_CONTAINER (container), widget);
1475 /*! \brief Make <top> and <bottom> the children of <container>,
1476 * removing other parent-child relations if necessary.
1478 static void
1479 reparent2 (GtkWidget *container, GtkWidget *top, GtkWidget *bottom)
1481 GtkWidget *top_parent = gtk_widget_get_parent (top);
1482 GtkWidget *bottom_parent = gtk_widget_get_parent (bottom);
1483 if (top_parent == container && bottom_parent == container)
1484 return;
1486 if (top_parent != NULL)
1487 gtk_container_remove (GTK_CONTAINER (top_parent), top);
1488 if (bottom_parent != NULL)
1489 gtk_container_remove (GTK_CONTAINER (bottom_parent), bottom);
1491 do {
1492 GList *old_children =
1493 gtk_container_get_children (GTK_CONTAINER (container));
1494 if (old_children == NULL)
1495 break;
1496 gtk_container_remove (GTK_CONTAINER (container), old_children->data);
1497 } while (1);
1499 if (GTK_IS_PANED (container)) {
1500 gtk_paned_pack1 (GTK_PANED (container), top, TRUE, FALSE);
1501 gtk_paned_pack2 (GTK_PANED (container), bottom, FALSE, FALSE);
1502 } else if (GTK_IS_BOX (container)) {
1503 gtk_box_pack_start (GTK_BOX (container), top, TRUE, TRUE, 0);
1504 gtk_box_pack_start (GTK_BOX (container), bottom, FALSE, FALSE, 0);
1505 } else
1506 g_assert_not_reached ();
1509 /*! \brief Put the appropriate widget hierarchy for vertical layout in place.
1511 static void
1512 compselect_update_vertical_hierarchy (GschemCompselectDockable *compselect)
1514 gboolean expanded0 = gtk_expander_get_expanded (
1515 GTK_EXPANDER (compselect->preview_expander));
1516 gboolean expanded1 = gtk_expander_get_expanded (
1517 GTK_EXPANDER (compselect->attribs_expander));
1519 GtkWidget *container0 = expanded0 ? compselect->preview_paned
1520 : compselect->preview_box;
1521 GtkWidget *container1 = expanded1 ? compselect->attribs_paned
1522 : compselect->attribs_box;
1524 reparent (compselect->preview_expander,
1525 expanded0 ? compselect->preview_content : NULL);
1526 reparent (compselect->attribs_expander,
1527 expanded1 ? compselect->attribs_content : NULL);
1529 reparent2 (container0, compselect->vbox, compselect->preview_expander);
1530 reparent2 (container1, container0, compselect->attribs_expander);
1531 reparent (compselect->top, container1);
1534 /*! \brief Set the current layout for this dockable.
1536 * Tiled layout is the classical behavior of gschem: the tree view is
1537 * on the left, preview and attributes are on the right.
1539 * Vertical layout is used for tall aspect ratios: the tree view is at
1540 * the top, preview and attributes are below and expandable.
1542 static void
1543 compselect_set_tiled (GschemCompselectDockable *compselect, gboolean tiled)
1545 GtkWidget *top_widget = gtk_bin_get_child (GTK_BIN (compselect->top));
1547 if (tiled) {
1548 if (top_widget == compselect->vbox)
1549 return;
1551 gtk_container_set_border_width (
1552 GTK_CONTAINER (compselect->preview_content), 5);
1553 gtk_container_set_border_width (
1554 GTK_CONTAINER (compselect->attribs_content), 5);
1556 reparent (compselect->preview_frame, compselect->preview_content);
1557 reparent (compselect->attribs_frame, compselect->attribs_content);
1558 reparent (compselect->top, compselect->vbox);
1559 gtk_widget_show (compselect->vpaned);
1561 } else {
1562 if (top_widget == compselect->attribs_paned ||
1563 top_widget == compselect->attribs_box)
1564 return;
1566 gtk_container_set_border_width (
1567 GTK_CONTAINER (compselect->preview_content), 0);
1568 gtk_container_set_border_width (
1569 GTK_CONTAINER (compselect->attribs_content), 0);
1571 gtk_widget_hide (compselect->vpaned);
1572 compselect_update_vertical_hierarchy (compselect);
1576 /*! \brief Return whether tiled layout is in effect.
1578 static gboolean
1579 compselect_is_tiled (GschemCompselectDockable *compselect)
1581 GtkWidget *top_widget = gtk_bin_get_child (GTK_BIN (compselect->top));
1583 return top_widget != compselect->attribs_paned &&
1584 top_widget != compselect->attribs_box;
1587 /*! \brief Remove prelight state from an expander.
1589 * Expanders are prelit (painted in a shaded color) while the pointer
1590 * hovers over them. This prelight state is not correctly removed
1591 * when the widget hierarchy changes. This function synthesizes a
1592 * leave event to un-prelight the expander explicitly.
1594 static void
1595 compselect_unprelight_expander (GschemCompselectDockable *compselect,
1596 GtkWidget *expander)
1598 GdkWindow *window = NULL;
1599 if (expander == compselect->preview_expander)
1600 window = compselect->preview_expander_event_window;
1601 if (expander == compselect->attribs_expander)
1602 window = compselect->attribs_expander_event_window;
1604 if (window == NULL)
1605 return;
1607 GdkEvent *event = gdk_event_new (GDK_LEAVE_NOTIFY);
1608 event->crossing.window = g_object_ref (window);
1609 event->crossing.send_event = TRUE;
1610 event->crossing.subwindow = g_object_ref (window);
1611 event->crossing.time = GDK_CURRENT_TIME;
1612 event->crossing.x = 0;
1613 event->crossing.y = 0;
1614 event->crossing.x_root = 0;
1615 event->crossing.y_root = 0;
1616 event->crossing.mode = GDK_CROSSING_STATE_CHANGED;
1617 event->crossing.detail = GDK_NOTIFY_UNKNOWN;
1618 event->crossing.focus = FALSE;
1619 event->crossing.state = 0;
1621 gtk_widget_event (expander, event);
1622 gdk_event_free(event);
1625 /*! \brief Callback function: Size of the whole dockable changed.
1627 * Changes from and to tiled layout when the aspect ratio of the
1628 * dockable becomes wider and taller than 1:2, respectively.
1630 static void
1631 compselect_top_size_allocate (GtkWidget *widget,
1632 GdkRectangle *allocation,
1633 gpointer user_data)
1635 compselect_set_tiled (GSCHEM_COMPSELECT_DOCKABLE (user_data),
1636 allocation->width * 2 > allocation->height);
1639 /*! \brief Callback function: Pointer hovers over expander.
1641 * In order to synthesize leave notify events for the expanders, we
1642 * need pointers to their event windows. These can't be accessed
1643 * directly, so as a workaround, wait for an enter notify event and
1644 * store the pointers for later.
1646 static gboolean
1647 compselect_expander_enter_notify_event (GtkWidget *widget,
1648 GdkEvent *event,
1649 gpointer user_data)
1651 GschemCompselectDockable *compselect = GSCHEM_COMPSELECT_DOCKABLE (user_data);
1653 if (widget == compselect->preview_expander)
1654 compselect->preview_expander_event_window = event->crossing.window;
1655 if (widget == compselect->attribs_expander)
1656 compselect->attribs_expander_event_window = event->crossing.window;
1658 return FALSE;
1661 /*! \brief Callback function: Expander activated.
1663 * Update the widget hierarchy and prelight state of the expanders.
1665 static void
1666 compselect_expander_notify_expanded (GObject *expander,
1667 GParamSpec *pspec,
1668 gpointer user_data)
1670 GschemCompselectDockable *compselect = GSCHEM_COMPSELECT_DOCKABLE (user_data);
1672 if (!compselect_is_tiled (compselect)) {
1673 compselect_unprelight_expander (compselect, GTK_WIDGET (expander));
1674 compselect_update_vertical_hierarchy (compselect);
1678 /*! \brief Callback function: Size of preview/attribute area changed.
1680 * This function does two things:
1682 * - When the content widgets are assigned a size for the first time,
1683 * it calculates the difference between the stored and actual size
1684 * and moves the corresponding vpaned's handle accordingly. (This
1685 * is necessary because there is no direct way to set the handle
1686 * position relative to the far end of the paned.)
1688 * - On subsequent size changes, it updates the stored size for that
1689 * widget.
1691 static void
1692 compselect_content_size_allocate (GtkWidget *widget,
1693 GdkRectangle *allocation,
1694 gpointer user_data)
1696 GschemCompselectDockable *compselect = GSCHEM_COMPSELECT_DOCKABLE (user_data);
1697 if (compselect_is_tiled (compselect))
1698 return;
1700 gboolean *size_allocated;
1701 const gchar *key;
1702 gint default_height;
1703 GtkPaned *vpaned;
1705 if (widget == compselect->preview_content) {
1706 size_allocated = &compselect->preview_size_allocated;
1707 key = "preview-height";
1708 default_height = 150;
1709 vpaned = GTK_PANED (compselect->preview_paned);
1710 } else if (widget == compselect->attribs_content) {
1711 size_allocated = &compselect->attribs_size_allocated;
1712 key = "attribs-height";
1713 default_height = 150;
1714 vpaned = GTK_PANED (compselect->attribs_paned);
1715 } else
1716 g_assert_not_reached ();
1718 if (*size_allocated == FALSE) {
1719 gint height = eda_config_get_int (eda_config_get_user_context (),
1720 GSCHEM_DOCKABLE (compselect)->group_name,
1721 key, NULL);
1722 if (height <= 0)
1723 height = default_height;
1725 gtk_paned_set_position (vpaned,
1726 gtk_paned_get_position (vpaned)
1727 - height
1728 + allocation->height);
1729 *size_allocated = TRUE;
1730 return;
1733 if (allocation->height > 0)
1734 eda_config_set_int (eda_config_get_user_context (),
1735 GSCHEM_DOCKABLE (compselect)->group_name,
1736 key, allocation->height);
1739 static GtkWidget *
1740 compselect_create_widget (GschemDockable *dockable)
1742 GschemCompselectDockable *compselect = GSCHEM_COMPSELECT_DOCKABLE (dockable);
1743 GtkWidget *inuseview, *libview, *notebook;
1744 GtkWidget *combobox, *preview;
1746 /* notebook for library and inuse views */
1747 inuseview = create_inuse_treeview (compselect);
1748 libview = create_lib_treeview (compselect);
1749 notebook = gtk_notebook_new ();
1750 compselect->viewtabs = GTK_NOTEBOOK (notebook);
1751 gtk_notebook_append_page (GTK_NOTEBOOK (notebook), inuseview,
1752 gtk_label_new (_("In Use")));
1753 gtk_notebook_append_page (GTK_NOTEBOOK (notebook), libview,
1754 gtk_label_new (_("Libraries")));
1756 /* vertical pane containing preview and attributes */
1757 compselect->preview_frame = gtk_frame_new (_("Preview"));
1758 compselect->attribs_frame = gtk_frame_new (_("Attributes"));
1759 compselect->vpaned = gtk_vpaned_new ();
1760 gtk_widget_set_size_request (compselect->vpaned, 25, -1);
1761 gtk_paned_pack1 (GTK_PANED (compselect->vpaned), compselect->preview_frame,
1762 FALSE, FALSE);
1763 gtk_paned_pack2 (GTK_PANED (compselect->vpaned), compselect->attribs_frame,
1764 FALSE, FALSE);
1766 /* horizontal pane containing selection and preview/attributes */
1767 compselect->hpaned = gtk_hpaned_new ();
1768 gtk_paned_pack1 (GTK_PANED (compselect->hpaned), notebook, TRUE, FALSE);
1769 gtk_paned_pack2 (GTK_PANED (compselect->hpaned), compselect->vpaned,
1770 TRUE, FALSE);
1772 /* behavior combo box at the bottom */
1773 combobox = create_behaviors_combo_box ();
1774 compselect->combobox_behaviors = GTK_COMBO_BOX (combobox);
1775 g_signal_connect (combobox, "changed",
1776 G_CALLBACK (compselect_callback_behavior_changed),
1777 compselect);
1779 /* top-level vbox */
1780 compselect->vbox = gtk_vbox_new (FALSE, DIALOG_V_SPACING);
1781 gtk_box_pack_start (GTK_BOX (compselect->vbox), compselect->hpaned,
1782 TRUE, TRUE, 0);
1783 gtk_box_pack_start (GTK_BOX (compselect->vbox), combobox, FALSE, FALSE, 0);
1784 gtk_widget_show_all (compselect->vbox);
1785 g_object_ref (compselect->vbox);
1788 /* preview area */
1789 preview = gschem_preview_new ();
1790 compselect->preview = GSCHEM_PREVIEW (preview);
1791 gtk_widget_set_size_request (preview, 160, 120);
1793 compselect->preview_content = gtk_alignment_new (.5, .5, 1., 1.);
1794 gtk_widget_set_size_request (compselect->preview_content, 0, 15);
1795 g_signal_connect (compselect->preview_content, "size-allocate",
1796 G_CALLBACK (compselect_content_size_allocate), compselect);
1797 gtk_container_add (GTK_CONTAINER (compselect->preview_content), preview);
1798 gtk_widget_show_all (compselect->preview_content);
1799 g_object_ref (compselect->preview_content);
1801 compselect->preview_box = gtk_vbox_new (FALSE, 3);
1802 gtk_widget_show (compselect->preview_box);
1803 g_object_ref (compselect->preview_box);
1805 compselect->preview_paned = gtk_vpaned_new ();
1806 gtk_widget_show (compselect->preview_paned);
1807 g_object_ref (compselect->preview_paned);
1809 compselect->preview_expander = gtk_expander_new_with_mnemonic (_("Preview"));
1810 gtk_expander_set_spacing (GTK_EXPANDER (compselect->preview_expander), 3);
1811 g_signal_connect (compselect->preview_expander, "enter-notify-event",
1812 G_CALLBACK (compselect_expander_enter_notify_event),
1813 compselect);
1814 g_signal_connect (compselect->preview_expander, "notify::expanded",
1815 G_CALLBACK (compselect_expander_notify_expanded),
1816 compselect);
1817 gtk_widget_show (compselect->preview_expander);
1818 g_object_ref (compselect->preview_expander);
1820 compselect->preview_expander_event_window = NULL;
1821 compselect->preview_size_allocated = FALSE;
1824 /* attributes area */
1825 compselect->attribs_content = create_attributes_treeview (compselect);
1826 gtk_widget_set_size_request (compselect->attribs_content, -1, 20);
1827 g_signal_connect (compselect->attribs_content, "size-allocate",
1828 G_CALLBACK (compselect_content_size_allocate), compselect);
1829 gtk_widget_show_all (compselect->attribs_content);
1830 g_object_ref (compselect->attribs_content);
1832 compselect->attribs_box = gtk_vbox_new (FALSE, 3);
1833 gtk_widget_show (compselect->attribs_box);
1834 g_object_ref (compselect->attribs_box);
1836 compselect->attribs_paned = gtk_vpaned_new ();
1837 gtk_widget_show (compselect->attribs_paned);
1838 g_object_ref (compselect->attribs_paned);
1840 compselect->attribs_expander =
1841 gtk_expander_new_with_mnemonic (_("Attributes"));
1842 gtk_expander_set_spacing (GTK_EXPANDER (compselect->attribs_expander), 3);
1843 g_signal_connect (compselect->attribs_expander, "enter-notify-event",
1844 G_CALLBACK (compselect_expander_enter_notify_event),
1845 compselect);
1846 g_signal_connect (compselect->attribs_expander, "notify::expanded",
1847 G_CALLBACK (compselect_expander_notify_expanded),
1848 compselect);
1849 gtk_widget_show (compselect->attribs_expander);
1850 g_object_ref (compselect->attribs_expander);
1852 compselect->attribs_expander_event_window = NULL;
1853 compselect->attribs_size_allocated = FALSE;
1856 /* top-level container widget (will contain one of the other widgets
1857 according to the selected layout) */
1858 compselect->top = gtk_alignment_new (.5, .5, 1., 1.);
1859 gtk_container_set_border_width (GTK_CONTAINER (compselect->top),
1860 DIALOG_BORDER_SPACING);
1861 g_signal_connect (compselect->top, "size-allocate",
1862 G_CALLBACK (compselect_top_size_allocate), compselect);
1863 gtk_widget_show (compselect->top);
1864 return compselect->top;
1867 static void
1868 compselect_constructed (GObject *object)
1870 G_OBJECT_CLASS (compselect_parent_class)->constructed (object);
1872 s_clib_add_update_callback (library_updated, object);
1875 static void
1876 compselect_dispose (GObject *object)
1878 GschemCompselectDockable *compselect = GSCHEM_COMPSELECT_DOCKABLE (object);
1880 s_clib_remove_update_callback (object);
1882 g_clear_object (&compselect->vbox);
1884 g_clear_object (&compselect->preview_content);
1885 g_clear_object (&compselect->preview_box);
1886 g_clear_object (&compselect->preview_paned);
1887 g_clear_object (&compselect->preview_expander);
1889 g_clear_object (&compselect->attribs_content);
1890 g_clear_object (&compselect->attribs_box);
1891 g_clear_object (&compselect->attribs_paned);
1892 g_clear_object (&compselect->attribs_expander);
1894 G_OBJECT_CLASS (compselect_parent_class)->dispose (object);
1897 static void
1898 compselect_finalize (GObject *object)
1900 GschemCompselectDockable *compselect = GSCHEM_COMPSELECT_DOCKABLE (object);
1902 g_free (compselect->selected_filename);
1904 if (compselect->filter_timeout != 0) {
1905 g_source_remove (compselect->filter_timeout);
1906 compselect->filter_timeout = 0;
1909 G_OBJECT_CLASS (compselect_parent_class)->finalize (object);