1.2.UI Events

1.2.1.Introduction

1.2.1.1.The YaST2 Event Model

1.2.1.1.1.Classic GUI Event Loops

Classic graphical user interface (GUI) programming is almost always event-driven: The application initializes, creates its dialog(s) and then spends most of its time in one central event loop.

When the user clicks on a button or enters text in an input field, he generates events. The underlying GUI toolkit provides mechanisms so the application can react to those events - perform an action upon button click, store the characters the user typed etc.; all this is done from callback functions of one kind or the other (whatever they may be called in the respective GUI toolkit).

In any case, it all comes down to one single event loop in the application from where small functions (let's call them callbacks for the sake of simplicity) are called when events occur. Those callbacks each contain a small amount of the application's GUI logic to do whatever is to be done when the respective event occurs. The overall application logic is scattered among them all.

This approach is called event-driven. Most GUI toolkits have adopted it.

Depending on the primary goal of a GUI application, this event-driven approach may or may not be appropriate. It is perfectly suitable for example for word processor applications, for web browsers or for most other GUI applications that have one central main window the user works with most of his time: The user is the driving force behind those kinds of applications; only he knows what he next wishes to do. The application has no workflow in itself.

Thus the event-driven application model fits perfectly here: The callbacks can easily be self-contained; there is little context information, and there are limited application-wide data.

1.2.1.1.2.The YaST2 Approach

Applications like YaST2 with all its installation and configuration workflows, however, are radically different. The driving force here is the application workflow, the sequence of dialogs the user is presented with.

Of course this can be modeled with a traditional event loop, but doing that considerably adds to the complexity of the application: Either the application needs a lot more callbacks, or the callbacks need to keep track of a lot of status information (workflow step etc.) - or both.

For the YaST2 UI, a different approach was chosen: Rather than having one central event loop and lots of callbacks, the flow control remains in the interpreted YCP code. User input is requested on demand - very much like in simplistic programming languages like the first versions of BASIC.

This of course means that there is no single one central "waiting point" in the program (like the event loop in the event-driven model), but rather lots of such waiting points spread all over the YCP code within each UserInput() or WaitForEvent() statement.

Side note: Of course a graphical UI like the YaST2 Qt UI still has to be prepared to perform screen redraws whenever the underlying window system requires that - i.e. whenever X11 sends an Expose (or similar) event. For this purpose the Qt UI is multi-threaded: One thread takes care of X event handling, one thread is the actual YCP UI interpreter. This instant screen redraw is what you lose when you invoke y2base with the "--nothreads" command line option.

YCP was meant to be an easy-to-understand programming language for developers who specialize in a particular aspect of system configuration or installation, not in GUI programming.

Practical experience with all the YaST2 modules developed so far has shown that application developers tend to adopt this concept of UserInput() very easily. On the other hand it is a widely known fact that event-driven GUI programming means a steep learning curve because (as mentioned before) it requires splitting up the application logic into tiny pieces for all the callbacks.

Thus, this design decision of YaST2 seems to have proven right much more often than there are problems with its downsides (which of course also exist).

1.2.1.1.3.Simplicity vs. Features

The basic idea of YaST2 UI programming is to create a dialog asking the user for some data and then continue with the next such dialog - meaning that most of those dialogs are basically forms to be filled in with an "OK" (or "Next") and a "Cancel" (or "Back") button. The YCP application is usually interested only in those button presses, not in each individual keystroke the user performs.

This is why by default UserInput() and related functions react to little more than button presses - i.e. they ignore all other events, in particular low-level events the widgets handle all by themselves like keystrokes (this is the input fields' job) or selecting items in selection boxes, tables or similar. Most YCP applications simply don't need or even want to know anything about that.

This makes YCP UI programming pretty simple. The basic principle looks like this:

{
    UI::OpenDialog(
            `VBox(
                ... // Some input fields etc.
                `HBox(
                    `PushButton(`id(`back ), "Back" ),
                    `PushButton(`id(`next ), "Next" )
                    )
                )
            );

    symbol button_id = UI::UserInput();

    if ( button_id == `next )
    {
        // Handle "Next" button
    }
    else if ( button_id == `back )
    {
        // Handle "Back" button
    }

    UI::CloseDialog();
}
                

Strictly spoken, you don't even require a loop around that - even though this is very useful and thus strongly advised.

All that can make UserInput() return in this example are the two buttons. Other widgets like input fields ( InputField), selection boxes etc. by do not do anything that makes UserInput() return - unless explicitly requested.

1.2.1.1.4.The notify Option

If a YCP application is interested in events that occur in a widget other than a button, the notify widget option can be used when creating it with UI::OpenDialog().

Example1.7.The notify option

UI::OpenDialog(...
  `SelectionBox(`id(`pizza ), `opt(`notify ), ... ),
   ...
  `Table(`id(`toppings), `opt(`notify, `immediate ), ... ),
  ...
)
                    

In general, the notify options makes UserInput() return when something "important" happens to that widget. The immediate option (always in combination with notify!) makes the widget even more "verbose".

Note: UserInput() always returns the ID of the widget that caused an event. You cannot tell the difference when many different types of event could have occured. This is why there are different levels of verbosity with `opt(`notify ) or `opt(`notify, `immediate ) and the new WaitForEvent() UI builtin function which returns more detailed information. A Table widget for example can generate both Activated and SelectionChanged WidgetEvents.

Exactly what makes UserInput() return for each widget class is described in full detail in the YaST2 event reference.


1.2.1.1.5.Downsides and Discussions

The YaST2 event handling model has been (and will probably always remain) a subject of neverending discussions. Each and every new team member and everybody who casually writes a YaST2 module (to configure the subsystem that is his real responsibility) feels compelled to restart this discussion.

The idea of having a function called UserInput() seems to conjure up ghastly memories of horrible times that we hoped to have overcome: The days of home-computer era BASIC programming or university Pascal lectures (remember Pascal's readln()?) or even low-tech primitive C programs (gets() or scanf() are not better, either).

But it's not quite like that. Even though the function name is similar, the concept is radically different: It is not just one single value that is being read, it is a whole dialog full of whatever widgets you see fit to put there. All the widgets take care of themselves; they all handle their values automatically. You just have to ask them (UI::QueryWidget()) for the values when you need them (leave them alone as long as you don't).

The similarity with computing stone age remains, however, in that you have to explicitly call UserInput() or related when you need user input. If you don't, you open your dialog, and a moment later when you continue in your code it closes again - with little chance for the user to enter anything.

Thus, the YaST2 approach has its intrinsic formalisms in that sequence:

OpenDialog(...);

UserInput();
QueryWidget(...);
QueryWidget(...);
QueryWidget(...);
...

CloseDialog();

                

This is the price to pay for this level of simplicity.

1.2.1.1.6.Design Alternatives

In the course of those discussions some design alternatives began to emerge:

  1. Use the single-event-loop and callback model like most other toolkits.

  2. Keep multiple event loops (like UserInput()), but add callbacks to individual widget events when needed so the YCP application can do some more fine-grained control of individual events.

  3. Keep multiple event loops, but return more information than this simplistic UserInput() that can return no more than one single ID.

Having just a single event loop would not really solve any problem, but create a lot of new ones: A sequence of wizard style dialogs would be really hard to program. Switching back and forth between individual wizard dialogs would have to be moved into some callbacks, and a lot of status data for them all to share (which dialog, widget status etc.) would have to be made global.

What a mess. We certainly don't want that.

All the callback-driven models have one thing in common: Most of the application logic would have to be split up and moved into the callbacks. The sequence of operations would be pretty much invisible to the application developer, thus the logical workflow would be pretty much lost.

Most who discussed that agreed that we don't want that, too.

Add to that the formalisms that would be required for having callbacks: Either add a piece of callback code (at least a function name) to UI::OpenDialog() for each widget that should get callbacks or provide a new UI builtin function like, say, UI::SetCallback() or UI::AddCallback() that gets a YCP map that specifies at least the widget to add the callback to, the event to react to and the code (or at least a function name) to execute and some transparent client data where the application can pass arbitrary data to the callback to keep the amount of required global data down.

UI::RemoveCallback()

It might look about like this:

define void selectionChanged( any widgetID, map event, any clientData ) {
   ...
   // Handle SelectionChanged event
   ...
};

define void activated( any widgetID, map event, any clientData ) {
   ...
   // Handle Activated event
   ...
};

...
UI::OpenDialog(
   ...
   `Table(`id(`devices ), ... ),
   ...
);
...
UI::AddCallback(`id(`devices ), `SelectionChanged, nil );
UI::AddCallback(`id(`devices ), `Activated, nil );

                

If you think "oh, that doesn't look all too bad", think twice. This example is trivial, yet there are already three separate places that address similar things:

  • The callback definitions. Agreed, you'll need some kind of code that actually does the application's business somewhere anyway. But chances are that the callbacks are no more than mere wrappers that call the functions that actually do the application's operations. You don't want to mix up all the back engine code with the UI related stuff.

  • Widget creation with UI::OpenDialog()

  • Adding callbacks with UI::AddCallback()

A lot of GUI toolkits do it very much this way - most Xt based toolkits for example (OSF/Motif, Athena widgets, ...). But this used to be a source of constant trouble: Change a few things here and be sure that revenge will come upon you shortly. It simply adds to the overall complexity of something that is already complex enough - way enough.

Bottom line: Having callbacks is not really an improvement.

What remains is to stick to the general model of YaST2 but return more information - of course while remaining compatible with existing YCP code. We don't want (neither can we economically afford to) break all existing YCP code. So the existing UI builtin functions like UserInput() or PollInput() have to remain exactly the same. But of course we can easily add a completely new UI builtin function that does return more information.

This is what we did. This is how WaitForEvent() came into existence. It behaves like UserInput(), but it returns more information about what really happened - in the form of an event map rather than just a single ID. That map contains that ID (of course) plus additional data depending on the event that occured.

One charming advantage of just adding another UI builtin is that existing code does not need to be touched at all. Only if you want to take advantage of the additional information returned by WaitForEvent() you need to do anything at all.

So let's all hope with this approach we found a compromise we all can live with. While that probably will not prevent those discussions by new team members, maybe it will calm down the current team members' discussion a bit. ;-)

1.2.1.2.Event Delivery

1.2.1.2.1.Event Queues vs. One Single Pending Event

Since the YaST2 UI doesn't have a single event loop where the program spends most of its time, an indefinite period of time may pass between causing an event (e.g., the user clicks on a widget) and event delivery - the time where the (YCP) application actually receives the event and begins processing it. That time gap depends on exactly when the YCP code executes the next UserInput() etc. statement.

This of course means that events that occured in the mean time need to be stored somewhere for the YCP code to pick them up with UserInput() etc.

The first approach that automatically comes to mind is "use a queue and deliver them first-in, first-out". But this brings along its own problems:

Events are only useful in the context of the dialog they belong to. When an event's dialog is closed or when a new dialog is opened on top of that event's dialog (a popup for example) it doesn't make any more sense to handle that event. Even worse, it will usually lead to utter confusion, maybe even damage.

Imagine this situation: The user opens a YaST2 partitioning module just to have a look at his current partitioning scheme.

Side note: This scenario is fictious. The real YaST2 partitioning module is not like that. Any similarities with present or past partitioning modules or present or past YaST2 hackers or users is pure coincidence and not intended. Ah yes, and no animals were harmed in the process of making that scenario. ;-)

  • The main dialog with an "OK" button (with, say, ID `ok) opens.

  • It takes some time to initialize data in the background.

  • The user clicks "OK".

  • The background initialization takes some more time.

  • The user becomes impatient and clicks "OK" again.

  • The background initialization still is not done.

  • The user clicks "OK" again.

  • The initialization is done. Usually, the YCP code would now reach UserInput() and ueued events would be delivered (remember, this is only a fictious scenario - the UI does not really do that). The first "OK" click from the queue is delivered - i.e. UserInput() returns `ok.

    But this doesn't happen this time: The initialization code found out that something might be wrong with the partitioning or file systems. It might make sense to convert, say, the mounted /usr file system from oldLameFs-3.0 to newCoolFs-0.95Beta - which usually works out allright, but of course you never know what disaster lies ahead when doing such things with file systems (and, even worse, with an experimental beta version).

  • The initialization code opens a popup dialog with some text to informs the user about that. The user can now click "OK" to do trigger the file system conversion or "Cancel" to keep everything as it is.

  • The handler for that popup dialog calls UserInput() - which happily takes the next event from the queue - the `ok button click that doesn't really belong to that dialog, but UserInput() cannot tell that. Neither can the caller. It simply gets `ok as if the user had clicked the "OK" button in the popup.

  • The program has to assume the user confirmed the request to convert the file system. The conversion starts.

  • The experimental beta code in newCoolFs-0.95Beta cannot handle the existing data in that partition as it should. It asks if it is allright to delete all data on that partition. Another popup dialog opens with that question.

  • The handler for that confirmation popup takes the next event from the queue which is the third `ok click that should have gone to the main window. But the handler doesn't know that and takes that `ok as the confirmation it asked for.

  • /usr is completely emptied. Half of the system is gone (along with most of YaST2's files). The disaster is complete - the system is wrecked beyond repair.

Argh. What a mess.

Yes, this example is contrived. But it shows the general problem: Events belong to one specific dialog. It never makes any sense to deliver events to other dialogs.

But this isn't all. Even if the internal UI engine (the libyui) could make sure that events are only delivered to the dialog they belong to (maybe with a separate queue for each dialog), events may never blindly be taken from any queue. If the user typed (or clicked) a lot ahead, disaster scenarios similar to the one described above might occur just as well.

Events are context specific. The dialog they belong to is not their only context; they also depend on the application logic (i.e. on YCP code). This is another byproduct of the YaST2 event handling approach.

It has been suggested to use (per-dialog) event queues, but to flush their contents when the dialog context changes:

  • When a new dialog is opened (OpenDialog())

  • When the current dialog is closed (CloseDialog())

  • When parts of the dialog are replaced (ReplaceWidget())

  • Upon the YCP application's specific request (new UI builtin FlushEvents())

Exactly when and how this should happen is unclear. Every imaginable way has its downsides or some pathologic scenarios. You just can't do this right. And YCP application developers would have to know when and how this happens - which is clearly nothing they should be troubled with.

This is why all current YaST2 UIs have onle one single pending event and not a queue of events. When a new event occurs, it usually overwrites any event that may still be pending - i.e. events get lost if there are too many of them (more than the YCP application can and wants to handle).

1.2.1.2.2.Event Reliability

While it may sound critical to have only one single pending event, on this works out just as everybody expects:

  • When the YCP application is busy and the user clicks wildly around in the dialog, only the last of his clicks is acted upon. This is what all impatient users want anyway: "do this, no, do that, no, do that, no, cancel that all". The "Cancel" is what he will get, not everything in the sequence he clicked.

  • The YCP application does not get bogged down by a near-endless sequence of events from the event queues. If things are so sluggish that there are more events than the application can handle in the first place, getting even more to handle will not help any.

  • YaST2 dialogs are designed like fill-in forms with a few (not too many) buttons. The input field widgets etc. are self-sufficient; they do their own event handling (so no typed text will get lost). No more than one button click in each dialog makes sense anyway. After that the user has to wait for the next dialog to answer more questions. It does not make any sense to queue events here; the context in the next dialog is different anyway.

As described above, events can and do get lost if there are too many of them. This is not a problem for button clicks (the most common type of event), and it should not be a problem for any other events if the YCP application is written defensively.

1.2.1.2.3.Defensive Programming

Don't take anything for granted. Never rely on any specific event to always occur to make the application work allright.

In particular, never rely on individual SelectionChanged WidgetEvents to keep several widgets in sync with each other. If the user clicks faster than the application can handle, don't simply count those events to find out what to do. Always treat that as a hint to find out what exactly happened: Ask the widgets about their current status. They know best. They are what the user sees on the screen. Don't surprise the user with other values than what he can see on-screen.

In the past, some widgets that accepted initially selected items upon creation had sometimes triggered events for that initial selection, sometimes not. Even though it is a performance optimization goal of the UI to suppress such program-generated events, it cannot be taken for granted if they occur or not. But it's easy not to rely on that. Instead of writing code like this:

{
    // Example how NOT to do things

    UI::OpenDialog(
                    ...
                    `SelectionBox(`id(`colors ),
                        [
                            `item(`id("FF0000"), "Red" ),
                            `item(`id("00FF00"), "Blue",true),  // Initially selected
                            `item(`id("0000FF"), "Green" )
                        ] 
                    )
    );

    // Intentionally NOT setting the initial color:
    //
    // Selecting an item in the SelectionBox upon creation will trigger a
    // SelectionChanged event right upon entering the event loop.
    // The SelectionChanged handler code will take care of setting the initial color.
    // THIS IS A STUPID IDEA!

    map event = $[];

    repeat
    {
          event = UI::WaitForEvent();

          if ( event["ID"]:nil == `colors )
          {
              if ( event["EventReason"]:nil == "SelectionChanged" )
              {
                  // Handle color change
                  setColor( UI::QueryWidget(`id(`colors ), `SelectedItem ) );
              }
          }
          ...
    } until ( event["ID"]:nil == `close );
}

                
{
    // Fixed the broken logic in the example above

    UI::OpenDialog(
                    ...
                    `SelectionBox(`id(`colors ),
                    [
                    `item(`id("FF0000"), "Red" ),
                    `item(`id("00FF00"), "Blue",true),  // Initially selected
                    `item(`id("0000FF"), "Green" )
                    ] ),
                    );

    // Set initial color
    setColor( UI::QueryWidget(`id(`colors ), `SelectedItem ) );

    map event = $[];

    repeat
    {
        event = UI::WaitForEvent();

        if ( event["ID"]:nil == `colors )
        {
            if ( event["EventReason"]:nil == "SelectionChanged" )
            {
                // Handle color change
                setColor( UI::QueryWidget(`id(`colors ), `SelectedItem ) );
            }
        }
        ...
    } until ( event["ID"]:nil == `close );
}

                

It's that easy. This small change can make code reliable or subject to failure on minor outside changes - like a version of the Qt lib that handles things differently and sends another SelectionChanged Qt signal that might be mapped to a SelectionChanged WidgetEvents - or does not send that signal any more like previous versions might have done.

Being sceptical and not believing anything, much less taking anything for granted is an attitude that most programmers adopt as they gain more an more programming experience.

Keep it that way. It's a healthy attitude. It helps to avoid a lot of problems in the first place that might become hard-to-find bugs after a while.

1.2.2.Event-related UI Builtin Functions

This section describes only those builtin functions of the YaST2 user interface that are relevant for event handling. The YaST2 UI has many more builtin functions that are not mentioned here. Refer to the UI builtin reference for details.

The Event-related UI Builtin are available in the reference

1.2.3.Event Reference

1.2.3.1.Event Maps in General

Use WaitForEvent() to get full information about a YaST2 UI event. UserInput() only returns a small part of that information, the ID field of the event map.

The event map returned by WaitForEvent() always contains at least the following elements:

Map KeyValue TypeValid ValuesDescription
EventTypestring
  • The type of this event.

  • Use this for general event classification.

IDanyThe ID (a widget ID for WidgetEvents) that caused the event. This is what UserInput() returns.
EventSerialNointeger>= 0The serial number of this event. Intended for debugging.

1.2.3.2.Event Types

1.2.3.2.1.WidgetEvent

All WidgetEvents have these map fields in common:

Map KeyValue TypeValid ValuesDescription
EventTypestringWidgetEvent(constant)
EventReasonstring The reason for this event. This is something like an event sub-type. Use this to find out what the user really did with the widget.
IDanyThe ID of the widget that caused the event. This is what UserInput() returns.
WidgetIDanyThe ID of the widget that caused the event. This is nothing but an alias for "ID", but with this alias you can easily find out if this is a widget event at the same time as you retrieve the widget ID: No other events than WidgetEvent have this field.
WidgetClassstringPushButton SelectionBox Table CheckBox ...The class (type) of the widget that caused the event.
WidgetDebugLabelstring

The label (more general: the widget's shortcut property) of the widget that caused the event - in human readable form without any shortcut markers ("&"), maybe abbreviated to a reasonable length.

This label is translated to the current locale (the current user's language).

This is intended for debugging so you can easily dump something into the log file when you get an event.

Wigets that don't have a label don't add this field to the event map, so make sure you use a reasonable default when using a map lookup for this field: Don't use nil, use "" (the emtpy string) instead.

1.2.3.2.2.Activated WidgetEvent
Map KeyValue TypeValid ValuesDescription
EventReasonstringActivated(constant)

An Activated WidgetEvent is sent when the user explicitly wishes to activate an action.

Traditionally, this means clicking on a PushButton or activating it with some other means like pressing its shortcut key combination, moving the keyboard focus to it and pressing space.

Some other widgets (Table, SelectionBox, Tree) can also trigger this kind of event if they have the notify option set.

User interface style hint: YCP applications should use this to do the "typical" operation of that item - like editing an entry if the dialog has an "Edit" button. Use this Activated WidgetEvent only as a redundant way (for "power users") of invoking an action. Always keep that "Edit" (or similar) button around for novice users; double-clicks are by no way obvious. The user shouldn't need to experiment how to get things done.

Widget TypeWidget OptionsAction to Trigger the Event
PushButton(none)
  • Single click on the button (Qt).

  • Press space on the button.

  • Press return anywhere in the dialog. This activates the dialog's default button if it has any and if the respective UI can handle default buttons.

Table`opt(`notify)
  • Double click on an item (Qt).

  • Press space on an item.

SelectionBox`opt(`notify)
  • Double click on an item (Qt).

  • Press space on an item.

Tree`opt(`notify)
  • Double click on an item (Qt).

    Note: This will also open or close items that have children!

  • Press space on an item.

Note that MenuButton and RichText don't ever send WidgetEvents. They send MenuEvents instead.

1.2.3.2.3.ValueChanged WidgetEvent
Map KeyValue TypeValid ValuesDescription
EventReasonstringValueChanged(constant)

A ValueChanged WidgetEvent is sent by most interactive widgets that have a value that can be changed by the user. They all require the notify option to be set to send this event.

Widgets that have the concept of a "selected item" like SelectionBox, Table, or Tree don't send this event - they send a SelectionChanged WidgetEvent instead. One exception to this rule is the MultiSelectionBox which can send both events, depending on what the user did.

Widget TypeWidget OptionsAction to Trigger the Event
MultiSelectionBox`opt(`notify)Toggle an item's on/off state:
  • Click on an item's checkbox (Qt).

  • Press space on an item.

CheckBox`opt(`notify)Toggle the on/off state:
  • Single click the widget (Qt).

  • Press space on the widget.

RadioButton`opt(`notify)Set this RadioButton to on:
  • Single click the widget (Qt).

  • Press space on the widget.

No event is sent when the button's status is set to off because another RadioButton of the same RadioButtonGroup is set to on to avoid generating a lot of useless events: Only the on case is relevant for most YCP applications.
`opt(`notify)Enter text.
ComboBox`opt(`notify)
  • Select another value from the drop-down list:

    • Open the drop-down list and click on one of its items (Qt).

    • Open the drop-down list, use the cursor keys to move the selection and press space or return to actually accept that item.

      Simply opening the drop-down list and moving the cursor around in it (i.e. changing its selection) does not trigger this event.

  • Enter text (with `opt(`editable ) ).

IntField`opt(`notify)Change the numeric value:
  • Enter a number.

  • Click on the up button (Qt).

  • Click on the down button (Qt).

  • Press cursor up in the widget (NCurses).

  • Press cursor down in the widget (NCurses).

Slider`opt(`notify)
  • Move the slider.

  • Enter a number in the embedded IntField.

  • Use one of the embedded IntField's up / down button.

PartitionSplitter`opt(`notify)
  • Move the slider.

  • Enter a number in one of the embedded IntFields.

  • Use one of the embedded IntFields' up / down button.

1.2.3.2.4.SelectionChanged WidgetEvent
Map KeyValue TypeValid ValuesDescription
EventReasonstringSelectionChanged(constant)

A SelectionChanged WidgetEvent is sent by most widgets that have the concept of a "selected item" like SelectionBox, Table, or Tree when the selected item changes.

Note that the MultiSelectionBox widget can send a SelectionChanged event, but also a ValueChanged WidgetEvent depending on what the user did. This is one reason to keep SelectionChanged and ValueChanged two distinct events: Widgets can have both concepts which may be equally important, depending on the YCP application.

The ComboBox never sends a SelectionChanged event. It only sends ValueChanged WidgetEvents.

The rationale behind this is that merely opening the drop-down list without actually accepting one of its items is just a temporary operation in a separate pop-up window (the drop-down list) that should not affect the YCP application or other widgets in the same dialog until the user actually accepts a value - upon which event a ValueChanged WidgetEvent is sent.

Widget TypeWidget OptionsAction to Trigger the Event
SelectionBox
Qt:`opt(`notify)
NCurses:`opt(`notify,`immediate)
Select another item:
  • Click on an item (Qt).

  • Press cursor up in the widget.

  • Press cursor down in the widget.

Qt:`opt(`notify)
NCurses:`opt(`notify,`immediate)
Table `opt(`notify,`immediate)Select another item:
  • Click on an item (Qt).

  • Press cursor up in the widget.

  • Press cursor down in the widget.

Tree `opt(`notify)Select another item:
  • Click on an item (Qt).

  • Press cursor up in the widget.

  • Press cursor down in the widget.

MultiSelectionBox`opt(`notify)Select another item:
  • Click on an item's text (not on the checkbox) (Qt).

  • Press cursor up in the widget.

  • Press cursor down in the widget.

1.2.3.2.5.MenuEvent
Map KeyValue TypeValid ValuesDescription
EventTypestringMenuEvent(constant)
IDany

The ID of the menu item the user selected or the href target (as string) for hyperlinks in RichText widgets.

Notice:This is not the widget ID, it is a menu item or hyperlink ID inside that MenuButton or RichText widget!

A MenuEvent is sent when the user activates a menu entry in a MenuButton or a hyperlink in a RichText widget.

Since the ID of the MenuButton or RichText widget is irrelevant in either case, this is not another subclass of WidgetEvent; the ID field has different semantics - and remember, the ID field is the only thing what UserInput() returns so this is particularly important.

For most YCP applications this difference is purely academic. Simply use the ID and treat it like it were just another button's ID.

No notify option is necessary for getting this event. Both MenuButton and RichText deliver MenuEvents right away.

1.2.3.2.6.TimeoutEvent
Map KeyValue TypeValid ValuesDescription
EventTypestringTimeoutEvent(constant)
IDsymbol`timeout(constant)

A TimeoutEvent is sent when the timeout specified at WaitForEvent() or TimeoutUserInput() is expired and there is no other event pending (i.e. there is no other user input).

PollInput() never returns a TimeoutEvent; it simply returns nil if there is no input.

1.2.3.2.7.CancelEvent
Map KeyValue TypeValid ValuesDescription
EventTypestringCancelEvent(constant)
IDsymbol`cancel(constant)

A CancelEvent is an event that is sent when the user performs a general "cancel" action that is usually not part of the YCP application.

For the Qt UI, this means he used the window manager close button or a special key combination like Alt-F4 to close the active dialog's window. For the NCurses UI, this means he hit the ESC key.

User interface style hint: It is usually a good idea for each dialog to provide some kind of "safe exit" anyway. Most popup dialogs (at least those that have more than just a simple "OK" button) should provide a "Cancel" button. If you use the widget ID `cancel for that button, CancelEvents integrate seamlessly into your YCP application.

"Main window" type dialogs should have an "Abort" button or something similar. If you don't use the widget ID `cancel for that button, don't forget to handle `cancel or "CancelEvent" like that "Abort" button. The user should always have a safe way out of a dialog - preferably one that doesn't change anything. Don't forget to add a confirmation popup before you really exit if there are unsaved data that might get lost!

1.2.3.2.8.KeyEvent

KeyEvents are specific to the NCurses UI. They are not intended for general usage. The idea is to use them where the default keyboard focus handling is insufficient - for example, when the logical layout of a dialog is known and the keyboard focus should be moved to the logically right widget upon pressing the cursor right key.

Widgets deliver KeyEvents if they have `opt( keyEvent ) set. This is independent of the notify option.

It is completely up to the UI what key presses are delivered as key events. Never rely on each and every key press to be delivered.

Map KeyValue TypeValid ValuesDescription
EventTypestringKeyEvent(constant)
IDstring


CursorRight
CursorDown
F1
a
A
...

The key symbol of this event in human readable form. This is what UserInput() returns.
KeySymbolstring


CursorRight
CursorDown
F1
a
A
...

The key symbol of this event in human readable form. This is nothing but an alias for "ID", but with this alias you can easily find out if this is a key event at the same time as you retrieve the key symbol: No other events than KeyEvent have this field.
FocusWidgetIDanyThe ID of the widget that currently has the keyboard focus. Unlike a WidgetEvent, this is not the same as "ID".
FocusWidgetClassstringTextEntry SelectionBox ...The class (type) of the widget that has the keyboard focus.
FocusWidgetDebugLabelstring

The label (more general: the widget's shortcut property) of the focus widget - in human readable form without any shortcut markers ("&"), maybe abbreviated to a reasonable length.

This label is translated to the current locale (the current user's language).

This is intended for debugging so you can easily dump something into the log file when you get an event.

Wigets that don't have a label don't add this field to the event map, so make sure you use a reasonable default when using a map lookup for this field: Don't use nil, use "" (the emtpy string) instead.

Even though at first glance the KeyEvent map looks very much like the WidgetEvent's map, it is different in how the "ID" field is used: A KeyEvent uses it to return the key symbol, while a WidgetEvent returns the widget ID. This is intended to integrate more seamlessly with common usage of UserInput(): A YCP application can simply use UserInput() and check for a return value "CursorRight" etc. - which should not cause any trouble unless somebody uses this as a (badly chosen) widget ID.

1.2.3.2.9.DebugEvent
Map KeyValue TypeValid ValuesDescription
EventTypestringDebugEvent(constant)
IDsymbol`debugHotkey(constant)

A DebugEvent is an event type especially intended for debugging YCP code. It is sent when the user presses a special key combination.

For the Qt UI, this event is sent upon pressing Alt-Ctrl-Shift-D. There is currently no such key combination in the NCurses UI.

Use DebugEvents event to dump additional data to the log file or to open special debugging popup dialogs - but never do anything with it that might turn out to be a security hazard. Remember, even though the key combination is really awkward, sooner or later some users will get to know it, and they will experiment.