If you did not migrate your account yet, visit https://idp-portal-info.suse.com/
openSUSE:YaST development misc button Order
- 1 YaST and KDE vs. GNOME Button Order
- 1.1 Abstract
- 1.2 About Button Orders
- 1.3 Button Order in the YaST UI
- 1.4 ButtonBox Widget Details
YaST and KDE vs. GNOME Button Order
KDE and GNOME use different button orders. YaST needs to run in both environments. To support this properly, buttons need to be placed in a widget that handles the button order depending on the desktop environment, and those buttons need to be assigned a proper button role.
For YCP developers, it will be sufficient in most cases to replace a `HBox() that contains the buttons with a `ButtonBox().
About Button Orders
The button order is relevant for pop-up dialogs that have more than one button. Typically there is a positive answer like [OK] that will use any changes the user might have made in that dialog and proceed with those values.
For usability reasons, if there is more than one button, one button should be a "safe escape" (typically [Cancel] that closes the dialog without changing anything.
KDE Button Order
KDE uses the same button order as Windows: The "positive confirmation" button (usually [OK]) is the leftmost. If there is an [Apply] button, this is the next, followed by [Cancel] as the "safe escape route" button. If there is a [Help] button, it is the rightmost button.
This philosophy follows the reading order common in most Western languages: left to right. The positive confirmation ("OK") is considered the most commonly used button, thus it is placed as the first in the reading order.
The simplest case that is relevant for button order discussions.
In this example, the [Continue] button is the positive confirmation.
[Yes] is the positive confirmation here.
[Apply] is also a positive confirmation, albeit one that leaves the dialog open.
"Custom" buttons (here: [Do Something] and [Do More]) are placed between the buttons with predefined roles (right of [OK], [Apply], [Cancel], left of [Help]).
This dialog is also slightly resized to show how excess space is used: The buttons are centered horizontally.
GNOME Button Order
GNOME uses a button order similar to MacOS: The "positive confirmation" button is the rightmost. The "safe escape route" ("Cancel") is to the left of that button.
The general idea is that the location of [OK] and [Cancel] are always the same, and that the right bottom corner of a dialog is a very prominent place. It is also consistent with "wizard" style dialogs that have a [Next] button at the same place: The positive confirmation is always in the bottom right corner.
This is also why the buttons are right-aligned if there is more space than the buttons need.
[Continue] is the positive answer in this example.
Clearly, this is confusing: People always ask "yes or no", never "no or yes". This reverted order is counterintuitive. But it is used in GNOME for the sake of consistency: The positive confirmation ("Yes") is always the rightmost button.
Notice how even in this case [Cancel] is placed next to [OK], even though [Apply] is yet another positive answer.
GNOME very rarely uses [Apply] buttons: Most GNOME application use "instant apply", meaning that changes immediately take effect as they are done in the dialog.
YaST uses [Apply] only in very few cases, but it is being used; it would be quite awkward to use "instant apply" in a program that could possibly format your hard disk.
Slightly resized to show how the buttons are right-aligned if there is excess space.
Button Order in the YaST UI
The YaST UI now supports a dedicated ButtonBox widget that can can handle the button order all by itself.
In most cases, the migration for YCP code will be as simple as replacing the HBox widget that holds a dialog's buttons with a ButtonBox:
Old YCP code:
`HBox( `PushButton(`id(`ok ), _( "&OK" ) ), `PushButton(`id(`cancel), _( "&Cancel" ) ) )
New YCP code:
`ButtonBox( `PushButton(`id(`ok ), _( "&OK" ) ), `PushButton(`id(`cancel), _( "&Cancel" ) ) )
More YCP examples:
In general, the YCP code has to specify what role a button has:
- customButton (everything else)
okButton is the positive answer of a dialog that also closes it. It might be labelled [OK], but sometimes also [Continue] or [Yes].
In some cases it makes sense to use the verb directly that describes what the dialog does; for example, in a printing dialog it might be labelled [Print], in a file save dialog it might be [Save], etc.
cancelButton is the "safe escape". Typically it is [Cancel]. In [Yes] / [No] dialogs it is [No]. Other labels should be avoided.
applyButton is the positive anser that uses a dialogs's values, but does not close the dialog. If a dialog has such a button, it should always be [Apply].
helpButton is the button that invokes online help for this dialog. If a dialog has such a button, it should always be [Help].
customButton is the role for all other buttons that don't have any of the above predefined roles. A dialog can have any number of customButtons, whereas all other roles should be unique in that dialog.
YCP code can assign any of those roles as widget options to PushButton widgets:
`PushButton(`id(`print), `opt(`okButton), _( "&Print" ) )
It is generally not neccessary to explicitly specify `opt(`customButton); this is the default. Specify that only if any of the heuristics mentioned below guess wrong. In that case, `opt(`customButton) overrides the guess.
Heuristics to Auto-Assign Button Roles
The YaST UI has some built-in logic to recognize many common buttons automatically.
By Button Label
Well-known button labels are used, just like with automatically assigning function keys to buttons: The UI::SetFunctionKeys() function maps (translated!) button labels to function keys:
|Button Label||Function Key||Button Role|
See also Label::DefaultFunctionKeyMap() in Label.ycp
The advantage of this is that it even works with the translated labels (if used properly, of course).
By Function Key
Explicitly using `opt(`key_F10), `opt(`key_F9), or `opt(`key_F1) has a similar effect:
|Function Key||Button Role|
By Widget ID
Some common widget IDs are used to determine a button's role:
|Widget ID||Button Role|
(all IDs are compared case-insensitive)
See also YCPDialogParser::parseButtonBox() at YCPDialogParser.cc
The advantage of this is that it is independent of translated button labels and of UI::SetFunctionKeys().
But it might go wrong, too. In that case, it might be necessary to explicitly use `opt(`customButton) to override such an automatically assigned button role. Check the y2log for lines like this:
Guessed button role YApplyButton for YPushButton "Apply" at 0x80c01b4 from widget ID
Choosing a Button Order
- NCurses UI: Uses KDE button order
- Gtk UI: Uses GNOME button order
- Qt UI: Uses KDE button order by default, but can use GNOME button order, too:
- $DESKTOP_SESSION environment variable
- $WINDOWMANAGER environment variable
- $Y2_BUTTON_ORDER environment variable
- --gnome-button-order (command line argument to y2base)
- --kde-button-order (command line argument to y2base)
$DESKTOP_SESSION and $WINDOWMANAGER are set by the desktop environment. $Y2_BUTTON_ORDER is intended for power users who wish to override any defaults chosen by the former: Set it to "kde" or "gnome" (case insensitive).
ButtonBox Widget Details
A ButtonBox can have only PushButton child widgets. It will refuse anything else. This is by design: The ButtonBox takes care of margins, spacings and alignment. Any other widgets (like HSpacing, HStretch, Right, Left, Center, etc.) would only be in the way.
Typically, a ButtonBox should stretch the entire width of a dialog. It should be the bottom-most widget in the dialog.
Upon resizing, if there is not enough space, a ButtonBox widget will give up margins and spacings first before trying to resize buttons. If there is still not enough space, it will (in GNOME button order) give up the constraint to try to use the same width for all buttons.
The Sanity Check
If a ButtonBox has more than one button, it performs a sanity check: It checks if there is exactly one okButton and exactly one cancelButton. There can be exactly one applyButton and exactly one helpButton, but any number of customButtons.
If any of those constraints is violated, it throws an exception that will make UI::OpenDialog() fail (i.e., return false).
In rare cases, it makes sense to relax that sanity check.
More than one button, but no cancelButton, just an okButton
In that case, use `opt(`relaxSanityCheck):
`ButtonBox(`opt(`relaxSanityCheck), `PushButton(`id(`ok ), _( "&OK" ) ), `PushButton(`id(`cancel), _( "&Details" ) ) )
Use your common sense when to use that.