[Development] QAction-like API for QML

Shawn Rutledge shawn.rutledge at digia.com
Tue Dec 11 12:12:30 CET 2012


On Mon, Dec 10, 2012 at 07:39:36PM -0800, Alan Alpert wrote:
> Note that it has no-where near as many properties as QAction. toolTip
> and whatsThis for example are very desktop centric so not needed
> anymore. Because it's not actually doing the rendering it doesn't need
> enabled, visible or font. As for the rest I think it's better to start

Well as you pointed out at the beginning, action objects will be used for
desktop apps too.  So maybe the tooltip is still useful.

The point of enabled and visible is not that the action renders itself
(even with the widget implementation it does not render itself), but 
rather that when an action is used simultaneously by menu items and/or 
toolbar buttons on multiple application windows, you can disable or 
hide all of them at the same time.  I think this is just as important
a mechanism now as it ever was (unless you confine yourself to single-window
phone applications, which we are not).

> with the minimal essentials and add stuff later if it's needed. I'm on
> the fence about whether checkable needs to be in the first version
> even, I can't recall the last time I actually saw a checkable menu
> item.

I'm sure they will still get used.  I've used them myself, recently even.

As an aside, I think checkable menu items should offer the option to use
either an image or a Unicode character for the actual check mark; it can 
cut down on the number of extra image assets that need to be bundled.  But
I doubt that needs to be in the Action itself, because it tends to be the
same across the whole application, so there's no point in repeating it 
in each action.

> For the actual usecases of menus and toolbars, there's one more useful
> type. QAction can have a QMenu added to it for submenus, which seems
> like a leaky abstraction to me (not that it was as abstract in
> QtWidgets). So an ActionGroup type could be added for that
> functionality (and maybe the QActionGroup functionality), which might
> look something like this:
> 
> ActionGroup type:
> Action {
>     default property list<Action> actions
>     property bool exclusive: false
>     property bool collapsible: false
> }

So ActionGroup is a kind of Action, which inherits everything and adds
the children list, right?

> Exclusive is there to provide the old QActionGroup functionality,
> although I don't know if it's actually useful enough to be worth
> implementing in the first version of the API. The main point is the
> default actions property and the collapsible hint. The collapsible
> hint provides a way of implementing submenus, in that if the control
> set menu supports that they can collapse the actions in the group into
> one menu which has the ActionGroup's text for a title. If it's not

The terminology has the potential to be confusing but I can think of at 
least these use cases for action groups:
1) submenus
2) groups of menu items inline with other such groups, for the purpose of
making a visual boundary (such as a separator on either a menu or a tab bar, 
or grouping checkboxes into a group box on a dialog) as Samuel pointed out
3) making an exclusive group

So I think you are saying collapsible means that it will be a submenu 
which can be expanded on-demand?  An alternative would be to use an enum
named something like ActionGroupType in case we think of other use cases
later.  (Always a good question to ask whenever you create a bool variable, 
"could there ever be a third state").  In this case two bools could be
replaced with one enum, but you could then not have a group which is both
exclusive and a submenu, so you would have to create nested groups.  Maybe
it's more flexible that way, because it's also just as likely that you 
would have a submenu containing some individual actions plus an exclusive
subgroup.

ActionGroup {
	id: menuSet
	type: Action.Group
	Action { ... }
	Action { ... }
	ActionGroup {
		id: subMenuSet
		type: Action.Group
		Action { ... }
		ActionGroup {
			type: Action.ExclusiveGroup
			Action { text: "this way" }
			Action { text: "that way" }	
		}
	}
}

Actually we need to think very abstractly about the action tree.
A whole application probably only needs one tree, organized according
to the structure of the menubar (regardless of whether it is actually
a desktop application).  Think of it as a tree rather than how it will
be realized in components.  This is what we need for portability to 
the devices that we have today plus the ones we can't imagine yet, and
also for the sake of accessibility.  So, the terminology for properties
should never include words which are applicable only to menus, only to
toolbars or the like.  The tooltip text should rather be called 
mediumLengthDescription or something like that, and whatsThis (if we keep 
it at all) could be the longDescription (it's usually a paragraph or so, right?)

To abstract further, the metadata (different length descriptions, link to
the help text, etc.) should be put into a list so that it's extensible,
and there will not be empty fields on platforms that don't need those 
fields.  The syntax needs work but something like

Action {
  id: openFileAction
  text: "Open..."
  properties { 
    longText: "This will open a file dialog so that you can select a file."
    mediumText: "Open a file"
    helpURL: "qthelp://com.appco.whizbang.21/doc/files.html#fileOpen"
  }
}

You could add any number of extra properties without our having to 
pre-imagine them, so Action itself can indeed be kept quite minimal.
This is much better than e.g. keeping whatsThis as a separate field and 
having it fall into obsolescence.

> supported, the actions are still all there. This relies on a key
> difference from QActionGroup, which is that actions are automatically
> added to the menu. Here's a hypothetical example of how it could work
> in practice.
> 
> MenuControl {
>     //Default property is property list<Action> actions
>     ActionGroup {
>         id: fileActions
>         text: "File"
>         collapsible: true
>         Action { text: "New..."; onTriggered: launchNewFileDialog() }
>         Action { text: "Load..."; onTriggered: launchLoadFileDialog() }
>         Action { text: "Save..."; onTriggered: launchSaveFileDialog() }
>         ActionGroup {
>             text: "Recent"
>             collapsible: true
>             QtObjectRepeater {
>                 model: recentFiles
>                 delegate: Action { text: fileName; onTriggered:
> openFile(fileName) }
>             }
>         }
>     }
>     Action { id: quitAction; text: "Quit"; onTriggered: Qt.quit(); }
> }
> 
> ToolBarControl {
>     //Default property is  property list<Action> actions
>     actions: [fileActions, quitAction]
> }

In case it wasn't clear from above, I think the action tree should be a
separate entity, and then the actions should be realized inside the menus,
toolbars, dialogs etc.  That way we can keep the functionality of the
enabled and visible properties, and the onTriggered will be the same
regardless where the action was triggered from.  So I suppose that means
every action needs an ID, and then you would refer to them that way;
it would be very succinct whenever you want to just plop the entire
action tree into a menubar, but more cumbersome when you want to pick 
and choose individual actions or subtrees of them:

Menubar { actions: actionTree }

Toolbar {
	ToolButton { action: fileOpen }
	ToolButton { action: fileClose }
}

or maybe just give a list of IDs to a single actions property in the toolbar.

> the one id. If there was a Repeater that worked with non-gui types you
> could use that Repeater to generate actions dynamically. Speaking of

That's a good idea.




More information about the Development mailing list