Contexts are OR'd not AND'd?

17 views
Skip to first unread message

Simon Gemmell

unread,
Jan 21, 2013, 8:21:35 PM1/21/13
to qtil...@googlegroups.com
I'm still trying to work out how to get ACTION_MANAGER to work for my situation.

The current problem is that I always have TWO contexts active. One which is the widget which is currently in focus, and the second is the type of the element that is currently selected. So for example, lets say I have a Document which contains distinct Frames. If we're in a Frame I want to be able to have Actions for Add Frame Above and Add Frame Below, Delete Frame and Add Sub-Frame. I don't want the Delete Frame to be active when we're not in a Frame. Sounds easy enough. But as we discussed in the previous thread, I need to set the context to be the QTextEdit that is currently being edited so that the actions get forwarded to the right spot.  So I end up with a two contexts active at any time (the current widget and the type of element under the cursor). 

The problem is that Qtilities see's this as being active in EITHER context, so I end up getting this when I register the backend action in the second QTextEdi that is createdt:
ACTION_MANAGER->registerAction(CommonActions::removeFrameId, action, compositeContext);

   Attempting to register a backend action for a proxy action twice for a single context with name:  "Frame" . Last action will be ignored:  "&Delete Frame"

So in my situation, I'm needing a context which is actually AND'd together to be active rather than OR'd together. 

Anything I can do?





Simon Gemmell

unread,
Jan 21, 2013, 9:52:47 PM1/21/13
to qtil...@googlegroups.com
A thought: Maybe if it were a QList of sets of contexts? e.g. a list of lists with the convenience overload for a single list. So the inner list are AND'd to create a compositeContext, the outer list is OR'd so that you can have multiple sets of contexts.


Maybe I'm doing it wrong? Maybe I should be doing this: create composite contexts as a single context. So rather than have Frame and Editor123, I'd have Editor123EditingFrame and Editior123EditingDocument (and all the other variants). And then you'd have to register your actions with all the varying contexts. So if I wanted it just to be active in the Editor123 I'd need to register it for all possible types...

Jaco Naude

unread,
Jan 22, 2013, 1:12:59 AM1/22/13
to qtil...@googlegroups.com
Hi Simon

Continuing our discussions related to the action manager, before I get to your previous posts, the following issues was also fixed by this morning's commit: #79, #80, #81. 

I'm not sure I understand exactly the problem you are having with the document frame example you gave since I'm not sure exactly how you are doing it. You would need to register a context for every frame you have (it can be the same context id if you only allow editing one frame at a time and you unregister it once editing of the frame stops). The frame specific actions will be registered with this context, and general editing actions with the context of your editor. When the document gets focus, use the setNewContext() function on context manager with the context of the document. Then when you enter a frame append the context of the frame to it. 

Lastly, about the context aware line edit, are you happy with your solution using the new ability to unregister contexts from the context manager?

Regards,
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.

Simon Gemmell

unread,
Jan 22, 2013, 7:40:24 AM1/22/13
to qtil...@googlegroups.com
Hi Jaco,

Here is the problem in a little more detail - the Frames are inside the QTextEdit and you can only editing one frame at a time:

In my mainform:
command = ACTION_MANAGER->registerActionPlaceHolder("DeleteFrame", "Delete Frame", EditorKeySequences::deleteFrame);
command->action()->setIcon(QIcon(":/ICDE Icons/numbering_remove.png"));
menu->addAction(command);

In my QTextEdit subclass c'tor (or function called from the c'tor):

// Set up the context
// Find an integer which will uniquely identify this STE
int i = 0;
do 
{
   i++;
   contextString_ = IceContexts::editingDocument + QString::number(i);
} while (CONTEXT_MANAGER->hasContext(contextString_) == true);

CONTEXT_MANAGER->registerContext(contextString_); // Which is our Editor123 context

...
QList<int> thisContext;
thisContext.push_front(CONTEXT_MANAGER->contextID(contextString_));

...
action = new QAction("Delete Frame", this);
connect(action, SIGNAL(triggered()), SLOT(DeleteFrameAtCursor()));
ACTION_MANAGER->registerAction("DeleteFrame", action, thisContext);

Which works FINE, except I don't want to this item to be active when we're not in a Frame. So I do this

QList<int> thisContext;
thisContext.push_front(CONTEXT_MANAGER->contextID(contextString_));
thisContext.push_front(CONTEXT_MANAGER->contextID("Frame")); // I don't actually use it like this, but that's besides the point.

...
action = new QAction("Delete Frame", this);
connect(action, SIGNAL(triggered()), SLOT(DeleteFrameAtCursor()));
ACTION_MANAGER->registerAction("DeleteFrame", action, thisContext); 

Which also works fine so long as you only have one TextEdit. But when you have two, it produces an error which looks like this (I've changed the names a bit, so it's a bit out of whack):
Attempting to register a backend action for a proxy action twice for a single context with name:  "Frame" . Last action will be ignored:  "Delete Frame" 

Which it's doing because another TextEdit has registered the backend action for the "Frame" context already. 

The mechanism for changing the context is pretty much as you describe it:
void SmartTextEdit::focusInEvent(QFocusEvent *e)
{
   CONTEXT_MANAGER->setNewContext(contextString_);
   CONTEXT_MANAGER->appendContext(selectedElement_->TypeAsString()); // So if it's in a Frame, it'll append "Frame". 
}

and then whenever the cursor position changes it does:
if (selectedElement.Type() != unselectedElement.Type())
{
   CONTEXT_MANAGER->removeContext(unselectedElement.TypeAsString());
   CONTEXT_MANAGER->appendContext(selectedElement.TypeAsString());
}

Essentially I want to do this: 
QList<int> thisContext;
thisContext.push_front(CONTEXT_MANAGER->contextID(contextString_));
thisContext.push_front(CONTEXT_MANAGER->contextID("Frame")); // I don't actually use it like this, but that's besides the point.

...
action = new QAction("Delete Frame", this);
connect(action, SIGNAL(triggered()), SLOT(DeleteFrameAtCursor()));
ACTION_MANAGER->registerAction("DeleteFrame", action, thisContext); 

And have that action active when both of those contexts are active - not either, but both. Essentially that it treats thisContext as a tuple/set... does that make it clearer? As a tuple/set it will be unique, treated separately I'll get that error because the "Frame" context already has a backend.

REgards,
Simon

PS. Will respond to your other queries tomorrow. It's late here.

Simon Gemmell

unread,
Jan 22, 2013, 6:25:50 PM1/22/13
to qtil...@googlegroups.com
With the latest no icons are shown for any of my commands. If I add the action directly my icon is there (so it's no that my icons aren't loading properly or anything).

This is backwards:
if (!d->active_backend_action->icon().isNull())
        d->proxy_action->setIcon(d->proxy_action_backup->icon());
    else
        d->proxy_action->setIcon(d->active_backend_action->icon());


As a stylistic note, that's why I prefer explicit comparisons:
if (d->active_backend_action->icon().isNull() == false)

is a lot easier to see that that tiny thin exclamation mark. 


On Tuesday, January 22, 2013 5:12:59 PM UTC+11, Jaco Naude wrote:

Simon Gemmell

unread,
Jan 22, 2013, 6:47:22 PM1/22/13
to qtil...@googlegroups.com
And that's still not totally working - proxy actions/commands still don't show their icons, normal actions/commands do. Had a quick look, a
QAction* action = new QAction(this);

action->icon().isNull() is false.... so I'm not sure why no proxy actions are getting icons anymore.

Simon Gemmell

unread,
Jan 22, 2013, 10:16:01 PM1/22/13
to qtil...@googlegroups.com


Lastly, about the context aware line edit, are you happy with your solution using the new ability to unregister contexts from the context manager?

Doesn't seem to be doing the trick. I still get the warning:
Attempting to register a backend action for a proxy action twice for a single context with name:  "InLineEdit" . Last action will be ignored:  ""

Looking at deregisterContext, I can see it just removes the context - it doesn't remove any actions associated with that context. Is that by design?

The code I'm using is below - note I am unregistering in the destructor of the ContextAwareLineEdit.

QWidget* ContextAwareItemEditorFactory::createEditor(QVariant::Type type, QWidget* parent) const 
{
   QWidget* result = nullptr;
   if (type == QVariant::String)
   {
      result = new ContextAwareLineEdit(parent);
   }
   else
   {
      result = QItemEditorFactory::createEditor(type, parent);
   }
   return result;
}


ContextAwareLineEdit::ContextAwareLineEdit(QWidget* parent /*= nullptr*/)
   : QLineEdit(parent)
{   
   inLineEditContext_ = CONTEXT_MANAGER->registerContext(IceContexts::inLineEdit);

   QList<int> thisContext;
   thisContext.push_front(inLineEditContext_);

   // Cut/Copy/Paste to apply in this context

   QAction* action = new QAction(this);
   connect(action, SIGNAL(triggered()), SLOT(cut()));
   ACTION_MANAGER->registerAction(qti_action_EDIT_CUT, action, thisContext);

   action = new QAction(this);
   connect(action, SIGNAL(triggered()), SLOT(copy()));
   ACTION_MANAGER->registerAction(qti_action_EDIT_COPY, action, thisContext);

   action = new QAction(this);
   connect(action, SIGNAL(triggered()), SLOT(paste()));
   ACTION_MANAGER->registerAction(qti_action_EDIT_PASTE, action, thisContext);
}


ContextAwareLineEdit::~ContextAwareLineEdit()
{
   CONTEXT_MANAGER->unregisterContext(inLineEditContext_);
}


void ContextAwareLineEdit::focusInEvent(QFocusEvent* e)
{
   CONTEXT_MANAGER->setNewContext(inLineEditContext_);
   QLineEdit::focusInEvent(e);
}


I can rejig the code so that it registers the context and actions in the c'tor of ContextAwareItemEditorFactory and then hooks them up to ContextAwareLineEdit before it is returned in createEditor()... 

As is, when the ContextAwareLineEdit is deleted, it cleans up it's actions and unregisters the context, but you've still got some commands sitting in Qtilities which have had the QActions deleted from underneath them.


 

Simon Gemmell

unread,
Jan 23, 2013, 12:21:24 AM1/23/13
to qtil...@googlegroups.com
Back to this one: I'm playing around with some actions which need to be disabled when there is no selection. I have a "Convert paragraphs to ..." action, and whenever the selection changes, or in the focusInEvent(), it evaluates whether there is a selection and grabs that command out and enables / disables it by calling the following function:

void MyTextEdit::EnableDisableActionsBasedOnSelection()
{
   const bool hasSelection = this->textCursor().hasSelection();
   Dbc::NotNull(ACTION_MANAGER->command(qti_action_EDIT_COPY))->action()->setEnabled(hasSelection);
   Dbc::NotNull(ACTION_MANAGER->command(qti_action_EDIT_CUT))->action()->setEnabled(hasSelection);
   Dbc::NotNull(ACTION_MANAGER->command(CommonActions::convertParasToDictId))->action()->setEnabled(hasSelection);
}

This actually works surprisingly well. When it changes focus it enables/disables them, when it changes selection it enables/disables them - the only time it doesn't work is when we close the last tab, which I can easily fix by doing this in the destructor:
   CONTEXT_MANAGER->removeContext(contextString_);
   CONTEXT_MANAGER->unregisterContext(contextString_);

So maybe in my frame post earlier today I am relying too heavily on the contexts to set which items are enabled/disabled, and I should use more widget specific code to enable and disable them. - e.g. whenever the cursor position changes, check whether it's in a frame and enable/disable the frame related actions accordingly...

Regards,
Simon

Jaco Naude

unread,
Jan 24, 2013, 1:34:06 AM1/24/13
to qtil...@googlegroups.com
HI Simon

You are right, that's needed. That's what happens when you try to rush things :)

I've pushed the fixes from this earlier this morning.
It should work now.
Cheers
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

unread,
Jan 24, 2013, 1:37:55 AM1/24/13
to qtil...@googlegroups.com
Hi Simon

On the context question. Yes I think you make a good point. You can probably do it with context if we add something to the action manager where it does an AND based check on the active contexts. However I've looked at the sources and its not a trivial change. And to be honest the action manager code is quite complicated and I don't want to change it unless I have lots of time to make sure I do it right.

I think your solution where you register all actions with the editor's context is the best. Then you disable the backend actions locally in your editor depending on its state. I did similar things on a code editor implementation recently where actions like Undo and Redo should only be enabled when the document has undo/redo available. It works pretty well and is not too complicated. The trick now is just to listen to the signal on context manager and refresh your actions everytime the active context(s) changes. Also then obviously when the user selects/unselects a frame etc.

Let me know if this approach does the trick for you.
Cheers
Jaco

Simon Gemmell

unread,
Jan 24, 2013, 2:06:53 AM1/24/13
to qtil...@googlegroups.com
Maybe raise a ticket for it and address it later?

Simon Gemmell

unread,
Jan 24, 2013, 2:08:19 AM1/24/13
to qtil...@googlegroups.com
Hi Jaco,

Did you get to the bottom of placeholder action icons not showing? 

command = ACTION_MANAGER->registerActionPlaceHolder(CommonActions::referenceCandidateId, CommonActions::referenceCandidateText, EditorKeySequences::runRefCandidateFinder);
command->action()->setIcon(QIcon(":/ICDE Icons/search_a.png"));
menu->addAction(command);

Doesn't show the icon unless I add it to the backend. 

Simon

Jaco Naude

unread,
Jan 24, 2013, 2:38:12 AM1/24/13
to qtil...@googlegroups.com
Hi Simon

Let me look into that. What is happening (I think) is that the new way of backuping the parameters of the proxy action happens in the registerActionPlaceHolder() call. The setIcon() call afterwards sets it on the proxy action, not on the backup of the proxy action's parameters. Then when the proxy action gets an active backend without an icon it uses the icon of the backup action which did not have an icon at the time of the registerActionPlaceHolder() call.

Maybe I'll create an access function for the parameters backup action.
Regards,
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
 
 
 

Simon Gemmell

unread,
Jan 24, 2013, 2:49:26 AM1/24/13
to qtil...@googlegroups.com
What you have said makes sense.

If would be good if the registerActionPlaceHolder() could also take an icon, but it doesn't solve the problem for someone who sets the icon on the returned QAction. 

One solution could be to internally hook up to the QAction::changed() signal and overwrite your backup whenever it is fired. 

Jaco Naude

unread,
Jan 25, 2013, 2:36:40 AM1/25/13
to qtil...@googlegroups.com
Hi Simon.

Yes I've thought of that and its probably a good solution to add an icon to the registerActionPlaceHolder() function, as well as the connection on the internal side. However this is not just a simple connection because the proxy action will change everytime an backend action will become active. But I'll figure out a way that "makes sense" from a user perspective. For now I've just added the extra parameter to registerActionPlaceHolder().

Some new feature requests on Github that you might be interested in (#85, #86, #87). 

Cheers
Jaco
Reply all
Reply to author
Forward
0 new messages