A good example for Proxy Actions

17 views
Skip to first unread message

Simon Gemmell

unread,
Jan 17, 2013, 1:40:03 AM1/17/13
to qtil...@googlegroups.com
Hey,

So I've got a simple situation where I think proxy actions fit the bill so I thought I might share and make sure I'm getting it right.

It's essentially a tabified document editor, and I want to have menu items for actions like Insert->Table. Obviously I want to insert that table into the document that is currently showing, and I think proxy actions and contexts can make this pretty easy to do.

So my thoughts on how I'd use your library:
I have the single proxy action for Insert->Table and set up backend actions for each tab created. Each tab also creates it's own context (maybe using the document ID rather than the name) like "EditorTab.CurrentTabIsDoc12345" which is appended to the CONTEXT_MANAGER whenever the tab switches (and the previous tab's context is removed). The backend QActions are also registered with the ACTION_MANAGER by the tab to be active in the same context (EditorTab.CurrentTabIsDoc12345).

Voila. Hitting Insert->Table should fire off the signal from the action that is the current tab.

The other way I thought of doing it from just reading your docs is to make each tab inherit IContext, and when it becomes active add it to the object pool (and remove the previous tab). Doing so automatically adds the context. I can see why this would be useful for other widgets, but it doesn't seem like this feature was built for this use case, so I think we'd probably go with the first one.

So. Have I parsed your documentation and constructed a decent-ish use case? Or am I missing something?

Regards,
Simon

Jaco Naude

unread,
Jan 17, 2013, 3:54:46 AM1/17/13
to qtil...@googlegroups.com
Hi Simon

That is exactly the situation for which proxy actions were created. Your thought on using Qtilities to accomplish this is almost spot on. Let me describe the necessary steps:

1) Create a placeholder:
If you want the Insert->Table action to be in your main window bar, I suggest that you create a placeholder for it when your application starts. Placeholders allow you to "reserve" a spot in your application's menu bar for this action. Since your backend actions will typically only be created when you create new editors, it makes sense in most cases to create a placeholder when the application launches.

Creating a placeholder is done like this:

const char * const sci_editor_action_FILE_NEW                        = "File.New";
    Command* command = ACTION_MANAGER->registerActionPlaceHolder(constant_insert_table_id,QObject::tr("Table"),QKeySequence("Your Shortcut Here"));
    command->setCategory(category);
    file_menu->addAction(command);

2) Understanding IContext:

3) Creation of backed actions:

4) Controlling the active context:

5) Debugging it:


That should do the trick. Let me know if you have issues or if something is unclear.
Thanks
Jaco


--
To post to this group, send email to qtil...@googlegroups.com
To unsubscribe from this group, send email to
qtilities+...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/qtilities?hl=en?hl=en



--
Jaco Naude'

Qtilities: Building blocks for Qt applications.

Jaco Naude

unread,
Jan 17, 2013, 3:56:25 AM1/17/13
to qtil...@googlegroups.com
Hi Simon, 

Please ignore my previous email. It was sent by accident, will send it on when completed.

Regards,
Jaco

Jaco Naude

unread,
Jan 20, 2013, 1:01:54 PM1/20/13
to qtil...@googlegroups.com


Hi Simon

That is exactly the situation for which proxy actions were created. Your thought on using Qtilities to accomplish this is almost spot on. Let me describe the necessary steps:

1) Create a placeholder:
If you want the Insert->Table action to be in your main window bar, I suggest that you create a placeholder for it when your application starts. Placeholders allow you to "reserve" a spot in your application's menu bar for this action. Since your backend actions will typically only be created when you create new editors, it makes sense in most cases to create a placeholder when the application launches.

Creating a placeholder is done like this:

    // Create a constant somewhere which will be the id of your action.
    const char * const constant_insert_table_id= "Table.Insert";
    // Register a placeholder (notice that we don't provide a context here since we are going to provide backend actions at a later time)
    Command* command = ACTION_MANAGER->registerActionPlaceHolder(constant_insert_table_id,QObject::tr("Table"),QKeySequence("Your Shortcut Here"));
    // Specify the category under which the command must be listed in the shortcut management settings page:
    command->setCategory(QtilitiesCategory("Table Operations (or something like that)"));
    // Add the action to the needed menu:
    target_menu->addAction(command);

2) Understanding IContext:

From your email I got the impression that you don't understand the context manager correctly. Your application will have many contexts installed, with a subset of them being active at any time.

Thus, yes your tab should inherit IContext and provide a context for it. I normally create a context in my constructor doing something like this to ensure that its unique:

    QString context_string = "EditorTab";
    int count = 0;
    context_string.append(QString("%1").arg(count));
    while (CONTEXT_MANAGER->hasContext(context_string)) {
        QString count_string = QString("%1").arg(count);
        context_string.chop(count_string.length());
        ++count;
        context_string.append(QString("%1").arg(count));
    }
    CONTEXT_MANAGER->registerContext(context_string);
    // Store the context somehwere in your class:
    d->tab_context = context_string;

Then in the contextString() implementation of the IContext interface on your tab, just return d->tab_context. When you register the backend actions, you will use this context as shown in the next point.

3) Creation of backed actions:

When you construct your backend actions in the editor tab, you do something like this:

    // Get the context ID for this widget:
    int context_id = CONTEXT_MANAGER->registerContext(d->global_meta_type);
    QList<int> context;
    context.push_front(context_id);

And then register your actions like this:

    QAction* actionInsertTable = new QAction(QIcon(),tr("Table"),this);
    connect(actionInsertTable,SIGNAL(triggered()),SLOT(handleActionInsertTable()));
    ACTION_MANAGER->registerAction(constant_insert_table_id,d->actionInsertTable,context);

Qtilities will find the correct placeholder proxy action and add this new action it as an backend action for it.

4) Controlling the active context:

When your editor tab gets its focus you need to set the active context in the context manager. This will cause the correct backend action to be triggered when the proxy action is triggered.

Its easy to do, in your FocusIn event do this:

    CONTEXT_MANAGER->setNewContext(contextString(),true);

And in your FocusOut event do this:
    // Remove the context from the set of active contexts.
    CONTEXT_MANAGER->removeContext(contextString());

5) Debugging it:

The Qtilities debug widget is very useful to show visualize what is happening for you. It shows you the state of the context manager and shows all the backend actions registered for your proxy action.

That should do the trick. Let me know if you have issues or if something is unclear.
Thanks
Jaco
On Thu, Jan 17, 2013 at 8:40 AM, Simon Gemmell <simong...@gmail.com> wrote:

--
To post to this group, send email to qtil...@googlegroups.com
To unsubscribe from this group, send email to
qtilities+...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/qtilities?hl=en?hl=en



--
Jaco Naude'

Qtilities: Building blocks for Qt applications.

Simon Gemmell

unread,
Jan 22, 2013, 4:47:57 PM1/22/13
to qtil...@googlegroups.com

Hi Jaco,

Thanks for the detailed reply, it's got my mental juices going. I've replied inline.
I understand that by implementing it as an IContext some of your other helpers can take advantage of that (e.g. if you register that object in the object pool in automatically registers the context), but it's not strictly necessary to achieve the task since the context is just a string right? I can just register a context in the c'tor of my tab as you are doing there... From what I can see IContext provides contextHelpId() and a pure virtual contextString(). If I inherit IContext do I get any other benifits? I assume the debugger would still display my context if I were to register my context manually.

I'm asking this because I'm not sure I like that idea that everything ends up in this global object pool where everything is accessible from everywhere... I'm concerned it will make things very easy to use, but hard to maintain. e.g. Joe doing the database side can grab out pretty much any widget and change it's settings... data encapsulation etc. Essentially that you can stop designing your product because everything gets thrown in the pool... what are your feelings on this?

Don't get me wrong - I think it's great to have, it enables me to grab out actions from anywhere, but I'm not really in favour with EVERYTHING ending up in there. I guess one solution is just to have an internal (private) class which inheirts IContext which is added to the pool. It would still be deleted when the object is deleted but wouldn't make the tab accessible from anywhere...

>
> 3) Creation of backed actions:
>
> When you construct your backend actions in the editor tab, you do something like this:
>
>     // Get the context ID for this widget:
>
>     int context_id = CONTEXT_MANAGER->registerContext(d->global_meta_type);
>
>     QList<int> context;
>
>     context.push_front(context_id);
>
>
The global_meta_type part went over my head. I assume that's a member variable from whatever code you pulled this from - why are we registering another context? Don't we need to do this: int context_id = CONTEXT_MANAGER->contextID(d->tab_context) ?

> And then register your actions like this:
>
>     QAction* actionInsertTable = new QAction(QIcon(),tr("Table"),this);
>     connect(actionInsertTable,SIGNAL(triggered()),SLOT(handleActionInsertTable()));
>     ACTION_MANAGER->registerAction(constant_insert_table_id,d->actionInsertTable,context);
>
> Qtilities will find the correct placeholder proxy action and add this new action it as an backend action for it.
>
> 4) Controlling the active context:
>
> When your editor tab gets its focus you need to set the active context in the context manager. This will cause the correct backend action to be triggered when the proxy action is triggered.
>
> Its easy to do, in your FocusIn event do this:
>
>     CONTEXT_MANAGER->setNewContext(contextString(),true);
>
> And in your FocusOut event do this:
>
>     // Remove the context from the set of active contexts.
>     CONTEXT_MANAGER->removeContext(contextString());
>

A couple of things about doing it in FocusIn/Out:
1) It would have to be on the QTextEdit rather than the tab as the tab itself doesn't get focus.
2) When they click a menu item the QTextEdit loses focus, so it would remove that context and the action would essentially be disconnected right? It definitely loses focus, I tested this. I would need to set that context whenever the tab switches.
3) Wouldn't I want to append this context rather than set it? Wouldn't I want a scheme where I have the active contexts:
Editing
Editing.Document123
and then
Editing
Editing.Document987

For example, I would attach that Editing context to the Insert->Table so that if we're not editing (lets say you've got the contexts Browsing and Browsing.Document123 active) it disables that menu option.
Reply all
Reply to author
Forward
0 new messages