"reversible"
- From: Ryan Lortie <desrt desrt ca>
- To: gtk-devel-list gnome org
- Subject: "reversible"
- Date: Thu, 27 Sep 2012 16:01:34 -0400
hi,
One of the more annoying aspects of using GMenuModel from an application
that supports plugins is that adding items to a menu and being able to
remove them again in the future is not well-supported.
GMenuModel allows the use of empty sections as a (superior) stand-in for
the prior placeholder functionality of GtkUIManager and I've been
recommending them to people. I always get asked how to remove the items
from the menu again, however, and I rarely have a good answer for that.
It's usually something along the lines of "tag it with an attribute that
you will recognise and iterate/remove" or "add a subsection and then
iterate over the section to find your subsection (by pointer ID) and
remove it".
The GtkUIManager approach to this is to use integer tags to facilitate
backing out UI additions.
Although I've often considered adding this, it would probably not work
great with GMenuModel. There is no merging (in the GtkUIManager sense)
so you have to manually add your several menu items -- and would have to
keep around a list of numbers for later removal.
I've found myself musing in the past about some sort of start() and
end() API along these lines:
id = g_menu_start_adding (menu);
g_menu_append (menu, ...);
g_menu_append (menu, ...);
g_menu_append (menu, ...);
g_menu_stop_adding (menu);
then later:
g_menu_remove_additions (menu, id);
This would give something close to GtkUIManager but it's really ugly. I
don't particularly care for storing state on objects in this way.
I've also thought about this sort of approach:
g_menu_add_with_removal_id (menu, id, ...);
then later:
g_menu_remove_by_id (menu, id);
but this sort of thing turns into an API combinatorial-explosion-style
nightmare.
One place that we haven't been too shy about storing state in the past
is in thread-local storage. We have at least thread-local default main
contexts as well as thread-local cancellables that can be directly
manipulated by the user.
This leads to this idea:
id = g_menu_push_thread_removal_id ();
g_menu_add (menu, ...);
g_menu_add (menu, ...);
g_menu_pop_thread_removal_id ();
g_menu_remove_by_id (id);
But then I got to thinking that plugins add menu items, actions and
possibly many other things. It would be silly to have this for everything.
This led me to the idea of some object with a name like GReversible
(although I don't think the name is a great fit for the same reason that
GCancellable is slightly misnamed).
Certain operations could advertise themselves as "reversible". Adding
items to a menu or actions to a GActionMap are obvious candidates. Then
a plugin could have something like so:
struct _PluginState
{
GReversible *reversible;
};
void add_window (GtkApplication *app,
GtkWindow *window,
PluginState *state)
{
g_reversible_push (state->reversible);
g_action_map_add_action_entries (G_ACTION_MAP (window),
plugin_win_actions,
G_N_ELEMENTS (plugin_win_actions),
window);
g_reversible_pop ();
}
void plugin_init (PluginState *state,
GtkApplication *app)
{
state->reversible = g_reversible_new ();
forall (win in windows) { add_window (state); }
app.connect ("window-added", add_window, state);
g_reversible_push (state->reversible);
g_action_map_add_action_entries (G_ACTION_MAP (app),
plugin_app_actions,
G_N_ELEMENTS (plugin_app_actions),
app);
menu = gtk_application_get_app_menu (app);
add_stuff_to_menu (menu);
g_reversible_pop (state->reversible);
}
void plugin_remove (PluginState *state)
{
g_reversible_reverse (state->reversible);
g_clear_object (&state->reversible);
}
It's pretty clear that the functionality of the GReversible object is
quite close to that of GCancellable. It might make sense to share them.
Resetting the object probably makes even less sense here than it does
for resetting a cancellable.
One thing that doesn't make sense would be to share the thread-local
stack. The meaning of a reversible and a cancellable operation are very
much separate and should be kept so.
We could use g_reversible_enter() that returns an integer tag, but this
prevents us from having multiple groups of operations able to be
reversed by a single reversible. We could fix that by allowing the user
to specify an already-existing reversible ID.
On balance, I'd prefer not to use GCancellable -- probably creating
GReversible or using integer IDs makes most sense.
The trickiest bit of this proposal, in my opinion, is managing the
transition of classes from non-reversible to reversible. Someone may be
surprised to find that one day:
g_reversible_enter();
g_hash_table_insert();
g_reversible_leave();
g_reversible_reverse();
this code actually removes their inserted item from the hashtable (I
know this is a ridiculous case but there could be other less-ridiculous
ones).
This is a sort of transition that we've managed well in the past, in my
opinion. Objects have become thread-default-mainloop-aware in the past
without too much incompatible fallout. I think it would probably be
prudent to do a full review of all potentially-reversible APIs in GIO
and decide which ones will be reversible at the point that we introduce
the new API. In any case, the documentation for GReversible should
strongly suggest not pushing a reversible for long lengths of time.
When it comes to menus (and actiongroups) I'd probably manage the
semantics of reversibles in approximately this way:
- nesting is supported
- if a reversible is pushed then you may only call g_menu_remove() for
items that were added while under that reversible (ie: removal is
non-reversible)
- reversing a reversible will reverse actions done even if another
reversible was nested at the time. example:
enter (a);
enter (b);
add();
leave(b);
leave(a);
reverse(a); // would reverse the add
- removing items pushed while in a reversible from outside of that
reversible would work as usual but would not be recommended
Would this API make your life easier? Could it be better?
Cheers
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]