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.
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)
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.
90 gschem_action_register (gchar
*id
,
96 GschemActionType type
,
97 void (*activate
) (GschemAction
*, GschemToplevel
*))
99 GschemAction
*action
= g_new0 (GschemAction
, 1);
102 action
->icon_name
= icon_name
;
104 action
->label
= label
;
105 action
->menu_label
= menu_label
;
106 action
->tooltip
= tooltip
;
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
);
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.
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
);
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.
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.
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
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
;
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);
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
);
272 /* only then, fill in the thunk field */
273 action
->thunk
= s_thunk
;
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.
286 return scm_from_bool (SCM_SMOB_PREDICATE (action_tag
, smob
));
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
);
299 init_module_gschem_core_builtins (void *data
)
301 #include "actions.init.x"
306 mark_action (SCM smob
)
308 GschemAction
*action
= GSCHEM_ACTION (SCM_SMOB_DATA (smob
));
310 return action
->thunk
;
314 free_action (SCM smob
)
316 GschemAction
*action
= GSCHEM_ACTION (SCM_SMOB_DATA (smob
));
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
);
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
);
342 scm_puts ("#<gschem-action>", port
);
344 scm_remember_upto_here_1 (smob
);
345 return 1; /* non-zero means success */
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
);
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.
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
);
438 GObject parent_instance
;
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
,
463 static void dispatcher_set_active (Dispatcher
*dispatcher
,
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
,
471 static gpointer dispatcher_parent_class
= NULL
;
472 static guint dispatcher_signals
[LAST_SIGNAL
] = { 0 };
476 gschem_action_state_dispatcher_get_type ()
478 static GType 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 */
490 (GInstanceInitFunc
) dispatcher_instance_init
,
491 NULL
/* value_table */
494 type
= g_type_register_static (G_TYPE_OBJECT
,
495 "GschemActionStateDispatcher",
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
),
523 g_cclosure_marshal_VOID__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
),
533 g_cclosure_marshal_VOID__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
),
543 g_cclosure_marshal_VOID__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
),
553 g_cclosure_marshal_VOID__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
),
563 g_cclosure_marshal_VOID__STRING
,
570 dispatcher_instance_init (Dispatcher
*dispatcher
)
572 dispatcher
->sensitive
= TRUE
;
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
);
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
,
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.
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.
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.
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.
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.
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.
705 dispatcher_set_name (Dispatcher
*dispatcher
, gchar
*name
)
707 g_free (dispatcher
->name
);
708 dispatcher
->name
= g_strdup (name
);
712 dispatcher_set_label (Dispatcher
*dispatcher
, gchar
*label
)
714 g_free (dispatcher
->label
);
715 dispatcher
->label
= g_strdup (label
);
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 /******************************************************************************/
730 menu_item_toggled (GtkCheckMenuItem
*menu_item
, gpointer user_data
);
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
);
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
);
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
);
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.
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
);
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
,
811 gtk_image_set_from_icon_name (GTK_IMAGE (image
), action
->icon_name
,
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
822 gschem_accel_label_set_accel_string (GSCHEM_ACCEL_LABEL (label
),
824 gtk_container_add (GTK_CONTAINER (menu_item
), label
);
825 gtk_widget_show (label
);
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
);
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"
878 G_CALLBACK (gtk_label_set_label
), label
);
879 gchar
*l_str
= use_menu_label
? dispatcher
->menu_label
: dispatcher
->label
;
881 gtk_label_set_label (GTK_LABEL (label
), l_str
);
887 /******************************************************************************/
891 spin_button_value_changed (GtkSpinButton
*spin_button
,
892 GschemToplevel
*w_current
)
894 gtk_widget_grab_focus (w_current
->drawing_area
);
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
));
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.
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
);
927 gchar
*tooltip_text
= g_strdup_printf ("%s\n%s", action
->name
,
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
);
941 /******************************************************************************/
945 tool_button_toggled (GtkToggleToolButton
*button
, gpointer user_data
);
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
);
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
);
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.
990 gschem_action_create_tool_button (GschemAction
*action
,
991 GschemToplevel
*w_current
)
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
);
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
);
1008 gchar
*tooltip_text
= g_strdup_printf ("%s\n%s", action
->name
,
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
);
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
);
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
);