Bump gEDA version
[geda-gaf.git] / gschem / src / gschem_action.c
blob0b0ef729fe37a9bb84a84e2feba019804e633ea3
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
21 /*! \file gschem_action.c
22 * \brief Action mechanism.
24 * Actions are represented by a GschemAction struct which contains a
25 * pointer to the callback function as well as some metadata (action
26 * name, icon, etc.). To Scheme code, actions are visible as SMOBs,
27 * applicable foreign object wrappers around the action structs which
28 * behave like procedures but make it trivial to retrieve the
29 * GschemAction struct for a given Scheme action object.
31 * This design made several improvements possible:
33 * - Actions are defined in a single place: the DEFINE_ACTION macro in
34 * actions.c declares a global symbol referring to the action, sets
35 * the action metadata, and defines the action callback function.
36 * Menu items, toolbar buttons, and the context menu all share the
37 * same set of actions.
39 * - The state of actions representing an option (e.g., "Show/Hide
40 * Invisible Text") is indicated by a checkbox in the menu item and
41 * by the toolbar button being depressed. The state of mode actions
42 * (e.g., "Add Rectangle") is indicated by the toolbar button being
43 * depressed. When an action can't be executed (e.g., "Redo"), the
44 * menu item and toolbar button are insensitive.
46 * - Toolbar and context menu are configured the same way menus are.
48 * - "Repeat Last Action" (usually bound to ".") uses the same logic
49 * as the middle mouse button repeat action does, i.e., only actions
50 * that "make sense" qualify for repeating.
52 * Written by Roland Lutz 2019.
55 #include <config.h>
57 #include <stdio.h>
58 #ifdef HAVE_STDLIB_H
59 #include <stdlib.h>
60 #endif
61 #ifdef HAVE_STRING_H
62 #include <string.h>
63 #endif
65 #include "gschem.h"
67 #define DEFINE_ACTION(c_id, id, icon, name, label, menu_label, tooltip, type) \
68 GschemAction *action_ ## c_id; \
69 static void action_callback_ ## c_id (GschemAction *action, \
70 GschemToplevel *w_current)
71 #include "actions.c"
72 #undef DEFINE_ACTION
74 #include "actions.decl.x"
77 static scm_t_bits action_tag;
80 /*! \brief Register statically-defined gschem action.
82 * Allocates a new GschemAction struct, sets the meta-information
83 * passed as arguments, and creates a public top-level Scheme binding
84 * for the action in the current environment.
86 * \note This function should only be used indirectly by defining an
87 * action in actions.c.
89 GschemAction *
90 gschem_action_register (gchar *id,
91 gchar *icon_name,
92 gchar *name,
93 gchar *label,
94 gchar *menu_label,
95 gchar *tooltip,
96 GschemActionType type,
97 void (*activate) (GschemAction *, GschemToplevel *))
99 GschemAction *action = g_new0 (GschemAction, 1);
101 action->id = id;
102 action->icon_name = icon_name;
103 action->name = name;
104 action->label = label;
105 action->menu_label = menu_label;
106 action->tooltip = tooltip;
107 action->type = type;
108 action->activate = activate;
110 action->thunk = SCM_UNDEFINED;
112 /* actions are global objects, so their smob should be permanent */
113 action->smob = scm_permanent_object (
114 scm_new_smob (action_tag, (scm_t_bits) action));
116 /* create public binding */
117 scm_dynwind_begin (0);
119 gchar *name = g_strdup_printf ("&%s", action->id);
120 scm_dynwind_free (name);
122 scm_c_define (name, action->smob);
123 scm_c_export (name, NULL);
125 scm_dynwind_end ();
127 return action;
131 /*! \brief Run an action.
133 * If you want to invoke an action explicitly, use this function.
135 * Runs the activation function associated with \a action in the
136 * Scheme toplevel context of \a w_current.
138 void
139 gschem_action_activate (GschemAction *action,
140 GschemToplevel *w_current)
142 g_return_if_fail (action->activate != NULL);
143 g_return_if_fail (w_current != NULL);
145 scm_dynwind_begin (0);
146 g_dynwind_window (w_current);
147 action->activate (action, w_current);
148 scm_dynwind_end ();
152 /******************************************************************************/
155 /* The following functions define the Guile SMOB type which represents
156 * GschemAction structures in Scheme.
158 * Pointer identity is preserved by storing the SMOB representation of
159 * a GschemAction structure in its "smob" member. This means you can
160 * safely compare #<gschem-action ...> objects using "eq?".
162 * A GschemAction structure does not own its "smob" member; on the
163 * contrary, a user-defined action may be freed if its SMOB is
164 * garbage collected before an associated widget has been created.
167 /*! \brief Return whether a given Guile expression represents an action.
170 scm_is_action (SCM x)
172 return SCM_SMOB_PREDICATE (action_tag, x);
175 /*! \brief Return the action represented by a Guile expression.
177 GschemAction *
178 scm_to_action (SCM smob)
180 scm_assert_smob_type (action_tag, smob);
181 return GSCHEM_ACTION (SCM_SMOB_DATA (smob));
185 /*! \brief Callback function for activating user-defined actions.
187 * Actions defined from Scheme code don't have a hard-coded C callback
188 * function. Instead, this function is used, which calls the Scheme
189 * thunk stored in the action structure.
191 static void
192 activate_scheme_thunk (GschemAction *action, GschemToplevel *w_current)
194 g_return_if_fail (!SCM_UNBNDP (action->thunk));
195 g_scm_eval_protected (scm_list_1 (action->thunk), SCM_UNDEFINED);
198 /*! \brief Guile interface for creating user-defined actions.
200 * Creates a new action wrapping the given Guile "thunk" (that is,
201 * parameter-less function) and meta-information and returns a SMOB
202 * representing it.
204 static SCM
205 make_action (SCM s_icon_name, SCM s_name, SCM s_label, SCM s_menu_label,
206 SCM s_tooltip, SCM s_thunk)
208 GschemAction *action;
209 SCM smob;
211 SCM_ASSERT (scm_is_false (s_icon_name) ||
212 scm_is_string (s_icon_name), s_icon_name,
213 SCM_ARG1, "%make-action!");
214 SCM_ASSERT (scm_is_false (s_name) ||
215 scm_is_string (s_name), s_name,
216 SCM_ARG2, "%make-action!");
217 SCM_ASSERT (scm_is_false (s_label) ||
218 scm_is_string (s_label), s_label,
219 SCM_ARG3, "%make-action!");
220 SCM_ASSERT (scm_is_false (s_menu_label) ||
221 scm_is_string (s_menu_label), s_menu_label,
222 SCM_ARG4, "%make-action!");
223 SCM_ASSERT (scm_is_false (s_tooltip) ||
224 scm_is_string (s_tooltip), s_tooltip,
225 SCM_ARG5, "%make-action!");
226 SCM_ASSERT (scm_is_true (scm_thunk_p (s_thunk)), s_thunk,
227 SCM_ARG6, "%make-action!");
229 scm_dynwind_begin (0);
231 char *icon_name, *name, *label, *menu_label, *tooltip;
233 icon_name = scm_is_false (s_icon_name) ? NULL :
234 scm_to_utf8_string (s_icon_name);
235 scm_dynwind_free (icon_name);
237 name = scm_is_false (s_name) ? NULL :
238 scm_to_utf8_string (s_name);
239 scm_dynwind_free (name);
241 label = scm_is_false (s_label) ? NULL :
242 scm_to_utf8_string (s_label);
243 scm_dynwind_free (label);
245 menu_label = scm_is_false (s_menu_label) ? NULL :
246 scm_to_utf8_string (s_menu_label);
247 scm_dynwind_free (menu_label);
249 tooltip = scm_is_false (s_tooltip) ? NULL :
250 scm_to_utf8_string (s_tooltip);
251 scm_dynwind_free (tooltip);
253 action = g_new0 (GschemAction, 1);
255 action->id = NULL;
256 action->icon_name = g_strdup (icon_name);
257 action->name = g_strdup (name);
258 action->label = g_strdup (label);
259 action->menu_label = g_strdup (menu_label);
260 action->tooltip = g_strdup (tooltip);
261 action->type = GSCHEM_ACTION_TYPE_ACTUATE;
262 action->activate = activate_scheme_thunk;
264 action->thunk = SCM_UNDEFINED;
266 /* immediately create the smob and keep a reference on the stack
267 so the garbage collector knows about the allocated data */
268 action->smob = smob = scm_new_smob (action_tag, (scm_t_bits) action);
270 scm_dynwind_end ();
272 /* only then, fill in the thunk field */
273 action->thunk = s_thunk;
275 return smob;
278 /*! \brief Guile subr for testing whether a given Scheme expression
279 * represents an action.
281 * This is like scm_is_action but returns #t or #f instead of 1 or 0.
283 static SCM
284 action_p (SCM smob)
286 return scm_from_bool (SCM_SMOB_PREDICATE (action_tag, smob));
289 static void
290 init_module_gschem_core_action (void *data)
292 scm_c_define_gsubr ("%make-action", 6, 0, 0, make_action);
293 scm_c_define_gsubr ("%action?", 1, 0, 0, action_p);
295 scm_c_export ("%make-action", "%action?", NULL);
298 static void
299 init_module_gschem_core_builtins (void *data)
301 #include "actions.init.x"
305 static SCM
306 mark_action (SCM smob)
308 GschemAction *action = GSCHEM_ACTION (SCM_SMOB_DATA (smob));
310 return action->thunk;
313 static size_t
314 free_action (SCM smob)
316 GschemAction *action = GSCHEM_ACTION (SCM_SMOB_DATA (smob));
318 g_free (action->id);
319 g_free (action->icon_name);
320 g_free (action->name);
321 g_free (action->label);
322 g_free (action->menu_label);
323 g_free (action->tooltip);
325 return 0;
328 static int
329 print_action (SCM smob, SCM port, scm_print_state *pstate)
331 GschemAction *action = GSCHEM_ACTION (SCM_SMOB_DATA (smob));
333 if (action->id != NULL) {
334 scm_puts ("#<gschem-action ", port);
335 scm_puts (action->id, port);
336 scm_puts (">", port);
337 } else if (action->name != NULL) {
338 scm_puts ("#<gschem-action \"", port);
339 scm_puts (action->name, port);
340 scm_puts ("\">", port);
341 } else
342 scm_puts ("#<gschem-action>", port);
344 scm_remember_upto_here_1 (smob);
345 return 1; /* non-zero means success */
348 static SCM
349 equalp_action (SCM smob0, SCM smob1)
351 GschemAction *action0 = GSCHEM_ACTION (SCM_SMOB_DATA (smob0));
352 GschemAction *action1 = GSCHEM_ACTION (SCM_SMOB_DATA (smob1));
354 return scm_from_bool (action0 == action1);
357 static SCM
358 apply_action (SCM smob)
360 GschemAction *action = GSCHEM_ACTION (SCM_SMOB_DATA (smob));
361 gschem_action_activate (action, g_current_window ());
363 scm_remember_upto_here_1 (smob);
364 return SCM_UNDEFINED;
367 /*! \brief Initialize Scheme action interface.
369 * Sets up the action SMOB type and defines the modules <tt>(gschem
370 * core action)</tt> and <tt>(gschem core builtins)</tt>. Must be
371 * called before calling any other action-related functions.
373 void
374 gschem_action_init (void)
376 action_tag = scm_make_smob_type ("gschem-action", 0);
377 scm_set_smob_mark (action_tag, mark_action);
378 scm_set_smob_free (action_tag, free_action);
379 scm_set_smob_print (action_tag, print_action);
380 scm_set_smob_equalp (action_tag, equalp_action);
381 scm_set_smob_apply (action_tag, apply_action, 0, 0, 0);
383 scm_c_define_module ("gschem core action",
384 init_module_gschem_core_action, NULL);
386 scm_c_define_module ("gschem core builtins",
387 init_module_gschem_core_builtins, NULL);
391 /******************************************************************************/
394 /*! \class _Dispatcher
395 * \brief Action state dispatcher.
397 * Actions are global objects which aren't associated with a specific
398 * GschemToplevel. This means that there is no global "state" of an
399 * action--a given action may be sensitive in one toplevel and not in
400 * another with, say, a different selection.
402 * In order to resolve this, a GschemToplevel has one action state
403 * dispatcher object for each action that has widgets associated with
404 * it. The dispatcher object keeps track of the state of the action
405 * (whether it is currently sensitive and/or active) and provides a
406 * point for widgets to connect to in order to react to state changes.
408 * The dispatchers are stored in the \ref action_state_dispatchers
409 * field of the toplevel and created on-demand by \ref get_dispatcher.
412 #define TYPE_DISPATCHER \
413 (gschem_action_state_dispatcher_get_type ())
414 #define DISPATCHER(obj) \
415 (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_DISPATCHER, Dispatcher))
416 #define DISPATCHER_CLASS(klass) \
417 (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_DISPATCHER, DispatcherClass))
418 #define IS_DISPATCHER(obj) \
419 (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_DISPATCHER))
420 #define DISPATCHER_GET_CLASS(obj) \
421 (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_DISPATCHER, DispatcherClass))
423 typedef struct _DispatcherClass DispatcherClass;
424 typedef struct _Dispatcher Dispatcher;
426 struct _DispatcherClass {
427 GObjectClass parent_class;
429 void (*set_sensitive) (Dispatcher *dispatcher, gboolean sensitive);
430 void (*set_active) (Dispatcher *dispatcher, gboolean is_active);
432 void (*set_name) (Dispatcher *dispatcher, gchar *name);
433 void (*set_label) (Dispatcher *dispatcher, gchar *label);
434 void (*set_menu_label) (Dispatcher *dispatcher, gchar *menu_label);
437 struct _Dispatcher {
438 GObject parent_instance;
440 guint sensitive : 1;
441 guint active : 1;
443 gchar *name;
444 gchar *label;
445 gchar *menu_label;
448 enum {
449 SET_SENSITIVE,
450 SET_ACTIVE,
451 SET_NAME,
452 SET_LABEL,
453 SET_MENU_LABEL,
454 LAST_SIGNAL
457 static void dispatcher_class_init (DispatcherClass *class);
458 static void dispatcher_instance_init (Dispatcher *dispatcher);
459 static void dispatcher_finalize (GObject *object);
461 static void dispatcher_set_sensitive (Dispatcher *dispatcher,
462 gboolean sensitive);
463 static void dispatcher_set_active (Dispatcher *dispatcher,
464 gboolean is_active);
466 static void dispatcher_set_name (Dispatcher *dispatcher, gchar *name);
467 static void dispatcher_set_label (Dispatcher *dispatcher, gchar *label);
468 static void dispatcher_set_menu_label (Dispatcher *dispatcher,
469 gchar *menu_label);
471 static gpointer dispatcher_parent_class = NULL;
472 static guint dispatcher_signals[LAST_SIGNAL] = { 0 };
475 static GType
476 gschem_action_state_dispatcher_get_type ()
478 static GType type = 0;
480 if (type == 0) {
481 static const GTypeInfo info = {
482 sizeof (DispatcherClass),
483 NULL, /* base_init */
484 NULL, /* base_finalize */
485 (GClassInitFunc) dispatcher_class_init,
486 NULL, /* class_finalize */
487 NULL, /* class_data */
488 sizeof (Dispatcher),
489 0, /* n_preallocs */
490 (GInstanceInitFunc) dispatcher_instance_init,
491 NULL /* value_table */
494 type = g_type_register_static (G_TYPE_OBJECT,
495 "GschemActionStateDispatcher",
496 &info, 0);
499 return type;
503 static void
504 dispatcher_class_init (DispatcherClass *class)
506 dispatcher_parent_class = g_type_class_peek_parent (class);
508 G_OBJECT_CLASS (class)->finalize = dispatcher_finalize;
510 class->set_sensitive = dispatcher_set_sensitive;
511 class->set_active = dispatcher_set_active;
513 class->set_name = dispatcher_set_name;
514 class->set_label = dispatcher_set_label;
515 class->set_menu_label = dispatcher_set_menu_label;
517 dispatcher_signals[SET_SENSITIVE] =
518 g_signal_new ("set-sensitive",
519 G_OBJECT_CLASS_TYPE (class),
520 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
521 G_STRUCT_OFFSET (DispatcherClass, set_sensitive),
522 NULL, NULL,
523 g_cclosure_marshal_VOID__BOOLEAN,
524 G_TYPE_NONE, 1,
525 G_TYPE_BOOLEAN);
527 dispatcher_signals[SET_ACTIVE] =
528 g_signal_new ("set-active",
529 G_OBJECT_CLASS_TYPE (class),
530 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
531 G_STRUCT_OFFSET (DispatcherClass, set_active),
532 NULL, NULL,
533 g_cclosure_marshal_VOID__BOOLEAN,
534 G_TYPE_NONE, 1,
535 G_TYPE_BOOLEAN);
537 dispatcher_signals[SET_NAME] =
538 g_signal_new ("set-name",
539 G_OBJECT_CLASS_TYPE (class),
540 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
541 G_STRUCT_OFFSET (DispatcherClass, set_name),
542 NULL, NULL,
543 g_cclosure_marshal_VOID__STRING,
544 G_TYPE_NONE, 1,
545 G_TYPE_STRING);
547 dispatcher_signals[SET_LABEL] =
548 g_signal_new ("set-label",
549 G_OBJECT_CLASS_TYPE (class),
550 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
551 G_STRUCT_OFFSET (DispatcherClass, set_label),
552 NULL, NULL,
553 g_cclosure_marshal_VOID__STRING,
554 G_TYPE_NONE, 1,
555 G_TYPE_STRING);
557 dispatcher_signals[SET_MENU_LABEL] =
558 g_signal_new ("set-menu-label",
559 G_OBJECT_CLASS_TYPE (class),
560 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
561 G_STRUCT_OFFSET (DispatcherClass, set_menu_label),
562 NULL, NULL,
563 g_cclosure_marshal_VOID__STRING,
564 G_TYPE_NONE, 1,
565 G_TYPE_STRING);
569 static void
570 dispatcher_instance_init (Dispatcher *dispatcher)
572 dispatcher->sensitive = TRUE;
576 static void
577 dispatcher_finalize (GObject *object)
579 Dispatcher *dispatcher = DISPATCHER (object);
581 g_free (dispatcher->name);
582 g_free (dispatcher->label);
583 g_free (dispatcher->menu_label);
585 G_OBJECT_CLASS (dispatcher_parent_class)->finalize (object);
589 static Dispatcher *
590 get_dispatcher (GschemAction *action,
591 GschemToplevel *w_current)
593 Dispatcher *dispatcher = g_hash_table_lookup (
594 w_current->action_state_dispatchers, action);
596 if (dispatcher == NULL) {
597 dispatcher = g_object_new (TYPE_DISPATCHER, NULL);
598 g_hash_table_insert (w_current->action_state_dispatchers,
599 action, dispatcher);
602 return dispatcher;
606 /*! \brief Set whether an action is "sensitive", i.e., clickable.
608 * Updates all widgets associated with the action to indicate the new
609 * status. If \a sensitive is \c FALSE, menu items and tool buttons
610 * are displayed greyed-out and can't be selected by the user; if \a
611 * sensitive is \c TRUE, they can be selected normally.
613 * Even if an action has been marked as insensitive, it can still be
614 * activated normally via a hotkey.
616 void
617 gschem_action_set_sensitive (GschemAction *action, gboolean sensitive,
618 GschemToplevel *w_current)
620 Dispatcher *dispatcher = get_dispatcher (action, w_current);
621 g_return_if_fail (IS_DISPATCHER (dispatcher));
622 g_signal_emit (dispatcher, dispatcher_signals[SET_SENSITIVE], 0, sensitive);
626 /*! \brief Set whether an action is displayed as "active".
628 * Updates all widgets associated with the action to indicate the new
629 * status. Menu items are rendered depending on the action type.
630 * Toolbar buttons are rendered in "depressed" state if \a is_active
631 * is \c TRUE and in normal state if \a is_active is \c FALSE.
633 void
634 gschem_action_set_active (GschemAction *action, gboolean is_active,
635 GschemToplevel *w_current)
637 Dispatcher *dispatcher = get_dispatcher (action, w_current);
638 g_return_if_fail (IS_DISPATCHER (dispatcher));
639 g_signal_emit (dispatcher, dispatcher_signals[SET_ACTIVE], 0, is_active);
643 /*! \brief Set the displayed strings of an action.
645 * Updates all widgets associated with the action to show the new
646 * strings. Menu items show \a label or \a menu_label, depending on
647 * whether they are part of the main menu. Toolbar buttons show \a
648 * name as tooltip. All strings can be \c NULL, in which case the
649 * default string for the action is used.
651 * \note Setting a string to its default value is not the same thing
652 * as not setting the string at all.
654 void
655 gschem_action_set_strings (GschemAction *action,
656 gchar *name, gchar *label, gchar *menu_label,
657 GschemToplevel *w_current)
659 Dispatcher *dispatcher = get_dispatcher (action, w_current);
660 g_return_if_fail (IS_DISPATCHER (dispatcher));
662 g_signal_emit (dispatcher, dispatcher_signals[SET_NAME], 0,
663 name != NULL ? name : action->name);
664 g_signal_emit (dispatcher, dispatcher_signals[SET_LABEL], 0,
665 label != NULL ? label : action->label);
666 g_signal_emit (dispatcher, dispatcher_signals[SET_MENU_LABEL], 0,
667 menu_label != NULL ? menu_label : action->menu_label);
671 /*! \brief Dispatcher class closure for "set-sensitive" signal.
673 * Invoked whenever a "set-sensitive" signal is emitted for a
674 * dispatcher. Updates the dispatcher's \a sensitive flag so new
675 * widgets can be constructed in the correct state.
677 static void
678 dispatcher_set_sensitive (Dispatcher *dispatcher, gboolean sensitive)
680 dispatcher->sensitive = sensitive;
684 /*! \brief Dispatcher class closure for "set-active" signal.
686 * Invoked whenever a "set-active" signal is emitted for a dispatcher.
687 * Updates the dispatcher's \a active flag so new widgets can be
688 * constructed in the correct state.
690 static void
691 dispatcher_set_active (Dispatcher *dispatcher, gboolean is_active)
693 dispatcher->active = is_active;
697 /*! \brief Dispatcher class closures for "set-name", "set-label", and
698 * "set-menu-label" signals.
700 * Invoked whenever the corresponding signal is emitted for a
701 * dispatcher. Updates the dispatcher's stored string so new widgets
702 * can be constructed with the correct label.
704 static void
705 dispatcher_set_name (Dispatcher *dispatcher, gchar *name)
707 g_free (dispatcher->name);
708 dispatcher->name = g_strdup (name);
711 static void
712 dispatcher_set_label (Dispatcher *dispatcher, gchar *label)
714 g_free (dispatcher->label);
715 dispatcher->label = g_strdup (label);
718 static void
719 dispatcher_set_menu_label (Dispatcher *dispatcher, gchar *menu_label)
721 g_free (dispatcher->menu_label);
722 dispatcher->menu_label = g_strdup (menu_label);
726 /******************************************************************************/
729 static void
730 menu_item_toggled (GtkCheckMenuItem *menu_item, gpointer user_data);
732 static void
733 menu_item_set_active (GtkCheckMenuItem *menu_item, gboolean is_active)
735 /* make sure the toggle handler isn't called recursively */
736 g_signal_handlers_block_matched (
737 menu_item, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
738 G_CALLBACK (menu_item_toggled), NULL);
740 gtk_check_menu_item_set_active (menu_item, is_active);
742 g_signal_handlers_unblock_matched (
743 menu_item, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
744 G_CALLBACK (menu_item_toggled), NULL);
747 static void
748 menu_item_activate (GtkMenuItem *menu_item, gpointer user_data)
750 GschemAction *action = g_object_get_data (G_OBJECT (menu_item), "action");
751 GschemToplevel *w_current = GSCHEM_TOPLEVEL (user_data);
752 gschem_action_activate (action, w_current);
755 static void
756 menu_item_toggled (GtkCheckMenuItem *menu_item, gpointer user_data)
758 /* menu item self-toggles when clicked--undo this first */
759 menu_item_set_active (menu_item,
760 !gtk_check_menu_item_get_active (menu_item));
762 GschemAction *action = g_object_get_data (G_OBJECT (menu_item), "action");
763 GschemToplevel *w_current = GSCHEM_TOPLEVEL (user_data);
764 gschem_action_activate (action, w_current);
767 static char *
768 get_accel_string (GschemAction *action)
770 /* look up key binding in global keymap */
771 SCM s_expr = scm_list_2 (scm_from_utf8_symbol ("find-key"), action->smob);
772 SCM s_keys = g_scm_eval_protected (s_expr, scm_interaction_environment ());
773 return scm_is_true (s_keys) ? scm_to_utf8_string (s_keys) : NULL;
776 /*! \brief Create action menu item.
778 * Creates a GtkMenuItem, GtkImageMenuItem, or GtkCheckMenuItem widget
779 * (depending on the action type), configures it according to the
780 * action metadata, attaches the appropriate submenu (if applicable),
781 * and hooks the widget up with the corresponding dispatcher object so
782 * it will be updated when the action status changes.
784 GtkWidget *
785 gschem_action_create_menu_item (GschemAction *action,
786 gboolean use_menu_label,
787 GschemToplevel *w_current)
789 GtkWidget *menu_item;
791 if (action->type == GSCHEM_ACTION_TYPE_TOGGLE_CHECK)
792 menu_item = g_object_new (GTK_TYPE_CHECK_MENU_ITEM, NULL);
793 else if (action->type == GSCHEM_ACTION_TYPE_TOGGLE_RADIO)
794 menu_item = g_object_new (GTK_TYPE_CHECK_MENU_ITEM,
795 "draw-as-radio", TRUE, NULL);
796 else if (action->icon_name == NULL)
797 menu_item = g_object_new (GTK_TYPE_MENU_ITEM, NULL);
798 else {
799 menu_item = g_object_new (GTK_TYPE_IMAGE_MENU_ITEM, NULL);
801 GtkWidget *image = gtk_image_new ();
802 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), image);
804 /* If there's a matching stock item, use it.
805 Otherwise lookup the name in the icon theme. */
806 GtkStockItem stock_item;
807 if (gtk_stock_lookup (action->icon_name, &stock_item))
808 gtk_image_set_from_stock (GTK_IMAGE (image), action->icon_name,
809 GTK_ICON_SIZE_MENU);
810 else
811 gtk_image_set_from_icon_name (GTK_IMAGE (image), action->icon_name,
812 GTK_ICON_SIZE_MENU);
815 /* use custom label widget */
816 char *accel_string = get_accel_string (action);
817 GtkWidget *label = g_object_new (GSCHEM_TYPE_ACCEL_LABEL, NULL);
818 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
819 gtk_label_set_use_underline (GTK_LABEL (label), TRUE);
820 gtk_label_set_label (GTK_LABEL (label), use_menu_label ? action->menu_label
821 : action->label);
822 gschem_accel_label_set_accel_string (GSCHEM_ACCEL_LABEL (label),
823 accel_string);
824 gtk_container_add (GTK_CONTAINER (menu_item), label);
825 gtk_widget_show (label);
826 free (accel_string);
828 gtk_widget_set_tooltip_text (menu_item, action->tooltip);
830 /* attach submenus */
831 if (action == action_file_open_recent)
832 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item),
833 w_current->recent_chooser_menu);
834 else if (action == action_docking_area_left)
835 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item),
836 w_current->left_docking_area_menu);
837 else if (action == action_docking_area_bottom)
838 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item),
839 w_current->bottom_docking_area_menu);
840 else if (action == action_docking_area_right)
841 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item),
842 w_current->right_docking_area_menu);
843 else if (action == action_options_grid_size) {
844 GtkWidget *menu = gtk_menu_new ();
845 gtk_menu_shell_append (GTK_MENU_SHELL (menu),
846 gschem_action_create_menu_item (
847 action_options_scale_up_snap_size,
848 use_menu_label, w_current));
849 gtk_menu_shell_append (GTK_MENU_SHELL (menu),
850 gschem_action_create_menu_item (
851 action_options_scale_down_snap_size,
852 use_menu_label, w_current));
853 gtk_widget_show_all (menu);
854 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), menu);
857 /* register callback so the action gets run */
858 g_object_set_data (G_OBJECT (menu_item), "action", action);
859 if (GTK_IS_CHECK_MENU_ITEM (menu_item))
860 g_signal_connect (G_OBJECT (menu_item), "toggled",
861 G_CALLBACK (menu_item_toggled), w_current);
862 else
863 g_signal_connect (G_OBJECT (menu_item), "activate",
864 G_CALLBACK (menu_item_activate), w_current);
866 /* connect menu item to the dispatcher for status updates */
867 Dispatcher *dispatcher = get_dispatcher (action, w_current);
868 g_signal_connect_swapped (dispatcher, "set-sensitive",
869 G_CALLBACK (gtk_widget_set_sensitive), menu_item);
870 gtk_widget_set_sensitive (GTK_WIDGET (menu_item), dispatcher->sensitive);
871 if (GTK_IS_CHECK_MENU_ITEM (menu_item)) {
872 g_signal_connect_swapped (dispatcher, "set-active",
873 G_CALLBACK (menu_item_set_active), menu_item);
874 menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), dispatcher->active);
876 g_signal_connect_swapped (dispatcher, use_menu_label ? "set-menu-label"
877 : "set-label",
878 G_CALLBACK (gtk_label_set_label), label);
879 gchar *l_str = use_menu_label ? dispatcher->menu_label : dispatcher->label;
880 if (l_str != NULL)
881 gtk_label_set_label (GTK_LABEL (label), l_str);
883 return menu_item;
887 /******************************************************************************/
890 static void
891 spin_button_value_changed (GtkSpinButton *spin_button,
892 GschemToplevel *w_current)
894 gtk_widget_grab_focus (w_current->drawing_area);
897 static gboolean
898 create_grid_size_menu_proxy (GtkToolItem *tool_item,
899 GschemToplevel *w_current)
901 gtk_tool_item_set_proxy_menu_item (
902 tool_item, "gschem-grid-size-menu-id",
903 gschem_action_create_menu_item (
904 action_options_grid_size, FALSE, w_current));
906 return TRUE;
909 /*! \brief Create special toolbar widget for "Grid Size" action.
911 * This is called by \ref gschem_action_create_tool_button to create
912 * the special spin button widget.
914 static GtkToolItem *
915 create_grid_size_tool_item (GschemAction *action,
916 GschemToplevel *w_current)
918 GtkWidget *spin_button = x_grid_size_sb_new (w_current);
919 g_signal_connect (spin_button, "value-changed",
920 G_CALLBACK (spin_button_value_changed), w_current);
922 GtkToolItem *tool_item = gtk_tool_item_new ();
924 if (action->tooltip == NULL)
925 gtk_widget_set_tooltip_text (GTK_WIDGET (tool_item), action->name);
926 else {
927 gchar *tooltip_text = g_strdup_printf ("%s\n%s", action->name,
928 action->tooltip);
929 gtk_widget_set_tooltip_text (GTK_WIDGET (tool_item), tooltip_text);
930 g_free (tooltip_text);
933 g_signal_connect (tool_item, "create-menu-proxy",
934 G_CALLBACK (create_grid_size_menu_proxy), w_current);
936 gtk_container_add (GTK_CONTAINER (tool_item), spin_button);
937 return tool_item;
941 /******************************************************************************/
944 static void
945 tool_button_toggled (GtkToggleToolButton *button, gpointer user_data);
947 static void
948 tool_button_set_active (GtkToggleToolButton *button, gboolean is_active)
950 /* make sure the toggle handler isn't called recursively */
951 g_signal_handlers_block_matched (
952 button, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
953 G_CALLBACK (tool_button_toggled), NULL);
955 gtk_toggle_tool_button_set_active (button, is_active);
957 g_signal_handlers_unblock_matched (
958 button, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
959 G_CALLBACK (tool_button_toggled), NULL);
962 static void
963 tool_button_clicked (GtkToolButton *button, gpointer user_data)
965 GschemAction *action = g_object_get_data (G_OBJECT (button), "action");
966 GschemToplevel *w_current = GSCHEM_TOPLEVEL (user_data);
967 gschem_action_activate (action, w_current);
970 static void
971 tool_button_toggled (GtkToggleToolButton *button, gpointer user_data)
973 /* button self-toggles when clicked--undo this first */
974 tool_button_set_active (button,
975 !gtk_toggle_tool_button_get_active (button));
977 GschemAction *action = g_object_get_data (G_OBJECT (button), "action");
978 GschemToplevel *w_current = GSCHEM_TOPLEVEL (user_data);
979 gschem_action_activate (action, w_current);
982 /*! \brief Create action toolbar button.
984 * Creates a GtkToolButton or GtkToggleToolButton widget (depending on
985 * the action type), configures it according to the action metadata,
986 * and hooks it up with the corresponding dispatcher object so it will
987 * be updated when the action status changes.
989 GtkToolItem *
990 gschem_action_create_tool_button (GschemAction *action,
991 GschemToplevel *w_current)
993 GtkToolItem *button;
995 if (action == action_options_grid_size)
996 return create_grid_size_tool_item (action, w_current);
998 if (action->type != GSCHEM_ACTION_TYPE_ACTUATE)
999 button = g_object_new (GTK_TYPE_TOGGLE_TOOL_BUTTON, NULL);
1000 else
1001 button = g_object_new (GTK_TYPE_TOOL_BUTTON, NULL);
1003 gtk_tool_button_set_label (GTK_TOOL_BUTTON (button), action->label);
1005 if (action->tooltip == NULL)
1006 gtk_widget_set_tooltip_text (GTK_WIDGET (button), action->name);
1007 else {
1008 gchar *tooltip_text = g_strdup_printf ("%s\n%s", action->name,
1009 action->tooltip);
1010 gtk_widget_set_tooltip_text (GTK_WIDGET (button), tooltip_text);
1011 g_free (tooltip_text);
1014 GtkWidget *image = gtk_image_new ();
1015 gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (button), image);
1017 /* If there's a matching stock item, use it.
1018 Otherwise lookup the name in the icon theme. */
1019 GtkStockItem stock_item;
1020 if (gtk_stock_lookup (action->icon_name, &stock_item))
1021 gtk_image_set_from_stock (GTK_IMAGE (image), action->icon_name,
1022 GTK_ICON_SIZE_LARGE_TOOLBAR);
1023 else
1024 gtk_image_set_from_icon_name (GTK_IMAGE (image), action->icon_name,
1025 GTK_ICON_SIZE_LARGE_TOOLBAR);
1027 /* register callback so the action gets run */
1028 g_object_set_data (G_OBJECT (button), "action", action);
1029 if (GTK_IS_TOGGLE_TOOL_BUTTON (button))
1030 g_signal_connect (button, "toggled",
1031 G_CALLBACK (tool_button_toggled), w_current);
1032 else
1033 g_signal_connect (button, "clicked",
1034 G_CALLBACK (tool_button_clicked), w_current);
1036 /* connect button to the dispatcher for status updates */
1037 Dispatcher *dispatcher = get_dispatcher (action, w_current);
1038 g_signal_connect_swapped (dispatcher, "set-sensitive",
1039 G_CALLBACK (gtk_widget_set_sensitive), button);
1040 gtk_widget_set_sensitive (GTK_WIDGET (button), dispatcher->sensitive);
1041 if (GTK_IS_TOGGLE_TOOL_BUTTON (button)) {
1042 g_signal_connect_swapped (dispatcher, "set-active",
1043 G_CALLBACK (tool_button_set_active), button);
1044 tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (button),
1045 dispatcher->active);
1047 g_signal_connect_swapped (dispatcher, "set-name",
1048 G_CALLBACK (gtk_widget_set_tooltip_text), button);
1049 if (dispatcher->name != NULL)
1050 gtk_widget_set_tooltip_text (GTK_WIDGET (button), dispatcher->name);
1052 return button;