missing NULL terminator in set_config_x
[geda-gaf.git] / gschem / src / gschem_pagesel_dockable.c
blob23585c1335773e3938da0bc31c81adabd0577260
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 #ifdef HAVE_STDLIB_H
24 #include <stdlib.h>
25 #endif
26 #ifdef HAVE_STRING_H
27 #include <string.h>
28 #endif
30 #include "gschem.h"
31 #include "../include/gschem_pagesel_dockable.h"
32 #include "actions.decl.x"
35 static void pagesel_popup_menu (GschemPageselDockable *pagesel,
36 GdkEventButton *event);
37 static void pagesel_update (GschemPageselDockable *pagesel);
39 static void pagesel_class_init (GschemPageselDockableClass *class);
40 static GtkWidget *pagesel_create_widget (GschemDockable *dockable);
44 /*! \brief Update the list and status of <B>toplevel</B>'s pages.
45 * \par Function Description
46 * Updates the list and status of <B>toplevel</B>\'s pages if the page
47 * manager dialog is opened.
49 * \param [in] w_current The GschemToplevel object to update.
51 void x_pagesel_update (GschemToplevel *w_current)
53 if (w_current->pagesel_dockable != NULL)
54 pagesel_update (GSCHEM_PAGESEL_DOCKABLE (w_current->pagesel_dockable));
56 i_update_filename (w_current);
59 enum {
60 COLUMN_PAGE,
61 COLUMN_BASENAME,
62 COLUMN_PATH,
63 COLUMN_CHANGED,
64 NUM_COLUMNS
68 /*! \todo Finish function documentation!!!
69 * \brief
70 * \par Function Description
73 static void pagesel_callback_selection_changed (GtkTreeSelection *selection,
74 gpointer user_data)
76 GtkTreeModel *model;
77 GtkTreeIter iter;
78 GschemPageselDockable *pagesel = GSCHEM_PAGESEL_DOCKABLE (user_data);
79 GschemToplevel *w_current;
80 PAGE *page;
82 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
83 return;
86 w_current = GSCHEM_DOCKABLE (pagesel)->w_current;
87 gtk_tree_model_get (model, &iter,
88 COLUMN_PAGE, &page,
89 -1);
91 x_window_set_current_page (w_current, page);
94 static void pagesel_callback_row_activated (GtkTreeView *tree_view,
95 GtkTreePath *path,
96 GtkTreeViewColumn *column,
97 gpointer user_data)
99 GschemDockable *dockable = GSCHEM_DOCKABLE (user_data);
100 GschemToplevel *w_current = dockable->w_current;
101 GtkTreeModel *model;
102 GtkTreeIter iter;
103 PAGE *page = NULL;
105 model = gtk_tree_view_get_model (tree_view);
106 gtk_tree_model_get_iter (model, &iter, path);
107 gtk_tree_model_get (model, &iter, COLUMN_PAGE, &page, -1);
109 x_window_set_current_page (w_current, page);
111 if (gschem_dockable_get_state (dockable) == GSCHEM_DOCKABLE_STATE_DIALOG)
112 gschem_dockable_hide (dockable);
114 x_window_present (w_current);
115 gtk_widget_grab_focus (w_current->drawing_area);
118 /*! \todo Finish function documentation!!!
119 * \brief
120 * \par Function Description
123 static gboolean pagesel_callback_button_pressed (GtkWidget *widget,
124 GdkEventButton *event,
125 gpointer user_data)
127 GschemPageselDockable *pagesel = GSCHEM_PAGESEL_DOCKABLE (user_data);
128 gboolean ret = FALSE;
130 if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
131 pagesel_popup_menu (pagesel, event);
132 ret = TRUE;
135 return ret;
138 /*! \todo Finish function documentation!!!
139 * \brief
140 * \par Function Description
143 static gboolean pagesel_callback_popup_menu (GtkWidget *widget,
144 gpointer user_data)
146 GschemPageselDockable *pagesel = GSCHEM_PAGESEL_DOCKABLE (user_data);
148 pagesel_popup_menu (pagesel, NULL);
150 return TRUE;
153 static void pagesel_callback_popup (GtkMenuItem *menuitem,
154 gpointer user_data)
156 gschem_action_activate (g_object_get_data (G_OBJECT (menuitem), "action"),
157 GSCHEM_DOCKABLE (user_data)->w_current);
160 /*! \brief Popup context-sensitive menu.
161 * \par Function Description
162 * Pops up a context-sensitive menu.
164 * <B>event</B> can be NULL if the popup is triggered by a key binding
165 * instead of a mouse click.
167 * \param [in] pagesel The GschemPageselDockable object.
168 * \param [in] event Mouse click event info.
170 static void pagesel_popup_menu (GschemPageselDockable *pagesel,
171 GdkEventButton *event)
173 GtkTreePath *path;
174 GtkWidget *menu, *menuitem;
175 struct menuitem_t {
176 gchar *label;
177 GschemAction *action;
179 struct menuitem_t menuitems[] = {
180 { N_("New Page"), action_file_new },
181 { N_("Open Page..."), action_file_open },
182 { "-", NULL },
183 { N_("Save Page"), action_file_save },
184 { N_("Close Page"), action_page_close },
185 { NULL, NULL } };
186 struct menuitem_t *tmp;
188 if (event != NULL &&
189 gtk_tree_view_get_path_at_pos (pagesel->treeview,
190 (gint)event->x,
191 (gint)event->y,
192 &path, NULL, NULL, NULL)) {
193 GtkTreeSelection *selection;
194 selection = gtk_tree_view_get_selection (pagesel->treeview);
195 gtk_tree_selection_unselect_all (selection);
196 gtk_tree_selection_select_path (selection, path);
197 gtk_tree_path_free (path);
200 /* create the context menu */
201 menu = gtk_menu_new();
203 menuitem = gtk_image_menu_item_new_with_mnemonic (_("_Refresh List"));
204 gtk_image_menu_item_set_image (
205 GTK_IMAGE_MENU_ITEM (menuitem),
206 gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_MENU));
207 g_signal_connect_swapped (menuitem, "activate",
208 G_CALLBACK (pagesel_update), pagesel);
209 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
211 menuitem = gtk_separator_menu_item_new ();
212 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
214 for (tmp = menuitems; tmp->label != NULL; tmp++) {
215 if (strcmp (tmp->label, "-") == 0) {
216 menuitem = gtk_separator_menu_item_new ();
217 } else {
218 menuitem = gtk_menu_item_new_with_label (_(tmp->label));
219 g_object_set_data (G_OBJECT (menuitem), "action", tmp->action);
220 g_signal_connect (menuitem,
221 "activate",
222 G_CALLBACK (pagesel_callback_popup),
223 pagesel);
225 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
227 gtk_widget_show_all (menu);
228 /* make menu a popup menu */
229 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
230 (event != NULL) ? event->button : 0,
231 gdk_event_get_time ((GdkEvent*)event));
236 /*! \brief Handler for the notify::gschem-toplevel signal of GschemDialog
238 * \par Function Description
240 * When the gschem-toplevel property is set on the parent GschemDialog,
241 * we should update the pagesel dialog.
243 * \param [in] gobject the object which received the signal.
244 * \param [in] arg1 the GParamSpec of the property which changed
245 * \param [in] user_data user data set when the signal handler was connected.
247 static void notify_gschem_toplevel_cb (GObject *gobject,
248 GParamSpec *arg1,
249 gpointer user_data)
251 GschemPageselDockable *pagesel = GSCHEM_PAGESEL_DOCKABLE (gobject);
253 pagesel_update( pagesel );
257 /*! \todo Finish function documentation!!!
258 * \brief
259 * \par Function Description
262 GType gschem_pagesel_dockable_get_type()
264 static GType pagesel_type = 0;
266 if (!pagesel_type) {
267 static const GTypeInfo pagesel_info = {
268 sizeof (GschemPageselDockableClass),
269 NULL, /* base_init */
270 NULL, /* base_finalize */
271 (GClassInitFunc) pagesel_class_init,
272 NULL, /* class_finalize */
273 NULL, /* class_data */
274 sizeof (GschemPageselDockable),
275 0, /* n_preallocs */
276 NULL /* instance_init */
279 pagesel_type = g_type_register_static (GSCHEM_TYPE_DOCKABLE,
280 "GschemPageselDockable",
281 &pagesel_info, 0);
284 return pagesel_type;
287 /*! \todo Finish function documentation!!!
288 * \brief
289 * \par Function Description
292 static void pagesel_class_init (GschemPageselDockableClass *class)
294 GSCHEM_DOCKABLE_CLASS (class)->create_widget = pagesel_create_widget;
297 /*! \todo Finish function documentation!!!
298 * \brief
299 * \par Function Description
302 static GtkWidget *pagesel_create_widget (GschemDockable *dockable)
304 GschemPageselDockable *pagesel = GSCHEM_PAGESEL_DOCKABLE (dockable);
305 GtkWidget *scrolled_win, *treeview;
306 GtkTreeModel *store;
307 GtkCellRenderer *renderer;
308 GtkTreeViewColumn *column;
309 GtkTreeSelection *selection;
311 /* create the model for the treeview */
312 store = (GtkTreeModel*)gtk_tree_store_new (NUM_COLUMNS,
313 G_TYPE_POINTER, /* page */
314 G_TYPE_STRING, /* basename */
315 G_TYPE_STRING, /* path */
316 G_TYPE_BOOLEAN); /* changed */
318 /* create a scrolled window for the treeview */
319 scrolled_win = GTK_WIDGET (
320 g_object_new (GTK_TYPE_SCROLLED_WINDOW,
321 /* GtkContainer */
322 "border-width", 0,
323 /* GtkScrolledWindow */
324 "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
325 "vscrollbar-policy", GTK_POLICY_ALWAYS,
326 NULL));
327 /* create the treeview */
328 treeview = GTK_WIDGET (g_object_new (GTK_TYPE_TREE_VIEW,
329 /* GtkTreeView */
330 "model", store,
331 "tooltip-column", COLUMN_PATH,
332 NULL));
333 g_signal_connect (treeview,
334 "row-activated",
335 G_CALLBACK (pagesel_callback_row_activated),
336 pagesel);
337 g_signal_connect (treeview,
338 "button-press-event",
339 G_CALLBACK (pagesel_callback_button_pressed),
340 pagesel);
341 g_signal_connect (treeview,
342 "popup-menu",
343 G_CALLBACK (pagesel_callback_popup_menu),
344 pagesel);
345 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
346 gtk_tree_selection_set_mode (selection,
347 GTK_SELECTION_SINGLE);
348 g_signal_connect (selection,
349 "changed",
350 G_CALLBACK (pagesel_callback_selection_changed),
351 pagesel);
352 /* - first column: changed */
353 renderer = GTK_CELL_RENDERER (
354 g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
355 /* GtkCellRendererText */
356 "editable", FALSE,
357 "text", "*",
358 /* GtkCellRenderer */
359 "xalign", .5,
360 NULL));
361 column = GTK_TREE_VIEW_COLUMN (
362 g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
363 /* GtkTreeViewColumn */
364 "title", _("Chg"),
365 NULL));
366 gtk_tree_view_column_pack_start (column, renderer, TRUE);
367 gtk_tree_view_column_add_attribute (column, renderer, "visible", COLUMN_CHANGED);
368 gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
369 /* - second column: page name */
370 renderer = GTK_CELL_RENDERER (
371 g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
372 /* GtkCellRendererText */
373 "editable", FALSE,
374 NULL));
375 column = GTK_TREE_VIEW_COLUMN (
376 g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
377 /* GtkTreeViewColumn */
378 "title", _("Filename"),
379 NULL));
380 gtk_tree_view_column_pack_start (column, renderer, TRUE);
381 gtk_tree_view_column_add_attribute (column, renderer, "text", COLUMN_BASENAME);
382 gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
383 gtk_tree_view_set_expander_column (GTK_TREE_VIEW (treeview), column);
385 /* add the treeview to the scrolled window */
386 gtk_container_add (GTK_CONTAINER (scrolled_win), treeview);
387 /* set treeview of pagesel */
388 pagesel->treeview = GTK_TREE_VIEW (treeview);
390 /* add the scrolled window to the dialog vbox */
391 gtk_widget_show_all (scrolled_win);
393 g_signal_connect( pagesel, "notify::gschem-toplevel",
394 G_CALLBACK( notify_gschem_toplevel_cb ), NULL );
396 pagesel_update (pagesel);
397 return scrolled_win;
401 /*! \brief Update tree model of <B>pagesel</B>'s treeview.
402 * \par Function Description
403 * Updates the tree model of <B>pagesel</B>\'s treeview.
405 * Right now, each time it is called, it rebuilds all the model from the
406 * list of pages passed in.
407 * It is a recursive function to populate the tree store
409 * \param [in] model GtkTreeModel to update.
410 * \param [in] parent GtkTreeIter pointer to tree root.
411 * \param [in] pages GedaPageList of pages for this toplevel.
412 * \param [in] page The PAGE object to update tree model from.
414 static void add_page (GtkTreeModel *model, GtkTreeIter *parent,
415 GedaPageList *pages, PAGE *page)
417 GtkTreeIter iter;
418 PAGE *p_current;
419 GList *p_iter;
421 /* add the page to the store */
422 gtk_tree_store_append (GTK_TREE_STORE (model),
423 &iter,
424 parent);
426 if (page->is_untitled)
427 gtk_tree_store_set (GTK_TREE_STORE (model),
428 &iter,
429 COLUMN_PAGE, page,
430 COLUMN_BASENAME, _("(untitled page)"),
431 COLUMN_PATH, NULL,
432 COLUMN_CHANGED, page->CHANGED,
433 -1);
434 else {
435 /* get basename and abbreviated path */
436 gchar *basename = g_path_get_basename (page->page_filename);
437 gchar *path = NULL;
438 const gchar *homedir = g_get_home_dir ();
439 size_t hd_len = strlen (homedir);
440 if (hd_len > 1 && strncmp (page->page_filename, homedir, hd_len) == 0 &&
441 (homedir[hd_len - 1] == '/' || page->page_filename[hd_len] == '/'))
442 path = g_strdup_printf (
443 "~/%s", page->page_filename + hd_len + (homedir[hd_len - 1] != '/'));
445 gtk_tree_store_set (GTK_TREE_STORE (model),
446 &iter,
447 COLUMN_PAGE, page,
448 COLUMN_BASENAME, basename,
449 COLUMN_PATH, path != NULL ? path : page->page_filename,
450 COLUMN_CHANGED, page->CHANGED,
451 -1);
453 g_free (path);
454 g_free (basename);
457 /* search a page that has a up field == p_current->pid */
458 for ( p_iter = geda_list_get_glist( pages );
459 p_iter != NULL;
460 p_iter = g_list_next( p_iter ) ) {
462 p_current = (PAGE *)p_iter->data;
463 if (p_current->up == page->pid) {
464 add_page (model, &iter, pages, p_current);
469 /*! \todo Finish function documentation!!!
470 * \brief
471 * \par Function Description
472 * Recursive function to select the current page in the treeview
475 static void select_page(GtkTreeView *treeview,
476 GtkTreeIter *parent, PAGE *page)
478 GtkTreeModel *treemodel = gtk_tree_view_get_model (treeview);
479 GtkTreeIter iter;
480 PAGE *p_current;
482 if (!gtk_tree_model_iter_children (treemodel, &iter, parent)) {
483 return;
486 do {
487 gtk_tree_model_get (treemodel, &iter,
488 COLUMN_PAGE, &p_current,
489 -1);
490 if (p_current == page) {
491 gtk_tree_view_expand_all (treeview);
492 gtk_tree_selection_select_iter (
493 gtk_tree_view_get_selection (treeview),
494 &iter);
495 return;
498 select_page (treeview, &iter, page);
500 } while (gtk_tree_model_iter_next (treemodel, &iter));
504 /*! \todo Finish function documentation!!!
505 * \brief
506 * \par Function Description
509 static void pagesel_update (GschemPageselDockable *pagesel)
511 GtkTreeModel *model;
512 TOPLEVEL *toplevel;
513 PAGE *p_current;
514 GList *iter;
516 g_return_if_fail (GSCHEM_IS_PAGESEL_DOCKABLE (pagesel));
518 if (GSCHEM_PAGESEL_DOCKABLE (pagesel)->treeview == NULL)
519 return;
521 g_return_if_fail (GSCHEM_DOCKABLE (pagesel)->w_current);
523 toplevel = gschem_toplevel_get_toplevel (GSCHEM_DOCKABLE (pagesel)->w_current);
524 model = gtk_tree_view_get_model (pagesel->treeview);
526 /* wipe out every thing in the store */
527 gtk_tree_store_clear (GTK_TREE_STORE (model));
528 /* now rebuild */
529 for ( iter = geda_list_get_glist( toplevel->pages );
530 iter != NULL;
531 iter = g_list_next( iter ) ) {
533 p_current = (PAGE *)iter->data;
534 /* find every page that is not a hierarchy-down of another page */
535 if (p_current->up < 0 ||
536 s_page_search_by_page_id (toplevel->pages,
537 p_current->up) == NULL) {
538 add_page (model, NULL, toplevel->pages, p_current);
542 /* select the current page in the treeview */
543 select_page (pagesel->treeview, NULL, toplevel->page_current);