Copyright © 2005-2006 Novell Inc.
This document is not to be construed as a promise by any participating company to develop, deliver, or market a feature or a product.
Novell Inc. makes no representations or warranties with respect to the contents of this document, and specifically disclaims any express or implied warranties of merchantability or fitness or any particular purpose. Further, Novell Inc. reserves the right to revise this document and to make changes to its content, at any time, without obligation to notify any person or entity of such revisions or changes.
You are allowed to distribute unchanged copies of this document.
Table of Contents
s
STRING
TERM
.TERM
.Table of Contents
The most important thing reads: [DON'T PANIC!]
Administering a Linux system at the lowest level is sometimes not an easy task. If it should be done manually, it requires a very experienced and knowledgeable person, willing to browse and edit very many configuration files. As a result there have been many efforts to create intelligent tools that provide rather automatic means to accomplish this challenging and (at the same time) tedious task.
One of these tools is YaST
, the SUSE Linux™ installer. Being the result of a
rather long period of development, it is by now a very large and capable
system, well suited to install and administer a SUSE Linux™ system. While the
internal functionality of YaST
is quite multifaceted and therefore not
exactly easy to understand, it should not be kept as a secret. Rather the
world shall be encouraged to make use of the mechanisms YaST
can
provide.
This goal can be achieved because YaST
is not a closed monolithic
system but has a high degree of modularity. In fact it consists largely
of modules that could as well be created by people not related to YaST
development. For example hardware vendors could provide a YaST
module
for customizing specific system settings related to their particular
piece of hardware. From the user's point of view this would be much more
comfortable than editing configuration files by hand.
Of course this can't be done without some knowledge of the YaST
internal functionality. So this document tries to lighten things up by
advancing from the unsubtle connections in the beginning to more and more
detailed explanations towards the end. However, describing the
particularities of this matter in full detail would easily fill several
heavy books which in turn would contradict the goal of introductory
simplification. Furthermore some of these “details” are
subject to moderate change service, which would render
“static” documentation like this one outdated rather
quickly.
To alleviate these problems, this text very often refers to the
“official” YaST
developers documentation that can be found
in /usr/share/doc/packages/yast2*
(especially towards
the end). Aside from the references to be found in the following text,
this location provides very valuable information regarding the whole
YaST
environment. To have access to these files the following packages
must be installed:
yast2-devel
yast2-core-devel
liby2util-devel
yast2-packagemanager-devel
This document is subdivided into the following chapters:
Introduction. A brief explanation of the intention and nature of this document.
YaST - The Big Picture.
This is a short depiction of the YaST
installer and the YaST
environment as such. The architectural peculiarities of YaST
are
explained as far as it is necessary to understand the elucidations
that follow thereafter.
The YaST Language - YCP.
This chapter is dedicated to the YaST
language that constitutes
most of the high-level functionality of YaST
. Sections covering
the basic language elements are accompanied by others that deal with
user interface creation and program structure.
SCR Details.
In this chapter the YaST
System Configuration
Repository (SCR) is explained in some concise detail. It
shows how to access configuration data and hardware data from within
YaST
modules in a consistent way.
YaST Modules.
Some explanations regarding the different types of
YaST
modules as well as some rules for writing
them.
Appendix A. References.
Throughout this document there are numerous references to the YaST
developers documentation. To ease access to these links this appendix
is mostly a dense index of those references.
Throughout this document some conventions regarding the typeface of printed text are used:
Emphasized text is used to denote important parts of the text.
Product names, file names and paths are printed using
literal
typeface. Furthermore cut-outs from
programs that are embedded in the normal text flow are printed this
way.
Commands and command lines are printed using the command typeface.
Keyboard keys are denoted as in CTRL-C.
The description of programming language elements is displayed as shown below.
Synopsis: while (condition) loop_body
The parts of the language construct are printed like commands while any arguments are printed emphasized.
YaST
is the installation program used by SUSE Linux™ to install Linux on a
system and to administer this installed system thereafter. The notation
“program” is a bit misleading here because in fact YaST
consists of many components and layers. Therefore one may as well regard
YaST
as an installation and administering environment.
Among the most important components in this environment are the
YaST
-modules which are usually written in a YaST
-specific language
called YaST Control Language (YCP
). With
exception of some rare cases the whole high-level functionality of YaST
is formulated in YCP
. When YaST
is running, the YCP
-modules are
interpreted by the low-level YaST
-components and the YCP
-code makes
use of the infrastructure provided by them. The possibility to add such
modules at any time realizes the concept of extensibility that is
inherent in YaST
.
This concept of extensibility by means of modules has been designed into
YaST
from the very beginning. In fact YaST
-modules are the layer of
YaST
the user comes in contact with. Nearly every dialog on screen
during the installation is realized as a YaST
-module and there are also
modules that act behind the scenes to care of specific pieces of
hardware, e.g. the keyboard.
The YaST
-modules mentioned so far come ready-made with the distribution
and provide the core functionality to install and administer a
SUSE Linux™-system, but this need not and should not be the end. The extension
facility is intended to be also used by “third party
people”, e.g. hardware vendors, who want to contribute
YaST
-functionality in some way.
To pave the way for this intention to come alive this document will
provide some insight into the inner mechanics of YaST
. The primary goal
of the following elucidations is to make available the information that
is needed to write YaST
-modules that conform to the programming
paradigm imposed by YaST
.
What This Document Is.
This document explains in some detail how to extend the functionality
of YaST
by means of modules. Of course every module of more than
trivial functionality will have to make use of the features that are
provided by the YaST
-core-components and other YaST
-modules. While
the official YaST
developers documentation is the primary knowledge
base for all YaST
related information, it is a bit overwhelming for
everyone who tries to get into the matter for the first time. Therefore
this document tries to provide a gentle introduction by explaining
things rather explicitly in the beginning and getting more and more
concise towards the end. By providing very many references to the
developers documentation this document can be thought of as a
“guided index” to this voluminous material.
What This Document Is Not.
This document will not explain the “binary”
particularities related to YaST
, i.e. there will be no implementation
notes on how the low-level machinery of YaST
is realized. Because
YaST
implies the module concept for extensibility, only this approach
is promoted here. The “engine” that executes these modules
and how it is assembled is subject to the following explanations only
in so far as it is necessary to understand the interaction of the
various components.
The Audience Of This Document.
So this document is intended for all people who want to make use of the
YaST
-functionality by providing modules for a specific task.
Additionally the information presented herein might be interesting for
all those who want to adept something about the whys and wherefores
related to YaST
as such. Furthermore, to get most out of this
reading, the reader is supposed to have some programming experience in
a structured programming language, ideally C/C++. Some expertise in
functional programming could also be helpful but is not really
necessary.
Table of Contents
List of Figures
To be able to create a YaST
-module it is necessary to have understood
how the extensive YaST
-world is structured, which components there are,
what they do and how they do it. Therefore prior to going into closer
detail we'll step back from the blackboard and have a look at the big
picture first. By doing so you will have the opportunity to get an
overview of the ample terrain YaST
is living on. While you don't have
to understand each and every byte YaST
consists of, having seen the
whole issue will ease your understanding of the details we will come
across.
Table of Contents
YaST
has been invented to have an extensible and fairly standardized
means to install and manage SUSE Linux™ on a system. Basically YaST
serves
three main purposes:
Installation of SUSE Linux™ on a system
Configuration of the installed system
Administration of the installed system
To provide a solution to the resulting demands that has a lifetime
extensible well into the future this solution had to be flexible and
maintainable. Consequently some key concepts determined the design of
YaST
. In particular it was the strict separation of:
The user interface
The functional code doing the job
The data representing the current state of the system
Furthermore YaST
has some very specific attributes that make it
unique for the user as well as for those people who are developing it
or contributing to it. The following sections outline some of the
features of the YaST
installer that should be seen as a guiding line
for module development.
Managing a SUSE Linux™ system requires direct low-level access to the system
which generally means reading and writing configuration data. Of
course this could be done manually by a knowledgeable person using a
conventional editor. A more comfortable and in most cases safer way
is to use YaST
. Consequently YaST
must be able to handle this
configuration data on the system level. By handling the
original data YaST
activities take into
account manual editing that might also occur. Thus nobody is
forced to use YaST
exclusively for
configuration tasks.
In YaST
the access to system configuration data is realized by
means of a special component (or layer if you prefer), the
System Configuration Repository (SCR) (see below
and Access to the System (SCR in General)). The SCR component basically
consists of a number of so-called agents that
have been created to accomplish a specific kind of access. For
example there is an agent to run shell-commands and there is another
one that reads and writes ASCII-files of a specific format.
Additionally there are agents that provide access to the system
hardware e.g. by taking hold on the proc-file-system.
All these agents are gathered together under a common hood, the
SCR-API that can be used from within the YaST
-modules in a
consistent way. In summary the SCR provides kind of a
view on all kinds of data, either YaST2 internal
data, original system configuration files or hardware data.
YaST
implies lots of artificial intelligence to provide reasonable
suggestions for the various tasks. During installation the target
system is thoroughly analyzed with respect to its hardware components
and in most cases YaST
succeeds in suggesting a proper
configuration for them.
These suggestions are presented in an overview dialog that shows the
main characteristics of the system to be installed and how YaST
would handle them. If you are satisfied with these automatically
generated settings you can simply accept them. If not, each of the
system configuration categories can be “activated” to be
changed manually. This is where the workflows
come into their own.
If you decide to change a specific configuration category this is usually being done in a workflow. Workflows are used to lead you through the steps necessary to accomplish a specific task. The steps are generally small to avoid an information “overflow”. At the end of the sequence the task has been accomplished and the changes are made permanent in the system.
As was stated above, you are not forced to do it
this way. You could as well edit configuration files by hand but
YaST
can offer as much help as possible for this. Sometimes a
workflow has multiple branches for “novice” and
“expert” modes. The novice mode fills in the default
values and tries to determine as much as possible automatically. The
expert mode offers full control and allows to enter even unreasonable
values.
By providing pre-configured workflows and configuration data, it is
possible to automate almost arbitrary configuration tasks with
YaST
. From adding a user, to installing a completely configured
SUSE Linux™ on specific hardware, nearly everything is possible.
Every workflow is assembled from rather small steps, implemented by
means of YaST modules written in a
YaST
-specific scripting language, the YaST Control
Language (YCP
). These YaST
-modules are then called in
a predefined sequence to complete a specific task.
In fact it is possible to even write modules in
bash and Perl as long as
the module need not have a user interface, i.e. it is not
interactive. Such non-interactive modules typically handle specific
problems like controlling a particular piece of hardware and can be
called from within YCP
-modules. This building block approach makes
constructing complex workflows easy and maintainable.
The YCP
language is also used to control the user interface (UI)
presented on screen. The UI displays the information already known by
the system and retrieves the information entered by the user.
There are two modes of operation:
Text mode for console-based service
In text mode the user interface is presented in the NCurses environment that provides windowing capabilities and entry forms on a text-based console. Mouse support is neither possible nor necessary here because all dialogs can be operated using only the keyboard.
Graphics mode for X11-based service
In graphics mode the well-known Qt-system is used to present the dialogs in a graphical way using a running X11-server. Operating these dialogs follows the common habits of graphical user interfaces.
It is important to notice here that both methods principally use the
same YaST
-specific YCP
-API to build the dialogs. While there are
some (rare) cases where the YCP
-code has to distinguish these modes,
the dialogs are usually programed for both worlds in in one single
source with the same code.
In summary YaST
provides the following features, some of them
having already been mentioned above:
System access
YaST
provides thorough probing of the system hardware and
presents the information gathered thereby via the SCR-API. The
SCR is also the means for reading and writing configuration
files.
Reasonable Suggestions
Based on the system analysis and predefined configuration data,
YaST
is able to provide reasonable suggestions for almost any
configuration task.
Workflows
Management of particular configuration categories is usually realized in form of workflows that split up the whole task into small steps.
Modules and YCP
The steps constituting a workflow are usually realized as
YaST
-modules that are written in the YaST Control
Language (YCP
)
User interface
The user interface of YaST
is realized by means of a specific
API from within the YCP
-modules. This API supports a text-based
console-mode as well as a graphical X11-mode.
Internationalization
YaST
provides support for various languages.
Multi-platform support
YaST
provides support for various platforms like
Intel™ (x86), Apple™,
IBM™ (s390) etc.
Table of Contents
YaST2 is a modular system for Linux installation and system administration. The design goals include:
Flexibility
Extensibility
Maintainability
Network transparency
support administration of remote hosts or virtual machines on mainframes, machines without CD/DVD drives, rack-mounted machines
User interface independence
must run in graphical and text-only environments and serial consoles
Cover the whole range from novice users to expert system administrators
To achieve the above design goals, YaST2 is split up into a number of components for each individual task:
There is the core engine and to run scripts written in YCP (YaST2's own scripting language), Perl or (in future releases) other scripting languages.
The engine and scripts together form a YaST2 Module for the user.
Even though in most scenarios there is only one single machine, it is important to distinguish between the installation source machine and the installation target machine:
The installation source machine is the machine that holds the installation media - usually CDs or DVDs - and a mini-Linux called "inst-sys" that is copied from one of those installation media to that machine's RAM disk to have a basic operating system to work with on a "bare metal" machine (a machine that doesn't have an operating system installed yet). Most of that inst-sys is read-only, there is only limited disk space for temporary files, and since everything runs from a RAM disk the writable part of it is very volatile.
The installation target on the other hand is the machine that is to be installed or administered. That may be the same machine as the installation source machine (in fact, this is very common for PC installation or administration tasks), but it might as well be two distinct machines - a virtual machine on a mainframe computer or a remote rack-mounted machine without any display adapter or CD/DVD drives.
All communication with the installation target is handled via the System Configuration Repository (SCR) to guarantee the network abstraction design goal. This is much easier said than done, however: YaST2 module developers always have to keep in mind that it is strictly forbidden to access system files (or any other system resources, for that matter) directly, even if there may be very convenient CPAN Perl modules to do that. Rather, SCR is to be used instead - always. Otherwise everything might run fine if installation source and target are the same machine, but break horribly if they are not.
SCR in itself is also modularized: All calls are handled by "agents" that each
know how to handle a particular configuration "path" like "/etc/fstab"
or
"/etc/passwd"
. That may be a simple file, but it may also be a directory
hierarchy like "probe" - this particular agent handles all kinds of hardware
probing, from mouse and display adapters to storage device controllers (like
SCSI or IDE controllers), disks attached to each individual controller or
partitions on those disks. Paths are denoted like ".etc.fstab" for SCR. YCP
even has a special data type "path" for just this case (a special kind of
string with some special operations).
SCR agents handle no more than three calls:
SCR::Read()
SCR::Write()
SCR::Execute()
The first argument is always the path to handle, but there may be any number of additional parameters, depending on the agent.
While Read() and Write() are obvious, Execute() may not be: This is intended for some kinds of agents that actually run a program on the installation target. In particular, the ".target.bash" agent does that - it runs a "bash" shell on the target machine and accepts a shell command as an argument. This is the tool of choice for tasks such as creating backup copies of configuration files or running any special command on the target machine - and again, the distinction between installation source and installation target machine becomes very important: You want run these commands on the (possibly remote) target machine, not on the machine that happens to hold the installation media.
SCR agents can easily added when needed. There are frameworks available to write SCR agents in C++, in Perl, or as Bash shell scripts as well as several ready-made parsers for different file formats like the ".ini" file parser that can handle files with "key = value" pairs or the "anyagent" that generalizes that concept even more using regular expressions. Those parsers return YCP lists and maps ready for further processing.
Typically, a YaST2 module for a specific installation or administration task includes a set of YCP or Perl scripts as well as some SCR agents to handle its particular configuration files.
Given the wide variety of machines that can possibly be handled with YaST2, it is important to keep the user interface (UI) abstraction in mind - very much like the SCR, the UI does not necessarily run on the installation target machine. It doesn't even need to run on the same machine as the WFM.
The UI provides dialogs with "widgets" - user interface elements such as input fields, selection lists or buttons. It is transparent to the calling application if those widgets are part of a graphical toolkit such as Qt, or text based (using the NCurses library) or something completely else. An input field for example only guarantees that the user can enter and edit some value with it. A button only provides means to notify the application when the user activated it - by mouse click (if the UI supports using pointing devices such as a mouse), by key press or however else.
The UI has a small number of built-in functions - for example:
UI::OpenDialog() accepts a widget hierarchy as an argument and opens a dialog with those widgets
UI::CloseDialog() closes a dialog
UI::QueryWidget() returns a widget's property such as the current value of an input field or selection box
UI::ChangeWidget() changes a widget's property
UI::UserInput() waits until the user has taken some action such as activate a button - after which the application can call UI::QueryWidget() for each widget in the dialog to get the current values the user entered. The application does not have to handle every key press in each input field directly - the widgets are self-sufficient to a large degree.
There is virtually no low-level control for the widgets - nor is it necessary or even desired to have that. You don't specify a button's width or height - you specify its label to be "Continue", for example, and it will adapt its dimensions accordingly. If desired, more specific layout constraints can be specified: For example, buttons can be arranged in a row with equal width each. The UI will resize them as needed, giving them additional margins if necessary.
The existing UIs provide another layer of network abstraction: The graphical UI uses the Qt toolkit which is based on the X Window System's Xlib which in turn uses the X protocol (usually) running on top of TCP/IP. X Terminals can be used as well as a Linux console (that may be the installation source machine or the installation target machine or another machine connected via the network) running the X Window System or even X servers running on top of other operating systems.
The NCurses (text based) UI requires no more than a shell session - on a text terminal (serial console or other), on a Linux console, in an XTerm session, via ssh or whatever.
Currently, there is no web UI, but YaST2's concepts would easily allow for that if it proves useful or necessary.
The component broker is the central piece of YaST. It acts as a dispatcher for all other components: When a (YCP, Perl or whatever) script calls a function, the broker determines what component handles that function call based on the respective namespace identifier. It is transparent to the caller what programming language a function is written in; the component broker handles that kind of dispatching. The caller only needs to know the function name, its namespace and (or course) the required parameters.
For example, calls like UI::OpenDialog() go to the UI (the user interface), SCR::Read() to the SCR (the system configuration repository). Even scripts can provide namespaces via modules in YCP or Perl.
All communication between the different parts of YaST core is done via a predefined set of YCP data types - simple data types like string, integer, boolean etc., but also compound data types like maps (key / value pairs, also known as "hashes" in other programming languages) or lists (like arrays or vectors in other programming languages). For complex data structures, maps, lists and simple data types can be nested to any degree.
The core-engine of YaST
consists of some binary components
(modules) that are interconnected via YaST
-specific protocols.
There are clients as well as
servers that are responsible for specific tasks
that may have to be accomplished during a YaST
-session. According
to the well-known client-server-paradigm often used in software
technology, YaST
-servers are program modules that
passively await connections from certain clients
to process their requests. Clients on the other hand are
active components that send requests to the
servers thereby initiating certain actions.
For example the SCR and the UI act as server components that process
client-requests on demand. An example for a client module is the
stdio-component that can be used to connect the
YaST
-internal communication with a terminal.
Because this architectural specialty is meant to be used only by the
YaST
core developers to establish and maintain the low-level
machinery we will not go into more detail here. Instead we will focus
on the advocated method of extending YaST
at the “open
end” by creating YCP
-modules.
Table of Contents
List of Figures
List of Examples
The YaST
-language YCP
is a scripting language to be interpreted by the
YCP
-engine (YCP
interpreter) specially designed for
manipulation with a system configuration. Its syntax is very similar
to C programming language.
Because YCP
can make use of the whole infrastructure that
YaST
provides, the actions that can be accomplished with YCP
are
very powerful.
YCP
has the usual features of procedural languages and
some more, partially originating from the functional
programming paradigm:
Control structures like if/then/else, foreach-loops.
Compound data types like strings, lists and maps.
Function definition (procedures)
Variable scopes
Name spaces
Include files
UNIX command execution (via the YaST infrastructure)
On the following pages we will explore the YCP
language definition and
find out how to use YCP
to write “programs” that can be
executed by YaST
.
Table of Contents
Probably the best way to get into the matter is by means of a simple example.
The following little program opens a window that displays the string “Hello, World!” and provides a push button for termination.
Example 1.1. “Hello World” in YCP
{ string message = "Hello, World!"; UI::OpenDialog( `VBox( `Label( message ), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
In the following this code will be explained shortly in a line-by-line manner thereby touching some topics we will examine in detail later on.
{
The opening curly opens a so-called
block in YCP
. Blocks are used to
“glue” several YCP
-statements together to form an
entity that can be handled just like a single statement.
string message = "Hello, World!";
In this line we define a variable named “message” that
is of type string. In YCP
any variable definition
must imply a value assignment to avoid all
errors that might occur due to uninitialized variables. Here we
assign the constant string “Hello, World!”.
Furthermore the terminating semicolon is mandatory in YCP
to
indicate the end of a statement (just like C).
UI::OpenDialog(
This command opens a dialog on screen.
Because we want to display something, the code describing our
dialog has to be sent to the UI. This is being done by the leading
name space identifier UI::
. The (single) parameter that
is supplied here determines the content of the dialog.
`VBox(
This is a UI-statement related to the geometry of the dialog to be defined. As the name indicates it opens a (virtual) vertical box that displays all content in a column-wise manner. (Geometry management is described in more detail in Chapter 9, Controlling The User Interface).
The leading back-quote introduces a YCP
-feature that stems from
the functional programming paradigm. In YCP
-speak the
`VBox()
is a term. In YCP
,
terms are used as a structured constants and are typically
passed to functions provided by YaST infrastructure
as parameters as is done here with
OpenDialog()
.
`Label( message ),
Displaying strings in YCP
is done by means of
Labels. This statement gets one parameter, the
string variable we defined in the beginning. Because it is the
first of two parameters passed to `VBox()
this
line is not terminated with a semicolon but with a comma. As in
most programming languages commas are used to separate parameters
in YCP
.
`PushButton("&OK")
This statement displays a labeled push button. Since it is the
next element in the enclosing `VBox()
, it is
displayed immediately below the preceding label. The & in the
label string is a YaST
feature declaring the subsequent character
to be a key-shortcut. As a result the button can not only be
clicked with the mouse but also be activated by typing
ALT-O.
)
and );
The next two lines first close the open
`VBox()
and then the open
OpenDialog()
. Because
`VBox()
is passed as a parameter to
OpenDialog()
there is no need to terminate
the statement with a semicolon. OpenDialog()
on the other hand is a statement in the UI and
hence must be terminated with a semicolon.
UI::UserInput();
Here we hand over control to the UI which then awaits some sort of user input. In this case it simply waits for the push button to be pressed by the user. Consequently our program blocks at this point until the user really does it.
UI::CloseDialog();
After all the UI-related action has finished, i.e. when
UI::UserInput()
returns, we want to remove the
dialog we just created. This is done here.
}
Indicating the end of the block, the closing curly
bracket ends our little YCP
-program.
Now we can start the program using YaST. For this,
we will use a script /sbin/yast2
.
It is an envelope for easier setup of a running
YaST environment.
So if you are reading this document with a browser, you could
copy-and-paste the program listed above into a file
hello.ycp
, and then run
/sbin/yast2 hello.ycp
which should render the following
“spectacular” result.
Starting off with this simple example we will now explore the more
subtle details of YCP
. Since all programming is about handling of data
there must be a way to hold it in variables of different types. In the
next section you will get to know the various data types that YCP
knows about.
Table of Contents
Just like any other high-level programming language YCP has typed variables to hold data of different kinds:
This is the most simple data type. It has only one possible value:
nil
. Declaring variables of this type doesn't make
much sense but it is very useful to declare functions that need not
return any useful value. nil
is often also
returned as an error flag if functions fail in doing their job
somehow.
A symbol is a literal constant. It is denoted by a single backquote and a letter or underscore optionally followed by further letters, underscores or digits.
In contrast to C/C++, a YCP
boolean is a real data type with the
dedicated values true
and
false
. Comparison operations like
<
or ==
evaluate to a
boolean value. The if (...)
statement expects a
boolean value as the result of the decision clause.
This is a machine independent signed integer value that is
represented internally by a 64 bit value. The valid range is from
-2^63
through 2^63-1
. Integer
constants are written just as you would expect. You can write them
either decimal or hexadecimal by prefixing them with
0x
, or octal by prefixing them with
0
(just like in C/C++).
Floating point numbers. Because they are represented via the C
datatype double
the valid range is machine
dependent. Constants are written just as you would expect. The
decimal point is mandatory only if no exponent follows. Then there
must be at least one digit leading the decimal point. The exponent
symbol may be e
or E
.
Represents a character string of almost arbitrary length (limited only by memory restrictions). String constants consist of UNICODE characters encoded in UTF8. They are enclosed in double quotes.
The backslash in string can be used to mark special characters:
Representation | Meaning |
---|---|
\n | Newline (ASCII 10) |
\t | Tabulator |
\r | Carriage Return (ASCII 13) |
\b | Backspace |
\f | Form Feed |
\ abc | ASCII character represented by the octal value abc. Note that unlike in C, there must be exactly 3 octal digits! |
\ X | The character X itself. |
A backslash followed by a newline makes both the backslash and the
newline being ignored. Thus you can split a string constant over
multiple lines in the YCP
code.
Example 2.4. String constants
“This string ends with a newline character.\n” “This is also a newline: \012”
A byteblock simply is a sequence of zero or more bytes. The ASCII
syntax for a byteblock is #[hexstring]
. The
hexstring is a sequence of hexadecimal values,
lower and upper case letters are both allowed. A byte block
consisting of the three bytes 1, 17 and 254 can thus be written as
#[0111fE]
.
In most cases, however, you will not write a byteblock constant
directly into the YCP
code. You can use the SCR to read
and write byteblocks.
A list is a finite sequence of values. These values need not
necessarily have the same data type. List constants are denoted
by square brackets. In contrast to C it is possible to use complex
expressions as list members when defining a list constant. The
empty list is denoted by []
.
Example 2.6. List constants
[ ] [ 1, 2, true ] [ variable, 17 + 38, some_function(x, y) ] [ "list", "of", "strings" ]
Accessing the list elements is done by means of the index operator as
in my_list[1]:"error"
. The list elements are
numbered starting with 0, so index 1 returns the second element.
After the index operator there must be a colon
denoting a following default value that is
returned if the list access fails somehow. The default value should
have the type that is expected for the current list access, in this
case the string “error”.
Note 1: A list preserves order of its elements when iterating over them.
Note 2: There is also another method for accessing lists, originating
from the early days of YaST
. The command select(my_list,
1, "error")
also returns the second element of
my_list
. While this still works, it is deprecated
by now and may be dropped in the future.
A YCP
-map is an associative array. It is a list of key-value-pairs
with the keys being non-ambiguous, i.e. there are no two keys being
exactly equal. While you can use values of any type for keys and
values, you should restrict the keys to be of
type string because from experience other types
tend to complicate the code. Values of arbitrary
type on the other hand make the map a very flexible data container.
Maps are denoted with $[ key_0:value_0, key_1:value_1,
...]
. The empty map is denoted by $[]
.
Note: A map does not reserve order of its elements when iterating over them.
Example 2.7. Map constants
$[ ] $[ "/usr": 560, "/home" : 3200 ] $[ "first": true, "2": [ true, false ], "number" : 8+9 ]
Accessing the map elements is done by means of the index operator as
in my_map["os_type"]:"linux"
. This returns the
value associated with the key "os_type"
. As
with lists, a default value must be appended
(after a colon) that is returned if the given key does not
exist. Again it should have the type that is expected for the
current access, in this case the string “linux”.
You may have noticed that the syntax for accessing maps kind of resembles that of accessing lists. This is due to the fact that lists are realized as maps internally with constant keys 0, 1, 2, and so on.
Note: There is also another method for accessing maps, originating
from the early days of YaST
. The command lookup(my_map,
"os_type", "linux")
also returns the value associated
with the given key. While this still works, it is deprecated by now
and may be dropped in the future.
A path is something special to YCP
and similar to paths in TCL. It
is a sequence of path elements separated by dots. A path element can
contain any characters except \x00
. If it contains
something else than a-zA-Z0-9_-
it must be
enclosed in double quotes. The root path, i.e.
the root of the tree is denoted by a single dot. Paths can be used
for multiple purposes. One of their main tasks is the selection of
data from complex data structures like the SCR-tree (see Chapter 2, SCR Tree).
The backslash in paths can be used to mark a special characters:
Representation | Meaning |
---|---|
\n | Newline (ASCII 10) |
\t | Tabulator |
\r | Carriage Return (ASCII 13) |
\b | Backspace |
\f | Form Feed |
\ xXX | ASCII character represented by the hexadecimal value XX. |
\ X | The character X itself. |
Example 2.8. Path constants
. .17 .etc.fstab ."\nHello !\n".World ."\xff" == ."\xFF" ."\x41" == ."A" ."" != .
A term is something you won't find in C
,
Perl
, Pascal
or
Lisp
but you will find it in functional programming
languages like Prolog
for
example. It is a list plus a symbol, with the list written between
normal brackets. The term `alpha(17, true)
denotes
a symbol `alpha
and the list [ 17, true
]
as parameters for that symbol.
This looks pretty much like a function call.
You can also use the term as a parameter in another function call, for example to specify a user dialog.
Example 2.9. Term constants
`like_function_call(17, true) `HBox(`Pushbutton(`Id(`ok), "OK"), `TextEntry(`Id(`name), "Name"))
In the previous sections you have seen the data types YCP
knows
about. In most cases you will (and should) assign a certain data type
to every variable you declare. However, there might be cases when the
type of a variable is not really clear at coding time, e.g. (in some
rare cases) if you access the SCR to get some hardware data. While
you should try very hard to avoid this situation, there might be
cases where you can't.
To solve this problem, you may assign the type
any to your variable which makes it accept
assignments of any other valid type. However, because variables of
type any are highly deprecated in YaST
by now,
this “feature” will eventually be dropped in the near
future.
Table of Contents
Section not written yet...
Basically a block is a sequence of YCP
statements enclosed in curly
brackets. It can be a whole YCP
program as was the case with the
outermost block in hello.ycp from Section 1.1, “YCP Source”. What is special about blocks in YCP
is
that they represent a value and therefore can be assigned to a
variable. It is sometimes really useful to have those blocks as YCP
values because this makes it possible to use them as parameters to
function calls. Of course the syntactical structure of blocks can
become rather complex which leads to a description of the whole
language itself. Therefore we put this into a section of its own:
Chapter 8, YCP Program Structure.
For now the following examples should suffice.
Table of Contents
Section not written yet...
In Data type any you have seen the data type
any
. Because the value of type
any
can not be assigned to
a variable of any other type. FIXME.
So it is important to check its type with is(...)
and
then re-assigning it to a variable of the correct type. The following
example shows how this should be done.
Example 4.1. Type checking and data type any
// // Hypothetical example: // --------------------- // We don't know whether the SCR will return integers or floats... // any any_var = 0; integer int_var = 0; float float_var = 0.0; boolean int_case = false; any_var = SCR::Read(...); if ( is( any_var, integer ) ) { int_var = any_var; int_case = true; } else if ( is( any_var, float ) ) { float_var = any_var; int_case = false; } else { // Error... } if ( int_case ) { // Use int_var... } else { // Use float_var... }
As this is very cumbersome, you should try to
avoid this oddity in any case. If it is ineluctable, do it as shown
above to stay compatible with future YaST
behavior.
Table of Contents
From the interpreters point of view any YCP
value is an expression and
thus can be evaluated. How the evaluation is done
in a particular case depends on the data type of the expression.
Because the block data type is somewhat special with respect to evaluation it will be explained first. The other basic data types will follow thereafter.
A YCP
block is a sequence of
statements enclosed in curly brackets. Upon
evaluation (execution), all the statements in the block are evaluated
one by one. Because blocks are also a valid data type in YCP
, they
can have a value (see Data type block). If a
block contains the special statement return(...)
,
then the returned value replaces the block upon evaluation.
The following code sample shows a block with some statements.
{ integer n = 1; while (n <= 10) { y2milestone("Number: %1", n); n = n + 1; } y2milestone("Returned number: %1", n); return n; }
It calculates the numbers 1 through 10 and prints these numbers into
the log file. The statement y2milestone(...)
used
for this is explained in YaST2 Logging along
with YCP
-logging as such. For now we are interested in the output
that is written to the log file. As can be seen below the loop is
executed 10 times and the counter has the value 11 after the loop.
Finally the last statement return(...)
determines
the value of the whole block, in this case 11
.
...ycp/block_01.ycp:6 Number: 1 ...ycp/block_01.ycp:6 Number: 2 ...ycp/block_01.ycp:6 Number: 3 ...ycp/block_01.ycp:6 Number: 4 ...ycp/block_01.ycp:6 Number: 5 ...ycp/block_01.ycp:6 Number: 6 ...ycp/block_01.ycp:6 Number: 7 ...ycp/block_01.ycp:6 Number: 8 ...ycp/block_01.ycp:6 Number: 9 ...ycp/block_01.ycp:6 Number: 10 ...ycp/block_01.ycp:10 Returned number : 11
The basic YCP
data types we got to know in Chapter 2, YCP Data Types are evaluated in a rather straight
forward way as will be shown in the following list.
Evaluation of basic data types
Simple data types
Most of the YCP data types can't be “evaluated” at
all, as they simply evaluate to themselves. This holds for the
simple types void
, boolean
,
integer
, float
,
string
, symbol
and
path
.
list
When evaluating a list, the interpreter evaluates all the list elements thereby forming a new list.
{ list list_var = [ 1 + 1, true || false, "foo" + "bar" ]; y2milestone("list_var: %1", list_var ); }
yields the log file entry
...ycp/list_eval.ycp:4 list_var: [2, true, "foobar"]
map
A map is handled similar to a list. The values (but not the keys) are evaluated to form a new map.
{ map map_var = $[ "one" : `one, `two : "one" + "one" ]; y2milestone("map_var: %1", map_var ); }
yields the log file entry
...ycp/map_eval.ycp:4 map_var: $["one":`one, `two:"oneone"]
term
Upon evaluation, term parameters are evaluated to form a new term.
{ term term_var = `val ( 1 + 1, true || false, "foo" + "bar" ); y2milestone("term_var: %1", term_var ); }
yields the log file entry
...ycp/term_eval.ycp:4 term_var: `val(2, true, "foobar")
All the evaluations we have seen above are closely related to
operators that may be used within an expression to act on the
variables. The next section will give an overview of the operators
that can be used in YCP
.
Table of Contents
As any other programming language YCP
knows a lot of operators that
can be used to act on data.
These are binary operators for comparison of two values. The result is always boolean.
Operator | Datatype | Description |
---|---|---|
== | almost all | True if operands are equal, otherwise false. |
< | almost all | True if left operand is smaller than the right one, otherwise false. |
> | almost all | True if left operand is greater than the right one, otherwise false. |
<= | almost all | True if left operand is smaller or equal to the right one, otherwise false. |
>= | almost all | True if left operand is greater or equal to the right one, otherwise false. |
!= | almost all | True if operands are not equal, otherwise false. |
These are logical operators, that works with boolean datatype, two are binary one is unary. The result is always boolean.
Operator | Datatype | Description |
---|---|---|
&& | boolean | True if both operands are true, otherwise false (logical and). |
|| | boolean | True if at least one of the operands is true, otherwise false (logical or). |
! | boolean | True if the operand if false, otherwise false (logical not). |
These are bit operators that works with integer, two are binary one is unary. The result is always integer.
Operator | Datatype | Description |
---|---|---|
& | integer | Bits of the result number are product of the bits of the operands (bit and). |
| | integer | Bits of the result number are count of the bits of the operands (bit or). |
~ | integer | Bits of the result number are reverted bits of operand (bit not). |
<< | integer | Bits of the result number are left shifted bits of the operands (bit shift left). |
>> | integer | Bits of the result number are right shifted bits of the operands (bit shift right). |
There math operators works with numeric data types (integer and float) and also with string. All are binary (except unary minus).
Operator | Datatype | Description |
---|---|---|
+ | integer, float, string | The result is sum of the numbers or concatenation of the strings. |
- | integer, float | The result is difference of the numbers. |
* | integer, float | The result is product of the numbers. |
/ | integer, float | The result is quotient of the numbers (number class is preserved, thus quotient of integers produce integer, etc). |
% | integer | The result is modulo. |
unary - | integer, float | The result is negative number. |
This is the operator known from C language ( condition ? expression : expression). The first operand is expression that can evaluate to boolean, types of second and third operands are code dependent. The result of the triple operator expression is the second operand in the case the first operand (condition) evaluates to true, the third one otherwise.
Code | Result | Comment |
---|---|---|
(3 > 2) ? true : false | true | The expression (3 > 2) evaluates to true, the result is true |
contains ([1, 2, 3], 5) ? "yes" : "no" | "no" | The expression contains ([1, 2, 3], 5) evaluates to false, the result is "no" |
(size ([]) > 0) ? 1 : -1 | -1 | The expression size ([]) > 0 evaluates to false, the result is -1 |
![]() | Note |
---|---|
Using brackets makes code cleaner, but is not necessary (according to operators precedence). |
![]() | Note |
---|---|
With the introduction of the index operator ( a = mapvar["key"]:default ), the sequence "]:" became a lexical token usable only for indexing, so watch out when using the triple operator with lists and maps. Use parentheses or white space. |
The table of operators precedence (from lowest to highest).
Direction | Operators |
---|---|
right | = |
left | ?: |
left | || |
left | && |
left | == != |
left | < <= > >= |
left | + - |
left | * / % |
left | << >> |
left | | |
left | & |
prefix | ! ~ - |
The bracket operator is the use of '[' and ']' like accessing arrays in C.
In YCP, this operator is used to ease handling with (possibly nested) lists and maps.
The bracket operator can be applied to any list or map variable and should be used in favour of (deeply) nested lookup() and select() cascades.
The access variant of the bracket operator is used for accessing
elements of a list or a map. It effectively replaces
select
for lists and lookup
for maps.
General syntax:
for simple lists:
<list-var>[
<index>]:
<default-value>
for nested lists:
<list-var>[
<index>,
<index> <
, ...>]:
<default-value>
index must be an integer and counts from 0 up to the number of elements-1.
It will return the default-value if you try to access an out-of-bounds element.
![]() | Important |
---|---|
Note that there must be no space between the closing bracket and the colon. |
Examples:
{ list list_of_numbers = [1, 2, 3]; // value of element with index 2 is3
integer three = list_of_numbers[
2]:0
; // evaluates to the default value0
, // because list element with index 42 doesn't exist integer zero = list_of_numbers[
42]:0
; // evaluates to4
integer number = [3, 4, 5][1]:8; list list_of_lists = [[1,2], [3,4], [5,6]]; // list_of_lists[1] ->[3,4]
// [3,4][0] ->3
// // returns true because the left side evaluates to3
// just as the right side does (three ==3
) return (list_of_lists[1
,0]:0
== three); }
General syntax:
for simple maps:
<map-var>[
<key>]:
<default-value>
Examples:
{ // map with string as a key, integer as a value map simple_map = $["a":1, "b":2, "c":3]; // evaluates to 3 integer three = simple_map["c"]:0; // evaluates to 0 integer zero = simple_map["notthere"]:0; }
{ // map with string as a key, integer as a value // this example also defines the data-types map <string, integer> simple_map = $["a":1, "b":2, "c":3]; // evaluates to 3 integer three = simple_map["c"]:0; // evaluates to 0 integer zero = simple_map["notthere"]:0; }
for nested lists:
<map-var>[
<key>,
<key> <
, ...>]:
<default-value>
key must have an allowed type for maps, integer, string, or symbol.
It will return default-value if you try to access an non existing key.
![]() | Important |
---|---|
Note that there must be no space between the closing bracket and the colon. |
Examples:
{ // map with string as a key and another map as a value map nested_map = $[ "a":$[1:2], "b":$[3:4], "c":$[5:6] ]; // nested_map["b"] -> $[3:4] ("b" is a key of the map) // $[3:4][3] -> 4 ( 3 is a key of the map) // // returns true return (nested_map["b",3]:0 == 4); }
{ // map with string as a key and another map as a value // this map has a defined data-type map <string, map <integer, integer> > nested_map = $[ "a":$[1:2], "b":$[3:4], "c":$[5:6] ]; // returns true return (nested_map["b", 3]:0 == 4); }
Since the bracket operator applies to list and maps, you can use it to access nested lists and maps. But be careful not to mix up the index/key types.
Examples:
{ map <string, list <integer> > map_of_lists = $[ "a":[1, 2, 3, 4], "b":[5, 6], "c":[7, 8, 9] ]; // evaluates to 3 integer three = map_of_lists["a", 2]:0; list <map <integer, integer> > list_of_maps = [ $[0:1], $[2:3], $[4:5] ]; // returns true return (list_of_maps[1,2]:0 == three); }
The bracket operator can also be used on the left side of an assignment (lvalue). This changes the list or map element in place (!!) and must be used with care.
If the map or list element does not exist, it will be created.
The bracket operator can therefore replace add
and change
.
Creating a new list element will extend the size of the list.
Holes will be filled with nil
. See the examples
below.
If used as an lvalue, the default value is not allowed.
Examples:
{ list numbers = [1, 2, 3]; // changes the second element // numbers == [1, 25, 3] now numbers[1] = 25; // extends the list to 7 elements (0-6) // numbers == [1, 25, 3, nil, nil, nil, 6] now numbers[6] = 6; // remove an element item with index 2 // numbers == [1, 25, nil, nil, nil, 6] now numbers = remove (numbers, 2); }
{ map <string, integer> new_map = $["a":1, "b":2, "c":3]; // changes the "c" element // new_map == $["a":1, "b":2, "c":42] now new_map["c"] = 42; // add a new element to m new_map["zz"] = 13; // remove an elment "a" // new_map == $["b":2, "c":42, "zz":13] now new_map = remove (new_map, "a"); // returns $["b":2, "c":42, "zz":13] return (new_map); }
A string that is localized by YaST2 via the
gettext mechanism (see gettext and ngettext in
libc for more informations). Basically a string to be translated via
gettext must be enclosed in _(...)
which causes
gettext to look up the translated string. It is even possible to
distinguish singular and plural verbalizations depending on a
parameter that denotes the actual number of something.
For a simple number-independent string you write
_(some_string_constant)
which causes its
translation.
If the string to be translated needs to be different depending on the
multiplicity of something then you write
_(singular_string_constant1, plural_string_constant2,
actual_number)
. If actual_number
equals 1 then the translation of the first string is used. Otherwise
the translation of the second string is used.
Note 1: There are languages that distinguish more than the two cases singular and plural. Principally gettext can handle even those cases as it allows more than two strings for selection, but that is beyond the scope of this document (see the gettext documentation).
Note 2: It is not possible to put something other than string constants between the brackets.
Example 7.1. Locale constants
_("Everybody likes Linux!") _("An error has occurred.", "Some errors have occurred", error_count)
Table of Contents
Now that we have learned how data can be stored and evaluated in YCP
,
we will take a look at the surrounding code structure that can be
realized. Code structure is created by means of
blocks and statements.
Despite not being “really”
statements, comments do (and should) belong to
the overall structure of a YCP
program. There are two kinds of
comments:
Single-line comments
Single-line comments may start at any position on the line and reach up to the end of this line. They are introduced with “//”.
Multi-line comments
Multi-line comments may also start at any position on the line
but they may end on another line below the starting line.
Consequently there must be a start tag
(“/*
”) and an end tag
(“*/
”) as is shown below.
Example 8.1. Comments
{ // A single-line comment ends at the end of the line. /* Multi-line comments may span several lines. */ y2milestone("This program runs without error"); }
Synopsis: data_type variable_name = initial_value;
Variable declarations in YCP
are similar to C. Before you can use a
variable, you must declare it. With the declaration you appoint the
new variable to be of a certain data_type which
means you can assign only values of that specific type. To avoid any
errors caused by uninitialized variables, a declaration
must imply a suitable value assignment.
Note: A variable declaration may occur at several points in the code which determines its validity (accessibility) in certain program regions (see Section 8.15, “Variable Scopes and blocks”).
Example 8.2. Variable Declaration
{ integer int_num = 42; float float_num = 42.0; string TipOfTheDay = "Linux is the best!"; integer sum = 4 * (int_num + 8); }
Synopsis: variable_name = value;
An assignment statement is almost the same as a declaration statement. Just leave out the declaration. It is an error to assign a value to a variable that has not already been declared or to a variable of different data type.
Example 8.3. Variable Assignment
{ integer number = 0; number = number + 1; number = 2 * number; number = "Don't assign me to integers!"; // This will cause an error!!! }
Synopsis: if (condition) then_part [ else else_part ]
Depending on condition only one of the code
branches then_part and
else_part is executed. The
else_part is optional and may be omitted. Both
then_part and
else_part may either be single statements or a
sequence of statements enclosed in curly brackets, i.e. a block. The
then_part is executed if and only if
condition evaluates to true
,
the else_part otherwise. It is an error if
condition evaluates to something other than
true
or false
.
Example 8.4. Conditional branch
{ integer a = 10; if ( a > 10 ) y2milestone("a is greater than 10"); else { // Multiple statements require a block... y2milestone("a is less than or equal to 10"); a = a * 10; } }
Example 8.5. Conditional branch with "else if
"
{ list &string& new_list = ["a", "new", "string"]; if (contains(new_list, "test")) y2milestone("this is a test"); else if (size(new_list) > 100) y2error("Too big list!"); else if (contains(new_list, "string")) y2milestone("this is a new list"); else y2error("Undefined behavior for list %1", new_list); }
Synopsis: while (condition) loop_body
The while() loop executes the attached
loop_body again and again as long as
condition evaluates to true
.
The loop_body may be either a single statement
or a block of statements.
Because condition is checked at the top of
loop_body, it may not be executed at all if
condition is false
right from
start.
Example 8.6. while() Loop
{ integer a = 0; while (a < 10) a = a + 1; while (a >= 0) { y2milestone("Current a: %1", a); a = a - 1; } }
Synopsis: do loop_body while (condition);
The do...while() loop executes the attached
loop_body again and again as long as
condition evaluates to true
.
The loop_body may be either a single statement
or a block of statements.
Because condition is checked at the bottom of
loop_body, it is executed at least once, even if
condition is false
right from
start.
Example 8.7. do...while() Loop
{ integer a = 0; do { y2milestone("Current a: %1", a); a = a + 1; } while (a <= 10); }
Synopsis: repeat loop_body until (condition);
The repeat...until() loop executes the attached
loop_body again and again as long as
condition evaluates to false
.
The loop_body may be either a single statement
or a block of statements.
Because condition is checked at the bottom of
loop_body, it is executed at least once, even if
condition is true
right from
start.
repeat...until() is similar to do...while() except that condition is logically inverted. The example below has been converted from the do...while() example.
Example 8.8. repeat...until() Loop
{ integer a = 0; repeat { y2milestone("Current a: %1", a); a = a + 1; } until (a > 10); }
Synopsis: break;
The break statement is used within loops to exit immediately. The execution is continued at the first statement after the loop.
Example 8.9. break statement
{ integer a = 0; repeat { y2milestone("Current a: %1", a); a = a + 1; if (a == 7) break; // Exit the loop here, if a equals 7. } until (a > 10); // Value 10 will never be reached. y2milestone("Final a: %1", a); // This prints 7. }
Example 8.10. break statement in foreach
{ foreach (string text, ["a", "new", "string", "break"], { // finishes the foreach loop if (text == "string") break; // "string" and "break" will never get here y2milestone("Current text is '%1'", text); }); }
Example 8.11. Nice break statement in foreach
{ // list of found prime-number list <integer> found = []; // start with number integer number = 2; // finish with number integer max_number = 20000; while (number < max_number) { boolean not_found = true; // try all already found numbers foreach (integer try, found, { if (number % try == 0) { not_found = false; break; } }); if (not_found) found = add (found, number); number = number + 1; } y2milestone("Sum: %1", size(found)); y2milestone("Found: %1", found); }
Example 8.12. break statement in listmap
{ integer counter = 0; // goes through the list and returns a map // made of this list map new_map = listmap (string text, ["a", "new", "string", "break"], { // finishes the listmap loop if (text == "string") break; counter = counter + 1; // returns one "key : value" pair of the new map return $[text : counter]; }); y2milestone("Returned map: %1", new_map); }
Synopsis: continue;
The continue statement is used within loops to abandon the current loop cycle immediately. In contrast to break it doesnt exit the loop but jumps to the conditional clause that controls the loop. So for a while() loop, it jumps to the beginning of the loop and checks the condition. For a do...while() loop or repeat...until() loop, it jumps to the end of the loop end checks the condition.
Example 8.13. continue statement in while
{ integer a = 0; while (a < 10) { a = a + 1; if (a % 2 == 1) continue; // % is the modulo operator. y2milestone("This is an even number: %1", a); } }
Example 8.14. continue statement in foreach
{ // lists all files in /tmp directory map cmd = (map) SCR::Execute(.target.bash_output, "ls -1a /tmp"); list <string> files = splitstring ( (string) cmd["stdout"]:"", "\n" ); // clearing memory cmd = nil; foreach (string filename, files, { if (regexpmatch(filename, "x")) { y2warning("Filename '%1' contains 'x'", filename); } if (size(filename) > 20) { y2error("Filename '%1' is longer than 20 chars", filename); // we don't work with files with longer names continue; } if (regexpmatch(filename, "^\.")) { y2milestone("Filename '%1' starts with a dot", filename); } else { y2milestone("Filename '%1' is what we are looking for", filename); } }); }
Example 8.15. continue statement in listmap
{ list <integer> random_integers = []; // filling up with random numbers while (size(random_integers) < 10) { random_integers = add (random_integers, random(32768)); } random_integers = toset(random_integers); map <integer, integer> new_map = listmap (integer number, random_integers, { // do not proccess huge numbers // actually, this finished the listmap and returns nil if (number > 32000) { y2error("A huge number has been found"); continue; } if (number % 3 == 0) { return $[number : number / 3]; } else { return $[number : number * 3]; } }); y2milestone("New map: %1", new_map); }
![]() | Note |
---|---|
The usage is similar to the break's one. |
Synopsis: return [ return_value ];
The return statement immediately leaves the current function or a current top level block (that contains it) and optionally assigns a return_value to this block. If blocks are nested, i.e. if the current block is contained in another block, the return statement leaves all nested blocks and defines the value of the outermost block.
However, if a block is used in an expression other than a block, and that expression is contained in an outer block, the return statement of the inner block won't leave the outer block but define the value of the inner block. This behavior is a as one would expect. For example in the iteration builtins in Section 8.16, “Applying Expressions To Lists And Maps”,
Example 8.16. return statement 1
{ // This block evaluates to 42. return 42; y2milestone("This command will never be executed"); }
Example 8.18. return statement 3
{ // This program evaluates to 3: integer a = 1 + { return 2; }; return a; }
Synopsis: data_type function_name ( [ typed_parameters ] ) function_body
A function definition creates a new function in the current namespace named function_name with a parameter list typed_parameters that has function_body attached for evaluation. The function_body must return a value of type data_type and the arguments passed upon function call must match the type definitions in typed_parameters.
Example 8.19. Function definition
{ void nothing() { y2milestone("doing nothing, returning nothing"); } integer half( integer value ) { return value / 2; } map <string, integer> get_some_map () { return listmap (string key, ["a", "b", "c"], { return $[key : random(999)]; }); } // This renders: ...nothing: nil - half: 21... y2milestone("nothing: %1 - half: %2", nothing(), half(42) ); // This renders something like: new half-random map: $["a":839, "b":393, "c":782] y2milestone("new half-random map: %1", get_some_map()); }
Synopsis: data_type function_name ( [ typed_parameters ] );
A function declaration allows you to declare only a header of a function without its body. It's main purpose is for indirect recursion etc. You have to provide a function definition with exactly the same arguments later in the same file. A new function will be declared in the current namespace named function_name with a parameter list typed_parameters.
Example 8.20. Function declaration
{ // declares the function - it is defined later void nothing(); integer half( integer value ) { return value / 2; } // This renders: ...nothing: nil - half: 21... // uses the function nothing y2milestone("nothing: %1 - half: %2", nothing(), half(42) ); // defines the function void nothing() { y2milestone("doing nothing, returning nothing"); } }
Synopsis: include " included file";
The include statement allows you to insert contents of a file at the given place in the current file. If the current file is a module, the contents of the included file will become a part of the module.
This is useful for dividing a large file into number of pieces. However, if a file is included more than once in a single block, the 2nd, 3rd etc. include statements are ignored.
The included file can be
a relative or an absolute file name. Relative
names are looked up with /usr/share/YaST2/include
as a base.
Example 8.21. Include a file
// this will include /usr/share/YaST2/include/program/definitions.ycp include "program/definitions.ycp";
Synopsis: import "name_space";
The import statement allows you to import another namespace (module) into the one you are just running in. Then you can access the global functions and variables of that namespace using the double-colon.
Namespaces, to be imported, are looked up in the
/usr/share/YaST2/modules/
directory.
{ import "Hostname"; import "IP"; // writes: Check FQDN: true y2milestone("Check FQDN: %1", Hostname::Check("www.example.com")); // writes: Check IP4: false y2milestone("Check IP: %1", IP::Check4("192.168.0.356")); }
This is often used for clients using some global API of modules but also for modules using global API of another ones. Please, be careful on creating cross-dependencies when creating an RPM package from sources.
In contrast to many other programming languages, YCP
variables can
be defined at (almost) any point in the code, namely between other
statements. Given that, there must be some rules regarding the
creation, destruction and validity of variables. Generally variables
are valid (accessible) within the block they are declared in. This
also covers nested blocks that may exist in this current block. The
valid program region for a variable is called a
scope.
Example 8.22. Variable scopes and blocks
{ // Declared in the outer block integer outer = 42; { // Declared in the inner block integer inner = 84; // This is OK. // Log: ...IN: inner: 84 - outer: 42 y2milestone("IN: inner: %1 - outer: %2", inner, outer); } // This yields an error because "inner" is not defined any more. y2milestone("OUT: inner: %1 - outer: %2", inner, outer); }
Additionally to the structural language elements described so far,
there are special commands that apply to lists
and
maps
. What is special about these commands is that
they apply an expression to the single elements of a list or
map. This is done in a functional manner,
i.e. the expression to be applied is passed as a parameter. Generally
this executes faster than a procedural loop
because the internal functionality is realized in a very effective
way.
Furthermore some of these commands create lists from maps, maps from maps, maps from lists etc., so that they can be used to avoid the cumbersome assembling of these compound data types in a procedural loop.
Synopsis (list): any
foreach (
type variable, list<type>, { expression }
);
Synopsis (map): any
foreach(
type_key variable_key, type_value variable_value,
map<type_key, type_value>, { expression }
);
This statement is a means to process the content of list or map in a sequential manner. It establishes an implicit loop over all entries of the list or map thereby executing the given expression with the respective entries. With lists the variable is a placeholder for the current entry. With maps, variable_key and variable_value are substituted for the respective key-value-pair.
![]() | Note |
---|---|
The return value of the last execution of expression determines the value of the whole foreach() statement. |
Example 8.23. foreach() Loop
{ // Exemplary foreach for list // This yields 3 foreach(integer value, [1, 2, 3], { return value; }); // Exemplary foreach for map // This yields 9 foreach(integer key, integer value, $[1:1, 2:4, 3:9], { y2milestone("value: %1", value); return value; }); }
Example 8.24. Sophisticated foreach() Loop
{ list <string> codes = ["X1", "D", "vT", "o", "T5h8"]; list <string> one_letter_codes = []; list <string> other_codes = []; foreach (string code, codes, { // all one-letter codes if (size(code) == 1) { one_letter_codes = add (one_letter_codes, code); // other ones } else { other_codes = add (other_codes, code); } }); // Results in: ["D", "o"] y2milestone("One-letter codes: %1", one_letter_codes); // Results in: ["X1", "vT", "T5h8"] y2milestone("Other codes: %1", other_codes); }
Synopsis: map<type1, type2>
listmap(
type3 variable, list<type3>, { expression returning
map<type1, type2> }
);
This statement is a means to process the content of list in a sequential manner. It establishes an implicit loop over all entries in list thereby executing the given expression with the respective entry. During execution variable is a placeholder for the current entry. For each element in list the expression is evaluated in a new context. The result of each evaluation MUST be a map with a single pair of key-value. All the returned key-value-pairs are assembled to form the new map that is returned.
Example 8.25. listmap() statement
{ // This results in $[1:"xy", 2:"xy", 3:"xy"] map <integer, string> m1 = listmap (integer s, [1, 2, 3], { return $[s: "xy"] }); // This results in $[11:2, 12:4, 13:6] map <integer, integer> m2 = listmap (integer s, [1, 2, 3], { integer a = s+10; integer b = s*2; list ret = [a, b]; return ret; }); y2milestone("map 1: %1 - map 2: %2", m1, m2); }
Synopsis (map): list<type1>
maplist(
type2 key, type3 value, map<type2, type3>,
{ block returning type1 }
);
Synopsis (list): list<type1>
maplist(
type2 variable, list<type2>,
{ block returning type1 }
);
This statement is a means to process the content of map or list in a sequential manner. It establishes an implicit loop over all entries in map or list thereby executing the given expression with the respective entries. With lists the variable is a placeholder for the current entry. With maps, key and value are substituted for the respective key-value-pair. For each element the expression is evaluated in a new context. All return values are assembled to form the new list that is returned.
![]() | Note |
---|---|
To exit the loop before it ends, use the break. See the usage in the break statement description. To skip to the next loop step, use the continue. See the usage in the continue statement description. |
Example 8.26. maplist() statement
{ // This results in [2, 4, 6] list<integer> l1 = maplist (integer s, [1, 2, 3], { reuturn s*2; }); // This results in [2, 6, 12] list<integer> l2 = maplist (integer k, integer v, $[1:2, 2:3, 3:4], { return k*v; }); y2milestone("list 1: %1 - list 2: %2", l1, l2); }
Synopsis: map<type1, type2>
mapmap(
type3 key, type4 value, map<type3, type4>,
{ expression returning map<type1, type2> }
);
This statement is a means to process the content of map in a sequential manner. It establishes an implicit loop over all entries in map thereby executing the given expression with key and value substituted for the respective key-value-pair. For each map element the expression is evaluated in a new context. The result of each evaluation MUST be a map with a single pair of key-value. All the returned key-value-pairs are assembled to form the new map that is returned.
Example 8.27. mapmap() statement
{ // This results in $[11:"ax", 12:"bx"] map<integer,string> m = mapmap (integer k, string v, $[1:"a", 2:"b"], { return [k+10, v+"x"]; }); y2milestone("map: %1", m); }
In the previous sections we have already seen some YCP
code that
dealt with the creation and handling of on-screen dialogs. These
examples were rather simple to show the basic
strategy of creating dialogs. Of course designing “real”
dialogs that do useful things is is bit more complicated and requires a
rather good knowledge of the instruments provided by the UI.
Because the UI has been designed to be most flexible, the possibilities
for creating and managing dialogs are quite versatile. Consequently the
instruments for doing this are rather diverse. In fact the UI extends
the basic YCP
language to a large extent, thereby providing the means
to create and manage on-screen dialogs.
For more information about the User Interface, handling events see the Layout HOWTO.
In the previous section the basic mechanisms suitable for managing
on-screen dialogs were presented. However, creating dialogs for each and
every application from scratch would be cumbersome and moreover is
unnecessary. For example the well-known layout that is presented with
nearly every YaST
dialog during installation was not
programmed anew for each dialog. Rather it is kind
of imported from a special YCP
module, the Wizard
that provides all the functionality necessary to create uniform
dialogs.
Furthermore, as time went by, during the development of the YaST
installer, the developers encountered situations where the same (or
similar) tasks ofttimes had to be accomplished at different locations
in the overall program flow. For example opening a popup to ask the
user a question with the predefined buttons “Yes” and
“No” is a procedure used very often.
This led to the development of predefined dialog elements that can (and
should) be included in the current YCP
source. Displaying the
Yes-No-popup from the example above is then reduced to calling a
function with respective parameters. Aside from avoiding the need to
redevelop such things again and again, another benefit is the ever same
visual appearance that adds up to the well-known YaST
look-and-feel.
Meanwhile there are also many functions that are not UI-related but
nonetheless very useful.
The omnium gatherum of all these elements has been collected to form
the so-called “YaST
Wizard”. In short the YaST
Wizard
consists of one YCP
module that provides the layout framework used in
the installation dialogs and some additional YCP
modules that provide
access to several common dialog elements needed rather often. Many
“generic” functions are at hand as well.
The following two sections cover these topics mostly by means of
references to the YaST
developers documentation.
In the previous section you got to know how to do a
YaST program. Well, normally YCP-scripts are
executed involving the whole YaST
-machinery, e.g. during
installation, which requires correct embedding of the script into the
surrounding YCP
environment. Fortunately there is a way to let run
YCP
-scripts isolated, i.e. stand-alone.
To do so we make use of the architectural separation of components
featured by YaST
. The “command line version” of YaST
is called y2base and can usually be found in
/usr/lib/YaST2/bin
. You could set the
PATH
to include this location to avoid typing in the
full path every time.
$> y2base -h Usage: y2base [LogOpts] Client [ClientOpts] Server [Generic ServerOpts] [Specific ServerOpts] LogOptions are: -l | --logfile LogFile : Set logfile ClientOptions are: -s : Get options as one YCPList from stdin -f FileName : Get YCPValue(s) from file '(any YCPValue)' : Parameter _IS_ a YCPValue Generic ServerOptions are: -p FileName : Evaluate YCPValue(s) from file (preload) '(any YCPValue)' : Parameter _IS_ a YCPValue to be evaluated Specific ServerOptions are any options passed on unevaluated. Examples: y2base installation qt Start binary y2base with intallation.ycp as client and qt as server y2base installation '("test")' qt Provide YCPValue '"test"' as parameter for client installation y2base installation qt -geometry 800x600 Provide geometry information as specific server options
This help page, showing the possible options in a call of
y2base, is rather self-explaining. For the moment
the interesting parameters are Client and
Server. In Section 2.4, “External Programs” we learned that YaST
consists
of several modules, some of them being
client-components and some others being
server-components. By invoking YaST
in the way
displayed above we can connect any client-component with any
server-component.
Because a YCP
-program (also called YCP
-module) can act as a
client-component, it is possible to connect it with a server-component
suitable of executing it. Since our “Hello,
World!”-program displays something on screen, we need to use
the UI as server-component in this case. As already said the UI is able
to use a text-based console environment as well as a graphical X11
environment which leads to the following two methods of running a
YCP
-script.
y2base file.ycp qt
This will execute file.ycp
in the graphical
Qt-UI.
y2base file.ycp ncurses
This will execute file.ycp
in the text-based
NCurses-UI.
Table of Contents
List of Figures
List of Tables
List of Examples
In the introductory chapter we have already heard something about
accessing the system with SCR. Because manipulating the system at the
lowest layer is all YaST
is about, we now want to take a closer look at
this topic.
Basically the SCR creates a consistent view of the system hardware and its configuration files. There are many dependencies between the different entities among those data and these dependencies have to be taken into consideration when manipulating them. For this being possible in a convenient way for the higher-level modules there must be an easy and consistent accessing method. This method is provided by the SCR as it presents kind of an abstraction of the various types of data to be handled.
Now the data “landscape” that must be covered here is rather heterogeneous. Hardware data and configuration data, both of most multifaceted type can hardly be handled by one single monolithic program. Therefore the SCR consists of a “head” that is accompanied by various helper programs, the so-called agents, each of them being specialized on a specific task.
For each category of system data there is a corresponding SCR-agent.
Their job is to map the real system data to YCP
-data structures so
that YCP
-modules can access them in a convenient way. In fact the
SCR-agents provide the YCP
data structures.
They come into existence with the presence of an SCR-agent that
provides them. Otherwise they wouldn't be there.
For example there is an agent that reads and writes the
/etc/sysconfig
files. The YCP
-representation of a
sysconfig
-variable is a single YCP
-string. When
reading, the agent reads the variable in the corresponding file and
creates a YCP
-string from it. When writing, the agent gets the new
value as YCP
-string and changes the variable in the corresponding file
accordingly.
It must be said here, that the set of agents may change over time. New agents may be created in the future and other ones might be abandoned if their functionality is obsolete or taken over by another agent. Generally this is no problem because for module development it is not (and should not be) necessary to know exactly which agent does what. As already said, the SCR provides an abstraction of the data to be handled and this abstraction comes into being in form of a tree, the SCR-tree.
As a computer's hardware and software configuration is quite complex, the SCR organizes all data in form of a tree. It resembles very much a file system with its folders, sub-folders and files whereby the tree structure reflects the thematic separation of the various configuration categories.
The SCR-tree consists of two different kinds of nodes:
Table 2.1. SCR Node Types
Data nodes |
Data nodes represent single pieces of data, for example a
sysconfig -entry or a mountpoint of a
file system in /etc/fstab . They are the
leaves of the tree and stand for actual
data to be handled.
|
Map nodes | Map nodes allow for navigation to the leaves just like the path components in the directory structure of a file system. This way map nodes are used to structure the data in a suitable manner. |
The names of the nodes in the SCR-tree can be concatenated resulting in
the creation of an SCR-path. An SCR-path is a
description were to find a node in the SCR-tree. It is a sequence of
path components each of them being a string. As we have seen in the
section Data type path YCP
-paths are prepended by
dots (.) which act as separators in compound paths. So
.foo.bar
is a valid YCP
-path. If
bar
is an SCR data node, then
SCR::Read(.foo.bar)
would render some data. If
bar
is a map node, then
SCR::Dir(.foo.bar)
would reveal the immediate
sub-nodes in the SCR-tree, e.g. ["big", "brown",
"fox"]
. The single dot (.) is also valid and denotes the
root of the whole SCR-tree. Consequently SCR::Dir(.)
will return a list of all the top-nodes in the SCR-tree.
In the figure below we see a (very small) cut-out of the SCR-tree that is related to hardware-specific information.
The light gray nodes are SCR-map-nodes denoting the path to the data.
They can be used with SCR::Dir(...)
to find out what
is below. So in the figure above SCR::Dir(.probe)
would return a list as [..."has_smp", "boot_arch",
"has_apm"...]
.
The dark grey nodes are SCR-data-nodes that stand for the actual data.
What can be done with them depends on the actual node (reading,
writing, executing), but usually SCR::Read(...)
is
possible. As is shown above
SCR::Read(.probe.boot_arch)
would return
"grub"
.
Now that we know how the SCR-landscape can be navigated we will take a look at how the data that is dug in there can be accessed. The accessing methods already implied above shall now be defined more precisely.
There are four accessing methods.
Reading
We speak of reading, when the agent reads some configuration file or scans the system hardware and produces a YCP data structure representing this information.
Writing
We speak of writing, when the agent gets some YCP data structure and creates or modifies some system configuration file according to these data.
Executing
We speak of executing, when the agent gets
some YCP data that can be interpreted as instruction and executes
it. Usually this is being done by means of another program, e.g.
the bash
.
Dir
Compared to the other accessing methods listed above, the
Dir-command is somewhat special. It takes as
argument an SCR-path that points to a specific node in the
SCR-tree. It returns a list of all the sub-paths that are
immediately below this node. This way it works just like a
dir-command in a file system. For example if
you apply this to the root of the SCR-tree (.), the answer would be
a list with all the top nodes known by the SCR , e.g.
["audio", ... , "yast2"]
.
[1]
As a rule all SCR-agents implement some of the four accessing methods listed above. However depending on the task the agent was made for, not all of them may be provided.
For convenient use from YCP
the accessing methods are realized by
means of an API, i.e. a defined set of
YCP
-functions that are understood. You can call these functions from
YCP
if you prepend the commands with the name space identifier
SCR::
which causes redirection to the SCR.
Table 3.1. The SCR-commands
Function | What it does |
---|---|
Read(path p) -> any
|
Reads the data represented by the node at path
p . The value returned can be any YCP data
type but it is always one single value.
|
Write(path p, any v) -> boolean
|
Writes the value v to the node at path
p . The boolean return value is
true on success. On error the return value
is false and a log entry is generated in the
log file. Reasons for errors can be a mistyped value
v or some problem with the periphery that
lies behind the data-node.
|
Execute(path p) -> boolean
|
This command is mostly used with the
system-agent (see Chapter 6, Useful SCR Agents). Usually the return value
indicates success or failure of the executed command.
|
Dir(path p) -> list(string)
|
Returns a list of all subtree nodes immediately below the node
p . For each such node the list contains a
string denoting its name. If p does not
point to a map node, i.e. the last path component is a leaf,
the command will return an empty list or
nil .
|
[1]
Unfortunately not all SCR-agents do support this command
properly. There may be agents that wrongly return an empty list
or even nil
when queried this way.
In the last section we saw some examples of how the SCR can be used
from YCP
. However if you only want to test or explore different
SCR-paths, writing a YCP
-script for every access can be cumbersome.
Fortunately the SCR-component can be run individually on the command
line of a terminal using a method very similar to the one we saw in
Section 1.3, “Advanced YaST2 command line parsing”.
In contrast to the method demonstrated there, this time we don't feed
a YCP
-script into YaST
. Instead we make another use of the
architectural separation of components featured by YaST
in that we
connect the so-called stdio-component with the
SCR-component. By doing so we can feed everything
we type on the command line into the SCR.
However, because of the “raw” nature of the
YaST
-internal communication paths, this method is not very
comfortable. You can't correct typos with
Backspace or Del here (the
SCR is not meant to be operated in this way). By
doing so we kind of “simulate” YaST
-internal
communication which normally forecloses any misspelling.
Furthermore, if you play around with the SCR in this manner you will be able to initiate privileged actions only if you are running the commands under the root-account.
![]() | Caution |
---|---|
If you run “manual” SCR-commands under the root-account, the SCR will “gracefully” fulfill all your wishes. So be careful with Write and Execute!!! |
Now operating the SCR this way can be shown best with some examples.
Example 5.1. Operating the SCR from the command line
$> /usr/lib/YaST2/bin/y2base stdio scr ([]) `Read(.probe.boot_arch) ("grub") `Read(.probe.version) ("Oct 7 2002, 15:05:08") `Read(.probe.has_smp) (false) `Read(.probe.has_apm) (true) `Read(.probe.boot_arch) (nil)
As is shown above, the command y2base stdio scr
starts YaST
in a specific way. It connects the YaST
client-component stdio with the server-component
scr. After that the SCR is running and awaits any
input on stdio which in this case is the console. To explore the
content of the SCR-tree you can now enter any SCR-commands just as you
would do in YCP
. The only difference is the absence of the
SCR::
name space identifier which must not be given
here as can be seen in the last line.
The SCR-world knows many agents for all sorts of tasks. Unfortunately this matter is subject to a rather high change service and not (yet) well documented. Therefore it is not easily possible to explain the details in a manner of “Which agent provides which paths for what reason?”. As a result only the most helpful agents are mentioned here along with references to the respective developers documentation.
Please note that even the developers documentation might be outdated to
some extent. Consequently the most reliable source of information are
the “real” files below
/usr/share/YaST2/
.
System Agent
This agent realizes access to the target system during installation.
/usr/share/doc/packages/yast2-core/agent-system/ag_system-builtins.html /usr/share/doc/packages/yast2-core/agent-system/ag_system-builtins.html
Background Agent
This agent runs shell commands in the background.
/usr/share/doc/packages/yast2-core/agents-perl/ag_background.html /usr/share/doc/packages/yast2-core/agents-perl/ag_background.html
Hardware Probe Agent
The agent being responsible for hardware probing.
/usr/share/doc/packages/yast2-core/agent-probe/hwprobe.html /usr/share/doc/packages/yast2-core/agent-probe/hwprobe.html
Any-Agent
This agent handles the access to configuration files of (almost) arbitrary syntax. The syntax to be understood must be specified in a configuration file.
/usr/share/doc/packages/yast2-core/agent-any/anyagent.html /usr/share/doc/packages/yast2-core/agent-any/anyagent.html
Ini-Agent
The Ini-agent is suitable for accessing configuration files with the well-known ini-file syntax.
/usr/share/doc/packages/yast2-core/agent-ini/ini.html /usr/share/doc/packages/yast2-core/agent-ini/ini.html
Modules Agent
This agent is the interface to the
/etc/modules.conf
file.
/usr/share/doc/packages/yast2-core/agent-modules/modules.html /usr/share/doc/packages/yast2-core/agent-modules/modules.html
Perl Agent
This agent is a means to call Perl scripts from within YCP
.
/usr/share/doc/packages/yast2-core/agents-perl/ycp-pm.html file:///usr/share/doc/packages/yast2-core/agents-perl/ycp-pm.html
Table of Contents
Creating modules for YaST
means extending its functionality. For this
being possible it is necessary to follow the infrastructural and
functional particularities of YaST
as well as some guidelines regarding
the interaction of the module with the user and the rest of the system.
In the following we'll have a closer look at these topics .
Throughout this document the term “YCP
module” was
mentioned repeatedly without providing a sharp definition. In fact the
term “module” is used quite loosely in the YaST
world,
because there are several kinds of modules
involved in different contexts. The following text shall lighten
this topic.
Different kinds of YCP modules
Generic YCP
modules
In principle every YCP
file that provides a distinct functionality
can be seen as a module. Typical representatives of this kind of
module are the inst_xxx.ycp
files that are part
of the YaST
installer. Modules of this kind mostly represent
rather self-contained functionality, e.g like
inst_keyboard.ycp
that provides the user dialog
for selecting a keyboard during installation. These modules are
usually called via CallFunction()
.
Library modules
This kind of module can be seen as what is called a library in
other programming languages. Usually these modules are a collection
of functions that must be included to be used.
As with other programming languages, including
in YCP
means merely text insertion that takes place each and every
time an include
is stated. This is often
adverse with respect to speed and memory consumption.
True YCP
modules
This kind of module is the most interesting one. True modules represent an “object oriented” approach to module design. Because the mechanisms associated with them deserve some special mention, the next section will cover this topic in more detail.
Table of Contents
True modules are rather new in the YaST
world and it is planned that
they will replace the old method of including
modules completely (with exception of some rare cases perhaps). The
following sections will outline the differences between these concepts.
YCP
, originally planned as a functional language, always did dynamic
(i.e. runtime) binding of variables. Although useful in many cases,
it's quite puzzling for someone used to “imperative”
languages. So you could well program the following block and get an
unexpected result.
{ integer x = 42; define f() ``{ return x; } ... // lots of lines x = 55; return f(); // will return 55 because of runtime binding of x! }
Another widely misused feature is to include global definitions.
While there was no alternative as long as include
was the only referencing instrument, this is certainly not a good
programming practice in view of speed and memory considerations.
In contrast to included modules, true modules have some distinct properties that are shown in the list below.
Definition-time bindings
Definitions are evaluated in the sequence of the program flow.
One-time inclusion
In contrast to include
the
import
statement includes the module only once
even if there are more than one import
statement in the program flow. Later imports are silently ignored.
Proprietary global namespace
The module definition implies a module declaration that determines the namespace of the module's global variable scope.
Local environment
Aside from the data located in the module's global namespace all other data defined in the module is purely local, i.e. is invisible from the outside.
Module constructor function
Each true module may have a constructor function that is automatically executed upon first import.
The following listing is a brief sample of a true module.
{ // This is a module called "Sample". // Therefore the file name MUST be Sample.ycp // The "module" statement makes the module accessible for 'import'. // module "Sample"; // This is a local declaration. // It can only be 'seen' inside the module. // integer local_var = 42; // This is a global declaration. // It can be accessed from outside with the name space identifier 'Sample::'. // global integer global_var = 27; // This is a global function. // It has access to global_var *and* local_var. // global define sample_f () ``{ return local_var + global_var; } }
The module above can be used with the import
statement. The syntax for file inclusion with
import
is similar to include
.
The interpreter automatically appends “.ycp” to the
filename and searches below
/usr/lib/YaST2/modules
. If the filename starts
with “./”, the file is loaded from the local directory.
The global declarations of the module can then be accessed with the
name space identifier Sample::
.
![]() | Note |
---|---|
The file name must match the module declaration! Inside modules, only variable or function declarations are allowed. Stand-alone blocks or any kind of evaluation statements are forbidden. |
{ // This imports the 'Sample'-module. // import "Sample"; // The global function is called with the respective name space identifier. // integer i = Sample::sample_f(); // == 69 // No access to local module variables. // i = Sample::local_var; // ERROR, no access possible ! // No problem with global variables. // i = Sample::global_var; // == 27 Sample::global_var = 0; // This variable is writable !! return Sample::sample_f(); // == 42, since global_var is 0 }
![]() | Note |
---|---|
The first encounter of the statement |
If a global function with the same name as the module is defined, it is treated as a constructor. The constructor is called after the module has been loaded and evaluated for the first time. Because of this the constructor could (and should) be defined at the beginning of the module. Despite being located “on top” it can make use of the functions declared later in the file.
Module constructors are used mostly for initialization purposes, e.g. for setting local variables to proper values. However, the actions within a constructor can be arbitrarily complex.
![]() | Note |
---|---|
Constructors can't have any arguments. The result of calling a constructor from the outside is ignored. |
{ // This is a module called "Class" with a constructor function. // module "Class"; // A globally accessible variable. // global integer class_var = 42; // This is the constructor (same name as the module). // global define Class() ``{ class_var = 12345; } } { // The usage of the "Class"-module. // import "Class"; return Class::class_var; // will be 12345 ! }
Table of Contents
Most often when a YaST
module shall be created, this module will have
some interaction with the user. This usually implies the creation of
dialogs to be displayed on screen. As you might have noticed the
dialogs that come ready-made with YaST
follow a distinct “look
and feel” which is due to the fact that the YaST
developers
follow some rules regarding the visual appearance as well as the
functional behavior of a dialog. The keywords here are usability and
GUI-consistency.
When it comes to user-interaction one concept that is stressed very often is usability or - more speaking - user-friendliness. If you have ever heard s.th. about ergonomics you may also know the term Human Computer Interaction (HCI). For us regular folks usability is probably the best notation because it best summarizes what's it all about. It means that the program in question is good “usable” by the user. In general that means that operating a screen dialog should enjoin as low a burden as possible on the user.
In order to have a good usability a system should satisfy the following criteria:
Users must be able to accomplish their goal with minimal effort and maximum results.
The system must not treat the user in a hostile fashion or treat the user as if they do not matter.
The system can not crash or produce any unexpected results at any point in the process.
There must be constraints on the users actions.
Users should not suffer from information overload.
The system must be consistent at every point in the process.
The system must always provide feedback to the user so that they know and understand what is happening at every point in the process.
![]() | Important |
---|---|
If you want to create an interactive |
All that said above in essence is an outline from a very good article by Todd Burgess. If you are interested in a more elaborate discussion of usability you may have a look at http://www.osOpinion.com/Opinions/ToddBurgess/ToddBurgess1.html
Table of Contents
FIXME: To be done...
Table of Contents
List of Examples
A lot of YaST documentation is generated from the source code. That's the reason why we should follow some rules when documenting the source code (especially functions and variables) to have the possibility of auto-generated documentation.
Why should we have it? It's not needed to create your own modules, functions or agents when such things alread exist. You can just use something that already exist. But the most important information is that it exists, where it exists and how to use it. All these pieces of information can be in the auto-generated documentation.
Additionally, if you develop some universal module with, e.g., functionality offering abstraction layer for reading and writing records from and into the OpenLDAP server, you should document the functionality itself to offer it to others.
Table of Contents
Most of the modules and clients are written in the YCP programming language.
Although YCP itself offers more possibilities howto write comments, there are some quite strict rules that needs to be followed. Don't be afraid, they are easy to remember.
Every YaST package, you have installed on your system, has its
auto-generated HTML-based documentation in the
/usr/share/doc/packages/_package_name_/.../autodocs/
directory. This documentation is generated by the
ycpdoc script — part of
the yast2-devtools
package.
![]() | Important |
---|---|
To recognize the comment that should be processed for the autodocs, ycpdoc needs this syntax: /** * * slash and two asterisks identify the comment * for file headers, functions and variables * */
/*** * * slash and three asterisks identify the initial comment * of file (intro) * */ If you don't use two (or three) asterisks, the comment doesn't get processed at all. |
Here you can see all available options and arguments of the ycpdoc script. This is the /usr/lib/YaST2/bin/ycpdoc --help command output:
Usage: ycpdoc -h|--help|--man ycpdoc [-d <dir>] [-s <dir>] [-f html|xml] [-i] [-] [-o] [-wr] files.ycp... Options and Arguments: -h Show this help screen -d dir, --outputdir=dir Output files are placed to directory dir -s dir, --strip=dir Strip only dir when generating output files (default all). Remaining slashes are converted to double underscores. -f format, --format=format Produce output in given format, html or xml. The option may be repeated. HTML is the default. XML produces ycpdoc.xml, the DTD is not stable yet. -O file, --xml-output=file For xml output, write to file. The default is "ycpdoc.xml", not respecting outputdir. -i, --noindex Do not generate index.html (intro.html, files.html) - Write output to stdout. Do not generate indexes. If there are more input files, generate only one output html file -o, --oldindex Old style of index: creates only index.html -wr Do not warn about undeclared return types --state For debugging, prints the parsing state for each line.
This is the example of generated HTML-based documentation. Run the
command /usr/lib/YaST2/bin/ycpdoc
/usr/share/YaST2/modules/FileUtils.ycp and open the just
generated HTML files in some HTML browser. First of all, see
the one that contains FileUtils.html
in its name.
You can see all global functions and variables documented there. Functions also with their parameters, return types, examples, etc.
This example of generated XML-based documentation. Run the
command /usr/lib/YaST2/bin/ycpdoc --format=xml
/usr/share/YaST2/modules/FileUtils.ycp and see the just
generated ycpdoc.xml
XML file.
![]() | Note |
---|---|
XML is not for humans, it's optimized for computers. That's also why the XML output of ycpdoc doesn't so look nice but it's very valuable for text-processing tools. |
Example 1.1. Examplary XML output reformated and shortened by hand
<?xml version="1.0" encoding="UTF-8"?> <ycpdoc> <files> <file_item key="usr/share/YaST2/modules/FileUtils.ycp"> <header> <authors> <ITEM>author</ITEM> </authors> <file>modules/FileUtils.ycp</file> <module>YaST2</module> <summary>summary</summary> </header> <provides> <provides_item> <descr></descr> <example_file></example_file> <file>usr/share/YaST2/modules/FileUtils.ycp</file> <global>1</global> <kind>function</kind> <name>Exists</name> <parameters> <parameters_item> <name>target</name> <type>string</type> </parameters_item> </parameters> <return>true if exists</return> <scruple></scruple> <see></see> <short>short description</short> <type>boolean</type> </provides_item> ... </provides> <requires> <requires_item> <kind>import</kind> <name>SCR</name> </requires_item> ... </requires> </file_item> </files> </ycpdoc>
The file header should show general information about the file name and location, author, and a general purpose.
Example 1.2. Exemplary file header
{ /** * File: modules/CRONConfig.ycp * Package: General YaST Modules * Authors: John The Fish <john@thesmallfish.net> * Flags: Stable * * $Id: $ * * This module offers to read, change and write the current * CRON setup for all system users. */ module "CRONConfig"; ... }
File header format:
Attribute: value
Possible Attributes
are:
Authors
Author
Depends
File
Flags
Internal
Module
Package
Summary
Defines the author(s) of the file.
Example 1.4. Multi-line Authors tag
Authors: Wendy Graceful <grace@example.com> Lars Kralewski <lars@example.com>
What does this file / module depend on.
Relative path where is this file located under the base YaST
/usr/share/YaST2/
path.
Defines the default value for the module stability (read: stability of module API).
Possible values are:
Stable
Unstable
If a module is marked as Internal, it will be omitted while generation the YaST overall documentation. This tag is not mandatory, by default a module is not internal.
Textual description of the package (YaST module) that this file belongs to. This tag is obsolete.
Defines the purpose of this file, such as what does do, how it does that, what it uses for that, etc.
Example 1.10. Multi-line Summary tag
Summary: This is a multiline summary for the exemplary YaST module that does nothing and has no valuable content at all.
Summary tag is something special —. You don't need to define the tag itself, just write a multiline text after at the bottom of the comment after the $Id: $ entry.
Example 1.11. Other Multi-line Summary tag
/** * File: modules/DoNothingAtAll.ycp * * ... * * $Id: $ * * This is a multiline summary for the exemplary YaST module * that does nothing and has no valuable content at all. */
The function header should describe all needed pieces of information about the function. Such as parameters, return value, function's textual description, examples, etc.
Example 1.12. Simple Example of Function Description
/** * Returns number of just connected users to the server. * You can filter these users by defining the filtering parameter. * * @param part_of_ip ("192.168.") that users are connected from * @return integer number of connected users * * @see AnotherFunction() */ global integer GetCountOfConnectedUsers (string part_of_ip) { ... }
Function description tags:
@deprecated
@descr
@example
@param
@ref
@return
@short
@since
@stable
@struct
@unstable
Defines that the function has been deprecated. Additionaly defines another function as the replacement.
Complete description of the function. It might describe the environment or detailed behavior.
Example 1.15. Automatic description
/** * This first line is identified as @short * * This multi-line @descr (after a newline) is automatically * taken as the function description. * * Additionally, this multi-line text is automatically taken as another paragraph * of the function @descr (the number of paragraphs is unlimited). */
Block of examples of usage, they are as exported as they are written (like <pre> in HTML).
Example 1.16. Multiline @example block
@example list <string> servers = GetListOfNSServers("example.com."); boolean success = AddNewNSServer("ns4.example.com.", "example.com.");
Describes the parameter of the function. The order
of @param
tags is significant.
Example 1.17. @param tag
/** * Adds a new NS Server into list of domain NS servers. * * @param new_ns_server FQDN * @param domain * @return boolean successfull */ global boolean AddNewNSServer (string new_ns_server, string domain) { ... return success; }
Single-line version of @see
tag.
Description of the return value.
Example 1.19. @return tag
/** * Removes the NS Server from list of domain NS servers. * * @param ns_server FQDN * @param domain * @return boolean successfull */ global boolean RemoveNSServer (string ns_server, string domain) { ... boolean success = true; return success; }
Short single-line description of the function.
Example 1.21. Automatic description
/** * This first line is identified as @short * * This multi-line @descr (after a newline) is automatically * taken as the function description. */
See the @descr
tag.
Since which version thix function exists. This is not a commonly used tag.
Defines that the function has a stable API and we don't plan to change it for years.
See tag @unstable.
This tag might changes the default value of the file's stability of the API (just for the current function).
This tag is used to represent the used data-structure closer. It's as represented as it's written (such as the <pre> HTML tag).
Example 1.24. @struct tag
@struct returns $[ // example.com. "domain" : "domain name", // list of NS servers assigned to the domain "ns_servers" : [ "ns1", "ns2", ... ], // map of MX servers $[ "server_name" : priority ] "mx_servers" : $[ "mx1" : 10, "mx2" : 5 ], ]
The YCP variable description can contain almost all pieces
of information as the YCP
function
description
except @param
and @return
tags that don't make sense here. You can, for instance, describe
the variable's textual description, examples, structures, etc.
However, it's usual that only the description is given.
Example 1.26. Simple Example of Variable Description
/** * Defines the number of connected users for each IP. * There might be more connected users from one IP. * * @struct $[ * "192.168.0.1" : 5, * "192.168.0.12" : 1, * "192.168.0.35" : 3, * ] * * @see GetNumberOfConnectedUsers() */ global map <string, integer> number_of_connected_users = nil;
Example 1.27. The Most Usual Usage
/** * Stores the currently selected domain */ global string current_domain = "";
Function description tags:
@deprecated
@descr
@example
@ref
@short
@since
@stable
@struct
@unstable
See the YCP function description for usage and parameters of tags mentioned above.
The file header should show what is the SCR Agent good for, supported acces types (read/write/execute) and examples how to use it.
Example 3.1. Exemplary SCR Agent Definition
/** * File: * sshd.scr * Summary: * SCR Agent for reading/writing /etc/ssh/sshd_config * using the ini-agent * Access: * read/write * Authors: * John The Fish <john@thesmallfish.net> * Example: * Dir(.sshd) * (["Port", "X11Forwarding", "Compression", "MaxAuthTries", "PermitRootLogin"]) * * Read(.sshd.Compression) * (["yes"]) * * Write(.sshd.Compression, "no") * (true) * * $Id: sshd.scr 11113 2005-10-20 14:15:16Z jtf $ * * Fore more information about possible keys and values * consult with the sshd_config man pages `man sshd_config`. */ .sshd `ag_ini( ... )
Table of Contents
List of Examples
Table of Contents
This is a set of basic routines for manipulating packages.
The module Package is the module for handling package installation , it works on the target system and correctly differentiates between normal and autoinstallaton mode.
PackageSystem is for situations when you need immediate action (for example to start the client).
Package
PackageSystem
The function names should be self-descriptive, so there are no comments here. Feel free to ask if you are in doubts. (FIXME)
boolean Package::InstallMsg(<string> package, <string> message);
boolean Package::InstallAllMsg(list<string> packages, <string> message);
boolean Package::InstallAnyMsg(list<string> packages, <string> message);
boolean Package::RemoveMsg(<string> package, <string> message);
boolean Package::RemoveAllMsg(list<string> packages, <string> message);
![]() | Note |
---|---|
GUI based, do not install if Mode::config is defined, only in PackageSystem) |
The module Popup.ycp
contains commonly used popup dialogs
for general usage. Use those dialogs rather than creating your own custom
dialogs whenever possible.
If you have your own definitions of equivalent popups (which is very likely),
please remove them as soon as possible and use the popups from
Popup.ycp
. The new popups usually require fewer
parameters, e.g., you no longer need to explicitly pass standard button
labels like Yes
or No
etc.
as parameters (we had to do that because of technical limitations with the
translator module, but we found a workaround for that).
There are several versions for each type of popup, a simple version with a minimum number of parameters and one or more "expert" versions where you can pass lots of parameters to fine-tune many details.
Use the simplest version one whenever you can. It's normally the version that makes most sense for most cases.
If you use an expert version, try to stick to the default behaviour (i.e. the behaviour of the simple version) as closely as possible. Change only parameters you really need to change. In particular, only change the default button for very good reasons.
If there is a specialized simple version, use it whenever you can.
For example, use Popup::Warning
rather than Popup::AnyMessage
with the same message if you want to display a warning. This way we can make
sure all warnings look the same and make them easily recognizable
as warnings.
Sense or nonsense of providing headlines for each popup is a cause for endless discussion - we've been through that. Sometimes headlines make sense, sometimes they don't.
As a general rule of thumb, don't provide a headline that is more or less the same as the message itself, i.e. don't
// Example how NOT to use popups: // // - The headline is redundant // // - The text is too verbose // // - The text contains a reference to "YaST2" // (the user shouldn't need to know what that is) { import "Popup"; boolean answer = Popup::YesNoHeadline( "Create Backup?", // superfluous headline "Should YaST2 create a backup of the configuration files?" ); }
this is plainly redundant. This could be done much better like this:
// Improved version of ask_create_backup_bad.ycp: // // Note there is no superfluous headline any more, // the text is concise, and there is no more reference // to "YaST2" (a user shouldn't need to know what that is) { import "Popup"; boolean answer = Popup::YesNo( "Create a backup of the configuration files?" ); }
i.e. concise question, no lyrics around it, clear, plain and simple.
If you need to provide more background information to the users so they can understand what tragedy could befall their machine should they chose either alternative, a headline makes perfect sense for the more experienced user who gets to this kind of question quite frequently:
// Example when to use a headline: // // There is lengthy text that advanced users might have read several times // before, so we give him a concise headline that identifies that dialog so he // can keep on working without having to read everything again. { import "Popup"; string long_text = "Resizing the windows partition works well in most cases,\n" + "but there are pathological cases where this might fail.\n" + "\n" + "You might lose all data on that disk. So please make sure\n" + "you have an up-to-date backup of all relevant data\n" + "for disaster recovery.\n" + "\n" + "If you are unsure, it might be a good idea to abort the installation\n" + "right now and make a backup." ; boolean answer = Popup::YesNoHeadline( "Resize Windows Partition?", long_text ); }
![]() | Note |
---|---|
This example might be a good candidate for ContinueCancel() - see below |
If you use headlines, please use them to either
classify the type of popup (Error, Warning, ...)
summarize the question.
Please DON'T use headlines like Question
etc.
- that doesn't have any informative value, yet it forces the user
to read this useless headline.
For all those reasons, most popups come in a generic version where you can pass a headline ("Headline" is included in the names of those) and a simple version for general usage.
There are predefined messages for commonly used texts for the dialogs.
Use them when you use the expert version of a dialog
- don't pass your own messages if you can avoid it! Not only makes
this life easier for our translators (they need to translate stuff like
Cancel
over and over again), it also gives us a chance
to provide consistent keyboard shortcuts throughout the entire program.
Example 1.1.
Use
`OpenDialog( ... `HBox( :-) `PushButton( `id(`ok ), `opt(`default), Label::OKButton() ), `PushButton( `id(`retry ), Label::RetryButton() ), `PushButton( `id(`cancel), Label::CancelButton() ) ) )
Do not use
`OpenDialog( ... `HBox( :-( `PushButton( `id(`ok ), `opt(`default), _("&OK") ), `PushButton( `id(`retry ), "&Retry" ), `PushButton( `id(`cancel), "&Cancel" ) ) )
- even whenever you create your own dialogs.
The first part of the name always is the message itself literally
Retry
, the suffix indicates the type
Button
to indicate whether or not it has a keyboard
shortcut. Currently we have Label::xxxButton
(with
keyboard shortcut) and Label::xxxMsg
(without shortcut).
For confirmation of possibly dangerous things, use Popup::ContinueCancel
.
Only when there are two really distinct paths of decision use
Popup::YesNo
.
And no, cancelling the entire process doesn't count here -
this is no equivalent decision.
The positive case (Yes
or
Continue
) is the default. If you don't like that,
use the generic Popup::AnyQuestion
directly and pass `focus_no for the focus parameter.
![]() | Important |
---|---|
Remember: Only do that for very good reasons - i.e. when it's a really dangerous decision that typically results in loss of data that can't easily be restored. |
If you need to pass other button labels, think twice.
If you really need this, use Popup::AnyQuestion
.
But NEVER use it so simply change the order of default buttons - i.e. NEVER create dialogs like this one:
// Bad Popup design: Default button order exchanged // // DON'T DO THIS! { import "Popup"; import "Label"; boolean dont_do_it = Popup::AnyQuestion( Popup::NoHeadline(), "Format hard disk?", Label::CancelButton(), Label::ContinueButton(), `yes ); // initial focus - "Cancel" in this case }
// Bad Popup design: Default button order exchanged // // DON'T DO THIS! { import "Popup"; import "Label"; boolean dont_do_it = Popup::AnyQuestion( Popup::NoHeadline(), "Show installation log?", Label::NoButton(), Label::YesButton(), `no ); // button role reversed - "Yes" }
simple specification in the YaST module
automatic help
automatic checking of arguments (types, format)
interactive session without UI
The aim of the module is to provide as automatic interface as possible for controlling the module. To support interactive sessions, the YaST module needs to provide command-handling loop similar to concept of event-handling in GUIs.
If the module does not need to do any special handling of the actions, it can use the "commandline" include (a wrapper for CommandLine). The include defines a single function "CommandLineRun()", which implements a standard event-loop and returns true on success. The module just needs to specify handlers for actions, user-interface, initialization and finishing.
Example 1.2. Simple CommandLine definition
{ define void deleteHandler( map options ) ``{ string dev = options["device"]:""; CommandLine::Print("Deleting: "+dev); if(Lan::Delete(dev) && Lan::Commit()) CommandLine::Print("Success"); else CommandLine::Print("Error"); } ... map cmdline = $[ "help" : "Configuration of network cards", "id" : "lan", "guihandler": ``(LanSequence()), "initialize": ``(Lan::Read()), "finish" : ``(Lan::Finish()), "actions" : $[ "list" : $[ "help" : "display configuration summary", "example" : "lan list configured", "handler" : ``(listHandler()) ], "add" : $[ "help" : "add a network card", "handler" : ``(addHandler()) ], "delete" : $[ "help" : "delete a network card", "handler" : ``(deleteHandler()) ] ], ... ]; import "Lan"; include "commandline/commandline.ycp"; CommandLineRun(cmdline); /* EOF */ }
The UI handler is specified in the "guihandler" key of the command line description map. It must take no arguments and return boolean, true on success.
The initialize resp. finish handler is specified by the "initialize" resp. "finish" key of the description map. They do not take any arguments and must return boolean, true on success. Notice that "initialize" and "finish" handlers are not used if a user asked for GUI (guihandler is used instead and therefore it must do initializing and finishing on its own). The handler for an action is specified in the "handler" key of the action description map. Each handler must take a single argument containing the options entered by the user and return a boolean, true on success. If the handler returns "false", the command line will abort for non-interactive handling. This is useful for handling error states. However, a handler must use CommandLine::Abort() to stop event-handling in the interactive mode.
The CommandLine module is stateful, i.e., it contains a current state of command line parsing/interactive console control of a YaST module. Therefore, the CommandLineRun() handles the commands as follows:
standard UI start of a module - CommandLine::StartGUI will return true in this case
command given as an argument - the inner while loop will be done only once
interactive controling of a module - the while loop will be done as long as the user does not enter "exit" or "quit"
shows the help text for the command
starts interactive session without UI
shows the command-specific help
option to supress the progress messages
These are available in interactive mode only:
![]() | Note |
---|---|
If the map does not follow the following rules, an error will be emitted into log. |
The map describing the command line interface of a module must contain "id" entry containing the name of the module. The map can contain global help text, a list of actions, a list of options and a list for mapping of options to actions (which optionscan be used for which actions). Each action and option must have its help text.
Actions is a map with the action name as a key. For each action, it isnecessary to provide the help text. Optionally, action can have defined an example of usage.
A list of flags can be specified to change the default behavior of the parameter checker. It is a list with key "options". Currently known flags:
for unknown parameters, do not check their validity can be used for passing unknown options into the handler
Example 1.3. Actions map definition
"actions" : $[ "list" : $[ "help": "display configuration summary", "example": "lan list configured", "options": [ "non_strict" ] ], "add" : $[ "help": "add a network card" ], ... ],
Options is a map with the option name as a key. For each option, it is necessary to provide a description text in "help" key, and a "type" key (for options with arguments only). Optionally, an option can contain an example.
There are two kinds of options: flags and options, which require an argument.For flags ommit type key, or specify type as "". A type is a string describing the type.basic types supported are string, boolean, integer.
Special types:
In this case, you need to specify "typespec" key containing the regular expression the argument should be matched against.
In this case, the typespec key must contain a list of possible values as strings
Example 1.4. Options map definition
"options" : $[ "propose" : $[ "help": "propose a configuration", "example": "lan add propose", "type": "" ], "device": $[ "help": "device ID", "type": "string", "example": "lan add device=eth0" ], "blem" : $[ "help": "other argument (without spaces)", "type": "regex", "typespec": "^[^ ]+$" ], "atboot": $[ "help": "should be brought up at boot?", "type": "enum", "typespec": ["yes","no"] }
The actions and options are grouped together using mappings. Currently, you can mapan action to a set of options. You can't specify required/optional options, all ofthem are optional.
Example 1.5. Mappings between actions and their options
"mappings" $[ "list" : [ "configured", "unconfigured" ], "add" : [ "device", "ip", "netmask", "blem" ], "delete": [ "device" ] ]
If you need to write your own event loop, this is a part of the CommandLine API useful for this:
returns true, if the user asked to start up the module GUI
reads a new command line in interactive mode, splits the arguments into a list
parse (and scan if needed) next command and return its map
lower-level function to parse the command line, check the validity
returns true, if the last command was already returned
returns true, if the user asked to cancel the changes
prints the string and then a message how to obtain the help
prints the string
same as CommandLine::Print, but the string is printed only in verbose mode
Example 1.6. Example of an event-loop
import "CommandLine"; if( ! CommandLine::Init( description_of_commands, Args() ) ) return; if( CommandLine::StartGUI() ) { <do standard GUI module> return; } <initialize call> while( ! CommandLine::Done() ) { map command = CommandLine::Command(); <handle commands here> } <finish call>
The CommandLine::Init() returns boolean whether the module has something to do. If the user only requested help or the arguments passed were not correct, the module should stop processing.
The CommandLine::Command() will do the user interaction if necessary. Also, it will handle all supported"system" commands, like "help", "exit" and "quit".
![]() | Note |
---|---|
In interactive mode, the communication uses /dev/tty. In non-interactive commandline mode it prints everything to standard error output. |
For an example without using the event-loop provided by the
CommandLine module, see lan-simple.ycp
.
/** * File: clients/lan.ycp * Package: Network configuration * Summary: Network cards main file * Authors: Michal Svec <msvec@suse.cz> * * $Id: lan-simple.ycp 10158 2003-06-23 12:48:40Z visnov $ * * Main file for network card configuration. * Uses all other files. */ { /*** * <h3>Network configuration</h3> */ import "CommandLine"; include "network/lan/wizards.ycp"; /** * Command line definition */ map cmdline = $[ "help" : "Configuration of network cards", "id" : "lan", "actions" : $[ "list" : $[ "help" : "display configuration summary", "example" : "lan list configured" ], "add" : $[ "help" : "add a network card" ], "delete" : $[ "help" : "delete a network card" ] ], "options" : $[ "propose" : $[ "help" : "propose a configuration", "example" : "lan add propose", "type" : "" ], "configured" : $[ "help" : "list only configured cards" ], "unconfigured" : $[ "help" : "list only not configured cards" ], "device" : $[ "help" : "device ID", "type" : "string", "example" : "lan add device=eth0" ], "ip" : $[ "help": "device address", "type": "ip" ], "netmask" : $[ "help": "network mask", "type": "netmask" ], ], "mappings" : $[ "list" : [ "configured", "unconfigured" ], "add" : [ "device", "ip", "netmask" ], "delete": [ "device" ], ] ]; /* The main () */ y2milestone("----------------------------------------"); y2milestone("Lan module started"); /* Initialize the arguments */ if(!CommandLine::Init(cmdline, Args())) { y2error("Commandline init failed"); return false; } /* Start GUI */ if(CommandLine::StartGUI()) { any ret = nil; ret = LanSequence(); y2debug("ret=%1", ret); y2milestone("Lan module finished"); y2milestone("----------------------------------------"); return ret; } /* Init */ CommandLine::Print("Initializing"); import "Lan"; import "Progress"; CommandLine::Print("Reading"); Progress::off(); Lan::Read(); /* Init variables */ string command = ""; list flags = []; map options = $[]; string exit = ""; list l = []; while(!CommandLine::Done()) { map m = CommandLine::Command(); command = m["command"]:"exit"; options = m["options"]:$[]; /* list */ if(command == "list") { CommandLine::Print("\nSummary\n"); string summary = sformat("%1\n", Lan::Summary(false)); CommandLine::Print(summary); } /* del */ else if(command == "delete") { string dev = options["device"]:""; CommandLine::Print("Deleting: "+dev); if(Lan::Delete(dev) && Lan::Commit()) CommandLine::Print("Success"); else CommandLine::Print("Error"); } /* add */ else if(command == "add") { string dev = options["device"]:""; } else { /* maybe we got "exit" or "quit" */ if( !CommandLine::Done() ) { CommandLine::Print("Unknown command (should not happen)"); continue; } } } if(!CommandLine::Aborted()) { CommandLine::Print("Writing"); Lan::Write(); CommandLine::Print("Finish"); } else { CommandLine::Print("Quit (without saving)"); } /* Finish */ y2milestone("Lan module finished"); y2milestone("----------------------------------------"); /* EOF */ }
For an example with the standard event-loop provided by the
commandline.ycp include, see lan-simpler.ycp
.
/** * File: clients/lan.ycp * Package: Network configuration * Summary: Network cards main file * Authors: Michal Svec <msvec@suse.cz> * * $Id: lan-simpler.ycp 24831 2005-08-11 15:51:55Z visnov $ * * Main file for network card configuration. * Uses all other files. */ { /*** * <h3>Network configuration</h3> */ import "CommandLine"; include "network/lan/wizards.ycp"; /** * Command line definition */ map cmdline = $[ "help" : "Configuration of network cards", "id" : "lan", "guihandler": ``(LanSequence()), "initialize": ``(Lan::Read()), "finish" : ``(Lan::Write()), "actions" : $[ "list" : $[ "help" : "display configuration summary", "example" : "lan list configured", "handler" : ``(listHandler()) ], "add" : $[ "help" : "add a network card", "handler" : ``(addHandler()) ], "delete" : $[ "help" : "delete a network card", "handler" : ``(deleteHandler()) ] ], "options" : $[ "propose" : $[ "help" : "propose a configuration", "example" : "lan add propose", "type" : "" ], "configured" : $[ "help" : "list only configured cards" ], "unconfigured" : $[ "help" : "list only not configured cards" ], "device" : $[ "help" : "device ID", "type" : "string", "example" : "lan add device=eth0" ], "ip" : $[ "help": "device address", "type": "ip" ], "netmask" : $[ "help": "network mask", "type": "netmask" ], ], "mappings" : $[ "list" : [ "configured", "unconfigured" ], "add" : [ "device", "ip", "netmask" ], "delete": [ "device" ], ] ]; /** handler for action "list" */ define void listHandler( map options ) ``{ CommandLine::Print("\nSummary\n"); string summary = CommandLine::Rich2Plain( sformat("%1\n", mergestring( Lan::Summary(false), "") ) ); CommandLine::Print(summary); } /** handler for action "add" */ define void addHandler( map options ) ``{ string dev = options["device"]:""; } /** handler for action "delete" */ define void deleteHandler( map options ) ``{ string dev = options["device"]:""; CommandLine::Print("Deleting: "+dev); if(Lan::Delete(dev) && Lan::Commit()) CommandLine::Print("Success"); else CommandLine::Print("Error"); } import "Lan"; CommandLineRun( cmdline ); /* Finish */ y2milestone("Lan module finished"); y2milestone("----------------------------------------"); /* EOF */ }
If you need to activate or de-activate services in YaST, use module Service. It is a replacement for the old client runlevel_adjust that you should have been using before You may also use it to obtain information about services. Following functions are self-containing.
Service::Adjust (string name,
string action)
does some operation with init script.
name is the name of the init script.
action is one of:
"enable"
enables service, which means that if service is not set to run in any runlevel, it will set it to run in default runlevels. Otherwise it will not touch the service. Use it when you want the service to run, because it preserves user-defined state. If user sets service to run in some non-default runlevels, this will not destroy her own settings.
"disable"
disables service, which means that it sets it not to run in any runlevel.
"default"
sets service to run in its default runlevels, which are the runlevels listed in init script comment, fiels Default-Start.
"enable" and "disable" take no action when init script links are OK, but "default" always runs insserv.
Service::Adjust ("cups", "disable");
Service::Finetune
(string name, list rl)
sets service to run in list rl.
rl is list of strings.
Service::Finetune ("cups", [ "5" ]);
Service::Finetune ("cups", [ "S", "3", "5" ]);
Service::RunInitScript
(string name, string param)
runs init script name
with parameter param. Returns init scripts exit
value. I do not think it is worth to import module
Service only because of this one function when you
may simply call SCR::Execute (.target.bash, ...)
with the same result. But if you use Service for something else, this
function may increase readability of the code.
Service::RunInitScript ("cups", "restart");
Service::Status (string name)
,
Service::Info (string name)
and
Service::FullInfo
(string name)
Service::Status tells whether service runs. It calls init script status and returns exit value.
Service::Info returns information about service. The map contains:
$[ "defstart" : [ ... ], // list of strings "defstop" : [ ... ], // list of default runlevels "start" : [ ... ], // list of strings "stop" : [ ... ], // list of real runlevels "reqstart" : [ ... ], // list of strings "reqstop" : [ ... ], // prerequisites "description": string,// description ]
Values "def{start|stop}", "req{start|stop}" and "description" are taken from init script comments. "start" and "stop" are taken from links. Service::FullInfo combines info from Service::Info and Service::Status into one map. It adds key "started" with integeral value of script status. Service is enabled if "start" is not empty.
Service::Enabled
(string name)
returns true if service is set to run in any
runlevel. False otherwise.
import "Service"; boolean Read () { if (0 != Service::RunInitScript ("foo", "restart")) { y2error ("Can not start service"); return false; } // ... your code ... } boolean Write () { // ... write settings ... // set service to correct state if (start_it) { // enable service Service::Adjust ("foo", "enable"); // reload changed settings Service::RunInitScript ("foo", "reload"); } else { // disable service Service::Adjust ("foo", "disable"); // and stop it Service::RunInitScript ("foo", "stop"); } }
It has sense to run some services only in case that some other service runs. Now the only case known to me is portmap that is needed by NFS, NIS and others. Runlevel UI cares about this case. But if you use functions like Service::Adjust, you must take care about these dependencies yourself. If you know about other dependencies, let me know, I will implement them in UI.
Just for your info: Runleve UI warns user who wants to stop service xdm. If you know about another cases where it is wise idea to warn user, please let us know...
Table of Contents
List of Examples
Table of Contents
This is both a tutorial and a reference on how to lay out YaST2 dialogs.
Since experience shows that most people begin this with only a vague perception of YaST2's concepts, it also includes some basics that might be covered in other YaST2 documentation as well - just enough to get started.
If you are in a hurry or - like most developers - you don't like to read docs, you can skip this section and move right on to the next section. That is, if you think you know what the next few headlines mean. You can always come back here later.
Just don't ask anything that is explained here on the yast2-hackers mailing list - you'll very likely just get a plain RTFM answer shot right back into your face. And this is the FM, so read it if you need explanations. ;-)
The UI (user interface) is that part of YaST2 that displays dialogs. It is a separate process which uses a separate interpreter. Always think of it as something running on a different machine: There is the machine you want to install with YaST2 (i.e. the machine where the disks will be formatted etc.) and there is the machine that displays the dialogs - the UI machine. In most cases, this will actually be the same machine. But it doesn't need to be. Both parts of YaST2 might as well run on different machines connected via a serial line, a network or by some other means of communication (telepathy? ;-) ).
The logical consequence of this is that the UI uses its own
separate set of function definitions and variables. You need to be
real careful not to mix that up. Always keep in mind what part of
YaST2 needs to do what and what variables need to be stored where.
You can easily tell by the UI
prefix within the YCP code
what parts are getting executed by the UI.
A widget is the most basic building block of the UI. In short, each single dialog item like a PushButton, a SelectionBox or a TextEntry field is a widget. But there are more: Most static texts in dialogs are widgets, too. And there are a lot of widgets you can't see: Layout boxes like HBox or VBox and many more that don't actually display something but arrange other widgets in some way.
See the widget reference for details and a list of all available widgets.
There are several different UIs for YaST2. There is the Qt based UI (y2qt) as a graphical frontend which requires the X Window System; this is what most people know as the "normal" YaST2 UI. But there is also a NCurses based UI (y2ncurses) for text terminals or consoles.
That means, of course, that all YaST2 dialogs need to be written in a way that is compatible with each of those UIs. This is why libyui was introduced as an intermediate abstract layer between the YCP application and the UI. You do not communicate directly with either y2qt, y2ncurses - you communicate with the libyui.
Thus, YaST2 dialogs need to be described logically rather than in terms of pixel sizes and positions: You specify some buttons to be arranged next to each other rather than at positions (200, 50), (200, 150), (200, 200) etc. - whatever exactly this "next to each other" means to the specific UI.
Add to that the fact that there are several dialog languages to choose from: User messages or button labels have different lengths in different languages. Just compare the length of English messages to those in German or French, and you'll discover another good reason not to hard-code coordinates.
In addition to that, always keep in mind that the same dialog might require a different amount of space in a different UI. Overcrowded dialogs don't look good in the Qt UI. In the NCurses UI, they will very likely break completely: There simply isn't as much space available (80x25 characters vs. 640x480 pixels).
Each widget has a so-called nice size - this is the size the widget would like to have in order to look nice. E.g. for PushButtons that means the entire button label fits into the button. Likewise for labels.
Then there are widgets that don't have a natural nice size. For example, what size should a SelectionBox get? It can scroll anyway, so anything that makes at least one line of the list visible will satisfy the basic requirements. More space will make it look nicer; but how much is enough? The widget cannot tell that by itself.
Such widgets report a somewhat random size as their nice size. This is a number chosen for debugging purposes rather than for aesthetics. You almost always need to specify the size from the outside for that very reason. Always supply a weight for such widgets or surround them with spacings.
By default, all dialogs will be as large as they need to be - up to full screen size (which is UI dependent - 640x480 pixels for Qt, 80x25 characters for NCurses): The outermost widget is asked what size it would like to have, i.e. its nice size. If that outermost widget has any children, for example because it is a layout box, it will ask all of its children and sum up the individual sizes. Those in turn may have to ask their children and so on. The resulting size will be the dialog's initial size - unless, of course, this would exceed the screen size (UI dependent, see above).
You can force full screen size for any dialog by setting the
`defaultsize
option when opening it:
OpenDialog( `opt(`defaultsize ), `VBox(...) );
This will create a dialog of 640x480 pixels (y2qt) or 80x25 characters (y2ncurses) - regardless of its contents.
Use this for main windows or for popup dialogs with very much the same semantics - e.g. many of the YaST2 installation wizard's "expert" dialogs. Even though they are technically popup dialogs and they return to the main thread of dialog sequence they have main window semantics to the user.
Use your common sense when considering whether or not to use this feature for a particular dialog.
Note: Not every UI may be capable of this feature. This is only a hint to the UI; you cannot blindly rely on it being honored.
This section covers the widgets used for creating dialog layouts - the kind of widgets that are less obvious to the user. If you are interested in the "real" widgets, i.e. the kind you can actually see, please refer to the widget reference.
This is the most basic and also the most natural layout widget. The HBox widget arranges two or more widgets horizontally, i.e. left to right. The VBox arranges two or more widgets vertically, i.e. top to bottom.
The strategy used for doing this is the same, just the dimensions (horizontal / vertical) are different. Each child widget will be positioned logically next to its neighbor. You don't have to care about exact sizes and positions; the layout box will do that for you.
See the description of the layout algorithm for details.
For creating more complex layouts, nest HBox and VBox widgets into each other. Usually you will have a structure very much like this:
`VBox( `HBox(...), `HBox(...), ... )
i.e. a VBox that has several HBoxes inside. Those in turn can have VBoxes inside etc. - nest as deep as you like.
Almost every kind of layout can be broken down into such columns (i.e. VBoxes) or rows (i.e. HBoxes). If you feel you can't do that with your special layout, try using weights.
By default, each widget in a layout box (i.e. in a HBox or a VBox) will get its nice size, no more and no less. If for any reason you don't want that, you can exactly specify the proportions of each widget in the layout box. You do that by supplying the widgets with a weight (to be more exact: by making it the child of a weight widget, a HWeight or a VWeight).
You can specify percentages for weights, or you can choose random numbers. The layout engine will add the weights of all children of a layout box and calculate percentages for each widget automatically. Specify a HWeight for HBox children and a VWeight for VBox children.
Example 1.1. Specifying Proportions 1
`HBox( `HWeight( 20, `PushButton( "OK" ) ), `HWeight( 50, `PushButton( "Cancel" ) ), `HWeight( 30, `PushButton( "Help" ) ) )
In this example, the "OK" button will get 20%, the "Cancel" button 50% and the "Help" button 30% of the available space. In this example, the weights add up to 100, but they don't need to.
Note: This dialog looks extremely ugly - don't try this at home, kids ;-)
The weight ratios will be maintained at all times, even if that means violating nice size restrictions (i.e. a widget gets less space than it needs). You are the boss; if you specify weights, the layout engine assumes you know what you are doing.
Example 1.2. Specifying Proportions 2
(See also creating widgets of equal size in the common layout techniques section)
`HBox( `HWeight( 1, `PushButton( "OK" ) ), `HWeight( 1, `PushButton( "Cancel" ) ), `HWeight( 1, `PushButton( "Help" ) ) )
Note: This is a very common technique.
In this example all buttons will get an equal size. The button with the largest label will determine the overall size and thus the size of each individual button.
Please note how the weights do not add up to 100 here. The value "1" is absolutely random; we might as well have specified "42" for each button to achieve that effect.
Example 1.3. Specifying Proportions 3
The YaST2 wizard layout reserves 30% of horizontal space for the help text (a RichText widget) and the remaining 70% for the rest of the dialog. The important part of that code (simplified for demonstration purposes) looks like that:
`HBox( `HWeight( 30, `RichText( "Help text") ), `HWeight( 70, `VBox( ... // the dialog contents `HBox( `PushButton( "Back"), `HCenter( `PushButton( "Abort Installation") ), `PushButton( "Next") ) ) ) )
Specifying the size of the help text like that is important for most kinds of widgets that can scroll - like the RichText widget used here, for example. The RichText widget can take any amount of space available; it will wrap lines by itself as long as possible and provide scroll bars as necessary. Thus, it cannot supply any reasonable default size on its own - you must supply one. We chose 30% of the screen space - which of course is absolutely random but suits well for the purposes of YaST2.
Use this technique for widgets like the SelectionBox, the Table widget, the Tree widget, the RichText, but also for less obvious ones like the InputField.
Note: This list may be incomplete. Use your common sense.
When you don't want parts of a dialog to be resized just because some neighboring widget needs more space, you can insert stretch widgets to take any excess space. Insert a HStretch in a HBox or a VStretch in a VBox. Those will act as "rubber bands" and leave the other widgets in the corresponding layout box untouched.
You can also insert several stretches in one layout box; excess space will be evenly distributed among them.
If there is no excess space, stretch widgets will be invisible. They don't consume any space unless there is too much of it (or unless you explicitly told them to - e.g. by using weights).
Some widgets that are not stretchable by default can be made
stretchable by setting the `hstretch
option. PushButtons are typical
candidates for this: They normally consume only as much space as
they really need, i.e. their nice size.
With the `hstretch
option, however, they can grow and take
any extra space - very much like stretch widgets.
Please note, however, that all widgets for with a weight are implicitly stretchable anyway, so
specifying `opt(`hstretch)
or `opt(`vstretch)
for
them as well is absolutely redundant.
Use HSpacing or VSpacing to create some empty space within a layout. This is normally used for aesthetical reasons only - to make dialogs appear less cramped.
The size of a spacing is specified as a float number, measured in units roughly equivalent to the size of a character in the respective UI (1/80 of the full screen width horizontally , 1/25 of the full screen width vertically). Fractional numbers can be used here, but text based UIs may choose to round the number as appropriate - even if this means simply ignoring a spacing when its size becomes zero.
You can combine the effects of a spacing and a stretch if you specify a hstretch or a vstretch option for it: You will have a rubber band that will take at least the specified amount of space. Use this to create nicely spaced dialogs with a reasonable resize behaviour.
Example 1.4. Spacings
`HBox( `PushButton( "OK" ), `HSpacing( `opt(`hstretch), 0.5), `PushButton( "Cancel" ) )
This will create two buttons with a spacing between them. When the dialog is resized, the spacing will grow.
Alignments are widgets that align their single child widget in some way.
HCenter centers horizontally, VCenter centers vertically, HVCenter centers both horizontally and vertically. The others align their child as the name implies.
More often than not, you could achieve the same effect with a clever combination of spacings, but sometimes this might require an additional HBox within a HBox or a VBox within a VBox, i.e. more overhead.
Sometimes you wish to squeeze any extra space from a part of a dialog. This might be necessary if you want to draw a frame around a RadioBox in a defaultsize dialog: You want the frame drawn as close as possible to the RadioButtons, not next to the window frame with lots of empty space between the frame and the RadioButtons. Use a squash widget for that purpose:
`HVCenter( `HVSquash( `Frame( "Select Software categories", `VBox( ... ) ) ) )
This is not exactly a layout-only widget - you can see it. It is being mentioned here more because like layout widgets it can have children.
Use a Frame to visually group widgets that logically belong together - such as the RadioButtons of a RadioBox or a group of CheckBoxes that have a meaning in common (e.g. individual file permissions, software categories to install, ...).
Note: Do not overuse frames. They have a nice visual effect, but only if used sparingly.
You may need to put a squash widget around the frame in order to avoid excessive empty space between the frame and its inner widgets.
The RadioButtonGroup is a widget go logically group individual RadioButton widgets. It does not have a visual effect or an effect on the layout. All it does is to manage the one-out-of-many logic of a RadioBox: When one RadioButton is selected, all the others in the same RadioBox (i.e. in the same RadioButtonGroup) must be unselected.
Please notice that this might not be as trivial as it seems to be at first glance: There might be some outer RadioBox that switches between several general settings, enabling or disabling the others as necessary. Any of those general settings might contain another RadioBox - which of course is independent of the outer one. This is why you really need to specify the RadioButtonGroup.
You usually just surround the VBox containing the RadioButtons with a The RadioButtonGroup.
Don't forget to include your RadioBox within a frame! RadioButtonGroup, Frame and HVSquash usually all come together.
Example 1.5. Grouping RadioButtons
`HVCenter( `HVSquash( `Frame( "Select Installation Type", `RadioButtonGroup( `VBox( `RadioButton(...), `RadioButton(...), `RadioButton(...) ) ) ) ) )
A ReplacePoint
is a "marker" within the widget hierarchy of a layout. You can
later refer to it with ReplaceWidget()
. Use this to cut
out a part of the widget hierarchy and paste some other
sub-hierarchy to this point.
The YaST2 wizard dialogs use this a lot: The main window stays the same, just some parts are replaced as needed - usually the large part to the right of the help text, between the title bar and the "previous" and "next" buttons.
A ReplacePoint has no other visual or layout effect.
Use the case studies in this section as building blocks for your own dialogs.
Remember that even though most of the examples use a horizontal layout (a HBox), the same rules and techniques apply in the vertical dimension as well - just replace HBox with VBox, HWeight with VWeight etc.
Screen shot of the Layout-Buttons-Equal-Growing.ycp example
You can easily make several widgets the same size - like in this example. Just specify equal weights for all widgets:
`HBox( `HWeight(1, `PushButton( "OK" ) ), `HWeight(1, `PushButton( "Cancel everything" ) ), `HWeight(1, `PushButton( "Help" ) ) )
The widgets will grow or shrink when resized. They will always retain equal sizes:
The same example, resized larger.
The same example, resized smaller.
Screen shot of the Layout-Buttons-Equal-Even-Spaced1.ycp example
Widgets with a weight (such as these buttons) are implicitly stretchable. If you don't want the widgets to grow, insert stretches without any weight between them. They will take all excess space - but only if there is no weight specified (otherwise, the stretches would always maintain a size according to the specified weight - not what is desired here).
`HBox( `HWeight(1, `PushButton( "OK" ) ), `HStretch(), `HWeight(1, `PushButton( "Cancel everything" ) ), `HStretch(), `HWeight(1, `PushButton( "Help" ) ) )
The same example, resized larger. Notice how the stretches take the excess space.
The same example, resized smaller. The stretches don't need any space if there is not enough space anyway.
Screen shot of the Layout-Buttons-Equal-Even-Spaced2.ycp example. Notice the spacing between the buttons.
If you want some space between the individual widgets, insert a spacing. You could use both a spacing and a stretch, but specifying the stretchable option for the spacing will do the trick as well - and save some unnecessary widgets:
`HBox( `HWeight(1, `PushButton( "OK" ) ), `HSpacing(`opt(`hstretch), 3), `HWeight(1, `PushButton( "Cancel everything" ) ), `HSpacing(`opt(`hstretch), 3), `HWeight(1, `PushButton( "Help" ) ) )
The value "3" used here for the spacing is absolutely random, chosen just for aesthetics. Use your own as appropriate.
The same example, resized larger. Notice how the spacings take the excess space.
The same example, resized smaller.
As you can see, the spacings have one disadvantage here: They need the space you specified even if that means that there is not enough space for the other widgets.
As mentioned before, most kinds of widgets that can scroll don't have a natural nice size. If the overall size of your layout is fixed by some other means (e.g. because it is a full screen dialog), you can have it take the remaining space or specify proportions with weights.
If this is not the case, create such "other means" yourself: Surround the scrollable widget with widgets of a well-defined size, e.g. with spacings.
Prevent the spacings from actually using precious screen space themselves by putting a VSpacing in a HBox or a HSpacing in a VBox - it will resize the corresponding layout box in its secondary dimension. It will take no space in its primary dimension.
Example 1.6. Specifying the Size of Scrollable Widgets
`VBox( `HSpacing(40), // make the scrollable widget at least 40 units wide `HBox( `VSpacing(10), // make the scrollable widget at least 10 units high `Table(...) // or any other scrollable widget ) )
See also the Table2.ycp, Table3.ycp, Table4.ycp and Table5.ycp examples.
As a general rule of thumb, use this technique whenever you place a scrollable widget in a non-defaultsize dialog. Don't leave the size of such widgets to pure coincidence - always explicitly specify their sizes.
printf() is your best friend when debugging - every
seasoned programmer knows that. YaST2 has something very much like
that: y2log(), available both in YCP and in the C++ sources.
It is being used a lot, and you can add your own in your YCP code.
Thus, if something strange happens, check the log file - either in
your home directory (~/.y2log
) or the system wide log file
(/var/log/y2log
).
You can increase the level of verbosity by setting the
Y2DEBUG
environment variable to 1 - both in your shell and
at the boot prompt (for debugging during an installation) - boot
with something like
linux Y2DEBUG=1
Log files will be wrapped when they reach a certain size - i.e.
the current log file is renamed to ~/.y2log-1
,
~/.y2log-2
etc. or /var/log/y2log-1
,
/var/log/y2log-2
etc., and a new log file is begun.
If the layout engine complains about widgets not getting their nice size and tell you to check the layout, please do that before you write a bug report. More often than not that just means that your dialog is overcrowded. That doesn't only raise technical problems: In that case your dialog most likely is too complex and not likely to be understood by novice users. In short, you very likely have a problem with your logical design, not with the layout engine. Consider making it easier or splitting it up into several dialogs - e.g. an easy-to-understand novice level base dialog and an advanced "expert" dialog. Use YaST2's partitioning, software selection and LILO configuration dialogs as examples for how to do this.
You might also consider replacing some widgets with others that don't use as much screen space - e.g. use a ComboBox rather than a SelectionBox, or a ComboBox rather than a RadioBox. But always keep in mind that this just reduces screen space usage, not complexity. Plus, widgets like the ComboBox frequently are harder to operate from a user's point of view because they require more mouse clicks or keys presses to get anything done. Use with caution.
When you created a new dialog or substantially changed an existing one always remember to check it with the other UIs, too. If it looks good with the Qt UI that doesn't mean it looks good with the NCurses UI as well - it might even break completely. There might be too many widgets or parts of widgets may be invisible because of insufficient screen space.
If you don't like that idea always remember some day you might be that poor guy who can't run YaST2 with Qt - maybe because of a brand new graphics card the X server doesn't support yet or maybe because you have to install a server system that just has a serial console.
The text based version may not need to look as good (but it would sure be nice if it did), but it needs to work. That means all widgets must be there and be visible. If they are not, you really need to rearrange or even redesign your dialog. Possibly before somebody from the support department finds it out the hard way - because a user complained badly about it.
Very much the same like the previous issue: Consider somebody who wants or needs to operate your dialog without a mouse. Maybe he doesn't have one or maybe it doesn't work - or maybe he uses the NCurses UI. There are even a lot of users who can work a whole lot quicker if they can use keyboard shortcuts for common tasks - e.g. activating buttons or jumping to text input fields. You can and should provide keyboard shortcuts for each of those kinds of widgets.
Of course this needs to be double-checked with each of the translated versions: Keyboard shortcuts not only are language dependent (so users can memorize them), they are even contained within messages files. The translators need to include their own in the respective language, and that means chances are some of the sort cuts are double used - e.g. Alt-K may be used twice in the same dialog, which renders the second use ineffective. Always check that, too.
You don't need to know the internals of the YaST2 UI layout engine in order to be able to create YaST2 dialogs. But this kind of background knowledge certainly helps a lot when you need to debug a layout - i.e. when a dialog you programmed behaves "strange" and doesn't look at all like you expected.
A HBox lays out its children horizontally, a VBox vertically. How they do that is very much the same except for the dimensions: The HBox uses horizontal as its primary dimension, the VBox vertical. The other dimension is called the secondary dimension (vertical for the HBox, horizontal for the VBox).
Calculating the nice size in the secondary dimension is easy: It is the maximum of the nice sizes of all children. Thus, for a HBox this is the nice height of the highest child, for a VBox this is the nice width of the widest child.
If any child is a layout box itself (or any other container widget), this process will become recursive for the children of that layout box etc. - this holds true for both the primary and the secondary dimension.
In the primary dimension things are a bit more complicated: First, the nice sizes of all children without weights are summed up.
Then the sizes of all children with weights are added to that sum - in such a way that each of those gets at least its nice size, yet all weights are maintained with respect to each other. I.e. when a button is supposed to get 30% he must get it, but its label must still be completely visible.
Maybe some of the children with weights need to be resized larger because of those restrictions. Exactly how large is calculated based on the so-called boss child. This is the one widget that commands the overall size of all children with weights, the one with
max ( nice size / weight )
The boss child's nice size and its weight determine the accumulated nice size of all children with weights. The other children with weights will be resized larger to get their share of that accumulated size according to their individual weights.
By the way this is why all children with weights are implicitly stretchable - most of them will be resized larger so the weights can be maintained at all times.
Each widget has a SetSize() method. This will be called recursively for all widgets from top (i.e. the outer dialog) to bottom. When a dialog is opened, the UI determines how large a dialog should become. The UI tries to use the dialog's nice size, if possible - unless the defaultsize option is set or the nice size exceeds the screen size, in which case the screen size is used.
After the dialog is opened, the SetSize() method will be called again when:
The user resizes a dialog.
A significant portion of the dialog changes - e.g. because of ReplaceWidget().
All of those cases will cause a re-layout of the entire dialog.
For layout boxes, the SetSize() method works like this:
If none of the children of a layout box has a weight, any extra space (i.e. space in excess of the nice size) is evenly distributed among the stretchable children. All non-stretchable children get their nice size, no more.
If there are not any stretchable children, there will be empty space at the end of the layout (i.e. to the right for a HBox and at the bottom of a VBox). If any child has a weight, all children without weights will get no more than their nice sizes - no matter whether or not they are stretchable.
The rest of the space will be distributed among the children with weights according to the individual weights.
There is one exception to that rule, however: If there is more space than the weighted childrens' nice size and there are any stretches or stretchable spacings without weights, the excess space will be evenly distributed among them.
This may sound like a very pathological case, but in fact only this gives the application programmer a chance to create equal sized widgets that don't grow, maybe with a little extra space between them. Simple popup dialogs with some buttons are typical examples for this, and this is quite common.
There should be enough space for any layout box: By default, the overall size of a dialog is calculated based on its nice size. But this might exceed the full screen size, or the user might manually have resized the dialog (some UIs are capable of that) - both of which cases will cause a dialog to get less than its nice size.
If there is not enough space, the layout engine will complain about that fact in the log file, asking you to "check the layout". Please do that if this message always appears when a certain dialog is opened - you may have to rearrange your dialog so all widgets properly fit into it.
Anyway, if it happens, some widgets will get less than their nice size and probably will not look good; some might even be completely invisible.
Even then, as long as there is enough space for all children without weights, those will get their nice sizes. Only the remaining space will be distributed among the children with weights.
If the space isn't even enough for the children without weights, each of them will have to spend some of its space to make up for the loss. The layout engine tries to treat each of them equally bad, i.e. each of them has to give some space.
This behaviour may be somewhat unexpected, but not only is this compatible with older versions of the YaST2 UI, it also comes very handy for simple layout tasks like this (taken from the Label1.ycp example):
`VBox( `Label( "Hello, world" ), `PushButton( "OK" ) )
This button will be centered horizontally - without the need for a HCenter around it.
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.
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).
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.
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()
.
Example 1.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.
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.
In the course of those discussions some design alternatives began to emerge:
Use the single-event-loop and callback model like most other toolkits.
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.
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. ;-)
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).
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.
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.
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
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 Key | Value Type | Valid Values | Description |
---|---|---|---|
EventType | string |
| |
ID | any | The ID (a widget ID for WidgetEvents) that caused the event. This is what UserInput() returns. | |
EventSerialNo | integer | >= 0 | The serial number of this event. Intended for debugging. |
All WidgetEvents have these map fields in common:
Map Key | Value Type | Valid Values | Description |
---|---|---|---|
EventType | string | WidgetEvent | (constant) |
EventReason | string | 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. | |
ID | any | The ID of the widget that caused the event. This is what UserInput() returns. | |
WidgetID | any | The 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. | |
WidgetClass | string | PushButton SelectionBox Table CheckBox ... | The class (type) of the widget that caused the event. |
WidgetDebugLabel | string |
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. |
Map Key | Value Type | Valid Values | Description |
---|---|---|---|
EventReason | string | Activated | (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 Type | Widget Options | Action to Trigger the Event |
---|---|---|
PushButton | (none) |
|
Table | `opt(`notify) |
|
SelectionBox | `opt(`notify) |
|
Tree | `opt(`notify) |
|
Note that MenuButton and RichText don't ever send WidgetEvents. They send MenuEvents instead.
Map Key | Value Type | Valid Values | Description |
---|---|---|---|
EventReason | string | ValueChanged | (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 Type | Widget Options | Action to Trigger the Event |
---|---|---|
MultiSelectionBox | `opt(`notify) | Toggle an item's on/off state:
|
CheckBox | `opt(`notify) | Toggle the on/off state:
|
RadioButton | `opt(`notify) | Set this RadioButton to on:
|
`opt(`notify) | Enter text. | |
ComboBox | `opt(`notify) |
|
IntField | `opt(`notify) | Change the numeric value:
|
Slider | `opt(`notify) |
|
PartitionSplitter | `opt(`notify) |
|
Map Key | Value Type | Valid Values | Description |
---|---|---|---|
EventReason | string | SelectionChanged | (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 Type | Widget Options | Action to Trigger the Event | ||||
---|---|---|---|---|---|---|
SelectionBox |
| Select another item:
| ||||
Qt: | `opt(`notify) | |||||
NCurses: | `opt(`notify,`immediate) | |||||
Table | `opt(`notify,`immediate) | Select another item:
| ||||
Tree | `opt(`notify) | Select another item:
| ||||
MultiSelectionBox | `opt(`notify) | Select another item:
|
Map Key | Value Type | Valid Values | Description |
---|---|---|---|
EventType | string | MenuEvent | (constant) |
ID | any |
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.
Map Key | Value Type | Valid Values | Description |
---|---|---|---|
EventType | string | TimeoutEvent | (constant) |
ID | symbol | `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.
Map Key | Value Type | Valid Values | Description |
---|---|---|---|
EventType | string | CancelEvent | (constant) |
ID | symbol | `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!
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 Key | Value Type | Valid Values | Description |
---|---|---|---|
EventType | string | KeyEvent | (constant) |
ID | string |
| The key symbol of this event in human readable form. This is what UserInput() returns. |
KeySymbol | string |
| 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. |
FocusWidgetID | any | The ID of the widget that currently has the keyboard focus. Unlike a WidgetEvent, this is not the same as "ID". | |
FocusWidgetClass | string | TextEntry SelectionBox ... | The class (type) of the widget that has the keyboard focus. |
FocusWidgetDebugLabel | string |
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.
Map Key | Value Type | Valid Values | Description |
---|---|---|---|
EventType | string | DebugEvent | (constant) |
ID | symbol | `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.
Table of Contents
List of Examples
Table of Contents
The product control enables customization of the installation makes it possible to enable and disable features during installation in the final installed product. It controls the workflow and what is really shown to the user during installation.
Beside workflow configuration, other system variables are configurable and can be predefined by the system administrator, to name a few: the software selection, environment settings such as language, time zone and keyboard can be configured and would override default variables provided with shipped products.
The idea of having a pre-defined installation workflow and pre-defined system settings provides a middle ground between manual installation and automated installation.
The product configuration file is provided as a text file on the installation media and defines various settings needed during installation. The following is a list of supported configuration options:
Workflow
Replaces the static workflow list with a configurable list using the product configuration file. Entire sections of the workflow can be skipped.
For example, it is possible to set the language variable in the configuration file if the installation language is to be forced for some reason, eg. if an IT department wants to force French installations, say in Quebec, Canada, then the entire dialogue can be skipped. If the IT department is to recommend some settings but still give the user the choice to change the default settings, the language dialogue will be shown with French preselected.
If none of the above options is used, the default dialogue settings are shown.
Proposals
As with the workflow, proposals are also configurable. For example, certain products would skip some proposals. In the proposal screen the pre-configured settings can be shown with the possibility to change them or with inactive links if the configuration is to be forced.
System Variables
Let the user define system variables like language, keyboard, time zone, window manager, display manager etc. The defined variables will be used as defaults in the respective dialogues.
Package Selections and additional individual packages
Define what base package selection and add-on selections should be used for the installation. Additionally provide the possibility to define a list of additional packages. All packages and selections can be selected depending on the architecture using a special architecture attribute in the configuration file.
Partitioning
Integrates flexible partitioning into configuration file, instead of the separate file currently used.
Scripting and Hooks
To customize installation further more, hooks and special slots can be defined where the user can execute scripts. For example, scripts can be executed at the very beginning of the installation (After processing the configuration file), in the installation system before initial boot, in the chroot-ed environment and after initial boot and before/after every step in the workflow. Scripting languages supported during installation are currently Shell, Perl.
The control file is implemented in simple structured XML syntax which so far has been used for automated installation . The XML structure used can be mapped easily to YaST data structures and all data types available in YaST are supported for easy data access and manipulation.
The primary use of the control file is to configure the workflow of the installation and it offers the possibility to predefine a certain setup, but it also defines product installation features and other product related variables.
![]() | Note |
---|---|
Note that the control file is not an optional tool to help customize
installation, it is required during installation and without the file,
installation may fail or lead to unexpected results. YaST provides a
default and general control file which is always available in the
system. The general and product independent control files is installed by
the package yast2-installation in
|
During installation, linuxrc searches for the a file
named control.xml
on the installation medium (CD,
NFS, FTP..) and copies the file into the installation system and makes
the file available to YaST. YaST then starts and looks for the control
file in 3 location before it starts with the installation workflow:
/control.xml
Usually the file is in top directory after it has been copied by linuxrc and during initial installation phase.
/var/lib/YaST2/control.xml
After reading the file, and before second installation phase, the
control file is copies from the top directory to
/var/lib/YaST2/control.xml
/usr/share/YaST2/control/control.xml
This is the location where yast2-installation installs the file in all products. The file is the same on all products.
One of the main reasons for using the control is to provide non YaST developers the ability to change the installation behavior and customize various settings without the need to change and re-build YaST packages.
The control files for the various SUSE products are maintained out of the YaST development trees and include separate internal and product specific packages.
Using the control file, multiple workflows can be defined for different modes and installation stages. Thus, the element workflows in the control file evaluates to a list of workflows.
Beside defining what YaST clients should be executed during installation, the workflow configuration also let you specify the wizard steps and how they should appear during graphical installation.
A workflow list element is a map with the following elements:
label
The label of the workflow as it appears on the left side of the wizard. For example Base Installation
defaults
The default arguments to the clients. This is a map element.
stage
This options defines the stage or phase of installation.. Possible values are initial for the initial stage and continue for the workflow of the installation after reboot
mode
Defines installation mode. Several modes are available, most important modes are:
installation
update
autoinst
modules
This is the actual workflow and is a list of elements describing the order in which the installation should proceed.
A module element is a map with the following configuration options:
name: The name of the module. All installation clients and modules have a unified prefix (inst_) which can be ommited here. For example, if the YaST file for the module is called inst_test, then the name in the control file is test
label: The label of the module in the step dialog. This is an optional element. If it is not set, the label of the previous module is used.
arguments: The arguments for the module is a comma separated list which can accept booleans and symbols.
The following listing shows a typical installation workflow:
<workflows config:type="list"> <workflow> <!-- 'label' is what the user will see --> <label>Base Installation</label> <!-- default settings for all modules --> <defaults> <!-- arguments for the clients --> <arguments>false,false</arguments> <!-- allowed architectures "all", "i386", "i386,ia64,x86_64" --> <archs>all</archs> </defaults> <stage>initial</stage> <mode>installation,update</mode> <modules config:type="list"> <module> <name>info</name> <arguments>false,true</arguments> </module> <module> <name>proposal</name> <arguments>true,true,`ini</arguments> <label>Installation Settings</label> </module> <module> <name>do_resize</name> <update config:type="boolean">false</update> <archs>i386,x86_64,ia64</archs> <label>Perform Installation</label> </module> <module> <name>prepdisk</name> <!-- Multiple modules with the same 'label' will be collapsed to one single user-visible step. The step is considered finished when the last module with the same 'label' is finished. --> <label>Perform Installation</label> </module> <module> <name>kickoff</name> <label>Perform Installation</label> </module> <module> <name>rpmcopy</name> <label>Perform Installation</label> </module> <module> <name>finish</name> <label>Perform Installation</label> </module> </modules> </workflow> </workflows>
Part of the installation workflows are proposal screens, which consists of group of related configuration settings. For example Network, Hardware and the initial Installation proposal.
If you with for some reason to add or modify a proposal, which is discourged because of configuration dependencies, then this would be possible using the control file.
<proposal> <type>network</type> <stage>continue,normal</stage> <proposal_modules config:type="list"> <proposal_module>lan</proposal_module> <proposal_module>dsl</proposal_module> <proposal_module>isdn</proposal_module> <proposal_module>modem</proposal_module> <proposal_module>proxy</proposal_module> <proposal_module>remote</proposal_module> </proposal_modules> </proposal>
The proposal in the above listing is displayed in the so called continue mode which is the second phase of the installation. The proposal consists of different configuration options which are controled using a special API.
Currently, proposals names and captions as fixed and cant be changed. It is not possible to create a special proposal screen, instead those available should be used: network, hardware, service.
In the workflow, the proposals are called as any workflow step with an additional argument identifying the proposal screen to be started. (`net for network, `hw for hardware and `service for service proposals. The following examples shows how the network proposal is called as a workflow step:
<module> <label>Network</label> <name>proposal</name> <arguments>true,true,`net</arguments> </module>
It is possible to define some installation variables (language, timezone, keyboard,.. ) and force them in the proposal. User will still be able to change them however.
The following variables can be set:
Timezone
Language
Keyboard
Auto Login (not recommended for multi-user environments and server installations)
IO Scheduler
Default is as.
Desktop Scheduler
the following example shows all options above
<globals> <enable_autologin config:type="boolean">true</enable_autologin> <language>de_DE</language> <timezone>Canada/Eastern</timezone> <use_desktop_scheduler config:type="boolean">true</use_desktop_scheduler> <io_scheduler>as</io_scheduler> </globals>
These options usually enable or disable some installation feature.
(boolean) enable_firewall - firewall will proposed as either enabled or disabled in the network proposal.
(boolean) enable_clone - clonning feature will be either enabled or disabled.
(boolean) skip_language_dialog - the language dialog might be skipped (if language already selected).
(boolean) show_online_repositories - either shows or hides the "online repositories" feature check-box.
(boolean) root_password_as_first_user - automatically selects or deselects the checkbox that makes Users configuration to set the password entered for a first user also for the user root. If not defined, default is false
(boolean) enable_autoconfiguration - enables a check box in dialog that offers to switch the automatic configuration either on or off. Default is false.
(boolean) autoconfiguration_default - defines a default value whether to use the automatic configuration. It works even if enable_autoconfiguration is turned off, but user would not be able to change it. Default is false.
(string) base_product_license_directory - directory where the base-product licenses are stored (license.txt, license.de_DE.txt, ...).
(boolean) rle_offer_rulevel_4 - defines whether runlevel 4 should be offered in Runlevel Editor. Defaul value is false if not set.
(boolean) enable_kdump - defines whether kdump is proposed as enabled in installation proposal. kdump_proposal client call has to be added into proposal otherwise this variable does not have any effect.
(boolean) write_hostname_to_hosts - defines whether the currently assigned hostname is written to /etc/hosts with IPv4 address 127.0.0.2. Defaul value is false if not set.
(boolean) default_ntp_setup - NTP configuration proposes a default ntp server if set to true. Default value is false.
(string) polkit_default_privs - Adjusts /etc/sysconfig/security/POLKIT_DEFAULT_PRIVS to the defined value. If not set or empty, sysconfig is untouched.
In the globals section, there are also helper variables for the installation and debugging:
save_instsys_content - is a list of entries that should be copied from the installation system to the just installed system before first stage is finished and system reboots to the second stage.
This example shows how content of the /root/ directory is copied to the /root/inst-sys/ directory on the installed system:
<globals> <save_instsys_content config:type="list"> <save_instsys_item> <instsys_directory>/root/</instsys_directory> <system_directory>/root/inst-sys/</system_directory> </save_instsys_item> </save_instsys_content> </globals>
(boolean) debug_workflow - defines whether steps with the very same name in workflow should not be collapsed. If true steps are not collapsed and a step ID is added after the step name. The default is false. This feature should be off in the production phase.
(boolean) debug_deploying - defines whether deploying should write more debug logs and some more debugging features in the workflow. The default is false. This feature should be off in the production phase.
Even if users are performing new reinstallation of their system, installation process can backup some files or directories before their disks are formatted and restore them after the installation. For instance, SSH keys are reused.
Typically, there is only one system previously installed, if there are more systems, the one with the newest access time to required files is chosen.
See the example:
<globals> <copy_to_system config:type="list"> <copy_to_system_item> <!-- Files are restored directly to "/" after installation --> <copy_to_dir>/</copy_to_dir> <!-- Files that must be all present on the previous system --> <mandatory_files config:type="list"> <file_item>/etc/ssh/ssh_host_key</file_item> <file_item>/etc/ssh/ssh_host_key.pub</file_item> </mandatory_files> <!-- Files thay may be present and are used if exist --> <optional_files config:type="list"> <file_item>/etc/ssh/ssh_host_dsa_key</file_item> <file_item>/etc/ssh/ssh_host_dsa_key.pub</file_item> <file_item>/etc/ssh/ssh_host_rsa_key</file_item> <file_item>/etc/ssh/ssh_host_rsa_key.pub</file_item> </optional_files> </copy_to_system_item> <copy_to_system_item> <!-- Files are restored to a special directory (and used by YaST later) --> <copy_to_dir>/var/lib/YaST2/imported/userdata/</copy_to_dir> <!-- They finally appear as "/var/lib/YaST2/imported/userdata/etc/shadow" "/var/lib/YaST2/imported/userdata/etc/passwd" ... --> <mandatory_files config:type="list"> <file_item>/etc/shadow</file_item> <file_item>/etc/passwd</file_item> <file_item>/etc/login.defs</file_item> <file_item>/etc/group</file_item> </mandatory_files> </copy_to_system_item> </copy_to_system> </globals>
In the globals section, there is a copy_to_system list of copy_to_system_item entries.
Every copy_to_system_item entry consists of:
(string) copy_to_dir - files are finally stored into the mentioned directory, they additionally keep their path in the previous filesystem, e.g., file /etc/file copied to directory /var/lib/YaST2/ will be finally stored as /var/lib/YaST2/etc/file
(list) mandatory_files - list of (string) file_item entries, one entry for one file or directory; these files are mandatory and must all exist on the source system; if any of the files are missing, such system is skipped
(list) optional_files - list of (string) file_item entries, one entry for one file or directory; files are optional and are copied if they exist; missing files are skipped
This is another feature defined in globals section. Automatic Configuration is called via the script inst_automatic_configuration at the end of the second stage installation. Having the configuration in control file enables this function for another modes and makes it very well configurable.
This is an example of AC setup:
<productDefines xmlns="http://www.suse.com/1.0/yast2ns" xmlns:config="http://www.suse.com/1.0/configns"> <globals> <!-- List of steps in AC --> <automatic_configuration config:type="list"> <!-- One step definition --> <ac_step> <text_id>ac_1</text_id> <type>scripts</type> <ac_items config:type="list"> <ac_item>initialization</ac_item> <ac_item>hostname</ac_item> <ac_item>netprobe</ac_item> <ac_item>rpmcopy_secondstage</ac_item> </ac_items> <icon>yast-lan</icon> </ac_step> <ac_step> <text_id>ac_3</text_id> <type>proposals</type> <ac_items config:type="list"> <ac_item>x11</ac_item> <ac_item>printer</ac_item> <ac_item>sound</ac_item> <ac_item>tv</ac_item> </ac_items> <icon>yast-hwinfo</icon> </ac_step> </automatic_configuration> </globals> <texts> <!-- Label used during AC, uses "text_id" from "ac_step" --> <ac_1><label>Initialization...</label><ac_1> <ac_3><label>Configuring hardware...</label><ac_3> </texts> </productDefines>
AC setup automatic_configuration consists of list of several ac_step definitions. On definition for one AC step. These steps can be compared to sets of scripts or sets of installation proposals, e.g., network proposal that consists of lan, modem, ... and firewall proposals which might depend on each others proposals.
Every single ac_step consists of
text_id - which is the very same ID as used in texts (you have to define the AC label there).
type - defines how the AC step items will be handled. Possible values are scripts or proposals. More types cannot be mixed within one AC step. All scripts are called only once one by one, all proposals in one AC step are called first with MakeProposal parameter then again all with Write parameter.
ac_items - is a list of scripts or proposals each in a separate ac_item.
For scripts an ac_item is a name of YaST client script without inst_ prefix, e.g., firewall would call inst_firewall script.
For proposals an ac_item is a name of YaST proposal without _proposal suffix, e.g., firewall would call firewall_proposal.
icon - plain icon filename (from 22x22 directory) without suffix and without any explicit directory name, e.g., yast-network.
In the software section you can define how is the selection of software handled during installation or update.
This is a list of supported entries in software:
Additionally, you can configure how updating of packages should be performed. The following options are available:
delete_old_packages
Do not delete old RPMs when updating.
delete_old_packages_reverse_list
Inverts the delete_old_packages rule for products defined as list of regular expressions matching installed product name (SuSE-release).
<!-- Delete old packages of all products but OES, SLES 9, SLE 10 and SLD 10 --> <software> <delete_old_packages config:type="boolean">true</delete_old_packages> <delete_old_packages_reverse_list config:type="list"> <regexp_item>^UnitedLinux .*$</regexp_item> <regexp_item>^Novell Open Enterprise Server Linux.*</regexp_item> <regexp_item>^SUSE (LINUX|Linux) Enterprise Server 9$</regexp_item> <regexp_item>^SUSE (LINUX|Linux) Enterprise Server 9 .*$</regexp_item> <regexp_item>^SUSE (LINUX|Linux) Enterprise Server 10.*$</regexp_item> <regexp_item>^SUSE (LINUX|Linux) Enterprise Desktop 10.*$</regexp_item> <!-- Don't forget to define product itself (Service Pack) --> <regexp_item>^SUSE (LINUX|Linux) Enterprise Server 10 SP.*$</regexp_item> <regexp_item>^SUSE (LINUX|Linux) Enterprise Desktop 10 SP.*$</regexp_item> </delete_old_packages_reverse_list> </software>
silently_downgrade_packages
Allows packager to downgrade installed packages during upgrade workflow.
silently_downgrade_packages_reverse_list
Inverts the silently_downgrade_packages rule for products defined as list of regular expressions matching installed product name (SuSE-release).
<!-- For SLES10, packages are not downgraded --> <software> <silently_downgrade_packages config:type="boolean">true</silently_downgrade_packages> <silently_downgrade_packages_reverse_list config:type="list"> <regexp_item>^SUSE (LINUX|Linux) Enterprise Server 10.*$</regexp_item> </silently_downgrade_packages_reverse_list> </software>
only_update_selected
One can update (only update packages already installed) or upgrade (also install new packages with new functionality). For example, SLES should do "update", not "upgrade" by default
only_update_selected_reverse_list
Inverts the only_update_selected for products defined as list of regular expressions matching installed product name (SuSE-release).
<!-- Only update packages but install new packages when upgrading any SUSE Linux or openSUSE --> <software> <only_update_selected config:type="boolean">true</only_update_selected> <only_update_selected_reverse_list config:type="list"> <regexp_item>^SUSE Linux [0-9].*</regexp_item> <regexp_item>^openSUSE [0-9].*</regexp_item> </only_update_selected_reverse_list> </software>
The other option defines how product upgrading in general is defined.
products_supported_for_upgrade
List of known products supported for upgrade (SuSE-release). Old releases or other distributions will report warning.
<software> <products_supported_for_upgrade config:type="list"> <regexp_item>^Novell LINUX Desktop 9.*</regexp_item> <regexp_item>^SUSE LINUX Enterprise Server 10.*</regexp_item> <regexp_item>^SUSE LINUX Enterprise Desktop 10.*</regexp_item> <regexp_item>^openSUSE .*</regexp_item> </products_supported_for_upgrade> </software>
All products (regular expressions) are matching the string which can be found in /etc/*-release file.
Regular expressions in <regexp_item>s can contain standard regular expressions, such as
The circumflex ^ and the dollar sign $ as boundary characters for strings
asterisk *, plus + and question mark ? for repeating or existency
dot . for wild-card character
square brackets [] for list of possible characters
circle brackets () for listing possibilities
special all-locale class-expressions [:alnum:], [:alpha:], [:blank:], [:cntrl:], [:digit:], [:graph:], [:lower:], [:print:], [:punct:], [:space:], [:upper:], [:xdigit:]
These regular expressions are evaluated as YCP.
online_repos_preselected
Online Repositories are pre-selected by default to be used. This item can change the default behavior.
This part defines not only all the desktops for Desktop Selection dialog during installation but also the default_desktop must be defined
Example of supported desktops:
<productDefines xmlns="http://www.suse.com/1.0/yast2ns" xmlns:config="http://www.suse.com/1.0/configns"> <software> <supported_desktops config:type="list"> <one_supported_desktop> <name>gnome</name> <desktop>gnome</desktop> <label_id>desktop_gnome</label_id> <logon>gdm</logon> <cursor>DMZ</cursor> <packages>gdm</packages> <order config:type="integer">1</order> <patterns>gnome x11 base</patterns> <icon>pattern-gnome</icon> <description_id>description_gnome</description_id> </one_supported_desktop> <one_supported_desktop> <name>kde</name> <desktop>startkde4</desktop> <!-- Generic ID used in texts below --> <label_id>desktop_kde</label_id> <logon>kdm4</logon> <cursor>DMZ</cursor> <packages>kde4-kdm</packages> <order config:type="integer">1</order> <patterns>kde x11 base</patterns> <icon>pattern-kde4</icon> <!-- Generic ID used in texts below --> <description_id>description_kde</description_id> </one_supported_desktop> </supported_desktops> </software> <texts> <desktop_gnome><label>GNOME</label></desktop_gnome> <!-- See 'desktop_kde' in 'supported_desktops' --> <desktop_kde><label>KDE 4.1</label></desktop_kde> <description_gnome><label>Some description</label></description_gnome> <!-- See 'description_kde' in 'supported_desktops' --> <description_kde><label>Some description</label></description_kde> </texts> </productDefines>
Section supported_desktops contains list of one or more one_supported_desktop sections.
Keys sypported in one_supported_desktop
(string) name
Unique ID.
(string) desktop
Desktop to start (gnome, startkde4, startkde3, startxfce4, ...).
(string) label_id
Text ID used for desktop selection label.
(string) logon
Logon manager to start (gdm, kdm4, kdm3, xdm, ...).
(string) cursor
Cursor theme.
(string) packages
(whitespace-separated).
(integer) order
Numeric order or the desktop in Desktop Selection dialog. Number 1 is reserved for major desktops that are displayed with description (description_id is required). If the very same order is used for more than one desktops, they are sorted alphabetically.
(string) patterns
Patterns to select for the desktop (whitespace-separated).
(string) icon
Icon used in Desktop Selection dialog, just a name of an icon from $current_theme/icons/64x64/apps/ directory, without .png suffix.
(string) description_id
Text ID used for desktop selection label.
System scenarios contain definition of dialog inst_scenarios in the first stage installation. It offeres several base-scenarios but only one of them can be selected as the selected one.
Example of configured scenarios:
<productDefines xmlns="http://www.suse.com/1.0/yast2ns" xmlns:config="http://www.suse.com/1.0/configns"> <software> <!-- list of supported scenarios --> <system_scenarios config:type="list"> <!-- one scenario --> <system_scenario> <!-- 'id' matches the text 'scenario_game_server' --> <id>scenario_game_server</id> <!-- space-separated list of patterns --> <patterns>game_server-pattern high-load-server</patterns> <!-- plain icon filename (from 32x32 directory) without suffix --> <icon>yast-system</icon> </system_scenario> <system_scenario> <id>scenario_web_server</id> <patterns>web_server-pattern</patterns> <icon>yast-http-server</icon> </system_scenario> <system_scenario> <id>scenario_nfs_server</id> <patterns>nfs_server-pattern</patterns> <icon>yast-nfs-server</icon> </system_scenario> </system_scenarios> <!-- this scenario (id) is selected by default --> <default_system_scenario>scenario_nfs_server</default_system_scenario> </software> <texts> <!-- dialog caption --> <scenarios_caption><label>Server Base Scenario</label></scenarios_caption> <!-- informative text between caption and listed scenarios --> <scenarios_text><label>SUSE Linux Enterprise Server offers several base scenarios. Choose the one that matches your server the best.</label></scenarios_text> <!-- matches the 'id' of one 'system_scenario' --> <scenario_game_server><label>Game Server</label></scenario_game_server> <scenario_web_server><label>Web Server</label></scenario_web_server> <scenario_nfs_server><label>NFS Server</label></scenario_nfs_server> </texts> </productDefines>
System scenarios are defined inside the software section. Section system_scenarios consists of several system_scenario definitions. Every single system_scenario consists of:
id - unique identificator of a selection.
patterns - space-separated list of patterns covering the software scenario.
icon - plain icon filename (from 32x32 theme directory) without suffix.
Selection labels must be defined in texts section. Scenarios ids are used as link identificators.
<software> <system_scenario> <id>scenario_id</id> </system_scenario> </software> <texts> <scenario_id><label>Some Label</label></scenario_id> </texts>
Section software also contains optional default_system_scenario that defines id of the default scenario.
There are some important texts that has to be defined for the dialog layout
scenarios_caption - used as a dialog caption for the Scenarios dialog.
scenarios_text - used as an informative text describing the available selections below.
If present, the partition proposal will be based on the data provided in the control file.
Space allocation on a disk happens in the following order. First all partition get the size allocated that is determined by the size parameter of the partition description. If a disk cannot hold the sum of these sizes this disk is not considered for installation. If all demands by the size parameter are fulfilled and there is still space available on the disk, the partitions which have a parameter "percent" specified are increased until the size demanded by by "percent" is fulfilled. If there is still available space on the disk (this normally only can happen if the sum of all percent values are below 100), all partitions that are specified with a size of zero are enlarged as far as possible. If a "maxsize" is specified for a partition, all enlargement are only done up to the specified maxsize.
If more than one of the available disks is eligible to hold a certain partition set, the disk is selected as follows. If there is a partition allocated on that disk that has its size specified by keywords "percent" or by "size=0" and does not have a "maxsize" value set then the desired size for this partition is considered to be unlimited. If a partition group contains a partition which an unlimited desired size, the disk that maximizes the partition size for the unlimited partitions is selected. If all partitions in a partition group are limited in size then the smallest disk that can hold the desired sizes of all partitions is selected for that partition group.
If there are multiple partition groups the the partition group with the lowest number (means highest priority) get assigned its disk first. Afterward the partition group with the next priority gets assigned a the optimal disk from the so far unassigned disks.
The following elements are global to all disks and partitions:
prefer_remove
true|false
true
If set to false the partition suggestion tries to use gaps on the disks or to re-use existing partitions. If set to true then the partition suggestion prefers removal of existing partitions.
remove_special_partitions
true|false
false
If set to false YaST2 will not remove some special partitions (e.g. 0x12 Compaq diagnostics, 0xde Dell Utility) if they exists on the disk even if prefer_remove is set to true. If set to true YaST2 will remove even those special partitions.
![]() | Caution |
---|---|
Caution: Since some machines are not even bootable any more when these partitions are removed one should really know what he does when setting this to true |
keep_partition_fsys
comma separated list of reiser, xfs, fat, vfat, ext2, ext3, jfs, ntfs, swap
Empty list
Partitions that contain filesystems in that list are not deleted even if prefer_remove is set to true.
keep_partition_id
comma separated list of possible partition ids
Empty list
Partitions that have a partition id that is contained in the list are not deleted even if prefer_remove is set to true.
keep_partition_num
comma separated list of possible partition numbers
Empty list
Partitions that have a partition number that is contained in the list are not deleted even if prefer_remove is set to true.
To configure individual partitions and disks, a list element is used with its items describing how should the partitions be created and configured
The attributes of such a partition are determined by several elements. These elements are described in more detail later.
![]() | General remarks to all option values |
---|---|
If there is a blank or a equal sign (=) contained in an option value, the values has to be surrounded by double quotes ("). Values that describe sizes can be followed by the letters K, M, G. (K means Kilobytes, M Megabytes and G Gigabytes). |
mount
<mount>swap</mount>
This entry describes the mount point of the partition. For a swap partition the special value "swap" has to be used.
fsys
<fsys>reiser</fsys>
This entry describes the filesystem type created on this partition. Possible Filesystem types are: reiser, ext2, ext3, xfs, vfat, jfs, swap If no filesystem type is given for a partition, reiserfs is used.
formatopt
<formatopt>reiser<formatopt>
This entry describes the options given to the format command. Multiple options have to be separated by blanks. There must not be a blank between option letter and option value. This entry is optional.
fstopt
<fstopt>acl,user_xattr<fstopt>
This entry describes the options written to /etc/fstab
. Multiple
options have to be separated by comma. This entry is optional.
label
<label>emil<label>
If the filesystem can have a label, the value of the label is set to this value.
id
<id>0x8E<id>
This keyword makes it possible to create partitions with partition ide other than 0x83 (for normal filesystem partitions) or 0x82 (for swap partitions). This make it possible to create LVM or MD partitions on a disk.
size
<size>2G<size>
This keyword determines the size that is at least needed for a
partition. A size value of zero means that YaST2 should try to make
the partition as large as possible after all other demands regarding
partition size are fulfilled. The special value of "auto" can be
given for the /boot
and swap partition. If auto is set for a /boot or
swap partition YaST2 computes a suitable partition size by itself.
percent
<percent>30<percent>
This keyword determines that a partition should be allocated a certain percentage of the available space for installation on a disk.
maxsize
<maxsize>4G<maxsize>
This keyword limits the maximal amount of space that is allocated to a certain partition. This keyword is only useful in conjunction with a size specification by keyword "percent" or by an entry of "size=0".
increasable
<increasable config:type="boolean">true<increasable>
false
After determining the optimal disk usage the partition may be increased if there is unallocated space in the same gap available. If this keyword is set, the partition may grow larger than specified by the maxsize and percent parameter. This keyword is intended to avoid having unallocated space on a disk after partitioning if possible.
disk
<disk>2<disk>
This keyword specifies which partitions should be placed on which disks if multiple disks are present in the system. All partitions with the same disk value will be placed on the same disk. The value after the keyword determines the priority of the partition group. Lower numbers mean higher priority. If there are not enough disks in the system a partition group with lower priority is assigned a separate disks before a partition group with higher priority. A partition without disk keyword is implicitly assigned the highest priority 0.
Example 1.1. Flexible Partitioning
If in the example below the machine has three disks then each of the
partition groups gets on a separate disk. So one disk will hold
/var
, another disk will hold
/home
and another disk will hold
/
, /usr
and /opt
.
If in the above example the machine has only two disks then /home
will still be on a separate disk (since it has lower priority than
the other partition groups) and /
,
/usr
,
/opt
and /var
will share the other disk.
If there is only one disk in the system of course all partitions will be on that disk. To make the flexible partitioning possible, use_flexible_partitioning option must be se to true and partitions must be surrounded with flexible_partitioning tag.
<partitioning> <use_flexible_partitioning config:type="boolean">true</use_flexible_partitioning> <flexible_partitioning> <partitions config:type="list"> <partition> <disk config:type="integer">3</disk> <mount>/var</mount> <percent config:type="integer">100</percent> </partition> <partition> <disk config:type="integer">2</disk> <mount>/</mount> <size>1G</size> </partition> <partition> <disk config:type="integer">2</disk> <mount>/usr</mount> <size>2G</size> </partition> <partition> <disk config:type="integer">2</disk> <mount>/opt</mount> <size>2G</size> </partition> <partition> <disk config:type="integer">1</disk> <mount>/home</mount> <percent config:type="integer">100</percent> </partition> </partitions> </flexible_partitioning> </partitioning>
A more complete example with other options is shown below:
<partitioning> <use_flexible_partitioning config:type="boolean">true</use_flexible_partitioning> <flexible_partitioning> <partitions config:type="list"> <partition> <disk config:type="integer">2</disk> <mount>swap</mount> <size>auto</size> </partition> <partition> <disk config:type="integer">1</disk> <fstopt>defaults</fstopt> <fsys>reiser</fsys> <increasable config:type="boolean">true</increasable> <mount>/</mount> <size>2gb</size> </partition> <partition> <disk config:type="integer">2</disk> <fstopt>defaults,data=writeback,noatime</fstopt> <fsys>reiser</fsys> <increasable config:type="boolean">true</increasable> <mount>/var</mount> <percent config:type="integer">100</percent> <size>2gb</size> </partition> </partitions> </flexible_partitioning> <prefer_remove config:type="boolean">true</prefer_remove> <remove_special_partitions config:type="boolean">false</remove_special_partitions> </partitioning>
It is possible to add hooks before and after any workflow step for further customization of the installed system and to to perform non-standard tasks during installation.
Two additional elements define custom script hooks:
prescript: Executed before the module is called.
postscript: Executed after the module is called.
Both script types accept two elements, the interpreter used (shell or perl) and the source of the scripts which is embedded in the XML file using CDATA sections to avoid confusion with the XML syntax. The following example shows how scripts can be embedded in the control file:
<module> <name>info</name> <arguments>false,true</arguments> <prescript> <interpreter>shell</interpreter> <source> <![CDATA[#!/bin/sh touch /tmp/anas echo anas > /tmp/anas ]]> </source> </prescript> </module>
Some kind of texts can be, of course, placed in several parts of the control file but they wouldn't be translated. This control file section makes it possible to mark some texts for translation.
The structure is rather easy:
<texts> <!-- Unique tag that identifies the text --> <some_text_id> <label>Some XML-escaped text: <b>bold </b>.</label> </some_text_id> <congratulate> <label><p><b>Congratulations!</b></p></label> </congratulate> </texts>
Translated texts can be got using ProductControl::GetTranslatedText (text_id) call.
Everywhere, product B depends on product A, there is no dependency related to product C. A, B and C are add-on products.
If there are two add-on products which want to insert their steps into the same location of the installation workflow (or proposal), they are inserted in the same order as the products are added. A must be added before B (otherwise adding B fails), steps of A are always prior to steps of B.
In order to avoid collisions of internal names of proposal items or sequence steps, all items should have its internal name prefixed by the add-on product name.
Item is always added at the end of the proposal. Multiple items are possible.
Specified item(s) are removed from proposal. Useful when add-on product extends functionality of the base product. If product B wants to remove an item of product A, must specify the name of the product as well. Product C cannot remove items of products A or B (and vice versa), product A cannot remove items of product B.
Usable in the same cases as the case above. If an item has been replaced by another item(s) of product A before applying changes of product B, the item(s) of product A will be replaced by item(s) of product B. Items of product C cannot be replaced by items of product A or B (and vice versa), such combination of products cannot be installed at the same time.
Before each step of base product installation, additional step can be inserted (eg. another proposal). For the order of additionally added steps, the same rules as for items of proposal will be applied.
The steps can be appended at the end of installation sequence.
The same rules for removing and replacing steps of the installation workflow as for proposal items will be applied.
The same rules as for steps of the installation workflow are valid here. There will be some points in the inst_finish where performing additional actions makes sense (at least one before moving SCR to chroot and one after).
Add-on product may replace whole second stage of installation. It should be used only in rare cases, as there is no possibility to merge two workflows completely written from scratch. If a product replaces the workflow, all changes of all products which replaced it before (in case of installation of multiple products) are gone. Add-on products selected after this product update the new workflow (which may not work, as the steps usually have different naming). This is perfectly OK if there are dependencies between add-on products.
The workflow can be replaced only for specified installation mode. If it is replaced, it must be replaced for all architectures.
New proposal can be added, as the proposal handling routines are generic. The information which is for current product in control.xml file has to be provided, and the proposal must be added as a step into the installation workflow. Basically, adding proposal has two steps:
defining the proposal (name, items,...)
adding a new step to the workflow referring to the new added proposal
There will be following files in the root directory of the add-on product's CD:
servicepack.tar.gz – tarball with files which are needed for the installation, both together with base product and separatelly. Special files inside this tarball:
installation.xml – the control file of the add-on product
the whole tarball or installation.xml can be missing if add-on product doesn't provide any custom installer, in this case, only its packages are added to the package manager dialog, and packages/patterns/... required by the product are selected by the solver
(optional) setup.sh – script which starts the installation automatically once the CD is in the drive
(optional) files needed to make the CD bootable (kernel, initrd, isolinux,...)
There is only a single control file to describe both an add-on and standalone product installation. It is called installation.xml. In principle, it contains a diff description containing the changes to be applied to the installation workflow plus a workflow, which is used for standalone product installation. The reason why both installation methods are stored in a single file is that the product features has to be shared as well as some proposals and clients can be reused.
The proposals which are defined for standalone installation are also available for the installation together with the base product. They don't have to be defined twice.
The files are located in the top directory of the add-on product installation source.
Because there are no really usable open source XML diff tools (the existing ones are typically written in Java), we define a special purpose file format aimed to cover the cases as described in the previous chapter.
In principle, the format is a list of directives to be applied to the existing control.xml. In principle, the file is a control file defining its own proposals, workflows etc. The control file has a special section, which defines changes to the existing workflow and proposals.
<?xml version="1.0"?> <productDefines xmlns="http://www.suse.com/1.0/yast2ns" xmlns:config="http://www.suse.com/1.0/configns"> <!-- .mo-file must be in installation tarball --> <textdomain>OES</textdomain> <!-- these options override base product's ones --> <globals> <additional_kernel_parameters></additional_kernel_parameters> </globals> <software> <selection_type config:type="symbol">auto</selection_type> </software> <partitioning> <evms_config config:type="boolean">false</evms_config> <root_max_size>10G</root_max_size> </partitioning> <network> <force_static_ip config:type="boolean">false</force_static_ip> <network_manager>laptop</network_manager> </network> <!-- base product's list is preserved, these are appended --> <clone_modules config:type="list"> <clone_module>printer</clone_module> </clone_modules> <proposals config:type="list"> <!-- put proposals for standalone product installation here --> </proposals> <!-- workflow for standalone product installation --> <workflows config:type="list"> <workflow> <defaults> <archs>all</archs> </defaults> <label>Preparation</label> <!-- mode and stage must be set this way --> <mode>installation</mode> <stage>normal</stage> <modules config:type="list"> <module> <label>License Agreement</label> <name>license</name> <enable_back>no</enable_back> <enable_next>yes</enable_next> </module> </modules> </workflow> </workflows> <!-- stuff for installation together with base products --> <update> <proposals config:type="list"> <proposal> <label>OES Installation Settings</label> <mode>installation,demo,autoinstallation</mode> <stage>initial</stage> <name>initial</name> <enable_skip>no</enable_skip> <append_modules config:type="list"> <append_module>module_1</append_module> <append_module>module_2</append_module> </append_modules> <remove_modules config:type="list"> <remove_module>module_3</remove_module> <remove_module>module_4</remove_module> </remove_modules> <replace_modules config:type="list"> <replace_module> <replace>old_module</replace> <new_modules config:type="list"> <new_module>module_5</new_module> <new_module>module_6</new_module> </new_modules> </replace_module> </replace_modules> </proposal> </proposals> <workflows config:type="list"> <workflow> <defaults> <archs>all</archs> <enable_back>no</enable_back> <enable_next>no</enable_next> </defaults> <mode>installation</mode> <stage>initial</stage> <append_modules config:type="list"> <module> <heading>yes</heading> <label>OES configuration</label> </module> <module> <label>Perform Installation</label> <name>a1_netsetup</name> </module> <module> <label>Perform Installation</label> <name>a2_netprobe</name> </module> </append_modules> <remove_modules config:type="list"> <remove_module>finish</remove_module> </remove_modules> <insert_modules config:type="list"> <insert_module> <before>perform</before> <modules config:type="list"> <module> <label>Perform Installation</label> <name>i1_netprobe</name> </module> </modules> </insert_module> </insert_modules> <replace_modules config:type="list"> <replace_module> <replace>language</replace> <modules config:type="list"> <module> <label>Perform Installation</label> <name>r1_language</name> </module> </modules> </replace_module> </replace_modules> </workflow> </workflows> <inst_finish> <before_chroot config:type=”list”> <module>before_chroot_1</module> <module>before_chroot_2</module> </before_chroot> <after_chroot config:type=”list”> <module>after_chroot_1</module> <module>after_chroot_2</module> </after_chroot> <before_umount config:type=”list”> <module>before_umount_1</module> <module>before_umount_2</module> </before_umount> </inst_finish> </update> </productDefines>
Text domain is important for YaST to handle translations properly. The appropriate set of .mo-files must be present to have the texts related to the control file translated.
<textdomain>OES</textdomain>
The proposals are defined the same way as for the base product. The workflow for the standalone installation must have the mode and stage set
<mode>installation</mode> <stage>normal</stage>
The label of the proposal can be modified. The mode, stage, and proposal name has to be specified, other options (enable_skip, architecture) are optional. The modes, stages, and architectures do not
<proposal> <label>OES Installation Settings</label> <mode>installation,demo,autoinstallation</mode> <stage>initial</stage> <name>initial</name> <enable_skip>no</enable_skip> [.....] </proposal>
Adding an item to a proposal is possible at the end only. If the proposal has tabs, the items are added to a new created tab.
<append_modules config:type="list"> <append_module>module_1</append_module> <append_module>module_2</append_module> </append_modules>
<remove_modules config:type="list"> <remove_module>module_3</remove_module> <remove_module>module_4</remove_module> </remove_modules>
The replacement is available in 1:N mode – one client is to be replaced by one or more new clients. If you need M:N use remove and replace together.
<replace_modules config:type="list"> <replace_module> <replace>old_module</replace> <new_modules config:type="list"> <new_module>module_5</new_module> <new_module>module_6</new_module> </new_modules> </replace_module> </replace_modules>
The workflow to update is identified the same way as other workflows. The archs, modes, and installation don't need tobe alligned to the same groups as in the base product workflows.
<workflow> <defaults> <archs>all</archs> <enable_back>no</enable_back> <enable_next>no</enable_next> </defaults> <mode>installation</mode> <stage>initial</stage> [...] </workflow>
<append_modules config:type="list"> <module> <heading>yes</heading> <label>OES configuration</label> </module> <module> <label>Perform Installation</label> <name>a1_netsetup</name> </module> <module> <label>Perform Installation</label> <name>a2_netprobe</name> </module> [...] </append_modules>
<insert_modules config:type="list"> <insert_module> <before>perform</before> <modules config:type="list"> <module> <label>Perform Installation</label> <name>i1_netprobe</name> </module> [...] </modules> </insert_module> </insert_modules>
<remove_modules config:type="list"> <remove_module>finish</remove_module> [...] </remove_modules>
<replace_modules config:type="list"> <replace_module> <replace>language</replace> <modules config:type="list"> <module> <label>Perform Installation</label> <name>r1_language</name> </module> [...] </modules> </replace_module> </replace_modules>
In CODE 10, the last step of an installation commonly known as inst_finish has been modularized, so it's possible to control the clients started at the end of the 1st stage. In principle, this phase runs in a chroot environment – all system access is done via chrooted process.
There are 3 cases that an add-on product can modify the workflow...
<inst_finish_stages config:type="list"> <before_chroot> <label>Copy Theme</label> <steps config:type="list"> <step>copy_theme</step> [...] </steps> </before_chroot> </inst_finish_stages>
<inst_finish_stages config:type="list"> <chroot> <label>Update Configuration</label> <steps config:type="list"> <step>pkg</step> [...] </steps> </chroot> </inst_finish_stages>
<inst_finish_stages config:type="list"> <before_umount> <label>Disconnect Network Disks</label> <steps config:type="list"> <step>iscsi_disconnect</step> [...] </steps> </before_umount> </inst_finish_stages>
All new steps are added at the end of the current list in the particular inst_finish workflow. It is not possible to remove any other inst_finish clients or replace them.
To replace a workflow, just create workflows as in base product control file. The important is that the stage of the workflow is set to
<stage>continue</stage>
and the mode is set for the specified mode.
The algorithm is rather straightforward. Every time, remove is applied first, then replace and the last step is add. This is done per product, so first the changes by product A are applied, then by product B etc.
One of the most important data stored in the control.xml file are the values to influence the behavior of YaST code, like proposals etc. The idea is the same as for workflow/proposal adaptation: by redefining a value, the resulting values are changed. Within YaST, the options are accessible via ProductFeatures module. No new option groups can be defined. Options which are defined by the base product, but not by the add-on product, are kept unchanged (base product's value is used).
<globals> <additional_kernel_parameters></additional_kernel_parameters> </globals> [...] <software> <selection_type config:type="symbol">auto</selection_type> </software>
At the end of the installation, a profile for AutoYaST can be generated. The profile will be generated using modules from the base product and modules specified in the add-on product control file.
<clone_modules config:type="list"> <clone_module>printer</clone_module> [...] </clone_modules>
The network code is instructed to force a static IP address.
The control file contains steps for both standalone installation and installation together with the base product. In the standalone installation workflow, selecting and installing packages is missing, these steps need to be prepended to the workflow.
<?xml version="1.0"?> <productDefines xmlns="http://www.suse.com/1.0/yast2ns" xmlns:config="http://www.suse.com/1.0/configns"> <textdomain>OES</textdomain> <network> <force_static_ip config:type="boolean">true</force_static_ip> <network_manager_is_default config:type="boolean">false</network_manager_is_default> </network> <proposals config:type="list"> <proposal> <name>oes</name> <stage>continue,normal</stage> <mode>installation</mode> <proposal_modules config:type="list"> <proposal_module>oes-ldap</proposal_module> <proposal_module>imanager</proposal_module> <proposal_module>lifeconsole</proposal_module> <proposal_module>linux-user-mgmt</proposal_module> <proposal_module>eguide</proposal_module> <proposal_module>novell-samba</proposal_module> <proposal_module>ifolder2</proposal_module> <proposal_module>ifolder</proposal_module> <proposal_module>ifolderwebaccess</proposal_module> <proposal_module>iprint</proposal_module> <proposal_module>nss</proposal_module> <proposal_module>netstorage</proposal_module> <proposal_module>novell-quickfinder</proposal_module> <proposal_module>novell-vo</proposal_module> <proposal_module>ncs</proposal_module> <proposal_module>ncpserver</proposal_module> <proposal_module>sms</proposal_module> </proposal_modules> </proposal> </proposals> <workflows config:type="list"> <workflow> <label>Preparation</label> <defaults> <archs>all</archs> </defaults> <mode>installation</mode> <stage>normal</stage> <modules config:type="list"> <module> <label>License Agreement</label> <name>inst_license</name> <enable_back>no</enable_back> <enable_next>yes</enable_next> </module> <module> <label>OES Configuration</label> <name>inst_check_cert</name> <enable_back>no</enable_back> <enable_next>yes</enable_next> </module> <module> <label>OES Configuration</label> <name>inst_proposal</name> <arguments>false,false,`product</arguments> <enable_back>no</enable_back> <enable_next>yes</enable_next> </module> <module> <label>OES Configuration</label> <name>inst_oes</name> <enable_back>yes</enable_back> <enable_next>yes</enable_next> </module> <module> <label>OES Configuration</label> <name>inst_oes_congratulate</name> <enable_back>no</enable_back> <enable_next>yes</enable_next> </module> </modules> </workflow> </workflows> <update> <workflows config:type="list"> <workflow> <defaults> <archs>all</archs> <enable_back>no</enable_back> <enable_next>no</enable_next> </defaults> <stage>continue</stage> <mode>installation</mode> <append_modules config:type="list"> <module> <label>OES Configuration</label> <name>inst_oes_congratulate</name> </module> </append_modules> <insert_modules config:type="list"> <insert_module> <before>suseconfig</before> <modules config:type="list"> <module> <label>OES Configuration</label> <name>inst_check_cert</name> </module> <module> <label>OES Configuration</label> <name>inst_edirectory</name> </module> <module> <label>OES Configuration</label> <name>inst_proposal</name> <arguments>false,true,`product</arguments> </module> <module> <label>OES Configuration</label> <name>inst_oes</name> </module> </modules> </insert_module> </insert_modules> </workflow> </workflows> </update> </productDefines>
The YaST firstboot utility (YaST Initial System Configuration), which runs after the installation is completed, lets you configure the before creation of the install image so that on the first boot after configuration, users are guided through a series of steps that allow for easier configuration of their desktops. YaST firstboot does not run by default and has to be configured to run by the user or the system administrator. It is useful for image deployments where the system in the image is completely configured. However, some final steps such as root password and user logins have to be created to personalize the system.
The default workflow for the interface is as follows:
The Welcome screen
The License Agreement
Date & Time
Network
Root Password
User Account
Hardware
Finish
During firstboot, two additional dialogs are shown for writing the data and running SuSEconfig which require no user interaction.
Firstboot is disabled by default. The yast2-firstboot package is not part of any software selection and has to be installed individually. During the Installation, click Software in the Installation Settings screen, then select the yast2-firstboot package in the Rest selection list.
Install the product on a master box, making sure that you install the firstboot package. Configure whole system including system services, hardware components,...
Create the empty file /var/lib/YaST2/reconfig_system
. This file will be deleted when firstboot configuration is completed. This can be done by issuing the command: touch /var/lib/YaST2/reconfig_system
After getting the system installed and prepared, shut it down and clone the hard disk to hard disk to all of the machines.
When the system comes up after a shutdown and cloning, the firstboot configuration utility will be started and the user will be presented with the configuration screens.
There are different ways the firstboot configuration utility can be used, one of them for creating installation images. The following step by step description shows how an image can be created after firstboot has been enabled.
Boot the master box using the rescue boot option.
Configure network in the rescue system.
Mount an NFS exported directory to /mnt.
Run dd if=/dev/hda of=/mnt/image.bin count=4000000 to store the master box's hard disk image onto the NFS server.
And to install the image you have just created:
Boot a user's machine using the rescue boot option.
Configure network in the rescue system.
Mount the NFS exported directory to /mnt.
Run dd if=/mnt/image.bin of=/dev/hda count=4000000.
Remove the boot media and boot the user's machine.
After firstboot comes up, configure the date and time, root password, and user account and any other additional settings.
The Post Configuration Utility (firstboot) expects the X server to be configured. If no X server is configured, it will automatically start in text mode.
The utility has standard and translated texts in the default
setup. If you want to change those texts, use the firstboot
configuration file,/etc/sysconfig/firstboot
.
This file lets you change the text of the following dialogs:
Welcome screen
License Agreement
Finish dialog
To do this, change the values of the respective variables (FIRSTBOOT_WELCOME_DIR, FIRSTBOOT_WELCOME_PATTERNS, FIRSTBOOT_LICENSE_DIR, FIRSTBOOT_NOVELL_LICENSE_DIR, and FIRSTBOOT_FINISH_FILE). This will give you dialogs with customized text. If the references files are in plain text, they will be shown as such automatically. If they contain any markup language, they will be formatted as rich text.
Variable FIRSTBOOT_WELCOME_DIR is path to the directory from which is the welcome message read, FIRSTBOOT_WELCOME_PATTERNS is a list of patterns or filenames which contains the message.
The license is read from file 'license.txt' or from 'license_<locale>.txt'. The license texts of the Novell base product are by default installed to the directory /etc/YaST2/licenses/base/, set different value to FIRSTBOOT_NOVELL_LICENSE_DIR if they are elsewhere. Use FIRSTBOOT_LICENSE_DIR to indicate a path to directory containing vendor licence texts; it is preferred to put these license texts into another subdirectory of /etc/YaST2/licenses/.
If you want to use FIRSTBOOT_FINISH_FILE, you need to update the firstboot workflow so that it calls firstboot_finish instead of inst_finish. The recommended way of customizing the congratulate text is specifying it in the firstboot.xml control file.
The variable LICENSE_REFUSAL_ACTION sets the action to be executed if the user does not accept the license. The following options are available:
halt: system is halted (shut down)
continue: continue with configuration
abort: Abort firstboot configuration utility and continue with the boot process.
Use the configuration option FIRSTBOOT_RELEASE_NOTES_PATH to show
release notes in the target language. The value of the option should
be a path to a directory with files using language dependent naming
(RELEASE-NOTES.{language}.rtf
). For english, the
following file will be needed: RELEASE-NOTES.{language}.rtf.
The original release notes for the installed product availabe in /usr/share/doc/release-notes
can be used as an
example.
The default firstboot workflow can be controled using one single
file which is a subset of the control.xml file used to control the
complete installation. The firstboot control file consists of
workflow and proposal configurations and can be used to add or remove
configuration screens depending on the end configuration of the
system. The file firstboot.xml is installed with the yast2-firstboot
package and can be found at the following location:
/etc/YaST2/firstboot.xml
.
This file can be modified to match the post installation requirements of the product in question. In addition to the default and pre-installed components, custom screens can be added to enable maximal flexiblity during post installation.
Instead of modifying the default /etc/YaST2/firstboot.xml file, it is possible to put the customized one to different location and specify path to it by altering the value of FIRSTBOOT_CONTROL_FILE variable in /etc/sysconfig/firstboot.
Since openSUSE11.0, installer does most part of the system configuration automatically, without user interaction. This feature is also available in firstboot stage. If you have the system installed and only partially configured (e.g. because of different hardware on your computers), enable inst_automatic_configuration step in the firstboot workflow. In the "globals" section of your workflow description file (firstboot.xml), define the steps that should be part of the Automatic Configuration process.
For detailed information, see "Automatic Configuration" section of
/usr/share/doc/packages/yast2-installation/control-doc/index.html
file, part of yast2-installation-devel-doc package.
You can add scripts to be executed at the end of the firstboot
configuration to customize the system depending on user input or the
environment of the machine. Scripts should be placed in
/usr/share/firstboot/scripts
or in a custom
location that can be set using the
/etc/sysconfig/firstboot
configuration
file.
It is possible to configure the firstboot process as a part of autoinstallation, so the system can be installed with most of the default values set via AutoYaST profile, leaving the rest to the end user during the firstboot sequence.
As a part of autoinstallation configuration, you need to provide all the changes mentioned above:
Customize /etc/sysconfig/firstboot
:
it can be done e.g. via
Sysconfig Editor in System section of AutoYaST configuration module.
Provide customized firstboot.xml file and point to its
location in FIRSTBOOT_CONTROL_FILE value of
/etc/sysconfig/firstboot
.
Enable Firstboot: do it via GUI in Misc/Firstboot section of AutoYaST configuration module or manually by adding the section
<firstboot> <firstboot_enabled config:type="boolean">true</firstboot_enabled> </firstboot>
to your AutoYaST profile.
This section contains description of several installation features.
You can easily add several additional products automatically just by using a modified installation repository or media.
During installation or upgrade from media (CD, DVD, HTTP server, ...) installation adds a primary installation repository, this repository can contain special configuration file with list of additional repositories that would be automatically added by YaST.
The configuration is written in XML - which means extending the format (adding new features) is easier comparing to the old plain-file format.
File add_on_products.xml is placed in the media root.
Commented example:
<?xml version="1.0"?> <add_on_products xmlns="http://www.suse.com/1.0/yast2ns" xmlns:config="http://www.suse.com/1.0/configns"> <!-- List of available products --> <product_items config:type="list"> <!-- The first product item --> <product_item> <!-- Product name visible in UI when offered to user (optional item) --> <name>Add-on Name to Display</name> <!-- Product URL (mandatory item) --> <url>http://product.repository/url/</url> <!-- Product path, default is "/" (optional item) --> <path>/relative/product/path</path> <!-- List of products to install from media, by default all products from media are installed (optional item) --> <install_products config:type="list"> <!-- Product to install - matching the metadata product 'name' (mandatory to fully define 'install_products') --> <product>Product-ID-From-Repository</product> <product>...</product> </install_products> <!-- If set to 'true', user is asked whether to install this product, default is 'false' (optional) --> <ask_user config:type="boolean">true</ask_user> <!-- Connected to 'ask_user', sets the default status of product, default is 'false' (optional) --> <selected config:type="boolean">true</selected> </product_item> <!-- Another product item --> <product_item /> </product_items> </add_on_products>
Mandatory items:
(string) url - repository URL; absolute or relative to the base installation repository; relative URL is useful when the same repository is used via several access methods (e.g., NFS+HTTP+FTP).
Absolute:
<url>http://example.com/SUSE_5.0/<url>
Relative:
<url>../SUSE_5.0/<url>
Optional items:
(string) name - Product name used when repositories are offered to user in UI before adding them, see ask_user for more; if not set, product URL and/or other items are used instead.
(string) path - Additional product path in the repository, useful when there are more product at one URL; the default is / if not set.
(boolean) ask_user - Users are asked whether to add such a product; products without this parameter are added automatically; default is false
(boolean) selected - Defines the default state of pre-selected state in case of ask_user used; default is false
(list <string>) install_products/product - List of products to add if there are more than one products at the repository URL; if not defined, all products are installed.
File add_on_products is an obsolete format of add_on_products.xml described above. It does not have additional features of the newer format and it is almost impossible to extend it.
Repositories listed in this file are added automatically with the primary installation repository.
Example:
http://some.product.repository/url1/ http://some/product.repository/url2/ /relative/product/path http://some.product.repository/url3/ / Product-1 Product-2
Repository entries are newline-separated, repository items are white-space-separated (tab or space).
Mandatory items:
First item: (string) url - repository URL; absolute or relative to the base installation repository; relative URL is useful when the same repository is used via several access methods (e.g., NFS+HTTP+FTP).
Absolute:
<url>http://example.com/SUSE_5.0/<url>
Relative:
<url>../SUSE_5.0/<url>
Optional items:
Second item: (string) path - Additional product path in the repository, useful when there are more product at one URL; the default is / if not set.
Third .. n item: (string) - products to add if there are more than one products at the repository URL; if not defined, all products are installed.
Table of Contents
After five releases, YaST2 is now smart enough to make reasonable proposals for (near) every installation setting, thus it is no longer necessary to ask the user that many questions during installation: Most users simply hit the [next] button anyway.
Hence, YaST2 now collects all the individual proposals from its submodules and presents them for confirmation right away. The user can change each individual setting, but he is no longer required to go through all the steps just to change some simple things. The only that (currently) really has to be queried is the installation language - this cannot reasonably be guessed (yet?).
The new YaST2 installation includes the following steps:
(Minimal) hardware probing - no user interaction required
Language selection - user picks installation language
Installation proposal - very much like the old installation summary just before the real installation started, only this time the user CAN change settings by clicking into the summary (or via a separate menu as a fallback).
Create / format partitions according to proposal / user selection - no user interaction required
Install software packages from CD / DVD / other installation media
After this, all that is remained left is basic system configuration like:
X11
Network interface(s)
Network services
Additional hardware (printer, sound card, scanner, ...)
YaST2 installation modules should cooperate with the main program in a consistent API. General usage:
inst_proposal (main program) creates empty dialog with RichText widget
inst_proposal calls each sub-module in turn to make proposal
user may choose to change individual settings (i.e., clicks on a hyperlink)
inst_proposal starts that module's sub-workflow which runs independently. After this, inst_proposal tells all subsequent (all?) modules to check their states and return whether a change of their proposal is necessary after the user interaction.
main program calls each sub-module to write the settings to the system
Each submodule provides a function dispatcher that can be called with 'CallFunction()'. The function to be called is passed as a parameter to this dispatcher. Parameters to the function are passed as another parameter in a map. The result of each call is a map, the contents of which depend on the function called.
The reason for this additional overhead is to provide extensibility and reusability for the installation workflow: A list of submodules to be called is read from file. This requires, however, that no explicit 'mod::func()' calls are used in 'inst_proposal.ycp'. Rather, the list contains the name of the submodule. Since each submodule is required to provide an identical API, this is sufficient.
Example 2.1. Proposal Example
Proposal is to call
input_devices (keyboard, mouse)
partitioning
software_selection
boot_loader
timezone
inst_proposal calls
map result = CallFunction (input_devices( "MakeProposal", $[ "force_reset" : false, "language_changed": false ] ) ); map result = CallFunction (partitioning ( "MakeProposal", $[ "force_reset" : false, "language_changed": false ] ) ); ...
If the user clicks on the hyperlink on "input_devices" in the proposal display, inst_proposal calls:
map result = CallFunction (input_devices( "AskUser", $[ "has_next": true ] ) );
![]() | Note |
---|---|
If any parameter is marked as "optional", it should only be specified if it contains a meaningful value. Don't add it with a 'nil' value. |
The dispatcher provides the following functions:
/** * Module: proposal_dummy.ycp * * $Id: dummy_proposal.ycp 14835 2004-02-27 02:37:39Z nashif $ * * Author: Stefan Hundhammer <sh@suse.de> * * Purpose: Proposal function dispatcher - dummy version. * Use this as a template for other proposal dispatchers. * Don't forget to replace all fixed values with real values! * * See also file proposal-API.txt for details. */ { textdomain "installation"; string func = (string) WFM::Args(0); map param = (map) WFM::Args(1); map ret = $[]; if ( func == "MakeProposal" ) { boolean force_reset = param["force_reset" ]:false; boolean language_changed = param["language_changed"]:false; // call some function that makes a proposal here: // // DummyMod::MakeProposal( force_reset ); // Fill return map ret = $[ "raw_proposal" : [ "proposal item #1", "proposal item #2", "proposal item #3" ], "warning" : "This is just a dummy proposal!", "warning_level" : `blocker ]; } else if ( func == "AskUser" ) { boolean has_next = param["has_next"]:false; // call some function that displays a user dialog // or a sequence of dialogs here: // // sequence = DummyMod::AskUser( has_next ); // Fill return map ret = $[ "workflow_sequence": `next ]; } else if ( func == "Description" ) { // Fill return map. // // Static values do just nicely here, no need to call a function. ret = $[ // this is a heading "rich_text_title" : _( "Dummy" ), // this is a menu entry "menu_title" : _( "&Dummy" ), "id" : "dummy_stuff" ]; } else if ( func == "Write" ) { // Fill return map. // ret = $[ "success" : true ]; } return ret; }
map MakeProposal
(
boolean force_reset
,
boolean language_changed
)
boolean force_reset
If 'true', discard anything that may be cached and start over from scratch. If 'false', use cached values from the last invocation if there are any.
boolean language_changed
The installation language has changed since the last call of
MakeProposal
. This is important only if there
is a language change mechanism in one of the other submodules.
If this parameter is "true", any texts the user can see in
the proposal need to be retranslated. The internal translator mechanism
will take care of this itself if the corresponding strings are once more
put through it (the _("...")
function). Only very few
submodules that translate any strings internally based on internal maps
(e.g., keyboard etc.) need to take more action.
MakeProposal()
returns a map containing:
list<string> links
A list of additional hyperlink ids used in summaries returned by this section. All possible values must be included.
Example:
["device_enable", "device_test"]
string preformatted_proposal
(optional)
Human readable proposal preformatted in HTML.
![]() | Note |
---|---|
You can use the HTML:: module for such formatting. |
list raw_proposal
(only used if 'preformatted_proposal' is not present in the result map)
Human readable proposal, not formatted yet. The caller will format each
list item (string) as a HTML list item
( "<li> ... </li>" )
.
The proposal can contain hyperlinks with ids listed in the list
links
.
The caller will make a HTML unsorted list of this, e.g.:
<ul> <li>string from list element #1</li> <li>string from list element #2</li> <li>string from list element #3</li> ... </ul>
string warning
(optional)
Warning in human readable format without HTML tags other than <br>.
The warning will be embedded in appropriate HTML format specifications according to 'warning_level' below.
symbol warning_level
(optional)
Determines the severity and the visual display of the warning. Valid values:
`blocker will prevent the user from continuing the installation. If any proposal contains a `blocker warning, the "accept" button in the proposal dialog will be disabled - the user needs to fix that blocker before continuing.
`fatal is like `blocker but also stops building the proposal
boolean language_changed
(optional)
This module just caused a change of the installation language. This is only relevant for the "language" module.
boolean mode_changed
(optional)
This module just caused a change of the installation mode. This is only relevant for the "inst mode" module.
boolean rootpart_changed
(optional)
This module just caused a change of the root partition. This is only relevant for the "root part" module.
string help
(optional)
map AskUser
(
boolean has_next
,
string chosen_id
)
boolean has_next
Use a "next" button even if the module by itself has only one step, thus would normally have an "OK" button - or would rename the "next" button to something like "finish" or "accept".
string chosen_id
If a section proposal contains hyperlinks and user clicks on one of them,
this defines the id of the hyperlink. All hyperlink IDs must be set in
the map retuned by Description
. If a user didn't click
on a proposal hyperlink, this parameter is not defined.
Run an interactive workflow - let user decide upon values he might want to change. May contain one single dialog, a sequence of dialogs or one master dialog with one or more "expert" dialogs. The module is responsible for controlling the workflow sequence (i.e., "next", "back" buttons etc.).
Submodules don't provide an "abort" button to abort the entire installation. If the user wishes to do that, he can always go back to the main dialog (the installation proposal) and choose "abort" there.
AskUser()
returns a map containing:
symbol workflow_sequence
`next
(default)
Everything OK - continue with the next step in workflow sequence.
`back
User requested to go back in the workflow sequence.
`again
Call this submodule again (i.e., re-initialize the submodule)
`auto
Continue with the workflow sequence in the current direction - forward if the last submodule returned `next, backward otherwise.
`finish
Finish the installation. This is specific to "inst_mode.ycp" when the user selected "boot system" there.
boolean language_changed (optional)
This module just caused a change of the installation language. This is only relevant for the "language" module.
map Description
()
Returns a map containing:
string rich_text_title
(Translated) human readable title for this section in
the RichText
widget without any HTML formatting.
This will be embedded in
<h3><a href="???"> ... </a></h3>
so make sure not to add any additional HTML formatting.
Keyboard shortcuts are not (yet?) supported, so don't include any '&' characters.
Example:
"Input devices"
string menu_title
(Translated) human readable menuentry for this section. Must contain a keyboard shortcut ('&'). Should NOT contain trailing periods ('...') - the caller will add them.
Example:
"&Input devices"
string id
Programmer readable unique identifier for this section. This is not auto-generated to keep the log file readable.
Example:
"input_dev"
This map may be empty. In this case, this proposal section will silently be ignored. Proposals modules may use this if there is no useful proposal at all. Use with caution - this may be confusing for the user.
![]() | Note |
---|---|
In this case, all other proposal functions must return a useful success value so they can be called without problems. |
map Write
()
Write the proposed (and probably modified) settings to the system. It is up to the proposal dispatcher how it remembers the settings. The errors must be reported using the Report:: module to have the possibility to control the behaviour from the main program.
This Write() function is optional. The dispatcher module is required to allow this function to be called without returning an error value if it isn't there.
Table of Contents
List of Examples
Table of Contents
This document is a user's guide to the YaST2 devtools (short for "development tools"), a utility collection to make developing YaST2 code easier - C++ as well as YCP.
Install the yast2-devtools
RPM or check out the devtools
module from
the YaST2 CVS and build and install it:
cd yast2 # your YaST2 CVS working directory cvs co devtools cd devtools make -f Makefile.cvs make sudo make install
See the Migration how to change your C++ or YCP module.
Build and use your package as before.
If make package
complains, fix the complaints. For a tempoarary package to
check whether or not build
works with your changes, use "make
package-local
" - but never check in a package to /work/src/done
that you created this way!
The YaST2 devtools are an add-on to the classic automake / autoconf environment YaST2 used to use.
Since the toplevel Makefile.am
is pretty much the same throughout all
YaST2 C++ or YCP modules yet contains more and more specialized make
targets, this toplevel Makefile.am
is now automatically generated.
The only thing that is (or, rather, "should be") different in all those
toplevel Makefile.am
files is the "SUBDIRS =
" line. This line is
moved to a SUBDIRS
in the
package's toplevel directory, much like RPMNAME
, VERSION
,
MAINTAINER
etc. - the rest of Makefile.am
is copied from a common
path /usr/share/YaST2/data/devtools/admin
. Thus, changes that should affect
all of YaST2's toplevel Makefile.am
files are much easier to do and all
YaST2 modules can benefit from them without the need to change (i.e. cvs
up
, edit, cvs ci
) all of over 85 individual files.
This implies, of course, that the toplevel Makefile.am
is no longer stored
in the CVS repository since it is now automatically generated.
On the downside, this of course implies that the files and scripts required for this new automagic are available at build time - i.e. on each YaST2 developer's development machine as well as in the build environment. Thus, you will need to either install the appropriate RPM or build the devtools manually - see the Quick Start section for details.
If you haven't done that yet, install the devtools - see the Quick Start section for details.
If you are not sure, check /usr/share/YaST2/data/devtools
- if you don't
have that directory, the devtools are not installed.
You can simply use the devtools-migration
script that comes with the
devtools package:
cd yast2/modules/mypackage y2tool devtools-migration cvs ci
This script performs the following steps:
Go to your package's toplevel directory:
cd yast2/modules/mypackage
Create a SUBDIRS
file from your existing
Makefile.am
.
![]() | Note |
---|---|
You can do without that grep 'SUBDIRS' Makefile.am | sed -e 's/SUBDIRS *= *//' >SUBDIRS
Getting rid of the " |
Add that new SUBDIRS
file to the CVS repository:
cvs add SUBDIRS
Get rid of the old Makefile.am
both locally and in the CVS repository -
this file will be automatically generated from now on:
cvs rm -f Makefile.am
Get rid of the old copyright notices (COPYING
,
COPYRIGHT.{english,german,french}
) both locally and in the CVS repository:
cvs rm -f COPYING COPYRIGHT.{english,french,german}
Those files will automatically be added to the tarball upon make package
,
make dist
and related commands.
Add Makefile.am
to the .cvsignore
file since it will be automatically
generated from now on (otherwise "cvs up" will keep complaining about it):
echo "Makefile.am" >>.cvsignore
Edit your .spec.in
file. Locate the neededforbuild
line and add
yast2-devtools
to it:
vi *.spec.in ... (locate "neededforbuild") (add "yast2-devtools") (save + quit)
OK, that was the wimp version. Here is the freak version:
perl -p -i -e 's/neededforbuild/neededforbuild yast2-devtools/' *.spec.in
Add the line that creates the toplevel Makefile.am
to your Makefile.cvs
:
vi Makefile.cvs (locate "aclocal") (add a new line above this:) [tab] y2tool y2automake (save + quit)
Again, a freak version for this:
perl -p -i -e 'print "\ty2tool y2automake\n" if /aclocal/' Makefile.cvs
The new Makefile.cvs
should look like this:
all: y2tool y2automake autoreconf --force --install
Double-check what you just did and check it into the CVS when everything looks OK.
"cvs up
" should print something like this:
M .cvsignore R COPYING R COPYRIGHT.english R COPYRIGHT.french R COPYRIGHT.german R Makefile.am M Makefile.cvs A SUBDIRS M myproject.spec.in
"cvs diff
"should print something like this:
Index: .cvsignore ... config.log aclocal.m4 +Makefile.am ... cvs server: Makefile.am was removed, no comparison available ... Index: Makefile.cvs ... all: + y2tool y2automake autoreconf --force --install ... cvs server: SUBDIRS is a new entry, no comparison available ... Index: myproject.spec.in ... -# neededforbuild autoconf automake ... +# neededforbuild yast2-devtools autoconf automake ... ...
Important: Don't build yet, otherwise Makefile.am
will be
regenerated and "cvs ci" will complain when trying to check all this in.
Check your changes in:
cvs ci
Test-build your package locally:
make -f Makefile.cvs && make && sudo make install
You should now have a new Makefile.am
.
For YaST2 translation modules (yast2-trans-...
), the Makefile.am
in the po
subdirectory is automatically generated as well. The strategy
for that is slightly different, though: The resulting Makefile.am
is
combined from Makefile.am.top
, Makefile.am.center
, and
Makefile.am.bottom
. The top and bottom files are used from the current
project, if there is such a file; otherwise, all files are taken from
/usr/share/YaST2/data/devtools/admin/po
. The center part is always
taken from there.
Add custom make
targets or variable definitions to the top or bottom part
as required. This may only be necessary for the yast2-trans-...
data
modules (e.g., keyboard, mouse, printers).
The migration script takes care of that: It migrates the po/
subdirectory, too, if there is one - and if there is a Y2TEXTDOMAIN
file in the the project toplevel directory. For data modules, the migration
script backs up the existing Makefile.am
to Makefile.am.bottom
(or to
Makefile.am.old
, if there already is a file named
Makefile.am.bottom
). Make sure to edit this file and get rid of
duplicate parts before checking in.
make package-local handles the file
*.spec.in. The file package/*.spec
created in the time of "make -f Makefile.cvs"
is overwitten in the time of "make package(-local)" with the
package/*.spec created by y2tool create-spec. But it should have the
same content.
With create-spec you can use more 'macros' in the *.spec.in:
writes the SuSE .spec comment
writes the usual header except BuildArch:, Requires:, Summary:
writes %prep with %setup
writes %build with usual make
writes %install with usual YCP make install
writes %clean with removing RPM_BUILD_ROOT
So the new *.spec.in could look like:
@HEADER-COMMENT@ # neededforbuild autoconf automake ycpdoc yast2-testsuite ... @HEADER@ Requires: yast2 yast2-trans-XXpkgXX yast2-lib-wizard yast2-lib-sequencer BuildArchitectures: noarch Summary: Configuration of XXpkgXX %description This package is a part of YaST2. It contains the necessary scripts to configure XXpkgXX. @PREP@ @BUILD-YCP@ @INSTALL-YCP@ @CLEAN@ %files %dir @yncludedir@/XXpkgXX /...
These paths are defined in your configure.in
generated by y2autoconf
and substituted by create-spec
. That means they are accessible in all
your Makefiles and can be uses in spec.in
files.
not for direct use
for documentation
for all yast2 programs not be started by the user.
for loadable plugins
for c header files
for translations files
for ycp clients
for ycp modules
for schema files (autoyast, control file)
for ycp includes
for scr files
for .desktop files (former *.y2cc)
for external programs that are yast2 components. here you have to append servers, servers_non_y2, clients or clients_non_y2.
for general data
for non theme-able images
for theme-able images (every theme must provide the same list of images)
In Makefile.am you can simply say
ybin_PROGRAMS = y2base
when you what the program y2base to be installed in ybindir. No need to change bindir or even prefix.
In the files section of your spec.in file use something like this:
@scrconfdir@/*.scr
Remember that the asterisk is only save when using a BuildRoot (and please use a BuildRoot).
If you need a define in a C++ file you have to pass it to the compiler. Simply use
AM_CXXFLAGS = -DPLUGINDIR=\"${plugindir}\"
in your Makefile.am.
Create a tarball from your module
and put it into the package/
directory. This also creates a spec file
from the .spec.in file.
This checks for cvs consistency (see make check-tagversion
) and whether or not you correctly
tagged that version (don't forget to increase the version number in
VERSION
!), then does everything make package-local
did.
This is performed by make package
prior to actually creating a tarball:
It checks whether or not you correctly tagged the current version. Use
"y2tool tagversion
" to do that once you increased the version number in
VERSION
.
![]() | Note |
---|---|
You will very likely never call this manually. |
This is performed by make package
prior to actually creating a tarball:
It checks whether or not everyting in this directory tree is checked into
CVS or Subversion. Any modified, added or removed files make this check fail.
![]() | Note |
---|---|
You will very likely never call this manually. |
This makes a package (i.e. it does everything "make package
" does and
checks it into the correct SuSE Linux distribution.
![]() | Note |
---|---|
This requires |
During execution YaST2 components create log messages. The purpose is to inform the user or the programmer about errors and other incidents.
The logging should be used instead of
fprintf(stderr,...)
to create logmessages of different
types. It can be better controlled, what to log and what not, where
to log and how to log.
Use y2debug()
for debugging messages,
y2warning()
for warnings and y2error()
for error
messages, syntax is same as printf(3)
.
Set "export Y2DEBUG=1"
in your .profile
or
run "Y2DEBUG=1 yast2"
.
If root
, see /var/log/YaST2/y2log
, otherwise
~/.y2log
for the output.
In the y2log
, entries are uniquely identified by the
filename and line number.
There exist six different log levels denoting incidents of different importance:
Debug messages, which help the programmers.
Normal log messages. Some important actions are logged. For example each time a YaST2 module is started, a log entry is created.
Some error has occured, but the execution could be continued.
Some major error has occured. The execution may be continued, but probably more errors will occur.
Some security relevant incident has occured.
Internal error. Please enter into Bugzilla or directly contact the programmers.
In the default setting the levels 1-5 are logged, level 0 (DEBUG) is switched off. See the Logging control and Environment control for more details how to control the logging and its levels.
According to the logging levels, use the following logging functions:
void y2debug(const char *format, ...); void y2milestone(const char *format, ...); void y2warning(const char *format, ...); void y2error(const char *format, ...); void y2security(const char *format, ...); void y2internal(const char *format, ...);
The parameter format
is the format string like the one
for printf(3)
void y2setLogfileName(const char *filename);
This function sets the logfile name. If the name cannot be open
for writing (append), it use the default logfiles. If you want to output the debug log
the stderr
, use "-"
as the argument for the
y2setLogfileName:
y2setLogfileName("-");
void y2logger(loglevel_t level, const char *format, ...); void y2vlogger(loglevel_t level, const char *format, va_list ap);
These functions are provided probably only for those who don't want to use the regular logging functions. For example for setting the loglevel acording to some rule.
As the filenames are not unique over the whole YaST2 source, you can specify the component name. Then the pair of the component name and the filename will uniquely identify the message.
Note: I think that the filenames should be self explaining and thus unique overall the whole source. Then the component name can be removed, but as now the filename is not unique, you can optionally specify the component name.
As the component is a more general property then filename, it
should be same in all messages in one file. So for one source file
it is defined only once, at the beginning of the file. And because
of implementation purposes (just) before the inclusion of y2log.h
:
#define y2log_component "y2a_mods" #include <ycp/y2log.h>
The YaST2 log is written to a file. If you work as normal
user, the default logfile is ~/.y2log
. If you work as
root, the file is /var/log/YaST2/y2log
. The logfile is
created with the permissions 600, since it may contain secret data
when the debug level is turned on.
If the logfile cannot be open, the stderr
is use
instead.
Each log entry consist of these fields:
date
The date when the log entry has been made.
time
The time when the log entry has been made.
level
The log entry level. See Logging levels.
hostname
The hostname of host where the yast2 runs.
pid
The process ID of the yast2 process.
component
The name of the current component. Optional and probably obsolete.
filename
The name of the source file where the log entry has been made.
function
The name of the function where the log entry has been made.
line
The line number where the log entry has been made.
message
The text of the log message.
The output format:
date time <level> hostname(pid) [component] filename(function):line message... date time <level> hostname(pid) filename(function):line message...
Example:
2000-10-13 15:35:36 <3> beholder(2971) [ag_modules] Modules.cc(quit):22 io=7 2000-10-13 15:35:37 <0> beholder(2971) ModulesAgent.cc(main):23 irq=7
The log control uses a simple ini-like configuration file. It is looked for at /etc/YaST2/log.conf for root and at $HOME/.yast2/log.conf for regular users.
Example log.conf file could look like:
[Log] file = true syslog = false debug = false [Debug] YCP = true agent-pam = true packagemanager = false
"syslog=true", which basically means remote-logging. The similar option "file=true" means use the usual log files for logging. You could also turn those off which means no logging would be done at all, but rather don't do that ;-)
The "debug=true" means basically the same as Y2DEBUG=1 (that envirnoment variable overrides the log.conf settings) and that is log by default all debug messages (if not said otherwise).
You can turn debuggin on ("agent-pam=true") for a particular component (even if "debug=false") and also turn debugging off (for the case that "debug=true").
To provide a useful example, normal developers would need something like this $HOME/.yast2/log.conf (and unset Y2DEBUG):
[Debug] YCP = true agent-pam = true
It means turn YCP debug messages on and also turn on some particular agent. The other debug are in most uninteresting, so let them turned off.
During installation , define the variable "Loghost" on the command line with the log server ip address (Loghost=192.168.1.1 ) and all messages will be sent to this host. If you add y2debug, debugging will also be activated in log.conf.
On the server side, using syslog-ng, you can have logging per host using the following filters:
source network { tcp(); udp(); }; destination netmessages { file("/var/log/messages.$HOST"); }; log { source(network); filter(f_messages); destination(netmessages); };
Additionally to the usual logfile control you can control some logging feature by the environment variables.
By setting this variable to an arbitrary value you turn on the debug log output. But only when entry control is not covered by the usual logfile control.
By setting this variable to an arbitrary value you turn on the debug log output. Everything will be logged.
By setting this variable to an arbitrary value you turn on the debug log output for the bash_background processes.
By this variable you can control the size of logfiles. See Logfiles for details.
By this variable you can control the number of logfiles. See Logfiles for details.
Example: call the module password
with QT
interface and debugging messages set to on:
bash$ Y2DEBUG=1 yast2 users
Simply invoke check_ycp
with the YCP file(s) you wish to check as
arguments:
check_ycp myfile.ycp
check_ycp *.ycp
The error messages should be self explanatory. They stick to the GNU standards for error messages, so you can use your favourite editor's (e.g. Emacs) function to process them.
Most of the checks can individually be turned off. Type
check_ycp -h
for a complete list of command line options. Those options are intentionally not listed here since such a list would inevitably be outdated before too long.
Even though using check_ycp
is pretty straightforward, some background
information is useful in order to understand what it does, its output and the
limitations of this tool - in short, what you can expect it to do and why not
to blindly rely on it.
check_ycp
is far from fool proof. In fact, it is pretty dumb. It just
tries to parse YCP code ("try" is the operative word here!) and applies various
checks for obvious programming errors. Some errors it will catch and report,
many it will not. But we (i.e. the YaST2 core development team) decided we'd
rather have a tool with limited capabilities than none at all.
Another reason for writing this document is pointing out why we try to enforce certain things, many of which are because of ergonomics or mere conventions in developing as a team, not true requirements of YaST2 or the YCP language.
The YaST2 team uses a standardized file header format for YCP modules. Standard fields are included there for various purposes - see the "why" sections of the individual checks.
Everything up to the first opening brace "{
" outside a comment is
considered part of the header. Nothing outside this portion of the file is
checked. You may, however, open and close as many comments as you like up to
this opening brace.
Leading asterisks "*
" at the start of lines are silently discarded
since they are often used to beautify multi line comments.
The comment markers themselves of course are also discarded for the checks:
/*
*/
//
Much code gets written by copying existing code. There is nothing wrong with
that (in fact, it saves a lot of work), but when you do, please change fields
accordingly - fields like Author
, Maintainer
,
Purpose
etc. - and the file name in Module
.
check_ycp
checks the file header for presence of at least one of
Author:
Authors:
Maintainer:
Maintainers:
If found, each entry is checked for some contents, i.e. it may not be completely empty (but use whitespace as you like).
The contents must include something that looks like an e-mail address.
There must be at least one person to contact when there are any problems or questions about the module. The full name is desired, but at least an e-mail address must be there to get in contact with the maintainer or the author.
Presence of a Id CVS / RCS identity marker is checked, e.g.
Id myfile.ycp,v 1.2 2001/02/14 18:04:50 sh Exp
This CVS / RCS ID is the only way of finding out exactly what CVS revision the
file has and what change date. The file date (what ls -l
shows) is
absolutely unreliable and irrelevant: This may have changed just by copying the
file around which didn't change anything.
This is important for bug tracking and for finding and fixing bugs - only when a developer knows what version of a file has been used he has a chance to reproduce a bug - or even make sure that a supposedly fixed bug didn't turn up again.
Presence of
Id:
is checked. There may be more characters before the closing dollar sign
"$
", but the exact contents is not checked.
![]() | Note |
---|---|
When creating a new file, it is absolutely sufficient to
include the unexpanded string (" |
If there is any message that is marked for translation with
_("
...")
, there must be a textdomain
statement.
The YaST2 translator module needs to know where to take the messages to be
translated from. This is what the textdomain
specification does.
Technically one textdomain
statement somewhere in the YCP program
would be sufficient, i.e. include files or modules called with
CallFunction()
don't really require an
additional textdomain
specification.
However, it is highy recommended all YCP files with translatable messages
include their own textdomain
statement so each YCP file is
self-sufficient in that regard, thus more easily reusable for other
purposes. This policy is enforced with this check.
After being stripped of all comments, the entire YCP code is scanned for the
translation marker sequence: An underscore immediately followed by an opening
parenthesis: _(
If this sequence is found, presence of translatable messages is assumed. If no
textdomain
statment is found there will be an error.
On the other hand, if there is no text to translate, a textdomain
statement is not necessary (but it can't hurt).
![]() | Note |
---|---|
Theoretically the " |
Literal strings in YCP code that contains HTML tags are usually help text that will be displayed in the YaST2 RichText widget. This HTML text is subjected to the sanity checks explained below.
Please notice that everything within double quotes "
will be
checked that contains anything surrounded by angle brackets
<
...>
- i.e. anything that looks remotely like an HTML
tag. Unknown tags will be silently ignored, but the entire text within the
quotes will be checked.
Limitation:
If a portion of help text lacks any HTML tag, it will not be checked since it
will not be recognized by check_ycp
as help text. Such completely
wrong portions of help text will slip through undetected, thus unchecked.
Each HTML text must start with a <p>
tag and end with a
</p>
tag.
There must be a corresponding closing </p>
tag for each opening
<p>
tag.
This is a basic requirement of HTML. The underlying YaST2 widgets may or may not be forgiving enough to tolerate missing tags, but we'd rather not rely on that.
Besides, no other types of paragraphs other than plain text paragraphs
<
p>
... <
/p>
are desired in
YaST2 help texts - in particular, no large font boldface headings etc.
See the intro of this section.
For each portion of HTML text:
<p>
tag is
permitted.</p>
tag is
permitted.</p>
and the next opening
<p>
tag is permitted.
See the intro of this section.
Each single portion of HTML text may contain exactly one paragraph, i.e. one
<
p>
... <
/p>
pair.
This is a convention to make life easier for the translators.
The tools used for extracting translatable texts from the sources (GNU
gettext
) detect differences between the last translated version of a
message and the current message from the latest source. They mark such messages
as fuzzy, i.e. the (human) translator is asked to have a good look at
it and decide whether there has been a real change in the message (thus it
needs to be retranslated) or just a cosmetic change (fixed typo, inserted
whitespace, reformatted the paragraph etc.).
This is a tedious task and it gets more tedious the longer each individual portion of text becomes. Changes from the old to the new version are hard to find if the portions are very long.
Plus, if they are that long it is very likely that always somewhere something has changed, thus the entire text is marked as fuzzy and needs careful manual checking which is not really necessary for all the text.
Split your help texts and use the YCP string addition operator to put them together.
Don't:
help_text = _("<p> bla blurb bla ... blurb bla blurb ... bla blurb bla ... </p> <p> bla blurb bla ... blurb bla blurb ... bla blurb bla ... </p>");
Instead, do:
// Help text (HTML like) help_text = _("<p> bla blurb bla ... blurb bla blurb ... bla blurb bla ... </p>"); // Help text (HTML like), continued help_text = help_text + _("<p> bla blurb bla ... blurb bla blurb ... bla blurb bla ... </p>");
Please also notice the comments for the translators just above the
text. The gettext
tools will automatically extract them along with the
text to translate and put them into the .po
file. The translators can
use them as additional hints what this text is all about.
See the intro of this section.
Such forced line breaks are plain superfluous. The HTML renderer will format the paragraph automatically - after each paragraph there will be a newline and some empty space to set each paragraph apart from the next.
There is no need nor is it desired to add extra empty space between paragraphs. This just looks plain ugly, even more so if this results in different spacings between several paragraphs of the same help text.
The most superfluous of those excess line breaks are those at the very end of a help text - after the last paragraph. Not only are they not good for anything, they sometimes even cause a vertical scroll bar to be displayed even though this would not be necessary otherwise.
Plus, there have been cases where erstwhile last help text paragraphs had been
rearranged so they now are in the middle of the help text - but unfortunately
the trailing <br>
tag had been forgotten and moved along with
the paragraph, thus causing different inter-paragraph spacings.
To make things even worse, fixing this breaks the translation for the affected paragraph: It will be marked as fuzzy just because of this even though it has not really changed.
We cannot entirely get rid of the <br>
tags (but we would like
to). Sometimes they are needed within paragraphs. But at least those
at the end of paragraphs we can do without.
Parameters to YaST2 UI widgets plus some commonly used functions
(e.g. Wizard::SetContents()
, Popup::Message()
etc.) are checked
where possible - if the parameters are simple string constants, maybe
surrounded by translation markers ("_("
...")
").
Optional widget parameters like `opt(...)
or `id(...)
are
ignored.
The following examples will be checked:
PushButton("OK");
PushButton( _("Cancel"));
PushButton(`id(`apply), _("Apply"));
PushButton(`opt(`default), _("OK"));
More complex parameters like variable contents or YCP terms cannot be checked.
The parser used in check_ycp
for that is really dumb. In fact, it only
scans for keywords like PushButton
outside string constants, tries to
find the corresponding matching pair of parentheses "(
...)
"
and splits everything inside into comma-separated subexpressions.
Only the most basic of those subexpressions are checked - only simple string
constants "
..."
or string constants marked for
translation _("
...")
.
The following examples will not be checked:
CheckBox( "/dev/"+device );
CheckBox( sformat("/dev/%1"), device );
CheckBox( GetDevName() );
string message = "OK"; PushButton( message );
Widgets that can have a keyboard shortcut (one character marked with an
ampersand "&
") are checked for presence of a keyboard shortcut.
![]() | Note |
---|---|
Consistency of the keyboard shortcuts is not checked,
only presence. |
This is for users whose mouse doesn't work (especially during installation time) as well as for experienced users who prefer working with the keyboard. Navigation from one widget to another is much easier when each widget that can get the keyboard focus can be reached with an [Alt] key sequence rather than repeatedly using the [Tab] key and/or the cursor keys.
There may be a lot more widgets that can have keyboard shortcuts than you expected. Basically, every widget that can somehow be operated with the keyboard (even if it is only scrolling) and that has an associated label (within, above or beside) can have a keyboard shortcut and should get one.
The widget parameter that acts as a label is checked for presence of exactly
one ampersand "&
".
See the widget checks of this section for more.
Widget parameters that are displayed literally as text are checked for
translation markers ("_("
...")
").
Every text message that ever gets to the end user is to be translated into the user's native language. This can only be made sure if the message is marked for translation.
See the intro of this section.
Presence of definitions of functions from the wizard lib (Package
yast2
) outside the wizard library itself is checked
such as Wizard::SetContents()
etc.
At the start of YaST2 develompent there was no other way of sharing code other
than simply copying it. Those days are gone; YCP now supports an
include
mechanism similar to C or C++.
Very general code like how to create the typical YaST2 wizard window layout has now been moved to the wizard lib, a collection of include files that provide such facilities. We want to get rid of duplicate code as soon as possible for obvious reasons (consistency, maintainability, efficiency).
Usage or presence of definitions of known obsolete functions is checked,
e.g. Popup::Message()
, Popup::YesOrNo()
etc.; using an equivalent
replacement function from the wizard lib's common_popups.ycp
include file is suggested.
Those functions are now superseded by those from
common_popups.ycp
. The replacement functions usually require less
parameters (thus are easier to use) and use a common and consistent widget
layout.
The definitions are checked very much like the wizard function definitions above; function and file names are hardwired here as well.
Usage of the obsolete functions is checked simply by checking for occurrence of the function name followed by an opening parenthesis (maybe with whitespace in between) somewhere in the code.
Presence of predefined message strings is checked, e.g. "&Next
",
"&Back
" etc.; using a corresponding function from the wizard
lib (Label
Module) is suggested,
e.g. Label::NextButton()
, Label::BackButton()
etc.
Ease the burden on the translators - with the predefined messages they don't need to translate the same standard texts over and over again.
Consistent messages for the same type of buttons etc. throughout all of YaST2.
Consistent keyboard shortcuts for the same button throughout all of YaST2.
If we ever need to change one of those standard messages, we can do that centralized.
The YCP code, stripped of comments, is checked for any one of the predefined
messages (including any keyboard shortcuts that may be there), surrounded by
translation markers ("_("
...")
").
Differences in spelling or only in whitespace will not be caught. If there is
no or another keyboard shortcut, the message will not be considered the same -
so if anybody uses "Ne&xt
" rather than "&Next
", this will go
undetected.
Alternative variable declarations are rejected, e.g.
string|void value = anything(); symbol|term result = UI::UserInput(); integer|string x = 42;
Just about the only situation where this made sense was when a variable might
sometimes be nil
to indicate some error condition. All other variants
of this are of purely academic nature or (more likely) poor programming
style. Since all YCP types can be nil
now, however, this feature
becomes totally redundant. It will very likely be dropped in the near future.
You probably don't want to perform all of the available checks for simple YCP
examples. Those should be concise and written for legibility rather than for
completeness. They will usually not contain a standard format file header with
all bells and whistles, no translation markers etc. - you don't want to bloat
HelloWorld.ycp
with all that stuff.
check_ycp
has a special example mode for just this purpose: It
turns off all checks that don't make sense for simple examples, yet allows you
to use check_ycp
anyway. If you think "well, what's left
then?" think about the future. check_ycp
can and will be expanded
to cover more and more checks, and even your examples can benefit from it.
For simple YCP examples (and only for them, please!) invoke check_ycp
with
the -x
command line option:
check_ycp -x HelloWorld.ycp
This turns off all checks that don't make sense for examples.
check_ycp
and Emacs go together well:
Invoke the Emacs compile command:
M-x compile
Edit the compile command ("make -k
" by default) in the
minibuffer; change it to the check_ycp
command you wish to invoke (you only need to do this once for each Emacs session):
check_ycp *.ycp
Use the next-error
function to go to the next error
check_ycp
has reported. The corresponding YCP file will automatically be
loaded into Emacs if needed, and Emacs will jump to the corresponding line
within that file.
If you haven't done so already, you might want to bind the compile
and
next-error
functions to keys in your ~/.emacs
file, e.g.
(global-set-key "f42" 'compile) (global-set-key "f43" 'next-error)
The real challenge here is to find a key that is not already in use for some other important function.
If you are a real hardcore YCP hacker, you can even go so far and change the
default compile command to check_ycp
in ~/.emacs
:
(setq compile-command "check_ycp *.ycp")
Everybody should be able to add checks for a new widget or a new function that uses keyboard shortcuts (unlikely) or translatable messages (very likely) - even without any knowledge of Perl:
Locate the check_widget_params()
function.
Add your widget to the list (the large regexp) near the function beginning, where all the other widgets are. Be careful not to include any whitespace (blanks or tabs) inside the parentheses. Wrong:
( MyNewWidget ) |
OK:
(MyNewWidget) |
Add an elsif()
branch to the large
if()
...elsif()
...elsif()
construction:
elsif ( $widget =~ /MyWidget/ ) { check_keyboard_shortcut ( $widget, $line_no, 1, @args ); check_translation ( $widget, $line_no, 1, @args ); }
You might have to change the third parameter according to your widget or
function: This is the number of the parameter to be checked (the first one is
1) after all `opt()
and `id()
parameters have been removed.
Of course you can omit the keyboard
shortcut check
(check_keyboard_shortcut()
) if it doesn't make sense for your
widget or function.
If there is more than one parameter to be checked for translatable messages, add a call to check_translation()
for
each.
Like Linus Torvalds once said: "Use the source, Luke!" ;-)
check_ycp
's sources are extensively commented, even the many regular
expressions used there. But changing those regexps really requires some
in-depth knowledge of regexps in general and Perl regexps in particular. If you
feel unsafe, better not touch them.
Other than that, use your creativity.
Target Audience:
The YaST2 UI (User Interface) features a macro recorder and player that records user interaction during installation or system configuration and plays those user actions at a later time in the same scenario.
The Qt (graphical) and NCurses (text mode) user interfaces both support the macro recorder. It is also independent of graphics mode, display resolution, widget theme, terminal type or other details of the desktop being used: Not low-level input device (mouse or keyboard) events are recorded but logical user actions such as "Accept button was activated", and widget status information is saved in terms as "the user name input field contains tux", not individual keystrokes that might include lots of cursor movement and hitting the "backspace" key.
Macros recorded in one environment using the Qt UI may be played in another using the NCurses UI - unless, of course, the dialogs in either situation have completely different contents because extended features were used the other UI is not capable of. This should occur only very rarely, however.
Since most readers are impatient and just want to know how to get it going, here is a quick start guide - but PLEASE read the other sections anyway to avoid disappointment or even severe data loss:
To play a macro, press Alt-Ctrl-Shift P.
A file selection box opens. Select the macro file you recorded earlier.
Alternatively, you can also supply a macro on the "y2base" command line:
/usr/lib/YaST2/bin/y2base some_yast2_module qt --macro /wherever/macro.ycp
/usr/lib/YaST2/bin/y2base some_yast2_module ncurses --macro /wherever/macro.ycp
This is currently the only way to play macros with the NCurses (text mode) UI - it doesn't provide special key combinations for recording or playing macros (yet).
The general idea of this macro recorder is to provide an easy way of automating repetetive tasks on the user level - for automated testing and to easily produce lots of screen shots in recurring situations.
For example, the SuSE Linux installation manuals include lots of screen shots that of course look differently in each language, and it is very desirable to have the screen shot in the same language as the rest of the manual. For the documentation department, this means that all required screen shots have to be made in all languages we ship translated manuals for, so all relevant installation scenarios have to be restaged with only the language being different. To make matters worse, the responsible person might not even understand all those languages and thus has to rely on guessing what button to click on.
With the macro recorder, he can record a macro in a language he understands, do all screen shots there, then restart the process, select another language and play the macro - all screen shots he took will then automatically be made in that language. Of course, not a button text like "Accept" is being recorded, but an internal logical button name that doesn't change depending on language.
The macro recorder is not intended as a poor man's substitute for AutoYaST, the automatic untattended installation - even though it can be (mis-) used that way to some degree.
If you have lots of machines to install in a similar way, use AutoYaST, not the macro recorder: The macro recorder is dumb. It will blindly repeat whatever you did while recording the macro. If however at some other machine the situation is only slightly different, this might easily not work any more: If there are hard disks of different size or with a different partitioning scheme and you didn't rely on YaST2's automatic modes but created partitions manually, this might fail on the other machine.
Then you will get an error dialog, at which point your macro will no longer match the situation, but of course the macro will not realize this and happily keep playing user actions that are completely out of sync with the dialogs on-screen. Usually this will just cause lots of more error dialogs, but chances are that it might keep working for a while and cause data loss on that machine.
Note: This might even happen at the same machine if the environment just changed a bit - if, say, you added a hard disk partition in the first run, this might fail in the second run (or in the third or fourth run) because there is no more free space on the disk. Then you will also get error dialogs.
AutoYaST on the other hand takes all this into account and reacts in a much more intelligent way.
Given the intentions and target user group of the macro recorder, there are some known limitations that will very likely remain for the forseeable future:
Some widgets in the Qt UI "eat" the special key combinations. If one of those has the keyboard focus, pressing Alt-Ctrl-Shift M (or P) will have no effect. But there is an easy workaround: Simply move the keyboard focus with the "Tab" key to another widget like a push button - this will not change the environment for macro recording or playing. It is otherwise irrelevant to the macro recorder which widget has the keyboard focus.
Qt selection boxes are particular notable for this - you have to hit "Tab" in the language selection in the first dialog of a YaST2 installation before you can start recording or playing a macro there.
The software package manager user interface does not support the macro recorder at all. This was the tradeoff for getting a user interface that powerful: All that dialog is one large widget written purely in C++ unlike almost all other YaST2 dialogs.
If you use the macro recorder, don't go into the detailed software selection.
The Qt version of the YaST2 control center also does not support the macro recorder at all. It is a relatively basic Qt/C++ program that acts as a program launcher for the YaST2 modules, but it is not connected to them very closely. This was also a tradeoff: It is optimized for pretty looks and fast startup time.
Here is an example of a macro recorded during the first part of a YaST2 installation:
In the language dialog, "German" was selected. Notice how there are no German texts to be seen anywhere inside the macro - only symbolic names are used.
Then in a popup asking whether to update or to do a new installation "installation" was chosen.
From the installation settings proposal "software" was selected to change the amount of software to install from "default system" to "minimal+X11".
Then the installation was started.
Example 1.1. YaST2 UI macro file
// YaST2 UI macro file generated by UI macro recorder // // Qt UI: Alt-Ctrl-Shift-M: start/stop Macro recorder // Alt-Ctrl-Shift-P: Play macro // // Each block will be executed just before the next UserInput(). // 'return' before the closing brace ( '}' ) of each block relinquishes control // back to the YCP source. // Inside each block arbitrary YCP code can be added manually. { { UI::ChangeWidget( `id (`language), `CurrentItem, "de_DE" ); // YSelectionBox // UI::MakeScreenShot( "/tmp/yast2-0000" ); UI::FakeUserInput( `language ); return; } { UI::ChangeWidget( `id (`language), `CurrentItem, "de_DE" ); // YSelectionBox UI::MakeScreenShot( "/tmp/screen-shots/language-selection.png" ); // UI::MakeScreenShot( "/tmp/yast2-0001" ); UI::FakeUserInput( `accept ); return; } { UI::ChangeWidget( `id (`install), `Value, true ); // YRadioButton // UI::MakeScreenShot( "/tmp/yast2-0002" ); UI::FakeUserInput( `ok ); return; } { // UI::MakeScreenShot( "/tmp/yast2-0003" ); UI::FakeUserInput( "software" ); return; } { UI::ChangeWidget( `id ("Minimal+X11"), `Value, true ); // YRadioButton UI::MakeScreenShot( "/tmp/screen-shots/sw-base-selection.png" ); // UI::MakeScreenShot( "/tmp/yast2-0004" ); UI::FakeUserInput( "Minimal+X11" ); return; } { UI::ChangeWidget( `id ("Minimal+X11"), `Value, true ); // YRadioButton // UI::MakeScreenShot( "/tmp/yast2-0005" ); UI::FakeUserInput( `accept ); return; } { // UI::MakeScreenShot( "/tmp/yast2-0006" ); UI::FakeUserInput( `accept ); return; } }
Each dialog that is opened gets its own block enclosed in curly braces ( "{..}" ). In each block, the status of each widget that holds status information is restored (UI::ChangeWidget()).
Then there is a chance to make a screen shot; the macro recorder automatically adds a UI::MakeScreenShot() statement at the appropriate place, assigning generic file names that end in numbers. This statement is commented out by default as indicated by leading double slashes ( "//" ) - this makes it simple to enable it if desired.
If the user explicitly hits the [PrintScreen] key to make a screen shot, another UI::MakeScreenShot() call (this time not commented out) will be added with the exact file name the user entered at the file selection box. The idea is to give the user a chance to assign more descriptive names to the screen shots.
After that, UI::FakeUserInput() simulates the same input the user had done while recording the macro. Usually, this is activation of a push button (like `accept as seen so many times above), but it can also be changing a selection box like in the language selection at the start of the macro (for insiders: if the widget has `opt(`notify) set).
The last line is a "return" statement that returns control flow from the macro block to the application that is being executed.
When the next dialog is executed (for insiders: when UI::UserInput() or related are called), the next macro block is executed.
So not only is the mechanism very generic, the code that is produced is also plain YCP code that is readable and that can easily be adapted if necessary.
Desktop files are kind of configuration files that define how particular applications behave in desktop environment, how they appear menu, etc. These desktop files follow the Desktop Entry Specification. Additionally, some YaST-related entries are defined for YaST-specific purpose.
Configuration files are stored under the /usr/share/applications/YaST2/ directory.
Entries begining with blank or # character are comments.
All entries are case-sensitive.
Desktop file entries are divided into groups. Each group is defined by a group-name using a square-brackets around it. This is a must-have group in every desktop file:
[Desktop Entry]
Entries are following the grop name below which they are defined. They use a simple definition:
Key=Value
Key can contain only letters, digits and a dash character.
Exec=/sbin/yast2 example
Entries, that are displayed to user, can have their values localized. This format uses the base key name and adds a locale name surrounded by a square brackets. Variables must be UTF-8 encoded. The locale string uses [language[_territory][.codeset][@modifier]] format:
Key=A.B.C. Key[de]=A.B.C. - German Key[en_US]=A.B.C. - American English Key[cs_CZ.UTF-8]=A.B.C. - Czech (UTF-8)
These localized definitions are used according to LC_MESSAGES variable - The best match wins (evaluated from left).
There are some mandatory entries that are required by the desktop file definition. Additionally we use a YaST-specific and AutoYaST-specific entries.
Key | Description | Value Type |
Type | Application, Link or Directory. YaST uses the type Application. | string |
Name | Application name. YaST module name for YaST purpose, e.g., Firewall. This entry is often used also with localization Name[$locale]. | string |
GenericName | Generic name of the application. In YaST used as description of the application purpose, e.g., Configure a firewall. This entry is often used also with localization GenericName[$locale]. | string |
Categories | A semicolon-separated list of categories in which the entry will be show in a menu, e.g., Qt;X-SuSE-YaST;X-SuSE-YaST-Security;. | string |
Icon | Path to an image or rather only a name of an icon to be displayed along with the application name in file managers and menus, e.g., yast-firewall. If only a name is used, the particular icon is looked up in the current theme. | string |
Exec | A command which is executed when the application is launched. It can have arguments. E.g., /sbin/yast2 firewall. | string |
Key | Description | Value Type |
X-SuSE-YaST-Call | Module name which is called with the /sbin/yast2 command from YaST Control Center, e.g., firewall or users. This entry is required. | string |
X-SuSE-YaST-Group | YaST group name. In YaST Control Center, YaST modules are listed under these groups. Possible values are: Hardware, Misc, Network, Net_advanced, Security, Software and System. This entry is required. | string |
X-SuSE-YaST-Argument | Additional argument(s) for YaST. They can be --fullscreen and/or --noborder. This entry can be empty. | string |
X-SuSE-YaST-SortKey | String for sorting an application in the YaST Control Center. This entry can be empty (than the default zzzzz is used). | string |
X-SuSE-YaST-Geometry | You can also set the default size for opening UI up, nevertheless, this feature doesn't seem to work. And, of course, this entry can be empty. | string |
X-SuSE-YaST-RootOnly | This entry defines whether the application will be visible only for root. Possible values are yes, yes, 1 or any other value which is considered meaning false. | boolean |
These entries are described in the AutoYaST Configuration Management System in section Configuration file
This is an example taken from the firewall.desktop file.
[Desktop Entry] Type=Application Categories=Qt;X-SuSE-YaST;X-SuSE-YaST-Security; # KDE-related entries X-KDE-SubstituteUID=true X-KDE-ModuleType=Library X-KDE-RootOnly=true X-KDE-HasReadOnlyMode=true X-KDE-Library=yast2 # YaST-related entries X-SuSE-YaST-Call=firewall X-SuSE-YaST-Group=Security X-SuSE-YaST-Argument= X-SuSE-YaST-RootOnly=true X-SuSE-YaST-AutoInst=all X-SuSE-YaST-Geometry= X-SuSE-YaST-SortKey= X-SuSE-YaST-AutoInstClonable=true X-SuSE-YaST-AutoInstRequires=lan X-SuSE-YaST-AutoInstSchema=firewall.rnc Icon=yast-firewall Exec=/sbin/yast2 firewall # Name with localizations Name=Firewall Name[es]=Cortafuegos Name[hu]=Tűzfal Name[ko]=방화벽 Name[sv]=Brandvägg Name[ja]=ファイアウォール Name[lt]=Ugniasienė Name[sl]=Požarni zid # GenericName with localizations GenericName=Configure a firewall GenericName[es]=Configuración del cortafuegos para usuarios avanzados GenericName[ko]=고급 사용자를 위한 방화벽 설정 GenericName[sv]=Brandväggsinställningar för avancerade användare GenericName[fr]=Configuration Firewall pour utilisateurs experts GenericName[ja]=上級のファイアウォール設定 GenericName[lt]=Ugniasienės konfigūravimas patyrusiems vartotojams GenericName[sl]=Nastavitve požarnega zidu za napredne uporabnike
Table of Contents
s
STRING
TERM
.TERM
.Table of Contents
ClientExists — Checks whether a YCP client exists
boolean
ClientExists
( | name) ; |
string
name
;Execute — Special interface to the system agent. Not for general use.
any
Execute
( | path, | |
options) ; |
path
path
;
any
options
;GetEnvironmentEncoding — Returns the encoding code of the environment where YaST is started
string
GetEnvironmentEncoding
( | ) ; |
GetLanguage — Returns the current language code (without modifiers !)
string
GetLanguage
( | ) ; |
Read — Special interface to the system agent. Not for general use.
any
Read
( | path, | |
options) ; |
path
path
;
any
options
;SCRGetName — Get the name of a scr instance.
string
SCRGetName
( | handle) ; |
integer
handle
;SCROpen — Create a new scr instance.
integer
SCROpen
( | name, | |
check_version) ; |
string
name
;
boolean
check_version
;SCRSetDefault — Sets the default scr instance.
void
SCRSetDefault
( | handle) ; |
integer
handle
;SetLanguage — Selects the language for translate()
string
SetLanguage
( | language, | |
encoding) ; |
string
language
;
string
encoding
;Write — Special interface to the system agent. Not for general use.
boolean
Write
( | path, | |
options) ; |
path
path
;
any
options
;call — Executes a YCP client or a Y2 client component.
any
call
( | name, | |
arguments) ; |
string
name
;
list
arguments
;This implies * that the called YCP code has full access to all module status in the currently running YaST.
The modulename is temporarily changed to the name of the called script or a component.
In the example, WFM looks for the file YAST2HOME/clients/inst_mouse.ycp and executes it. If the client is not found, a Y2 client component is tried to be created.
Table of Contents
Table of Contents
Table of Contents
Table of Contents
add — Create a new list with a new element
list
add
( | LIST, | |
VAR) ; |
list
LIST
;
any
VAR
;change — Changes a list. Deprecated, use LIST[size(LIST)] = value.
list
change
( | LIST, | |
value) ; |
list
LIST
;
any
value
;contains — Checks if a list contains an element
boolean
contains
( | LIST, | |
ELEMENT) ; |
list
LIST
;
any
ELEMENT
;filter — Filters a List
list
filter
( | VAR, | |
LIST, | ||
EXPR) ; |
any
VAR
;
list
LIST
;
block<boolean>
EXPR
;find — Searches for the first occurence of a certain element in a list
any
find
( | VAR, | |
LIST, | ||
EXPR) ; |
any
VAR
;
list
LIST
;
block
EXPR
;flatten — Flattens List
list
flatten
( | LIST) ; |
list<list>
LIST
;foreach — Processes the content of a list
any
foreach
( | VAR, | |
LIST, | ||
EXPR) ; |
any
VAR
;
list
LIST
;
block
EXPR
;list::reduce — Reduces a list to a single value.
flex1
list::reduce
( | x, | |
y, | ||
list, | ||
expression) ; |
flex1
x
;
flex1
y
;
list<flex1>
list
;
block<flex1>
expression
;Apply expression cumulatively to the values of the list, from left to right, to reduce the list to a single value. See http://en.wikipedia.org/wiki/Reduce_(higher-order_function) for a detailed explanation.
In this version the initial value is the first value of the list. Thus the list must not be empty.
list::reduce — Reduces a list to a single value.
flex1
list::reduce
( | x, | |
y, | ||
value, | ||
list, | ||
expression) ; |
flex1
x
;
flex2
y
;
flex1
value
;
list<flex2>
list
;
block<flex1>
expression
;Apply expression cumulatively to the values of the list, from left to right, to reduce the list to a single value. See http://en.wikipedia.org/wiki/Reduce_(higher-order_function) for a detailed explanation.
In this version the initial value is explicitly provided. Thus the list can be empty. Also the return type can be different from the type of the list.
listmap — Maps an operation onto all elements of a list and thus creates a map.
map
listmap
( | VAR, | |
LIST, | ||
EXPR) ; |
any
VAR
;
list
LIST
;
block
EXPR
;lsort — Sort A List respecting locale
list
lsort
( | LIST) ; |
list
LIST
;maplist — Maps an operation onto all elements of a list and thus creates a new list.
list<any>
maplist
( | VAR, | |
LIST, | ||
EXPR) ; |
any
VAR
;
list<any>
LIST
;
block
EXPR
;merge — Merges two lists into one
list
merge
( | LIST1, | |
LIST2) ; |
list
LIST1
;
list
LIST2
; Interprets two lists as sets and returns a new list that has all elements of the first list and all of the second list. Identical elements are preserved. The order of the elements in the new list is preserved. Elements of l1
are prior to elements from l2
. nil
as either argument makes the result nil
too.
prepend — Prepends a list with a new element
list
prepend
( | LIST, | |
ELEMENT) ; |
list
LIST
;
any
ELEMENT
;remove — Removes element from a list
list
remove
( | LIST, | |
e) ; |
list
LIST
;
integer
e
;select — Selects a list element (deprecated, use LIST[INDEX]:DEFAULT)
any
select
( | LIST, | |
INDEX, | ||
DEFAULT) ; |
list
LIST
;
integer
INDEX
;
any
DEFAULT
;
Gets theINDEX
'th value of a list. The first value has the index 0. The call select([1,2,3], 1) thus returns 2. ReturnsDEFAULT
if the index is invalid or if the found entry has a different type than the default value. Functionality replaced by syntax: list numbers = [1, 2, 3, 4]; numbers[2]:nil -> 3 numbers[8]:5 -> 5
setcontains — Checks if a sorted list contains an element
boolean
setcontains
( | LIST, | |
ELEMENT) ; |
list
LIST
;
any
ELEMENT
;sort — Sorts a List according to the YCP builtin predicate
list
sort
( | LIST) ; |
list
LIST
;sort — Sort list using an expression
list
sort
( | x, | |
y, | ||
LIST, | ||
EXPR) ; |
any
x
;
any
y
;
list
LIST
;
block
EXPR
; Sorts the list LIST
. You have to specify an order on the list elements by naming formal variables x
and y
and specify an expression EXPR
that evaluates to a boolean value depending on x
and y
. Return true if x
>y
to sort the list ascending.
The comparison must be an irreflexive one, that is ">" instead of ">=".
It is because we no longer use bubblesort (yuck) but std::sort which requires a <ulink url="href="http://www.sgi.com/tech/stl/StrictWeakOrdering.html">strict weak ordering</ulink>.
splitstring — Split a string by delimiter
list<string>
splitstring
( | STR, | |
DELIM) ; |
string
STR
;
string
DELIM
; Splits STR
into sub-strings at delimiter chars DELIM
. the resulting pieces do not contain DELIM
If STR
starts with DELIM
, the first string in the result list is empty If STR
ends with DELIM
, the last string in the result list is empty. If STR
does not contain DELIM
, the result is a singleton list with STR
.
sublist — Extracts a sublist
list
sublist
( | LIST, | |
OFFSET, | ||
LENGTH) ; |
list
LIST
;
integer
OFFSET
;
integer
LENGTH
;tolist — Converts a value to a list (deprecated, use (list)VAR).
list
tolist
( | VAR) ; |
any
VAR
;union — Unions of lists
list
union
( | LIST1, | |
LIST2) ; |
list
LIST1
;
list
LIST2
; Interprets two lists as sets and returns a new list that has all elements of the first list and all of the second list. Identical elements are merged. The order of the elements in the new list is preserved. Elements of l1
are prior to elements from l2
. nil
as either argument makes the result nil
too.
WARNING: quadratic complexity so far
Table of Contents
add — Add a key/value pair to a map
map
add
( | MAP, | |
KEY, | ||
VALUE) ; |
map
MAP
;
any
KEY
;
any
VALUE
;change — Change element pair in a map. Deprecated, use MAP[KEY] = VALUE.
change
( | MAP, | |
KEY, | ||
VALUE) ; |
map
MAP
;
any
KEY
;
any
VALUE
;filter — Filter a Map
map
filter
( | KEY, | |
VALUE, | ||
MAP, | ||
EXPR) ; |
any
KEY
;
any
VALUE
;
map
MAP
;
blocl
EXPR
;foreach — Process the content of a map
map
foreach
( | KEY, | |
VALUE, | ||
MAP, | ||
EXPR) ; |
any
KEY
;
any
VALUE
;
map
MAP
;
any
EXPR
;haskey — Check if map has a certain key
boolean
haskey
( | MAP, | |
KEY) ; |
map
MAP
;
any
KEY
;lookup — Select a map element (deprecated, use MAP[KEY]:DEFAULT)
any
lookup
( | MAP, | |
KEY, | ||
DEFAULT) ; |
map
MAP
;
any
KEY
;
any
DEFAULT
;maplist — Maps an operation onto all elements key/value and create a list
list
maplist
( | KEY, | |
VALUE, | ||
MAP, | ||
EXPR) ; |
any
KEY
;
any
VALUE
;
map
MAP
;
block
EXPR
;mapmap — Maps an operation onto all key/value pairs of a map
map
mapmap
( | KEY, | |
VALUE, | ||
MAP, | ||
EXPR) ; |
any
KEY
;
any
VALUE
;
map
MAP
;
block
EXPR
; Maps an operation onto all key/value pairs of the map MAP
and thus creates a new map. For each key/value pair of the map MAP
the expression EXPR
is evaluated in a new block, where the variable KEY
is assigned to the key and VALUE
to the value of the pair. The result is the map of those evaluations.
The result of each evaluation must be a map with a single entry which will be added to the result map.
remove — Remove key/value pair from a map
map
remove
( | MAP, | |
KEY) ; |
map
MAP
;
any
KEY
;Table of Contents
getenv — Change or add an environment variable
string
getenv
( | name) ; |
string
name
;is — Checks whether a value is of a certain type
boolean
is
( | value, | |
type) ; |
any
value
;
type
type
;random — Random number generator.
integer
random
( | MAX) ; |
integer
MAX
;setenv — Change or add an environment variable
boolean
setenv
( | variable, | |
value, | ||
overwrite) ; |
string
variable
;
string
value
;
boolean
overwrite
;setenv — Change or add an environment variable
boolean
setenv
( | variable, | |
value) ; |
string
variable
;
string
value
;sformat — Format a String
string
sformat
( | FORM, | |
PAR1, | ||
PAR2, | ||
...) ; |
string
FORM
;
any
PAR1
;
any
PAR2
;
any
...
;sleep — Sleeps a number of milliseconds.
void
sleep
( | MILLISECONDS) ; |
integer
MILLISECONDS
;y2debug — Log a message to the y2log.
void
y2debug
( | FORMAT, | |
PAR1, | ||
PAR2, | ||
...) ; |
string
FORMAT
;
any
PAR1
;
any
PAR2
;
any
...
;y2error — Log an error to the y2log.
void
y2error
( | FORMAT, | |
PAR1, | ||
PAR2, | ||
...) ; |
string
FORMAT
;
any
PAR1
;
any
PAR2
;
any
...
;y2internal — Log an internal message to the y2log.
void
y2internal
( | FORMAT, | |
PAR1, | ||
PAR2, | ||
...) ; |
string
FORMAT
;
any
PAR1
;
any
PAR2
;
any
...
;y2milestone — Log a milestone to the y2log.
void
y2milestone
( | FORMAT, | |
PAR1, | ||
PAR2, | ||
...) ; |
string
FORMAT
;
any
PAR1
;
any
PAR2
;
any
...
;y2security — Log a security message to the y2log.
void
y2security
( | FORMAT, | |
PAR1, | ||
PAR2, | ||
...) ; |
string
FORMAT
;
any
PAR1
;
any
PAR2
;
any
...
;y2useritem — Log an user-level system message to the y2changes
void
y2useritem
( | FORMAT, | |
PAR1, | ||
PAR2, | ||
...) ; |
string
FORMAT
;
any
PAR1
;
any
PAR2
;
any
...
;y2usernote — Log an user-level addional message to the y2changes
void
y2usernote
( | FORMAT, | |
PAR1, | ||
PAR2, | ||
...) ; |
string
FORMAT
;
any
PAR1
;
any
PAR2
;
any
...
;Table of Contents
Table of Contents
s
STRING
cryptbigcrypt — Encrypts a string using bigcrypt
string
cryptbigcrypt
( | UNENCRYPTED) ; |
string
UNENCRYPTED
;cryptblowfish — Encrypts a string with blowfish
string
cryptblowfish
( | UNENCRYPTED) ; |
string
UNENCRYPTED
;cryptmd5 — Encrypts a string using md5
string
cryptmd5
( | UNENCRYPTED) ; |
string
UNENCRYPTED
;deletechars — Removes all characters from a string
string
deletechars
( | STRING, | |
REMOVE) ; |
string
STRING
;
string
REMOVE
;dgettext — Translates the text using the given text domain
string
dgettext
( | textdomain, | |
text) ; |
string
textdomain
;
string
text
;dngettext — Translates the text using a locale-aware plural form handling
string
dngettext
( | textdomain, | |
singular, | ||
plural, | ||
value) ; |
string
textdomain
;
string
singular
;
string
plural
;
integer
value
;dpgettext — Translates the text using the given text domain and path
string
dpgettext
( | textdomain, | |
dirname, | ||
text) ; |
string
textdomain
;
string
dirname
;
string
text
;filterchars — Filters characters out of a String
string
filterchars
( | STRING, | |
CHARS) ; |
string
STRING
;
string
CHARS
;find — Returns position of a substring
integer
find
( | STRING1, | |
STRING2) ; |
string
STRING1
;
string
STRING2
;findfirstnotof — Searches string for the first non matching chars
integer
findfirstnotof
( | STRING, | |
CHARS) ; |
string
STRING
;
string
CHARS
;findfirstof — Finds position of the first matching characters in string
integer
findfirstof
( | STRING, | |
CHARS) ; |
string
STRING
;
string
CHARS
;findlastnotof — Searches the last element of string that doesn't match
integer
findlastnotof
( | STRING, | |
CHARS) ; |
string
STRING
;
string
CHARS
;findlastof — Searches string for the last match
integer
findlastof
( | STRING, | |
CHARS) ; |
string
STRING
;
string
CHARS
;issubstring — searches for a specific string within another string
boolean
issubstring
( | s, | |
substring) ; |
string
s
;
string
substring
;lsubstring — Extracts a substring in UTF-8 encoded string
string
lsubstring
( | STRING, | |
OFFSET, | ||
LENGTH) ; |
string
STRING
;
integer
OFFSET
;
integer
LENGTH
;lsubstring — Extracts a substring in UTF-8 encoded string
string
lsubstring
( | STRING, | |
OFFSET, | ||
LENGTH) ; |
string
STRING
;
integer
OFFSET
;
integer
LENGTH
;mergestring — Joins list elements with a string
string
mergestring
( | PIECES, | |
GLUE) ; |
list<string>
PIECES
;
string
GLUE
;Returns a string containing a string representation of all the list elements in the same order, with the glue string between each element.
List elements which are not of type strings are ignored.
mergestring (["", "abc", "dev", "ghi"], "/") -> "/abc/dev/ghi" mergestring (["abc", "dev", "ghi", ""], "/") -> "abc/dev/ghi/" mergestring ([1, "a", 3], ".") -> "a" mergestring (["1", "a", "3"], ".") -> "a" mergestring ([], ".") -> "1.a.3" mergestring (["abc", "dev", "ghi"], "") -> "abcdevghi" mergestring (["abc", "dev", "ghi"], "123") -> "abc123dev123ghi"
regexpmatch — Searches a string for a POSIX Extended Regular Expression match.
boolean
regexpmatch
( | INPUT, | |
PATTERN) ; |
string
INPUT
;
string
PATTERN
;regexppos — Returns a pair with position and length of the first match.
list
regexppos
( | INPUT, | |
PATTERN) ; |
string
INPUT
;
string
PATTERN
;regexpsub — Regex Substitution
string
regexpsub
( | INPUT, | |
PATTERN, | ||
OUTPUT) ; |
string
INPUT
;
string
PATTERN
;
string
OUTPUT
;regexptokenize — Regex tokenize
list
regexptokenize
( | INPUT, | |
PATTERN) ; |
string
INPUT
;
string
PATTERN
;Searches a string for a POSIX Extended Regular Expression match and returns a list of the matched subexpressions
If the pattern does not match, the list is empty. Otherwise the list contains then matchted subexpressions for each pair of parenthesize in pattern.
If the pattern is invalid, 'nil' is returned.
search — Returns position of a substring
integer
search
( | STRING1, | |
STRING2) ; |
string
STRING1
;
string
STRING2
;substring — Returns part of a string
string
substring
( | STRING, | |
OFFSET, | ||
LENGTH) ; |
string
STRING
;
integer
OFFSET
;
integer
LENGTH
;substring — Extracts a substring
string
substring
( | STRING, | |
OFFSET, | ||
LENGTH) ; |
string
STRING
;
integer
OFFSET
;
integer
LENGTH
;timestring — Returns time string
string
timestring
( | FORMAT, | |
TIME, | ||
UTC) ; |
string
FORMAT
;
integer
TIME
;
boolean
UTC
;toascii — Returns characters below 0x7F included in STRING
string
toascii
( | STRING) ; |
string
STRING
;tohexstring — Converts an integer to a hexadecimal string.
string
tohexstring
( | number) ; |
integer
number
;tohexstring — Converts an integer to a hexadecimal string.
string
tohexstring
( | number, | |
width) ; |
integer
number
;
integer
width
;tolower — Makes a string lowercase
string
tolower
( | s) ; |
string
s
;Table of Contents
TERM
.TERM
.add — Add value to term
term
add
( | TERM, | |
VALUE) ; |
term
TERM
;
any
VALUE
;remove — Remove item from term
term
remove
( | TERM, | |
i) ; |
term
TERM
;
integer
i
;Remove the i'th value from a term. The first value has the index 1 (!). (The index counting is for compatibility reasons with the 'old' remove which allowed 'remove(`term(1,2,3), 0) = [1,2,3]' Use 'argsof (term) -> list' for this kind of transformation.)
The yast2-core version < 2.17.16 returns nil if the index is invalid. This behavior has changed in version 2.17.16 to return unchanged term.
select, (deprecated,, use, TERM[ITEM]:DEFAULT) — Select item from term
select
( | TERM, | |
ITEM, | ||
DEFAULT) ; |
term
TERM
;
integer
ITEM
;
any
DEFAULT
;
(deprecated,
( | TERM, | |
ITEM, | ||
DEFAULT) ; |
term
TERM
;
integer
ITEM
;
any
DEFAULT
;
use
( | TERM, | |
ITEM, | ||
DEFAULT) ; |
term
TERM
;
integer
ITEM
;
any
DEFAULT
;
TERM[ITEM]:DEFAULT)
( | TERM, | |
ITEM, | ||
DEFAULT) ; |
term
TERM
;
integer
ITEM
;
any
DEFAULT
;
Gets thei
'th value of the termt
. The first value has the index 0. The callselect ([1, 2, 3], 1)
thus returns 2. Returns thedefault
if the index is invalid or the found value has a diffetent type thatdefault
. Functionality replaced with syntax: term a = `VBox(`VSpacing(2), `Label("string"), `VSpacing(2)); a[1]:`Empty() -> `Label ("string") a[9]:`Empty() -> `Empty ()
size — Returns the number of arguments of the term TERM
.
integer
size
( | TERM) ; |
term
TERM
;Table of Contents
Table of Contents
UI::UserInput — Waits for user input and returns a widget ID.
UI::UserInput() waits for the user to do some input. Normally this means it waits until the user clicks on a push button.
Widgets that have the notify option set can also cause UserInput() to return - i.e. to resume the control flow in the YCP code with the next statement after UserInput().
As long as the user does not do any such action, UserInput() waits, i.e. execution of the YCP code stops in UserInput(). In particular, entering text in input fields (TextEntry widgets) or selecting an entry in a list (SelectionBox widget) does not make UserInput() continue unless the respective widget has the notify option set.
UserInput() returns the ID of the widget that caused it to return. This is usually a button ID. It does not return any text entered etc.; use UI::QueryWidget() to retrieve the contents of the dialog's widgets.
Such a widget ID can be of any valid YCP type, but using simple types like symbol, string or maybe integer is strongly recommended.
Although it is technically still possible, using complex data types like map, list or even term (which might even contain YCP code to be executed with eval()) is discouraged. Support for this may be dropped without notice in future versions.
Since it depends on exactly what types the YCP application developer choses for his widgets, UserInput()'s return type is any. You may safely use a variable of the actual type you are using (usually symbol or string).
// UserInput.ycp // // Example for common usage of UI::UserInput() { // Build dialog with two input fields and three buttons. // // Output goes to the log file: ~/.y2log for normal users // or /var/log/YaST2/y2log for root. string name = "Tux"; string addr = "Antarctica"; UI::OpenDialog( `VBox( `TextEntry(`id(`name), "&Name:", name ), `TextEntry(`id(`addr), "&Address:", addr ), `HBox( `PushButton(`id(`ok ), "&OK" ), `PushButton(`id(`cancel ), "&Cancel" ), `PushButton(`id(`help ), "&Help" ) ) ) ); symbol widget_id = nil; // All widget IDs used here are symbols // Event loop repeat { widget_id = UI::UserInput(); if ( widget_id == `ok ) { // process "OK" button y2milestone( "OK button activated" ); // Retrieve widget contents name = UI::QueryWidget(`id(`name ), `Value ); addr = UI::QueryWidget(`id(`addr ), `Value ); } else if ( widget_id == `cancel ) { // process "Cancel" buttton // or window manager close button (this also returns `cancel) y2milestone( "Cancel button activated" ); } else if ( widget_id == `help ) { // process "Help" button y2milestone( "Help button activated" ); } // No other "else" branch necessary: None of the TextEntry widget has // the `notify option set, so none of them can make UserInput() return. } until ( widget_id == `ok || widget_id == `cancel ); // Close the dialog - but only after retrieving all information that may // still be stored only in its widgets: QueryWidget() works only for // widgets that are still on the screen! UI::CloseDialog(); // Dump the values entered into the log file y2milestone( "Name: %1 Address: %2", name, addr ); }
UI::PollInput — Checks for pending user input. Does not wait. Returns a widget ID or nil if no input is available.
PollInput() is very much like UserInput(), but it doesn't wait. It only checks if there is a user event pending - the user may have clicked on a button since the last call to PollInput() or UserInput().
If there is one, the ID of the widget (usually a button unless other widgets have the notify option set) is returned. If there is none, nil (the YCP value for "nothing", "invalid") is returned.
Use PollInput() to check if the user wishes to abort operations of long duration that are performed in a loop. Notice that PollInput() will result in a "busy wait", so don't simply use it everywhere instead of UserInput().
Notice there is also TimeoutUserInput() and WaitForEvent() that both accept a millisecond timeout argument.
// PollInput.ycp // // Example for common usage of UI::PollInput() { // Build dialog with two labels and a "stop" button. integer count = 0; integer count_max = 10000; UI::OpenDialog( `VBox( `Label( "Calculating..." ), `Label(`id(`count ), sformat( "%1 of %2", count, count_max ) ), `PushButton(`id(`stop), "&Stop" ) ) ); any widget_id = nil; // Event loop repeat { widget_id = UI::PollInput(); // Simulate heavy calculation sleep(200); // milliseconds // Update screen to show that the program is really busy count = count + 1; UI::ChangeWidget(`id(`count), `Value, sformat( "%1 of %2", count, count_max ) ); UI::RecalcLayout(); // Might be necessary when the label becomes wider } until ( widget_id == `stop || count >= count_max ); UI::CloseDialog(); }
UI::TimeoutUserInput — Waits for user input and returns a widget ID. Returns ID `timeout if no input is available for timeout milliseconds.
TimeoutUserInput() is very much like UserInput(), but it returns a predefined ID `timeout if no user input is available within the specified (millisecond) timeout.
This is useful if there is a reasonable default action that should be done in case of a timeout - for example, for popup messages that are not important enough to completely halt a longer operation forever.
User interface style hint: Use this with caution. It is perfectly OK to use timeouts for informational messages that are not critical in any way ("Settings are written", "Rebooting the newly installed kernel"), but definitely not if there are several alternatives the user can choose from. As a general rule of thumb, if a dialog contains just an "OK" button and nothing else, TimeoutUserInput() is appropriate. If there are more buttons, chances are that the default action will cause disaster for some users.
Remember, timeouts are nearly always a desperate means. They are always both too short and too long at the same time: Too short for users who know what message will come and too long for users who left to get some coffee while the machine is busy.
Another possible use of TimeoutUserInput() would be to periodically update the screen with data that keep changing (time etc.) while waiting for user input.
// TimeoutUserInput.ycp // // Example for common usage of UI::TimeoutUserInput() { // Build dialog with two labels and an "OK" button. integer countdown_sec = 30; integer interval_millisec = 200; integer countdown = countdown_sec * 1000 / interval_millisec; UI::OpenDialog( `VBox( `Label( "Rebooting Planet Earth..." ), `Label(`id(`seconds ), sformat( "%1", countdown_sec ) ), `PushButton(`id(`ok ), `opt(`default ), "&OK" ) ) ); any id = nil; // Event loop repeat { id = UI::TimeoutUserInput( interval_millisec ); if ( id == `timeout ) { // Periodic screen update countdown = countdown - 1; integer seconds_left = countdown * interval_millisec / 1000; UI::ChangeWidget(`id(`seconds ), `Value, sformat( "%1", seconds_left ) ); } } until ( id == `ok || countdown <= 0 ); UI::CloseDialog(); }
UI::WaitForEvent — Waits for user input and returns an event map. Returns ID `timeout if no input is available for timeout milliseconds.
WaitForEvent() is an extended combination of UserInput() and TimeoutUserInput(): It waits until user input is available or until the (millisecond) timeout is expired. It returns an event map rather than just a simple ID.
In the case of timeout, it returns a map with a timeout event.
The timeout argument is optional. If it isn't specified, WaitForEvent() (like UserInput()) keeps waiting until user input is available.
Use WaitForEvent() for more fine-grained control of events. It is useful primarily to tell the difference between different types of events of the same widget - for example, if different actions should be performed upon selecting an item in a SelectionBox or a Table widget. Notice that you still need the notify option to get those events in the first place.
On the downside, using WaitForEvent() means accessing the ID that caused an event requires a map lookup.
Notice that you still need UI::QueryWidget() to get the contents of the widget that caused the event. In the general case you'll need to QueryWidget most widgets on-screen anyway so delivering that one value along with the event wouldn't help too much.
Important: Don't blindly rely on getting each and every individual event that you think should come. The UI keeps track of only one pending event (which is usually the last one that occured). If many events occur between individual WaitForEvent() calls, all but the last will be lost. Read the introduction for the answer why. It is relatively easy to programm defensively in a way that losing individual events doesn't matter: Also use QueryWidget() to get the status of all your widgets. Don't keep redundant information about widget status in your code. Ask them. Always.
// WaitForEvent.ycp // // Example for common usage of UI::WaitForEvent() { // Build dialog with a selection box and some buttons. // // Output goes to the log file: ~/.y2log for normal users // or /var/log/YaST2/y2log for root. integer timeout_millisec = 20 * 1000; UI::OpenDialog( `VBox( `SelectionBox(`id(`pizza ), `opt(`notify, `immediate ), "Select your Pi&zza:", [ `item(`id(`napoli ), "Napoli" ), `item(`id(`funghi ), "Funghi" ), `item(`id(`salami ), "Salami" ), `item(`id(`prociutto ), "Prosciutto" ), `item(`id(`stagioni ), "Quattro Stagioni" ), `item(`id(`chef ), "A la Chef", true ) ] ), `HBox( `PushButton(`id(`ok ), "&OK" ), `PushButton(`id(`cancel ), "&Cancel" ), `HSpacing(), `PushButton(`id(`details ), "&Details..." ) ) ) ); map event = $[]; any id = nil; // Event loop repeat { event = UI::WaitForEvent( timeout_millisec ); id = event["ID"]:nil; // We'll need this often - cache it if ( id == `pizza ) { if ( event["EventReason"]:nil == "Activated" ) { // Handle pizza "activate" (double click or space pressed) y2milestone( "Pizza activated" ); id = `details; // Handle as if "Details" button were clicked } else if ( event["EventReason"]:nil == "SelectionChanged" ) { // Handle pizza selection change y2milestone( "Pizza selected" ); } } if ( id == `details ) { y2milestone( "Show details" ); } if ( id == `timeout ) { // Handle timeout y2milestone( "Timeout detected by ID" ); } if ( event["EventType"]:nil == "TimeoutEvent" ) // Equivalent { // Handle timeout y2milestone( "Timeout detected by event type" ); // Open a popup dialog UI::OpenDialog( `VBox( `Label( "Not hungry?" ), `PushButton(`opt(`default ), "&OK" ) ) ); UI::TimeoutUserInput( 10 * 1000 ); // Automatically close after 10 seconds UI::CloseDialog(); } } until ( id == `ok || id == `cancel ); UI::CloseDialog(); }
Table of Contents
AskForExistingDirectory — Ask user for existing directory
string
AskForExistingDirectory
( | startDir, | |
headline) ; |
string
startDir
;
string
headline
;AskForExistingFile — Ask user for existing file
string
AskForExistingFile
( | startWith, | |
filter, | ||
headline) ; |
string
startWith
;
string
filter
;
string
headline
;AskForSaveFileName — Ask user for a file to save data to.
string
AskForSaveFileName
( | startWith, | |
filter, | ||
headline) ; |
string
startWith
;
string
filter
;
string
headline
;BusyCursor — Sets the mouse cursor to the busy cursor
void
BusyCursor
( | ) ; |
Sets the mouse cursor to the busy cursor, if the UI supports such a feature.
This should normally not be necessary. The UI handles mouse cursors itself: When input is possible (i.e. inside UserInput() ), there is automatically a normal cursor, otherwise, there is the busy cursor. Override this at your own risk.
ChangeWidget — Changes widget contents
boolean
ChangeWidget
( | widgetId, | |
property, | ||
newValue) ; |
symbol
widgetId
;
symbol
property
;
any
newValue
;CheckShortcuts — Performs an explicit shortcut check after postponing shortcut checks.
void
CheckShortcuts
( | ) ; |
GetLanguage — Gets Language
string
GetLanguage
( | strip_encoding) ; |
boolean
strip_encoding
; Retrieves the current language setting from of the user interface. Since YaST2 is a client server architecture, we distinguish between the language setting of the user interface and that of the configuration modules. If the module or the translator wants to know which language the user currently uses, it can call GetLanguage
. The return value is an ISO language code, such as "de" or "de_DE".
If "strip_encoding" is set to "true", all encoding or similar information is cut off, i.e. everything from the first "." or "@" on. Otherwise the current contents of the "LANG" environment variable is returned (which very likely ends with ".UTF-8" since this is the encoding YaST2 uses internally).
GetProductName — Gets Product Name
string
GetProductName
( | ) ; |
Glyph — Returns a special character (a 'glyph')
string
Glyph
( | glyph) ; |
symbol
glyph
;Returns a special character (a 'glyph') according to the symbol specified.
Not all UIs may be capable of displaying every glyph; if a specific UI doesn't support it, a textual representation (probably in plain ASCII) will be returned.
This is also why there is only a limited number of predefined glyphs: An ASCII equivalent is required which is sometimes hard to find for some characters defined in Unicode / UTF-8.
Please note the value returned may consist of more than one character; for example, Glyph( `ArrowRight ) may return something like "->".
If an unknown glyph symbol is specified, 'nil' is returned.
HasSpecialWidget — Checks for support of a special widget type.
HasSpecialWidget
( | ) ; |
Checks for support of a special widget type. Use this prior to creating a widget of this kind. Do not use this to check for ordinary widgets like PushButton etc. - just the widgets where the widget documentation explicitly states it is an optional widget not supported by all UIs.
Returns true if the UI supports the special widget and false if not.
NormalCursor — Sets the mouse cursor to the normal cursor
void
NormalCursor
( | ) ; |
Sets the mouse cursor to the normal cursor (after BusyCursor), if the UI supports such a feature.
This should normally not be necessary. The UI handles mouse cursors itself: When input is possible (i.e. inside UserInput() ), there is automatically a normal cursor, otherwise, there is the busy cursor. Override this at your own risk.
OpenDialog — Opens a Dialog with options
boolean
OpenDialog
( | options, | |
widget) ; |
term
options
;
term
widget
; Same as the OpenDialog with one argument, but you can specify options with a term of the form `opt
.
The `mainDialog
option creates a "main window" dialog: The dialog will get a large "default size". In the Qt UI, this typically means 800x600 pixels large (or using a -geometry command line argument if present) or full screen. In the NCurses UI, this is always full screen.
`defaultsize
is an alias for `mainDialog
.
`wizardDialog
is a main dialog that will contain a wizard widget. For UIs that don't support this kind of specialized dialog, this is equivalent to `mainDialog
-- see also the HasWizardDialogSupport
entry of the map returned by UI::GetDisplayInfo()
.
The `warncolor
option displays the entire dialog in a bright warning color.
The `infocolor
option displays the dialog in a color scheme that is distinct from the normal colors, but not as bright as warncolor.
The `decorated
option is now obsolete, but still accepted to keep old code working.
The `centered
option is now obsolete, but still accepted to keep old code working.
PlayMacro — Plays a recorded macro
void
PlayMacro
( | macroFileName) ; |
string
macroFileName
;PollInput — Poll Input
any
PollInput
( | ) ; |
Doesn't wait but just looks if the user has clicked some button, has closed the window or has activated some widget that has the `notify
option set. Returns the id of the widget that has been selected or `cancel
if the user selected the implicit cancel button (for example he closes the window). Returns nil if no user input has occured.
PostponeShortcutCheck — Postpones Shortcut Check
void
PostponeShortcutCheck
( | ) ; |
Postpone keyboard shortcut checking during multiple changes to a dialog.
Normally, keyboard shortcuts are checked automatically when a dialog is created or changed. This can lead to confusion, however, when multiple changes to a dialog (repeated ReplaceWidget() calls) cause unwanted intermediate states that may result in shortcut conflicts while the dialog is not final yet. Use this function to postpone this checking until all changes to the dialog are done and then explicitly check with CheckShortcuts()
. Do this before the next call to UserInput()
or PollInput()
to make sure the dialog doesn't change "on the fly" while the user tries to use one of those shortcuts.
The next call to UserInput()
or PollInput()
will automatically perform that check if it hasn't happened yet, any an error will be issued into the log file.
Use only when really necessary. The automatic should do well in most cases.
The normal sequence looks like this:
PostponeShortcutChecks(); ReplaceWidget( ... ); ReplaceWidget( ... ); ... ReplaceWidget( ... ); CheckShortcuts(); ... UserInput();
QueryWidget — Queries Widget contents
any
QueryWidget
( | widgetId, | |
property) ; |
symbol
widgetId
;
symbol|term
property
; Queries a property of a widget of the topmost dialog. For example in order to query the current text of an InputField with id `name, you write QueryWidget( `id(`name), `Value )
. In some cases the propery can be given as term in order to further specify it. An example is QueryWidget( `id( `table ), `Item( 17 ) )
for a table where you query a certain item.
Recode — Recodes encoding of string from or to "UTF-8" encoding.
any
Recode
( | fromEncoding, | |
toEncoding, | ||
text) ; |
string
fromEncoding
;
string
toEncoding
;
string
text
;RecordMacro — Records Macro into a file
void
RecordMacro
( | macroFileName) ; |
string
macroFileName
;RedrawScreen — Redraws the screen
void
RedrawScreen
( | ) ; |
Redraws the screen after it very likely has become garbled by some other output.
This should normally not be necessary: The (specific) UI redraws the screen automatically whenever required. Under rare circumstances, however, the screen might have changes due to circumstances beyond the UI's control: For text based UIs, for example, system commands that cause output to every tty might make this necessary. Call this in the YCP code after such a command.
RunInTerminal — runs external program in the same terminal
RunInTerminal
( | external_program) ; |
string
external_program
;RunPkgSelection — Initializes and run the PackageSelector widget
any
RunPkgSelection
( | pkgSelId) ; |
any
pkgSelId
;SetConsoleFont — Sets Console Font
void
SetConsoleFont
( | console_magic, | |
font, | ||
screen_map, | ||
unicode_map, | ||
language) ; |
string
console_magic
;
string
font
;
string
screen_map
;
string
unicode_map
;
string
language
;SetFocus — Sets Focus to the specified widget
boolean
SetFocus
( | widgetId) ; |
symbol
widgetId
;SetFunctionKeys — Sets the (default) function keys for a number of buttons.
void
SetFunctionKeys
( | fkeys) ; |
map
fkeys
;This function receives a map with button labels and the respective function key number that should be used if on other `opt( `key_F.. ) is specified.
Any keyboard shortcuts in those labels are silently ignored so this is safe to use even if the UI's internal shortcut manager rearranges shortcuts.
Each call to this function overwrites the data of any previous calls.
SetLanguage — Sets the language of the UI
void
SetLanguage
( | lang, | |
encoding) ; |
string
lang
;
string
encoding
;SetProductName — Sets Product Name
void
SetProductName
( | prod) ; |
string
prod
;Sets the current product name ("SuSE Linux", "United Linux", etc.) for displaying in dialogs and in RichText widgets (for help text) with the RichText &product; macro.
This product name should be concise and meaningful to the user and not cluttered with detailed version information. Don't use something like "SuSE Linux 12.3-i786 Professional". Use something like "SuSE Linux" instead.
TextMode — Check if the UI is running in text (NCurses) mode.
true
TextMode
( | ) ; |
This checks if a text mode UI (NCurses) is currently running. Please notice that this is almost always bad style. The purpose of the YaST2 UI is to have an abstraction layer to avoid this kind of check.
When you find yourself using this built-in, please check if there is really no other way. In particular, NEVER rely on this check to make any assumptions on the UI's capabilities. Rather, use the map entries from UI::GetDisplayInfo() or UI::HasSpecialWidget().
If you feel you must make a difference between text mode and GUI mode for a dialog's layout, please check if that dialog is not simply overcrowded and thus should be redesigned - it is very likely that it is also overly complex.
Also keep in mind that layout units are UI independent; a spacing should consume about the same amount of space in all UIs. A Qt UI 800x600 main window corresponds to 80x25 layout units, i.e. the typical NCurses terminal size.
TimeoutUserInput — User Input with Timeout
any
TimeoutUserInput
( | timeout_millisec) ; |
integer
timeout_millisec
; Waits for the user to click some button, close the window or activate some widget that has the `notify
option set or until the specified timeout is expired. The return value is the id of the widget that has been selected or `cancel
if the user selected the implicit cancel button (for example he closes the window). Upon timeout, `timeout
is returned.
WidgetExists — Checks whether or not a widget with the given ID currently exists
boolean
WidgetExists
( | widgetId) ; |
symbol
widgetId
;WizardCommand — Runs a wizard command
boolean
WizardCommand
( | wizardCommand) ; |
term
wizardCommand
;Issues a command to a wizard widget with ID 'wizardId'.
This builtin is not for general use. Use the Wizard.ycp module instead.
For available wizard commands see file YCPWizardCommandParser.cc . If the current UI does not provide a wizard widget, 'false' is returned. It is safe to call this even for UIs that don't provide a wizard widget. In this case, all calls to this builtin simply return 'false'.
Table of Contents
AAA_All-Widgets — Generic options for all widgets
AAA_All-Widgets
( | ) ; |
notify
Make UserInput() return on any action in this widget. Normally UserInput() returns only when a button is clicked; with this option on you can make it return for other events, too, e.g. when the user selects an item in a SelectionBox ( if `opt( `notify ) is set for that SelectionBox ). Only widgets with this option set are affected.
disabled
Set this widget insensitive, i.e. disable any user interaction.
The widget will show this state by being greyed out (depending on the specific UI).
hstretch
Make this widget stretchable in the horizontal dimension.
vstretch
Make this widget stretchable in the vertical dimension.
hvstretch
Make this widget stretchable in both dimensions.
autoShortcut
Automatically choose a keyboard shortcut for this widget and don't complain in the log file about the missing shortcut. Don't use this regularly for all widgets - manually chosen keyboard shortcuts are almost always better than those automatically assigned. Refer to the style guide for details. This option is intended used for automatically generated data, e.g., RadioButtons for software selections that come from file or from some other data base.
key_F1
(NCurses only) activate this widget with the F1 key
key_F2
(NCurses only) activate this widget with the F2 key
key_Fxx
(NCurses only) activate this widget with the Fxx key
key_F24
(NCurses only) activate this widget with the F24 key
key_none
(NCurses only) no function key for this widget
keyEvents
(NCurses only) Make UserInput() / WaitForEvent() return on keypresses within this widget. Exactly which keys trigger such a key event is UI specific. This is not for general use.
This is not a widget for general usage, this is just a placeholder for descriptions of options that all widgets have in common.
Use them for any widget whenever it makes sense.
{ // (Minimalistic) Demo for automatically generated shortcuts. // // See 'AutoShortcut2.ycp' for a more realistic example. // // Please note this is _not_ how this option is meant to be used: // It is intended for automatically generated data, not for fixed widgets. // If you know your widget label at this point, manually add a keyboard // shortcut; this will almost always be much better than anything what can // be automatically generated. // // // There shouldn't be any complaints about shortcuts in the log file when this is started. UI::OpenDialog( `VBox( `RadioButtonGroup( `Frame( "Software Selection", `HVSquash( `VBox( `Left( `RadioButton(`opt(`autoShortcut), "Minimum System" ) ), `Left( `RadioButton(`opt(`autoShortcut), "Minimum X11 System" ) ), `Left( `RadioButton(`opt(`autoShortcut), "Gnome System" ) ), `Left( `RadioButton(`opt(`autoShortcut), "Default (KDE)" ) ), `Left( `RadioButton(`opt(`autoShortcut), "Default + Office" ) ), `Left( `RadioButton(`opt(`autoShortcut), "Almost Everything" ) ) ) ) ) ), `PushButton( "&OK" ) ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Demo for automatically generated shortcuts. // // This is a more realistic example - it points out how the `autoShortcut // option is intended to be used. See 'AutoShortcut1.ycp' for a simpler example. // // There shouldn't be any complaints about shortcuts in the log file when this is started. list sw_selections = [ "Minimum System", "Minimum X11 System", "Gnome System", "Default (KDE)", "Office System (KDE Based)", "Almost Everything", ]; term radio_box = `VBox(); foreach ( `sel, sw_selections, ``{ radio_box = add( radio_box, `Left( `RadioButton(`opt(`autoShortcut), sel ) ) ); } ); y2milestone( "radio_box: %1", radio_box ); UI::OpenDialog( `VBox( `RadioButtonGroup( `Frame( "Software Selection", `HVSquash( radio_box ) ) ), `PushButton( "&OK" ) ) ); UI::UserInput(); UI::CloseDialog(); }
BarGraph — Horizontal bar graph (optional widget)
BarGraph
( | values, | |
labels) ; |
list
values
;
list
labels
;labels
the labels for each part; use "%1" to include the current numeric value. May include newlines.
if ( HasSpecialWidget( `BarGraph ) {... `BarGraph( [ 450, 100, 700 ], [ "Windows used\n%1 MB", "Windows free\n%1 MB", "Linux\n%1 MB" ] )
// BarGraph1.ycp { if ( ! UI::HasSpecialWidget(`BarGraph) ) { UI::OpenDialog( `VBox( `Label("Error: This UI doesn't support the BarGraph widget!"), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); return; } UI::OpenDialog( `VBox( `HSpacing( 60 ), // wider default size `BarGraph( [450, 100, 700] ), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ if ( ! UI::HasSpecialWidget(`BarGraph) ) { UI::OpenDialog( `VBox( `Label("Error: This UI doesn't support the BarGraph widget!"), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); return; } UI::OpenDialog( `VBox( `HSpacing(80), // force width `HBox(`opt(`debugLayout), `BarGraph( `opt(`vstretch), [600, 350, 800], [ "Windows\nused\n%1 MB", "Windows\nfree\n%1 MB", "Linux\n%1 MB" ] ) ), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Advanced BarGraph example: // // Create a dialog with a BarGraph with a number of segments // and a "+" and a "-" button for each segment. { // Check for availability of the BarGraph widget - this is necessary since // this is an optional widget that not all UIs need to support. if ( ! UI::HasSpecialWidget(`BarGraph) ) { // Pop up error message if the BarGraph widget is not available UI::OpenDialog( `VBox( `Label("Error: This UI doesn't support the BarGraph widget!"), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); return; } // list values = [ 100, 200, 300, 150, 250, 120, 200, 120 ]; list<integer> values = [ 100, 100, 100, 100, 100, 100, 100, 100 ]; integer inc = 10; // increment / decrement for each button press // Create the main dialog: // // One BarGraph at the top, below that two rows of equal sized (thus the // weights) buttons, below that a "close" button. // // The "+" / "-" -buttons use an integer value as their ID which can be // used to point to the index of the value to be changed. If the ID is // negative it means subtract rather than add. term plus_buttons = `HBox(); term minus_buttons = `HBox(); integer i = 1; foreach( `val, values, ``{ plus_buttons = add( plus_buttons, `HWeight( 1, `PushButton(`id( i), "+" ) ) ); minus_buttons = add( minus_buttons, `HWeight( 1, `PushButton(`id(-i), "-" ) ) ); i = i+1; }); UI::OpenDialog( `VBox( `BarGraph(`id(`bar), values ), plus_buttons, minus_buttons, `PushButton(`id(`close), `opt(`default), "&Close") ) ); // Event processing loop - left only via the "close" button // or the window manager close button / function. any button_id = nil; do { button_id = UI::UserInput(); // wait for button click if ( button_id != `close && button_id != `cancel ) { integer sign = 1; if ( button_id < 0 ) { sign = -1; button_id = -(integer)button_id; } // Loop over the values. Increment the value corresponding to the // clicked button, decrement all others as to maintain the total // sum of all values - or vice versa for negative button IDs // (i.e. "-" buttons). list<integer> new_values = []; integer i = 0; while ( i < size( values ) ) { integer old_val = values[i]:0; if ( i+1 == button_id ) new_values = add( new_values, old_val + (sign*inc) ); else new_values = add( new_values, old_val + (-sign *(inc/( size(values)-1))) ); i = i+1; } values = new_values; UI::ChangeWidget(`id(`bar), `Values, values ); } } while ( button_id != `close && button_id != `cancel ); UI::CloseDialog(); }
BusyIndicator — Graphical busy indicator
BusyIndicator
( | label, | |
timeout) ; |
string
label
;
integer
timeout
;timeout
the timeout in milliseconds until busy indicator changes to stalled state, 1000ms by default
A busy indicator is a bar with a label that gives feedback to the user that a task is in progress and the user has to wait. It is similar to a progress bar. The difference is that a busy indicator can be used when the total number of steps is not known before the action starts. You have to send keep alive messages by setting alive to true every now and then, otherwise the busy indicator will change to stalled state.
There are some limitations due to technical reasons in ncurses ui: Only one BusyIndicator widget works at the same time. The BusyIndicator widget cannot be used together with an UserInput widget. Please use the TimeoutUserInput widget in a loop instead.
// Simple BusyIndicator example { integer timeout = 3000; // in milisenconds UI::OpenDialog( `VBox( `BusyIndicator(`id(`busy), "Sample busy indicator", timeout ), `PushButton(`id(`alive), "send &tick"), `Right(`PushButton(`id(`close), "&Close" ) ) ) ); while ( true ) { symbol button = (symbol) UI::TimeoutUserInput(100); if ( button == `alive ) { UI::ChangeWidget(`id(`busy), `Alive, true); } else if ( button == `close ) break; } UI::CloseDialog(); }
ButtonBox — Layout for push buttons that takes button order into account
ButtonBox
( | button1, | |
button2) ; |
term
button1
;
term
button2
;This widget arranges its push button child widgets according to the current button order.
The button order depends on what UI is used and (optionally) what desktop environment the UI currently runs in.
The Qt and NCurses UIs use the KDE / Windows button order:
[OK] [Apply] [Cancel] [Custom1] [Custom2] ... [Help]
[Continue] [Cancel]
[Yes] [No]
The Gtk UI uses the GNOME / MacOS button order:
[Help] [Custom1] [Custom2] ... [Apply] [Cancel] [OK]
[Cancel] [Continue]
[No] [Yes]
Certain buttons have a predefined role:
- okButton: Positive confirmation: Use the values from the dialog to do whatever the dialog is all about and close the dialog.
- applyButton: Use the values from the dialog, but leave the dialog open.
- cancelButton: Discard all changes and close the dialog.
- helpButton: Show help for this dialog.
In a [Continue] [Cancel] dialog, [Continue] has the okButton role. In a [Yes] [No] dialog, [Yes] has the okButton role, [No] has the cancelButton role.
The UI automatically recognizes standard button labels and assigns the proper role. This is done very much like assigning function keys (see UI::SetFunctionKeys()). The UI also has some built-in heuristics to recognize standard button IDs like `id(`ok), `id("ok"), `id(`yes), etc.
Sometimes it makes sense to use something like [Print] or [Delete] for the okButton role if printing or deleting is what the respective dialog is all about. In that case, the application has to explicitly specify that button role: Use `opt(`okButton).
Similarly, there are `opt(`cancelButton), `opt(`applyButton), `opt(`helpButton).
By default, a ButtonBox with more than one button is required to have one okButton and one cancelButton. `opt(`relaxSanityCheck) relaxes those requirements: It does not check for one okButton and one cancelButton. This should be used very sparingly -- use your common sense. One Example where this is legitimate is a pop-up dialog with [OK] [Details] for error messages that can be explained in more detail. Most dialogs with more than just an [OK] or a [Close] button should have a [Cancel] button.
ButtonBox widgets can have no other child widgets than PushButton widgets. ButtonBox widgets are horizontally stretchable and vertically non-stretchable. If there is more space, their layout policy (depending on KDE or GNOME button order) specifies whether to center or right-align the buttons.
// Example for ButtonBox { UI::OpenDialog(`VBox( `HVCenter( `Label( "Hello, world!" ) ), `ButtonBox( `PushButton(`id(`doit1), "Do &Something Very Cool" ), `PushButton(`id(`doit2), `opt(`key_F10, `customButton), "Do &More" ), `PushButton(`id(`help), "&Help" ), `PushButton(`id(`ok), "&OK" ), `PushButton(`id(`cancel), "&Cancel" ), `PushButton(`id(`apply), "&Apply" ) ) ) ); UI::UserInput(); UI::CloseDialog(); }
CheckBox — Clickable on/off toggle button
CheckBox
( | label, | |
checked) ; |
string
label
;
boolean|nil
checked
;checked
whether the check box should start checked - nil means tristate condition, i.e. neither on nor off
A checkbox widget has two states: Checked and not checked. It returns no user input but you can query and change its state via the Value
property.
{ UI::OpenDialog( `CheckBox("A &checked check box\nwith multi-line", true) ); UI::UserInput(); UI::CloseDialog(); }
{ UI::OpenDialog( `VBox( `Label("Select your extras"), `Left(`CheckBox(`id(`cheese), "Extra Cheese")), `Left(`CheckBox(`id(`pepr), "Pepperoni", true)), `PushButton("&OK") ) ); UI::UserInput(); boolean cheese = (boolean) UI::QueryWidget(`cheese, `Value); boolean pepr = (boolean) UI::QueryWidget(`pepr, `Value); UI::CloseDialog(); define string yesno(boolean b) ``{ if (b) return "yes"; else return "no"; }; UI::OpenDialog( `VBox( `Left(`Label("Extra Cheese: " + yesno(cheese))), `Left(`Label("Pepperoni: " + yesno(pepr))), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Build dialog with one check box and buttons to set its state to // on, off or "don't care" (tri-state). UI::OpenDialog( `VBox( `CheckBox(`id(`cb), "Format hard disk"), `HBox( `HWeight(1, `PushButton(`id(`setOn ), "Set on" ) ), `HWeight(1, `PushButton(`id(`setOff), "Set off" ) ), `HWeight(1, `PushButton(`id(`dontCare), "Don't care" ) ) ), `PushButton(`id(`ok), "&OK") ) ); // Input loop. Will be left only after 'OK' is clicked. any button = nil; repeat { button = UI::UserInput(); if ( button == `setOn ) UI::ChangeWidget ( `id(`cb), `Value, true ); else if ( button == `setOff ) UI::ChangeWidget ( `id(`cb), `Value, false ); else if ( button == `dontCare ) UI::ChangeWidget ( `id(`cb), `Value, nil ); } until ( button == `ok ); // Get the check box's value. // // Notice: The return value of UI::UserInput() does NOT return this value! // Rather, it returns the ID of the widget (normally the PushButton) // that caused UI::UserInput() to return. boolean cb_val = (boolean) UI::QueryWidget(`id(`cb), `Value); // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); // Convert the check box value to string. string valStr = "Don't care"; if ( cb_val == true ) valStr = "Yes"; if ( cb_val == false ) valStr = "No"; // Pop up a new dialog to echo the input. UI::OpenDialog( `VBox( `Label("Your selection:"), `Label(valStr), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ UI::OpenDialog( `VBox( `Label("Select your extras"), `Left(`CheckBox(`id(`cheese), "Extra Cheese")), `Left(`CheckBox(`id(`pepr), "Pepperoni", true)), `Left(`CheckBox(`id(`salami), `opt(`boldFont), "Extra Salami")), `PushButton("&OK") ) ); UI::UserInput(); boolean cheese = (boolean) UI::QueryWidget(`cheese, `Value); boolean pepr = (boolean) UI::QueryWidget(`pepr, `Value); boolean salami = (boolean) UI::QueryWidget(`salami, `Value); UI::CloseDialog(); define string yesno(boolean b) ``{ if (b) return "yes"; else return "no"; }; UI::OpenDialog( `VBox( `Left(`Label("Extra Cheese: " + yesno(cheese))), `Left(`Label("Pepperoni: " + yesno(pepr))), `Left(`Label("Extra Salami: " + yesno(salami))), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
CheckBoxFrame — Frame with clickable on/off toggle button
CheckBoxFrame
( | label, | |
checked, | ||
child) ; |
string
label
;
boolean
checked
;
term
child
;label
the text describing the check box
checked
whether the check box should start checked
child
the child widgets for frame content - typically `VBox(...) or `HBox(...)
noAutoEnable
do not enable/disable frame children upon status change
invertAutoAnable
disable frame children if check box is checked
This is a combination of the check box widget and the frame widget: A frame that has a check box where a simple frame would have its frame title.
By default, the frame content (the child widgets) get disabled if the check box is set to "off" (unchecked) and enabled if the check box is set to "on" (cheched).
`opt(`invertAutoEnable) inverts this behaviour: It makes YCheckBoxFrame disable its content (its child widgets) if it is set to "on" (checked) and enable its content if it is set to "off".
`opt(`noAutoEnable) switches off disabling and enabling the frame content (the child widgets) completely. In that case, use QueryWidget() and/or `opt(`immediate).
Please note that unlike YCheckBox this widget does not support tri-state - it is always either on or off.
// Trivial example for CheckBoxFrame { UI::OpenDialog( `VBox( `MarginBox( 1, 0.5, `CheckBoxFrame ( "E&xpert Settings", true, `VBox( `HBox( `InputField( "&Server" ), `ComboBox ( "&Mode", [ "Automatic", "Manual", "Debug" ] ) ), `Left( `CheckBox( "&Logging" ) ), `InputField( "&Connections" ) ) ) ), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Example for CheckBoxFrame with inverted check box: // The frame content becomes active if the check box is off { UI::OpenDialog( `VBox( `MarginBox( 1, 0.5, `CheckBoxFrame (`opt(`invertAutoEnable), "&Automatic", true, `VBox( `HBox( `InputField( "&Server" ), `ComboBox ( "&Mode", [ "Automatic", "Manual", "Debug" ] ) ), `Left( `CheckBox( "&Logging" ) ), `InputField( "&Connections" ) ) ) ), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Example for CheckBoxFrame without auto enable: // The application has to handle the check box { UI::OpenDialog( `VBox( `MarginBox( 1, 0.5, `CheckBoxFrame (`id(`use_suse_server), `opt(`noAutoEnable, `notify), "&SuSE Server", false, `VBox( `HBox( `InputField(`id(`server), "&Server" ), `ComboBox (`id(`mode ), "&Mode", [ "Automatic", "Manual", "Debug" ] ) ), `Left(`id(`logging), `CheckBox( "&Logging" ) ), `InputField(`id(`connections), "&Connections" ) ) ) ), `PushButton(`id(`ok), "&OK") ) ); symbol widget = nil; string old_server = ""; UI::FakeUserInput(`use_suse_server); // Use event loop to set up initial enabled/disabled states repeat { widget = (symbol) UI::UserInput(); if ( widget == `use_suse_server ) { y2debug( "Changing enabled states" ); boolean use_suse_server = (boolean) UI::QueryWidget(`use_suse_server, `Value ); UI::ChangeWidget(`server, `Enabled, ! use_suse_server ); UI::ChangeWidget(`mode , `Enabled, ! use_suse_server ); if ( use_suse_server ) { old_server = (string) UI::QueryWidget(`server, `Value ); UI::ChangeWidget(`server, `Value, "ftp://ftp.opensuse.org" ); } else { UI::ChangeWidget(`server, `Value, old_server ); } } } until ( widget == `ok || widget == `cancel ); UI::CloseDialog(); }
ComboBox — drop-down list selection (optionally editable)
ComboBox
( | label, | |
items) ; |
string
label
;
list
items
;A combo box is a combination of a selection box and an input field. It gives the user a one-out-of-many choice from a list of items. Each item has a ( mandatory ) label and an ( optional ) id. When the 'editable' option is set, the user can also freely enter any value. By default, the user can only select an item already present in the list.
The items are very much like SelectionBox items: They can have an (optional) ID, they have a mandatory text to be displayed and an optional boolean parameter indicating the selected state. Only one of the items may have this parameter set to "true"; this will be the default selection on startup.
![]() | Note |
---|---|
You can and should set a keyboard shortcut within the label. When the user presses the hotkey, the combo box will get the keyboard focus. |
`ComboBox( `id( `pizza ), "select your Pizza:", [ "Margarita", `item( `id( `na ), "Napoli" ) ] )
{ UI::OpenDialog( `VBox( `ComboBox( "Select your Pizza:", [ "Napoli", "Funghi", "Salami" ] ), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Create a combo box with three entries. // All entries have IDs to identify them independent of locale // (The texts might have to be translated!). // Entry "Funghi" will be selected by default. { UI::OpenDialog( `VBox( `ComboBox(`id(`pizza), "Select your Pizza:", [ `item(`id(`nap), "Napoli" ), `item(`id(`fun), "Funghi", true ), `item(`id(`sal), "Salami" ) ] ), `PushButton("&OK") ) ); UI::UserInput(); any items = UI::QueryWidget(`pizza, `Items); y2debug( "Items:\n%1", items ); // Get the input from the selection box. // // Notice: The return value of UI::UserInput() does NOT return this value! // Rather, it returns the ID of the widget (normally the PushButton) // that caused UI::UserInput() to return. any pizza=UI::QueryWidget(`id(`pizza), `Value); y2milestone( "Selected %1", pizza ); // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); // Evaluate selection string toppings = "nothing"; if ( pizza == `nap ) toppings = "Tomatoes, Cheese"; else if ( pizza == `fun ) toppings = "Tomatoes, Cheese, Mushrooms"; else if ( pizza == `sal ) toppings = "Tomatoes, Cheese, Sausage"; // Pop up a new dialog to echo the selection. UI::OpenDialog( `VBox( `Label("You will get a pizza with:"), `Label(toppings), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Create a combo box with three entries. // All entries have IDs to identify them independent of locale // (The texts might have to be translated!). // Entry "Funghi" will be selected by default. { UI::OpenDialog( `VBox( `ComboBox(`id(`pizza), `opt(`editable), "Select your Pizza:", [ `item(`id(`nap), "Napoli" ), `item(`id(`fun), "Funghi", true ), `item(`id(`sal), "Salami" ) ] ), `PushButton("&OK") ) ); UI::UserInput(); // Get the input from the selection box. // // Notice: The return value of UI::UserInput() does NOT return this value! // Rather, it returns the ID of the widget (normally the PushButton) // that caused UI::UserInput() to return. any pizza=UI::QueryWidget(`id(`pizza), `Value); y2milestone( "Selected %1", pizza ); // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); // Evaluate selection string toppings = "nothing"; if ( pizza == `nap ) toppings = "Tomatoes, Cheese"; else if ( pizza == `fun ) toppings = "Tomatoes, Cheese, Mushrooms"; else if ( pizza == `sal ) toppings = "Tomatoes, Cheese, Sausage"; // Pop up a new dialog to echo the selection. UI::OpenDialog( `VBox( `Label("You will get a pizza with:"), `Label(toppings), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Create an editable combo box with restricted input character set. { UI::OpenDialog( `VBox( `ComboBox(`id(`addr), `opt(`editable), "Enter hex address:", [ "0cff", "8080", "D0C0", "ffff" ] ), `PushButton("&OK") ) ); // Set the valid input characters. UI::ChangeWidget(`id(`addr), `ValidChars, "0123456789abcdefABCDEF" ); // Wait for user input. UI::UserInput(); // Get the input from the selection box. // // Notice: The return value of UI::UserInput() does NOT return this value! // Rather, it returns the ID of the widget (normally the PushButton) // that caused UI::UserInput() to return. any val=UI::QueryWidget(`id(`addr), `Value); y2milestone( "Selected %1", val ); // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); // Pop up a new dialog to echo the input. UI::OpenDialog( `VBox( `Label("You entered:"), `Label(val), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Example showing how to replace SelectionBox items { list pizza_list = [ "Pizza Napoli", "Pizza Funghi", "Pizza Salami", "Pizza Hawaii" ]; list pasta_list = [ "Spaghetti", "Rigatoni", "Tortellini" ]; UI::OpenDialog( `VBox( `ComboBox(`id(`menu), "Daily &Specials:", pizza_list ), `HBox( `PushButton(`id(`pizza), "Pi&zza" ), `PushButton(`id(`pasta), "&Pasta" ) ), `PushButton(`id(`ok), "&OK" ) ) ); symbol button = nil; do { button = (symbol) UI::UserInput(); if ( button == `pizza ) UI::ChangeWidget(`menu, `Items, pizza_list ); if ( button == `pasta ) UI::ChangeWidget(`menu, `Items, pasta_list ); } while ( button != `ok ); string order = (string) UI::QueryWidget(`menu, `Value ); UI::CloseDialog(); // // Show the result // UI::OpenDialog(`VBox( `Label( sformat( "Your order: %1", order ) ), `PushButton(`opt(`default), "&OK" ) ) ); UI::UserInput(); UI::CloseDialog(); }
{ UI::OpenDialog( `VBox( `ComboBox( `id( `input ), `opt( `editable ),"Combo Box", [ "pizza", "pasta", "pronta" ] ), `IntField( `id ( `field ), "Limit characters to...", -1, 100, -1 ), `PushButton( `id ( `butt ),"limit input" ), `PushButton( `id ( `exitButton ), "Exit" ) ) ); any ret = nil; ret = UI::UserInput(); while ( ret != `exitButton ) { integer chars = (integer) UI::QueryWidget(`id(`field), `Value); UI::ChangeWidget( `input, `InputMaxLength, chars ); ret = UI::UserInput(); } UI::CloseDialog(); }
DateField — Date input field
DateField
( | label, | |
initialDate) ; |
string
label
;
string
initialDate
;DownloadProgress — Self-polling file growth progress indicator (optional widget)
DownloadProgress
( | label, | |
filename, | ||
expectedSize) ; |
string
label
;
string
filename
;
integer
expectedSize
;label
label above the indicator
filename
file name with full path of the file to poll
expectedSize
expected final size of the file in bytes
This widget automatically displays the progress of a lengthy download operation. The widget itself (i.e. the UI) polls the specified file and automatically updates the display as required even if the download is taking place in the foreground.
![]() | Note |
---|---|
This is a "special" widget, i.e. not all UIs necessarily support it. Check for availability with |
if ( HasSpecialWidget( `DownloadProgress ) {... `DownloadProgress( "Base system (230k)", "/tmp/aaa_base.rpm", 230*1024 );
{ if ( ! UI::HasSpecialWidget(`DownloadProgress) ) { UI::OpenDialog( `VBox( `Label("Error: This UI doesn't support the DownloadProgress widget!"), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); return; } string filename = "/suse/sh/.y2log"; // string filename = "/var/log/y2log"; integer expected_size = 20 * 1024; UI::OpenDialog( `VBox( `DownloadProgress("YaST2 log file", filename, expected_size ), `HSpacing(50), // force width `PushButton(`opt(`default), "&Close") ) ); UI::UserInput(); UI::CloseDialog(); }
DumbTab — Simplistic tab widget that behaves like push buttons
DumbTab
( | tabs, | |
contents) ; |
list
tabs
;
term
contents
;This is a very simplistic approach to tabbed dialogs: The application specifies a number of tab headers and the page contents and takes care of most other things all by itself, in particular page switching. Each tab header behaves very much like a PushButton - as the user activates a tab header, the DumbTab widget simply returns the ID of that tab (or its text if it has no ID). The application should then take care of changing the page contents accordingly - call UI::ReplaceWidget() on the ReplacePoint specified as tab contents or similar actions (it might even just replace data in a Table or RichText widget if this is the tab contents). Hence the name DumbTab.
The items in the item list can either be simple strings or `item() terms with an optional ID for each individual item (which will be returned upon UI::UserInput() and related when the user selects this tab), a (mandatory) user-visible label and an (optional) flag that indicates that this tab is initially selected. If you specify only a string, UI::UserInput() will return this string.
This is a "special" widget, i.e. not all UIs necessarily support it. Check for availability with HasSpecialWidget( `DumbTab )
before using it.
![]() | Note |
---|---|
Please notice that using this kind of widget more often than not is the result of poor dialog or workflow design. Using tabs only hides complexity, but the complexity remains there. They do little to make problems simpler. This however should be the approach of choice for good user interfaces. It is very common for tabs to be overlooked by users if there are just two tabs to select from, so in this case better use an "Expert..." or "Details..." button - this gives much more clue to the user that there is more information available while at the same time clearly indicating that those other options are much less commonly used. If there are very many different views on data or if there are lots and lots of settings, you might consider using a tree for much better navigation. The Qt UI's wizard even has a built-in tree that can be used instead of the help panel. If you use a tree for navigation, unter all circumstances avoid using tabs at the same time - there is no way for the user to tell which tree nodes have tabs and which have not, making navigation even more difficult. KDE's control center or Mozilla's settings are very good examples how not to do that - you become bogged down for sure in all those tree nodes and tabs hidden within so many of them. |
if ( HasSpecialWidget( `DumbTab) {... `DumbTab( [ `item(`id(`page1), "Page &1" ), `item(`id(`page2), "Page &2" ) ], contents; }
// Minimalistic example for tab widget { if ( ! UI::HasSpecialWidget(`DumbTab ) ) { UI::OpenDialog( `VBox( `Label("Error: This UI doesn't support the DumbTab widget!"), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); return; } UI::OpenDialog( `VBox( `DumbTab( [ "Page 1", "Page 2", "Page 3" ], `RichText(`id(`contents), "Contents" ) ), `Right(`PushButton(`id(`close), "&Close" ) ) ) ); UI::DumpWidgetTree(); any input = nil; repeat { input = UI::UserInput(); if ( is( input, string ) ) { UI::ChangeWidget(`contents, `Value, (string) input ); } } until ( input == `close ); UI::CloseDialog(); }
// Typical usage example for tab widget { term address_page = `VBox( `Left( `Heading( "Address" ) ), `VSpacing(), `HCenter( `HSquash( `VBox( `HSpacing( 50 ), `InputField( "Name" ), `InputField( "E-Mail" ), `InputField( "Phone" ), `VSpacing(), `MultiLineEdit( "Comments" ), `VStretch() ) ) ) ); term overview_page = `VBox( `Left( `Heading( "DumbTab Widget Overview" ) ), `VSpacing(), `Label( "This kind of tab is pretty dumb - hence the name DumbTab.\n" + "You need to do most everything yourself.\n" + "Each tab behaves very much like a push button;\n" + "the YCP application is notified when the user clicks on a tab.\n" + "The application must take care to exchange the tab contents.\n" + "\n" + "Note: That means changes made in on tab are lost when switching\n" + "the tabs, e.g. text entered here in the address tab.") ); term style_hints_page = `VBox( `Left( `Heading( "GUI Style Hints" ) ), `VSpacing(), `Heading( "Using tabs is usually a result of poor dialog design." ), `VSpacing(), `Left( `Label( "Tabs hide complexity, they do not resolve it.\n" + "The problem remains just as complex as before,\n" + "only the user can no longer see it." ) ) ); UI::OpenDialog(`opt(`defaultsize), `VBox( `DumbTab( [ `item(`id(`address ), "&Address" ), `item(`id(`overview ), "&Overview" ), `item(`id(`style ), "GUI &Style Hints", true ) // true: selected ], `Left( `Top( `HVSquash( `VBox( `VSpacing(0.3), `HBox( `HSpacing(1), `ReplacePoint(`id(`tabContents ), style_hints_page ) ) ) ) ) ) ), `Right(`PushButton(`id(`close), "Cl&ose" ) ) ) ); while ( true ) { symbol widget = (symbol) UI::UserInput(); if ( widget == `close ) break; else if ( widget == `address ) UI::ReplaceWidget(`tabContents, address_page ); else if ( widget == `overview ) UI::ReplaceWidget(`tabContents, overview_page ); else if ( widget == `style ) UI::ReplaceWidget(`tabContents, style_hints_page ); } UI::CloseDialog(); }
Empty — Placeholder widget
Empty
( | ) ; |
Frame — Frame with label
Frame
( | label, | |
child) ; |
string
label
;
term
child
;label
title to be displayed on the top left edge
child
the contained child widget
This widget draws a frame around its child and displays a title label within the top left edge of that frame. It is used to visually group widgets together. It is very common to use a frame like this around radio button groups.
{ UI::OpenDialog( `VBox( `Frame ( "Hey! I&mportant!", `Label("Hello, World!") ), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ UI::OpenDialog( `VBox( `Frame ( "CPU &Speed", `RadioButtonGroup( `VBox( `Left(`RadioButton("Normal" )), `Left(`RadioButton("Overclocked" )), `Left(`RadioButton("Red Hot" )), `Left(`RadioButton("Melting", true )) ) ) ), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ UI::OpenDialog( `VBox( `Frame("Shrinkable Textentries", `HBox( `InputField(`opt(`shrinkable), "1"), `InputField(`opt(`shrinkable), "2"), `InputField(`opt(`shrinkable), "321"), `InputField(`opt(`shrinkable), "4") ) ), `PushButton(`opt(`default), "&OK" ) ) ); UI::UserInput(); UI::CloseDialog(); }
HBox, VBox — Generic layout: Arrange widgets horizontally or vertically
HBox
( | ) ; |
VBox
( | ) ; |
child1
the first child widget
child2
the second child widget
child3
the third child widget
child4
the fourth child widget ( and so on... )
The layout boxes are used to split up the dialog and layout a number of widgets horizontally ( HBox
) or vertically ( VBox
).
{ UI::OpenDialog( `VBox( `PushButton("First"), `PushButton("Second"), `PushButton("Third") ) ); UI::UserInput(); UI::CloseDialog(); }
{ UI::OpenDialog( `HBox( `PushButton("First" ), `PushButton("Second"), `PushButton("Third" ) ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Layout example: // // Build a dialog with three equal sized buttons. // // The equal `HWeight()s will make the buttons equal sized. // When resized, all buttons will resize equally in order to // maintain the equal layout weights. UI::OpenDialog( `HBox( `HWeight(1, `PushButton( `opt(`default), "&OK" ) ), `HWeight(1, `PushButton( "&Cancel everything" ) ), `HWeight(1, `PushButton( "&Help" ) ) ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Layout example: // // Build a dialog with three widgets without any weights. // // Each widget will get its "nice size", i.e. the size that makes // the widget's contents fit into it. // // Upon resize the widgets will keep their sizes if enlarged // (since none of them is stretchable), i.e. there will be empty // space to the right. // UI::OpenDialog( `HBox( `PushButton( `opt(`default), "OK" ), `PushButton( "Cancel everything" ), `PushButton( "Help" ) ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Layout example: // // Build a dialog with three widgets with different weights and // two widgets without any weight. // // All widgets will get at least their "nice size". The weighted // ones may get even more to maintain their share of the overall // weight. // // Upon resize all widgets will resize to maintain their // respective weights at all times. The non-weighted widgets will // retain their "nice size" regardless whether or not they are // stretchable. // UI::OpenDialog( `HBox( `HWeight( 33, `PushButton( `opt(`default), "OK\n33%" ) ), `PushButton( `opt(`hstretch), "Apply\nNo Weight" ), `HWeight( 33, `PushButton( "Cancel\n33%" ) ), `PushButton( "Reset to defaults\nNo Weight" ), `HWeight( 66, `PushButton( "Help\n66%" ) ) ) ); UI::UserInput(); UI::CloseDialog(); }
HSpacing, VSpacing, HStretch, VStretch — Fixed size empty space for layout
HSpacing
( | ) ; |
VSpacing
( | ) ; |
HStretch
( | ) ; |
VStretch
( | ) ; |
HSpacing and VSpacing are layout helpers to add empty space between widgets.
VStretch and HStretch act as "rubber bands" in layouts that take excess space. They have a size of zero if there is no excess space.
The size
given is measured in units roughly equivalent to the size of a character in the respective UI. Fractional numbers can be used here, but text based UIs may choose to round the number as appropriate - even if this means simply ignoring a spacing when its size becomes zero.
If size
is omitted, it defaults to 1. HSpacing()
will create a horizontal spacing with default width and zero height. VSpacing()
will create a vertical spacing with default height and zero width. HStretch()
will create a horizontal stretch with zero width and height. VStretch()
will create a vertical stretch with zero width and height.
A HStretch or VStretch with a size specification will take at least the specified amount of space, but it will take more (in that dimension) if there is excess space in the layout.
{ // Build dialog with one input field field, 4 Beatles buttons and an OK button. UI::OpenDialog( `VBox( `VSpacing(), `HBox( `Label("Name:"), `InputField(`id(`name), "") ), `VSpacing(0.2), `HBox( `PushButton(`id(`john), "&John" ), `HSpacing(0.5), `PushButton(`id(`paul), "&Paul" ), `HSpacing(3), `PushButton(`id(`george), "&George"), `HSpacing(0.5), `PushButton(`id(`ringo), "&Ringo" ) ), `VSpacing(0.5), `PushButton(`id(`ok), "&OK") ) ); // Wait for user input. any button = nil; // Input loop that only the OK button will leave. // The 4 Beatles buttons will just propose a name. repeat { button = UI::UserInput(); if ( button == `john ) UI::ChangeWidget(`id(`name), `Value, "John Lennon"); else if ( button == `paul ) UI::ChangeWidget(`id(`name), `Value, "Paul McCartney"); else if ( button == `george ) UI::ChangeWidget(`id(`name), `Value, "George Harrison"); else if ( button == `ringo ) UI::ChangeWidget(`id(`name), `Value, "Ringo Starr" ); } until ( button == `ok ); UI::CloseDialog(); }
{ // Layout example: // // Build a dialog with three equal sized buttons, // this time with some spacing in between. // // The equal `HWeight()s will make the buttons even sized. // When resized larger, all buttons will retain their size. // Excess space will go to the HSpacing() widgets between the // buttons, i.e. there will be empty space between the buttons. // // Notice the importance of `opt(`hstretch) for the `HSpacing()s // here: This is what makes the HSpacing()s grow. Otherwise, they // would retain a constant size, and the buttons would grow. UI::OpenDialog( `HBox( `HWeight(1, `PushButton( `opt(`default), "&OK" ) ), `HSpacing(`opt(`hstretch), 3), `HWeight(1, `PushButton( "&Cancel everything" ) ), `HSpacing(`opt(`hstretch), 3), `HWeight(1, `PushButton( "&Help" ) ) ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Layout example: // // Build a dialog with three equal sized buttons. // // The equal `HWeight()s will make the buttons equal sized. // When resized larger, all buttons will retain their size. // Excess space will go to the HStretch() widgets between the // buttons, i.e. there will be empty space between the buttons. UI::OpenDialog( `HBox( `HWeight(1, `PushButton( `opt(`default), "&OK" ) ), `HStretch(), `HWeight(1, `PushButton( "&Cancel everything" ) ), `HStretch(), `HWeight(1, `PushButton( "&Help" ) ) ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Layout example: // // Build a dialog with three equal sized buttons. // // The equal `HWeight()s will make the buttons equal sized. // When resized larger, all buttons will retain their size. // Excess space will go to the HStretch() widgets between the // buttons, i.e. there will be empty space between the buttons. UI::OpenDialog( `HBox( `HWeight(1, `PushButton( `opt(`default), "&OK" ) ), `HStretch(), `HWeight(1, `PushButton( "&Cancel everything" ) ), `HStretch(), `HWeight(1, `PushButton( "&Help" ) ) ) ); UI::UserInput(); UI::CloseDialog(); }
// Table example: Exchange complete table content { list foodItems = [ `item(`id(3), "Spaghetti", 8), `item(`id(4), "Steak Sandwich", 12), `item(`id(1), "Chili", 6), `item(`id(2), "Salami Baguette", nil) ]; list carItems = [ `item(`id(0), "Mercedes", 60000), `item(`id(1), "Audi", 50000), `item(`id(2), "VW", 40000), `item(`id(3), "BMW", 60000), `item(`id(3), "Porsche", 80000) ]; list itemLists = [ foodItems, carItems ]; integer listNo = 0; UI::OpenDialog( `VBox( `Heading("Prices"), `MinSize( 30, 10, `Table(`id(`table), `header("Name", "Price"), foodItems ) ), `Right( `HBox( `PushButton(`id(`next), "Change &Table Contents"), `PushButton(`id(`cancel), "&Close") ) ) ) ); while (UI::UserInput() != `cancel) { // Change table contents listNo = 1 - listNo; UI::ChangeWidget(`table, `Items, itemLists[ listNo ]:nil ); // Double check: Retrieve contents and dump to log y2milestone( "New table content:\n%1", UI::QueryWidget(`table, `Items ) ); } UI::CloseDialog(); }
// Table example: Exchange complete table content { list foodItems = [ `item(`id(3), "Spaghetti", 8), `item(`id(4), "Steak Sandwich", 12), `item(`id(1), "Chili", 6), `item(`id(2), "Salami Baguette", nil) ]; list carItems = [ `item(`id(0), "Mercedes", 60000), `item(`id(1), "Audi", 50000), `item(`id(2), "VW", 40000), `item(`id(3), "BMW", 60000), `item(`id(3), "Porsche", 80000) ]; list itemLists = [ foodItems, carItems ]; integer listNo = 0; UI::OpenDialog( `VBox( `Heading("Prices"), `MinSize( 30, 10, `Table(`id(`table), `header("Name", "Price"), foodItems ) ), `Right( `HBox( `PushButton(`id(`next), "Change &Table Contents"), `PushButton(`id(`cancel), "&Close") ) ) ) ); while (UI::UserInput() != `cancel) { // Change table contents listNo = 1 - listNo; UI::ChangeWidget(`table, `Items, itemLists[ listNo ]:nil ); // Double check: Retrieve contents and dump to log y2milestone( "New table content:\n%1", UI::QueryWidget(`table, `Items ) ); } UI::CloseDialog(); }
HSquash, VSquash, HVSquash — Layout aid: Minimize widget to its preferred size
HSquash
( | child) ; |
term
child
;
VSquash
( | child) ; |
term
child
;
HVSquash
( | child) ; |
term
child
; The Squash widgets are used to control the layout. A HSquash
widget makes its child widget nonstretchable in the horizontal dimension. A VSquash
operates vertically, a HVSquash
in both dimensions.
You can used this for example to reverse the effect of `Left
making a widget stretchable. If you want to make a VBox containing for left aligned CheckBoxes, but want the VBox itself to be non-stretchable and centered, than you enclose each CheckBox with a `Left( .. )
and the whole VBox with a HSquash( ... )
.
{ UI::OpenDialog(`opt(`defaultsize), `VBox( `VCenter( // Makes the HSquash stretchable vertically `HSquash( // Makes the VBox nonstretchable horizontally `VBox( `Left(`CheckBox("short")), `Left(`CheckBox("longer")), `Left(`CheckBox("even longer")), `Left(`CheckBox("yet even longer"))))), `Left(`PushButton("bottom left")) ) ); UI::UserInput(); UI::CloseDialog(); }
HWeight, VWeight — Control relative size of layouts
HWeight
( | weight, | |
child) ; |
integer
weight
;
term
child
;
VWeight
( | weight, | |
child) ; |
integer
weight
;
term
child
; This widget is used to control the layout. When a HBox
or VBox
widget decides how to devide remaining space amount two stretchable widgets, their weights are taken into account. This widget is used to change the weight of the child widget. Each widget has a vertical and a horizontal weight. You can change on or both of them. If you use HVWeight
, the weight in both dimensions is set to the same value.
Note: No real widget is created (any more), just the weight value is passed to the child widget.
{ UI::OpenDialog( `HBox( `HWeight(1, `PushButton("First Button (W: 50)")), `PushButton("Small Button"), `HWeight(1, `PushButton("Second Button (Weight 50 - this one determines the total width")) ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Layout example: // // Build a dialog with three equal sized buttons. // // The equal `HWeight()s will make the buttons equal sized. // When resized larger, all buttons will retain their size. // Excess space will go to the HStretch() widgets between the // buttons, i.e. there will be empty space between the buttons. UI::OpenDialog( `HBox( `HWeight(1, `PushButton( `opt(`default), "&OK" ) ), `HStretch(), `HWeight(1, `PushButton( "&Cancel everything" ) ), `HStretch(), `HWeight(1, `PushButton( "&Help" ) ) ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Layout example: // // Build a dialog with three equal sized buttons, // this time with some spacing in between. // // The equal `HWeight()s will make the buttons even sized. // When resized larger, all buttons will retain their size. // Excess space will go to the HSpacing() widgets between the // buttons, i.e. there will be empty space between the buttons. // // Notice the importance of `opt(`hstretch) for the `HSpacing()s // here: This is what makes the HSpacing()s grow. Otherwise, they // would retain a constant size, and the buttons would grow. UI::OpenDialog( `HBox( `HWeight(1, `PushButton( `opt(`default), "&OK" ) ), `HSpacing(`opt(`hstretch), 3), `HWeight(1, `PushButton( "&Cancel everything" ) ), `HSpacing(`opt(`hstretch), 3), `HWeight(1, `PushButton( "&Help" ) ) ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Layout example: // // Build a dialog with three equal sized buttons. // // The equal `HWeight()s will make the buttons equal sized. // When resized, all buttons will resize equally in order to // maintain the equal layout weights. UI::OpenDialog( `HBox( `HWeight(1, `PushButton( `opt(`default), "&OK" ) ), `HWeight(1, `PushButton( "&Cancel everything" ) ), `HWeight(1, `PushButton( "&Help" ) ) ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Layout example: // // Build a dialog with three widgets with different weights and // two widgets without any weight. // // All widgets will get at least their "nice size". The weighted // ones may get even more to maintain their share of the overall // weight. // // Upon resize all widgets will resize to maintain their // respective weights at all times. The non-weighted widgets will // retain their "nice size" regardless whether or not they are // stretchable. // UI::OpenDialog( `HBox( `HWeight( 33, `PushButton( `opt(`default), "OK\n33%" ) ), `PushButton( `opt(`hstretch), "Apply\nNo Weight" ), `HWeight( 33, `PushButton( "Cancel\n33%" ) ), `PushButton( "Reset to defaults\nNo Weight" ), `HWeight( 66, `PushButton( "Help\n66%" ) ) ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Layout example: // // Build a dialog with three widgets with different weights. // // Weights do not need to add up to 100 or any other special // number, but it helps the application programmer to keep track // of the percentage of each part of the layout. // // Notice how the second button commands the overall size of the // dialog since it has the largest "nice size" to "weight" ratio. // // Upon resize all widgets will resize to maintain their // respective weights at all times. // UI::OpenDialog( `HBox( `HWeight( 25, `PushButton( `opt(`default), "OK\n25%" ) ), `HWeight( 25, `PushButton( "Cancel everything\n25%" ) ), `HWeight( 50, `PushButton( "Help\n50%" ) ) ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Layout example: // // Build a dialog with three widgets with different weights. // // Weights do not need to add up to 100 or any other special // number, but it helps the application programmer to keep track // of the percentage of each part of the layout. // // Notice how the second button commands the overall size of the // dialog since it has the largest "nice size" to "weight" ratio. // // Upon resize all widgets will resize to maintain their // respective weights at all times. // UI::OpenDialog( `HBox( `HWeight( 1, `PushButton( `opt(`default), "OK\n25%" ) ), `HWeight( 1, `PushButton( "Cancel everything\n25%" ) ), `HWeight( 2, `PushButton( "Help\n50%" ) ) ) ); UI::UserInput(); UI::CloseDialog(); }
Image — Pixmap image
Image
( | imageFileName) ; |
string
imageFileName
;animated
show an animated image (MNG, animated GIF)
scaleToFit
scale the pixmap so it fits the available space: zoom in or out as needed
zeroWidth
make widget report a preferred width of 0
zeroHeight
make widget report a preferred height of 0
Displays an image if the respective UI is capable of that.
Use `opt( `zeroWidth )
and / or `opt( `zeroHeight )
if the real size of the image widget is determined by outside factors, e.g. by the size of neighboring widgets. With those options you can override the preferred size of the image widget and make it show just a part of the image. If more screen space is available, more of the image is shown, if not, the layout engine doesn't complain about the image widget not getting its preferred size.
`opt( `scaleToFit ) scales the image to fit into the available space, i.e. the image will be zoomed in or out as needed.
This option implicitly sets `opt( `zeroWidth ) and `opt( zeroHeight ), too since there is no useful default size for such an image. Use MinSize() or other layout helpers to explicitly set a size on such a widget.
Please note that setting both `opt( `tiled ) and `opt( `scaleToFit ) at once doesn't make any sense.
// Simple image example { UI::OpenDialog( `VBox( `Image (`id ("image"), "/usr/share/YaST2/theme/current/wallpapers/welcome.jpg", "fallback text"), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); if (UI::WidgetExists (`id ("image"))) { UI::ChangeWidget (`id ("image"), `Enabled, false); UI::UserInput(); UI::ChangeWidget (`id ("image"), `Enabled, true); UI::UserInput(); } else { y2error ("No such widget id"); } UI::CloseDialog(); }
// Animated image example { UI::OpenDialog( `VBox( `MinSize( 30, 10, `Image(`opt(`animated), "/usr/lib/qt3/doc/examples/widgets/trolltech.gif", "fallback text" ) ), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Animated image example { UI::OpenDialog( `VBox( `MinSize( 30, 10, `Image(`opt(`scaleToFit ), "/usr/share/wallpapers/alta-badia.jpg", "fallback text" ) ), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
InputField, TextEntry, Password — Input field
InputField
( | label, | |
defaulttext) ; |
string
label
;
string
defaulttext
;
TextEntry
( | label, | |
defaulttext) ; |
string
label
;
string
defaulttext
;
Password
( | label, | |
defaulttext) ; |
string
label
;
string
defaulttext
;This widget is a one line text entry field with a label above it. An initial text can be provided.
![]() | Note |
---|---|
You can and should set a keyboard shortcut within the label. When the user presses the hotkey, the corresponding text entry widget will get the keyboard focus. Bug compatibility mode: If used as TextEntry(), `opt(`hstretch) is automatically added (with a warning in the log about that fact) to avoid destroying dialogs written before fixing a geometry bug in the widget. The bug had caused all TextEntry widgets to be horizontally stretchable, so they consumed all the horizontal space they could get, typically making them as wide as the dialog. When used with the new name InputField(), they use a reasonable default width. You can still add `opt(`hstretch), of course. |
{ UI::OpenDialog( `VBox( `InputField("Name:"), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Build dialog with one input field field and an OK button. UI::OpenDialog( `VBox( `InputField(`id(`name), "Name:"), `PushButton("&OK") ) ); // Wait for user input. UI::UserInput(); // Get the input from the input field field. // // Notice: The return value of UI::UserInput() does NOT return this value! // Rather, it returns the ID of the widget (normally the PushButton) // that caused UI::UserInput() to return. string name = (string) UI::QueryWidget(`id(`name), `Value); y2warning( "Name: %1", name ); // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); // Pop up a new dialog to echo the input. UI::OpenDialog( `VBox( `Label("You entered:"), `Label(name), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Build dialog with one input field field and an OK button. UI::OpenDialog( `VBox( `InputField(`id(`name), "You will never see this:"), `PushButton("&OK") ) ); // Set an initial value for the input field field. UI::ChangeWidget(`id(`name), `Value, "Averell Dalton"); // Change the input field field's label. UI::ChangeWidget(`id(`name), `Label, "Name:"); // Wait for user input. UI::UserInput(); // Get the input from the input field field. // // Notice: The return value of UI::UserInput() does NOT return this value! // Rather, it returns the ID of the widget (normally the PushButton) // that caused UI::UserInput() to return. string name = (string) UI::QueryWidget(`id(`name), `Value); // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); // Pop up a new dialog to echo the input. UI::OpenDialog( `VBox( `Label("You entered:"), `Label(name), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Build dialog with one input field field, 4 Beatles buttons and an OK button. UI::OpenDialog( `VBox( `InputField(`id(`name), "Name:"), `HBox( `PushButton(`id(`john), "&John" ), `PushButton(`id(`paul), "&Paul" ), `PushButton(`id(`george), "&George"), `PushButton(`id(`ringo), "&Ringo" )), `PushButton(`id(`ok), "&OK") ) ); // Wait for user input. any button = nil; // Input loop that only the OK button will leave. // The 4 Beatles buttons will just propose a name. repeat { button = UI::UserInput(); if ( button == `john ) UI::ChangeWidget(`id(`name), `Value, "John Lennon"); else if ( button == `paul ) UI::ChangeWidget(`id(`name), `Value, "Paul McCartney"); else if ( button == `george ) UI::ChangeWidget(`id(`name), `Value, "George Harrison"); else if ( button == `ringo ) UI::ChangeWidget(`id(`name), `Value, "Ringo Starr" ); } until ( button == `ok ); UI::CloseDialog(); }
{ // Build dialog with one input field field and an OK button. UI::OpenDialog( `VBox( `InputField(`id(`hex_digits), "Hex number:"), `PushButton("&OK") ) ); UI::ChangeWidget(`id(`hex_digits), `ValidChars, "0123456789abcdefABCDEF" ); // Wait for user input. UI::UserInput(); // Get the input from the input field field. // // Notice: The return value of UI::UserInput() does NOT return this value! // Rather, it returns the ID of the widget (normally the PushButton) // that caused UI::UserInput() to return. string name = (string) UI::QueryWidget(`id(`hex_digits), `Value); // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); // Pop up a new dialog to echo the input. UI::OpenDialog( `VBox( `Label("You entered:"), `Label(name), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Build dialog with one input field field and an OK button. UI::OpenDialog( `VBox( `InputField(`id(`hex_digits), "Hex number:"), `PushButton("&OK") ) ); UI::ChangeWidget(`id(`hex_digits), `ValidChars, "0123456789abcdefABCDEF" ); // Wait for user input. UI::UserInput(); // Get the input from the input field field. // // Notice: The return value of UI::UserInput() does NOT return this value! // Rather, it returns the ID of the widget (normally the PushButton) // that caused UI::UserInput() to return. string name = (string) UI::QueryWidget(`id(`hex_digits), `Value); // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); // Pop up a new dialog to echo the input. UI::OpenDialog( `VBox( `Label("You entered:"), `Label(name), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Build dialog with two password fields, an "OK" and a "Cancel" button. UI::OpenDialog( `VBox( `Password(`id(`pw1), "Enter password:"), `Password(`id(`pw2), "Confirm password:"), `HBox( `PushButton(`id(`ok), "&OK" ), `PushButton(`id(`cancel), "&Cancel") ) ) ); any button = nil; string pw1 = ""; string pw2 = ""; // Input loop. Will be terminated when the same password has been // entered in both fields or when 'Cancel' has been clicked. repeat { // Wait for Input. button = UI::UserInput(); // Get the values from both password fields. pw1 = (string) UI::QueryWidget(`id(`pw1), `Value ); pw2 = (string) UI::QueryWidget(`id(`pw2), `Value ); if ( button != `cancel ) { if ( pw1 == "" && pw2 == "" ) { // Error popup if nothing has been entered. UI::OpenDialog( `VBox( `Label("You must enter a password."), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); } else if ( pw1 != pw2 ) { // Error popup if passwords differ. UI::OpenDialog( `VBox( `Label("The two passwords mismatch."), `Label("Please try again."), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); } } } until ( ( pw1 != "" && pw1 == pw2 ) || button == `cancel ); UI::CloseDialog(); }
{ // Build dialog with two password fields, an "OK" and a "Cancel" button. UI::OpenDialog( `VBox( `Password(`id(`pw1), "Enter password:"), `Password(`id(`pw2), "Confirm password:"), `HBox( `PushButton(`id(`ok), "&OK" ), `PushButton(`id(`cancel), "&Cancel") ) ) ); any button = nil; string pw1 = ""; string pw2 = ""; // Input loop. Will be terminated when the same password has been // entered in both fields or when 'Cancel' has been clicked. repeat { // Wait for Input. button = UI::UserInput(); // Get the values from both password fields. pw1 = (string) UI::QueryWidget(`id(`pw1), `Value ); pw2 = (string) UI::QueryWidget(`id(`pw2), `Value ); if ( button != `cancel ) { if ( pw1 == "" && pw2 == "" ) { // Error popup if nothing has been entered. UI::OpenDialog( `VBox( `Label("You must enter a password."), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); } else if ( pw1 != pw2 ) { // Error popup if passwords differ. UI::OpenDialog( `VBox( `Label("The two passwords mismatch."), `Label("Please try again."), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); } } } until ( ( pw1 != "" && pw1 == pw2 ) || button == `cancel ); UI::CloseDialog(); }
{ UI::OpenDialog( `VBox( `InputField( `id( `input ), "Input Field", "pizza, pasta, pronta" ), `IntField( `id ( `field ), "Limit characters to...", -1, 100, -1 ), `PushButton( `id ( `butt ),"limit input" ), `PushButton( `id ( `exitButton ), "Exit" ) ) ); any ret = nil; ret = UI::UserInput(); while ( ret != `exitButton ) { integer chars = (integer) UI::QueryWidget(`id(`field), `Value); UI::ChangeWidget( `input, `InputMaxLength, chars ); ret = UI::UserInput(); } UI::CloseDialog(); }
IntField — Numeric limited range input field
IntField
( | label, | |
minValue, | ||
maxValue, | ||
initialValue) ; |
string
label
;
integer
minValue
;
integer
maxValue
;
integer
initialValue
;label
Explanatory label above the input field
minValue
minimum value
maxValue
maximum value
initialValue
initial value
A numeric input field for integer numbers within a limited range. This can be considered a lightweight version of the <link linkend="Slider_widget">Slider</link> widget, even as a replacement for this when the specific UI doesn't support the Slider. Remember it always makes sense to specify limits for numeric input, even if those limits are very large (e.g. +/- MAXINT).
Fractional numbers are currently not supported.
{ UI::OpenDialog( `VBox( `IntField( "Percentage:", 0, 100, 50), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Simple IntField example { UI::OpenDialog( `VBox( `IntField( `id(`perc), "Percentage:", 0, 100, 50), `PushButton(`opt(`default), "&OK") ) ); UI::ChangeWidget(`perc, `Value, 42 ); UI::UserInput(); integer percentage = (integer) UI::QueryWidget(`perc, `Value); UI::CloseDialog(); UI::OpenDialog( `VBox( `Label( sformat( "You entered: %1%%", percentage) ), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
Label, Heading — Simple static text
Label
( | label) ; |
string
label
;
Heading
( | label) ; |
string
label
;outputField
make the label look like an input field in read-only mode
boldFont
use a bold font
A Label
is static text displayed in the dialog. A Heading
is static text with a bold and/or larger font. In both cases, the text may contain newlines.
{ UI::OpenDialog( `VBox( `Label("Hello, World!"), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ UI::OpenDialog( `VBox( `Label("Labels can have\nmultiple lines." ), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Build dialog with one label, 4 Beatles buttons and an OK button. UI::OpenDialog( `VBox( `Label("Select your favourite Beatle:"), `Label(`id(`beatle), `opt(`outputField), " "), `HBox( `PushButton(`id(`john), "John" ), `PushButton(`id(`paul), "Paul" ), `PushButton(`id(`george), "George"), `PushButton(`id(`ringo), "Ringo" )), `PushButton(`id(`ok), "&OK") ) ); // Wait for user input. any button = nil; // Input loop that only the OK button will leave. // The 4 Beatles buttons will just propose a name. repeat { button = UI::UserInput(); if ( button == `john ) UI::ChangeWidget(`id(`beatle), `Value, "John Lennon"); else if ( button == `paul ) UI::ChangeWidget(`id(`beatle), `Value, "Paul McCartney"); else if ( button == `george ) UI::ChangeWidget(`id(`beatle), `Value, "George Harrison"); else if ( button == `ringo ) UI::ChangeWidget(`id(`beatle), `Value, "Ringo Starr" ); // Recalculate the layout - this is necessary since the label widget // doesn't recompute its size upon changing its value. UI::RecalcLayout(); } until ( button == `ok ); // Retrieve the label's value. string name = (string) UI::QueryWidget(`id(`beatle), `Value); // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); // Pop up a new dialog to echo the input. UI::OpenDialog( `VBox( `VSpacing(), `HBox( `Label("You selected:"), `Label(`opt(`outputField), name), `HSpacing() ), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Label example using bold font { UI::OpenDialog(`VBox( `Label( "Label using normal font" ), `Label(`opt(`boldFont), "Label using bold font" ), `Label( "Label using normal font" ), `PushButton(`opt(`default), "&OK" ) ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Build dialog with one label, 4 Beatles buttons and an OK button. UI::OpenDialog( `VBox( `Label("My favourite Beatle:"), // `Heading(`id(`favourite), "Press one of the buttons below"), `Heading(`id(`favourite), "(please select one)"), `HBox( `PushButton(`id(`john), "John" ), `PushButton(`id(`paul), "Paul" ), `PushButton(`id(`george), "George"), `PushButton(`id(`ringo), "Ringo" )), `PushButton(`id(`ok), "&OK") ) ); // Wait for user input. any button = nil; // Input loop that only the OK button will leave. // The 4 Beatles buttons will just propose a name. repeat { button = UI::UserInput(); if ( button == `john ) UI::ChangeWidget(`id(`favourite), `Value, "John Lennon"); else if ( button == `paul ) UI::ChangeWidget(`id(`favourite), `Value, "Paul McCartney"); else if ( button == `george ) UI::ChangeWidget(`id(`favourite), `Value, "George Harrison"); else if ( button == `ringo ) UI::ChangeWidget(`id(`favourite), `Value, "Ringo Starr" ); } until ( button == `ok ); }
{ // Build dialog with one label, 4 Beatles buttons and an OK button. UI::OpenDialog( `VBox( `Label("My favourite Beatle:"), // `Heading(`id(`favourite), "Press one of the buttons below"), `Heading(`id(`favourite), "(please select one)"), `HBox( `PushButton(`id(`john), "John" ), `PushButton(`id(`paul), "Paul" ), `PushButton(`id(`george), "George"), `PushButton(`id(`ringo), "Ringo" )), `PushButton(`id(`ok), "&OK") ) ); // Wait for user input. any button = nil; // Input loop that only the OK button will leave. // The 4 Beatles buttons will just propose a name. repeat { button = UI::UserInput(); if ( button == `john ) UI::ChangeWidget(`id(`favourite), `Value, "John Lennon"); else if ( button == `paul ) UI::ChangeWidget(`id(`favourite), `Value, "Paul McCartney"); else if ( button == `george ) UI::ChangeWidget(`id(`favourite), `Value, "George Harrison"); else if ( button == `ringo ) UI::ChangeWidget(`id(`favourite), `Value, "Ringo Starr" ); } until ( button == `ok ); }
{ // Build dialog with one label, 4 Beatles buttons and an OK button. UI::OpenDialog( `VBox( `Label("My favourite Beatle:"), // `Heading(`id(`favourite), "Press one of the buttons below"), `Heading(`id(`favourite), "(please select one)"), `HBox( `PushButton(`id(`john), "John" ), `PushButton(`id(`paul), "Paul" ), `PushButton(`id(`george), "George"), `PushButton(`id(`ringo), "Ringo" )), `PushButton(`id(`ok), "&OK") ) ); // Wait for user input. any button = nil; // Input loop that only the OK button will leave. // The 4 Beatles buttons will just propose a name. repeat { button = UI::UserInput(); if ( button == `john ) UI::ChangeWidget(`id(`favourite), `Value, "John Lennon"); else if ( button == `paul ) UI::ChangeWidget(`id(`favourite), `Value, "Paul McCartney"); else if ( button == `george ) UI::ChangeWidget(`id(`favourite), `Value, "George Harrison"); else if ( button == `ringo ) UI::ChangeWidget(`id(`favourite), `Value, "Ringo Starr" ); } until ( button == `ok ); }
Left, Right, Top, Bottom, HCenter, VCenter, HVCenter — Layout alignment
Left
( | child, | |
"dir/pixmap.png") ; |
term
child
;
`BackgroundPixmap(
"dir/pixmap.png"
;
Right
( | child, | |
"dir/pixmap.png") ; |
term
child
;
`BackgroundPixmap(
"dir/pixmap.png"
;
Top
( | child, | |
"dir/pixmap.png") ; |
term
child
;
`BackgroundPixmap(
"dir/pixmap.png"
;
Bottom
( | child, | |
"dir/pixmap.png") ; |
term
child
;
`BackgroundPixmap(
"dir/pixmap.png"
;
HCenter
( | child, | |
"dir/pixmap.png") ; |
term
child
;
`BackgroundPixmap(
"dir/pixmap.png"
;
VCenter
( | child, | |
"dir/pixmap.png") ; |
term
child
;
`BackgroundPixmap(
"dir/pixmap.png"
;
HVCenter
( | child, | |
"dir/pixmap.png") ; |
term
child
;
`BackgroundPixmap(
"dir/pixmap.png"
;The Alignment widgets are used to control the layout of a dialog. They are useful in situations, where to a widget is assigned more space than it can use. For example if you have a VBox containing four CheckBoxes, the width of the VBox is determined by the CheckBox with the longest label. The other CheckBoxes are centered per default.
With `Left( widget )
you tell a widget that it should be laid out leftmost of the space that is available to it. Right, Top
and Bottom
are working accordingly. The other three widgets center their child widget horizontally, vertically or in both directions.
As a very special case, alignment widgets that have `opt(`hvstretch) (and related) set promote their child widget's stretchability to the parent layout. I.e., they do not align a child that is stretchable in that dimension, but stretch it to consume the available space. This is only very rarely useful, such as in very generic layout code where the content of an alignment widget is usually unknown, and it might make sense to, say, center a child that is not stretchable, and OTOH to stretch a child that is stretchable.
An optional background pixmap can be specified as the first argument. UIs that support background pixmaps will then use the specified file as a (tiled) backgound image.
If that name does not start with "/" or ".", the theme path ("/usr/share/YaST2/theme/current/") will be prepended.
{ UI::OpenDialog( `VBox( `Label("This is a long label which makes space"), `HBox( `Label("A"), `HCenter(`Label("B")), `Label("C") ) ) ); UI::UserInput(); UI::CloseDialog(); }
{ UI::OpenDialog( `VBox( `Label("This is a very long label that makes space"), `HBox( `PushButton("Normal"), `HCenter(`PushButton("HCenter")) ) ) ); UI::UserInput(); UI::CloseDialog(); }
{ UI::OpenDialog(`opt(`defaultsize), `VBox( `VCenter(`PushButton(`opt(`vstretch), "Button 1")), `VCenter(`PushButton(`opt(`vstretch), "Button 2")), `VCenter(`PushButton(`opt(`vstretch), "Button 3")) ) ); UI::UserInput(); UI::CloseDialog(); }
{ UI::OpenDialog( `VBox( `PushButton("This is a very long button - it reserves extra space for the label."), `HBox( `PushButton(`opt(`hstretch), "Stretchable button"), `ReplacePoint(`id(`rp), `Label("Label")) ) ) ); UI::UserInput(); UI::ReplaceWidget(`id(`rp), `Left(`Label("Left"))); UI::UserInput(); UI::ReplaceWidget(`id(`rp), `Right(`Label("Right"))); UI::UserInput(); UI::ReplaceWidget(`id(`rp), `HCenter(`Label("HCenter"))); UI::UserInput(); }
LogView — scrollable log lines like "tail -f"
LogView
( | label, | |
visibleLines, | ||
maxLines) ; |
string
label
;
integer
visibleLines
;
integer
maxLines
;label
(above the log lines)
visibleLines
number of visible lines (without scrolling)
maxLines
number of log lines to store (use 0 for "all")
A scrolled output-only text window where ASCII output of any kind can be redirected - very much like a shell window with "tail -f".
The LogView will keep up to "maxLines" of output, discarding the oldest lines if there are more. If "maxLines" is set to 0, all lines will be kept.
"visibleLines" lines will be visible by default (without scrolling) unless you stretch the widget in the layout.
Use ChangeWidget( `id( `log ), `LastLine, "bla blurb...\n" )
to append one or several line(s) to the output. Notice the newline at the end of each line!
Use ChangeWidget( `id( `log ), `Value, "bla blurb...\n" )
to replace the entire contents of the LogView.
Use ChangeWidget( `id( `log ), `Value, "" )
to clear the contents.
{ string part1 = "They sought it with thimbles, they sought it with care;\n" + "They pursued it with forks and hope;\n" + "They threatened its life with a railway-share;\n" + "They charmed it with smiles and soap. \n" + "\n"; string part2 = "Then the Butcher contrived an ingenious plan\n" + "For making a separate sally;\n" + "And fixed on a spot unfrequented by man,\n" + "A dismal and desolate valley. \n" + "\n"; string part3 = "But the very same plan to the Beaver occurred:\n" + "It had chosen the very same place:\n" + "Yet neither betrayed, by a sign or a word,\n" + "The disgust that appeared in his face. \n" + "\n"; string part4 = "Each thought he was thinking of nothing but \"Snark\"\n" + "And the glorious work of the day;\n" + "And each tried to pretend that he did not remark\n" + "That the other was going that way. \n" + "\n"; string part5 = "But the valley grew narrow and narrower still,\n" + "And the evening got darker and colder,\n" + "Till (merely from nervousness, not from goodwill)\n" + "They marched along shoulder to shoulder. \n" + "\n"; string part6 = "Then a scream, shrill and high, rent the shuddering sky,\n" + "And they knew that some danger was near:\n" + "The Beaver turned pale to the tip of its tail,\n" + "And even the Butcher felt queer. \n" + "\n"; string part7 = "He thought of his childhood, left far far behind--\n" + "That blissful and innocent state--\n" + "The sound so exactly recalled to his mind\n" + "A pencil that squeaks on a slate! \n" + "\n"; string part8 = "\"'Tis the voice of the Jubjub!\" he suddenly cried.\n" + "(This man, that they used to call \"Dunce.\")\n" + "\"As the Bellman would tell you,\" he added with pride,\n" + "\"I have uttered that sentiment once.\n" + "\n"; string thats_it = "\n\n*** Press [OK] once more to exit. ***"; UI::OpenDialog( `VBox( `LogView(`id(`log), "&Excerpt from \"The Hunting Of The Snark\" by Lewis Carroll", 5, // visible lines 10), // lines to store `PushButton(`opt(`default), "&OK") ) ); UI::ChangeWidget(`id(`log), `LastLine, part1 ); UI::UserInput(); UI::ChangeWidget(`id(`log), `LastLine, part2 ); UI::UserInput(); UI::ChangeWidget(`id(`log), `LastLine, part3 ); UI::UserInput(); UI::ChangeWidget(`id(`log), `LastLine, part4 ); UI::UserInput(); UI::ChangeWidget(`id(`log), `LastLine, part5 ); UI::UserInput(); UI::ChangeWidget(`id(`log), `LastLine, part6 ); UI::UserInput(); UI::ChangeWidget(`id(`log), `LastLine, part7 ); UI::UserInput(); UI::ChangeWidget(`id(`log), `LastLine, part8 ); UI::UserInput(); UI::ChangeWidget(`id(`log), `Value, thats_it); UI::UserInput(); UI::CloseDialog(); }
MarginBox — Margins around one child widget
MarginBox
( | horMargin, | |
vertMargin, | ||
child) ; |
float
horMargin
;
float
vertMargin
;
term
child
;horMargin
margin left and right of the child widget
vertMargin
margin above and below the child widget
child
The contained child widget
This widget is a shorthand to add margins to the sides of a child widget (which may of course also be a VBox or a HBox, i.e. several widgets).
Unlike more complex constructs like nested VBox and HBox widgets with VSpacing and HSpacing at the sides, the margins of a MarginBox have lower layout priorities than the real content, so if screen space becomes scarce, the margins will be reduced first, and only if the margins are zero, the content will be reduced in size.
`MarginBox( 0.2, 0.3, `Label( "Hello" ) ); `MarginBox( `leftMargin( 0.7,), `rightMargin( 2.0 ), `topMargin( 0.3 ), `bottomMargin( 0.8 ), `Label( "Hello" ) );
{ UI::OpenDialog( `VBox( `MarginBox( 10, 2, `Label("Hello, World!") ), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ UI::OpenDialog( `VBox( `MarginBox(`leftMargin( 10 ), `rightMargin( 20 ), `topMargin( 2 ), `bottomMargin( 3.5 ), `Label("Hello, World!") ), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
MenuButton — Button with popup menu
MenuButton
( | label, | |
menu) ; |
string
label
;
itemList
menu
; This is a widget that looks very much like a PushButton
, but unlike a PushButton
it doesn't immediately start some action but opens a popup menu from where the user can select an item that starts an action. Any item may in turn open a submenu etc.
UserInput()
returns the ID of a menu item if one was activated. It will never return the ID of the MenuButton
itself.
Style guide hint: Don't overuse this widget. Use it for dialogs that provide lots of actions. Make the most frequently used actions accessible via normal PushButtons
. Move less frequently used actions (e.g. "expert" actions) into one or more MenuButtons
. Don't nest the popup menus too deep - the deeper the nesting, the more awkward the user interface will be.
You can (and should) provide keybord shortcuts along with the button label as well as for any menu item.
`MenuButton( "button label", [ `item( `id( `doit ), "& Do it" ), `item( `id( `something ), "& Something" ) ] );
// Build a dialog with one menu button. // Wait the user selects a menu entry, // then close the dialog and terminate. // // Please note that it's pretty pointless to create menu entries without an ID: // You'd never know what entry the user selected. { UI::OpenDialog( `MenuButton( "&Create", [ `item(`id(`folder), "&Folder" ), `item(`id(`text), "&Text File" ), `item(`id(`image), "&Image" ) ] ) ); any id = UI::UserInput(); UI::CloseDialog(); y2milestone( "Selected: %1", id ); }
// Build a dialog with one menu button with a submenu. // Wait the user selects a menu entry, // then close the dialog and terminate. // // Please note that it's pretty pointless to create menu entries without an ID: // You'd never know what entry the user selected. { UI::OpenDialog( `MenuButton( "&Create", [ `item(`id(`folder), "&Folder" ), `menu( "&Document", [ `item(`id(`text), "&Text File" ), `item(`id(`image), "&Image" ) ] ) ] ) ); any id = UI::UserInput(); UI::CloseDialog(); y2milestone( "Selected: %1", id ); }
MinWidth, MinHeight, MinSize — Layout minimum size
MinWidth
( | size, | |
child, | ||
height) ; |
float|integer
size
;
term
child
;
float|integer
height
;
MinHeight
( | size, | |
child, | ||
height) ; |
float|integer
size
;
term
child
;
float|integer
height
;
MinSize
( | size, | |
child, | ||
height) ; |
float|integer
size
;
term
child
;
float|integer
height
;size
minimum width (for MinWidth or MinSize) or minimum heigh (for MinHeight)
child
The contained child widget
This widget makes sure its one child never gets less screen space than the specified amount. It implicitly makes the child stretchable in that dimension.
// Simple example for MinWidth widget { UI::OpenDialog( `VBox( // SelectionBox blown up with MinWidth `MinWidth( 40, `SelectionBox( "", [ "Napoli", "Funghi", "Salami" ] ) ), // All hstretchable widgets in the same VBox will get // at least as wide as specified with MinWidth `SelectionBox( "", [ "Napoli", "Funghi", "Salami" ] ), // The same SelectionBox with default width // `Left is necessary to take away horizontal stretchability `Left( `SelectionBox( "", [ "Napoli", "Funghi", "Salami" ] ) ), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Simple example for MinHeight widget { UI::OpenDialog( `VBox( `HBox( // SelectionBox blown up with MinHeight `MinHeight( 12, `SelectionBox( "", [ "Napoli", "Funghi", "Salami" ] ) ), // All vstretchable widgets in the same HBox will get // at least as wide as specified with MinHeight `MinWidth( 25, `SelectionBox( "", [ "Napoli", "Funghi", "Salami" ] ) ), // The same SelectionBox with default width // `Top is necessary to take away vertical stretchability `Top( `SelectionBox( "", [ "Napoli", "Funghi", "Salami" ] ) ) ), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Simple example for MinSize { UI::OpenDialog( `VBox( `MinSize( 50, 12, `RichText( "<h3MinSize example</h3>" + "<p>MinSize is particularly useful in connection with widgets" + " that can scroll, such as" + "<ul>" + "<li>RichText" + "<li>SelectionBox" + "<li>Table" + "<li>MultiLineEdit" + "</ul>" + "since those widgets don't have a reasonable default size." + "</p>" ) ), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
MultiLineEdit — multiple line text edit field
MultiLineEdit
( | label, | |
initialValue) ; |
string
label
;
string
initialValue
;This widget is a multiple line text entry field with a label above it. An initial text can be provided.
Note: You can and should set a keyboard shortcut within the label. When the user presses the hotkey, the corresponding MultiLineEdit widget will get the keyboard focus.
{ UI::OpenDialog( `VBox( `MultiLineEdit("Problem &description:"), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Build dialog with one multi line edit field and an OK button. UI::OpenDialog( `VBox( `HSpacing(60), // force width `HBox( `VSpacing(7), // force height `MultiLineEdit(`id(`problem), "Problem &description:", // label "No problem here") // initial value ), `PushButton("&OK") ) ); // Wait for user input. UI::UserInput(); // Get the input from the MultiLineEdit. // // Notice: The return value of UI::UserInput() does NOT return this value! // Rather, it returns the ID of the widget (normally the PushButton) // that caused UI::UserInput() to return. string input = (string) UI::QueryWidget(`id(`problem), `Value); // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); // Pop up a new dialog to echo the input. UI::OpenDialog( `VBox( `Label("You entered:"), `Label(input), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Build dialog with MuliLineEdit widget, // a character counter for the MuliLineEdit's contents as they are typed // and an OK button. { UI::OpenDialog( `VBox( `MultiLineEdit(`id(`problem), `opt(`notify), // make UI::UserInput() return on every keystroke "Problem &description:" ), `HBox( `Label("Number of characters entered:"), `Label(`id(`char_count), "0 ") ), `PushButton(`id(`ok), "&OK") ) ); any ret = nil; do { // Wait for user input. // // Since the MultiLineEdit is in "notify" mode, it, too, will cause // UI::UserInput() to return upon any single character entered. ret = UI::UserInput(); if ( ret == `problem ) // User typed some text { // Set the `char_count label to the number of characters entered // into the MultiLineEdit widget. UI::ChangeWidget(`id(`char_count), `Value, sformat( "%1", size( (string) UI::QueryWidget(`id(`problem), `Value) ) ) ); } } while ( ret != `ok ); // Get the input from the MultiLineEdit. // // Notice: The return value of UI::UserInput() does NOT return this value! // Rather, it returns the ID of the widget (normally the PushButton) // that caused UI::UserInput() to return. string input = (string) UI::QueryWidget(`id(`problem), `Value); // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); // Pop up a new dialog to echo the input. UI::OpenDialog( `VBox( `Label("You entered:"), `Label(input), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ UI::OpenDialog( `VBox( `MultiLineEdit( `id( `input ), "Multi Line Edit", "pizza\npasta\npronta" ), `IntField( `id ( `field ), "Limit characters to...", -1, 100, -1 ), `PushButton( `id ( `butt ),"limit input" ), `PushButton( `id ( `exitButton ), "Exit" ) ) ); any ret = nil; ret = UI::UserInput(); while ( ret != `exitButton ) { integer chars = (integer) UI::QueryWidget(`id(`field), `Value); UI::ChangeWidget( `input, `InputMaxLength, chars ); ret = UI::UserInput(); } UI::CloseDialog(); }
MultiSelectionBox — Selection box that allows selecton of multiple items
MultiSelectionBox
( | label, | |
items) ; |
string
label
;
list
items
; The MultiSelectionBox displays a ( scrollable ) list of items from which any number (even nothing!) can be selected. Use the MultiSelectionBox's SelectedItems
property to find out which.
Each item can be specified either as a simple string or as `item( ... )
which includes an ( optional ) ID and an (optional) 'selected' flag that specifies the initial selected state ('not selected', i.e. 'false', is default).
`MultiSelectionBox( `id( `topping ), "select pizza toppings:", [ "Salami", `item( `id( `cheese ), "Cheese", true ) ] )
{ // Simple MultiSelectionBox example: // // All items are simple strings, none has an ID, no item preselected. UI::OpenDialog( `VBox( `MultiSelectionBox( "Select pizza toppings:", [ "Cheese", "Tomatoes", "Mushrooms", "Onions", "Salami", "Ham" ] ), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ // More realistic MultiSelectionBox example: // // Items have IDs, some are preselected. // Notice 'false' is default anyway for the selection state, // so you may or may not explicitly specify that. UI::OpenDialog( `VBox( `MultiSelectionBox( "Select pizza toppings:", [ `item( `id(`cheese ), "Cheese" , true ), `item( `id(`tomatoes ), "Tomatoes" , true ), `item( `id(`mush ), "Mushrooms" , false ), `item( `id(`onions ), "Onions" ), `item( `id(`sausage ), "Salami" ), `item( `id(`pork ), "Ham" ) ] ), `PushButton( `opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Advanced MultiSelectionBox example: // // Retrieve the list of selected items and output it. { UI::OpenDialog( `VBox( `MultiSelectionBox( `id(`toppings), "Select pizza toppings:", [ `item( `id(`cheese ), "Cheese" , true ), `item( `id(`tomatoes ), "Tomatoes" , true ), `item( `id(`mush ), "Mushrooms" , false ), `item( `id(`onions ), "Onions" ), `item( `id(`sausage ), "Salami" ), `item( `id(`pork ), "Ham" ) ] ), `PushButton( `opt(`default), "&OK") ) ); UI::ChangeWidget(`toppings, `SelectedItems, [`sausage, `onions] ); UI::UserInput(); list selected_items = (list) UI::QueryWidget( `id(`toppings), `SelectedItems ); y2debug( "Selected items: %1", selected_items ); // Remember to retrieve the widget's data _before_ the dialog is closed, // i.e. before it is destroyed! UI::CloseDialog(); // Concatenate the list of selected toppings to one multi-line string. string pizza_description = ""; foreach ( `topping, selected_items, ``{ pizza_description = sformat( "%1\n%2", pizza_description, topping ); } ); // Open a new dialog to echo the selection. UI::OpenDialog( `VBox( `Label( "Your pizza will come with:\n" ), `Label( pizza_description ), `PushButton( `opt(`default), "&OK" ) ) ); UI::UserInput(); UI::CloseDialog(); }
// Example showing how to replace SelectionBox items { list all_toppings = [ "Cheese", "Ham", "Mushrooms", "Pepperoni", "Rucola", "Salami", "Tomatoes", "Tuna" ]; list veggie_toppings = [ "Cheese", "Mushrooms", "Pepperoni", "Rucola", "Tomatoes" ]; UI::OpenDialog(`HBox(`VSpacing(15), // layout trick: force minimum height `VBox( `HSpacing(25), // force minimum width `MultiSelectionBox(`id(`toppings), "Toppings:", all_toppings ), `Left( `CheckBox(`id(`veggie), `opt(`notify), "&Vegetarian" ) ), `PushButton(`id(`ok), "&OK" ) ) ) ); symbol button = nil; do { button = (symbol) UI::UserInput(); if ( button == `veggie ) { boolean vegetarian = (boolean) UI::QueryWidget(`veggie, `Value ); if ( vegetarian ) UI::ChangeWidget(`toppings, `Items, veggie_toppings ); else UI::ChangeWidget(`toppings, `Items, all_toppings ); } } while ( button != `ok ); list<string> order = (list<string>) UI::QueryWidget(`toppings, `SelectedItems ); UI::CloseDialog(); // // Show the result // UI::OpenDialog(`VBox( `Label( sformat( "Your order: %1", mergestring( order, ", " ) ) ), `PushButton(`opt(`default), "&OK" ) ) ); UI::UserInput(); UI::CloseDialog(); }
// Example showing how to replace SelectionBox items { list all_toppings = [ `item(`id( "Cheese" ), "Cheese", true ), `item(`id( "Tomatoes" ), "Tomatoes", true ), `item(`id( "Ham" ), "Ham" ), `item(`id( "Mushrooms" ), "Mushrooms" ), `item(`id( "Pepperoni" ), "Pepperoni" ), `item(`id( "Rucola" ), "Rucola" ), `item(`id( "Salami" ), "Salami" ), `item(`id( "Tuna" ), "Tuna" ) ]; list veggie_toppings = [ `item(`id( "Cheese" ), "Cheese", true ), `item(`id( "Tomatoes" ), "Tomatoes", true ), `item(`id( "Mushrooms" ), "Mushrooms" ), `item(`id( "Pepperoni" ), "Pepperoni" ), `item(`id( "Rucola" ), "Rucola" ) ]; UI::OpenDialog(`HBox(`VSpacing(15), // layout trick: force minimum height `VBox( `HSpacing(25), // force minimum width `MultiSelectionBox(`id(`toppings), "Toppings:", all_toppings ), `Left( `CheckBox(`id(`veggie), `opt(`notify), "&Vegetarian" ) ), `PushButton(`id(`ok), "&OK" ) ) ) ); symbol button = nil; do { button = (symbol) UI::UserInput(); if ( button == `veggie ) { boolean vegetarian = (boolean) UI::QueryWidget(`veggie, `Value ); if ( vegetarian ) UI::ChangeWidget(`toppings, `Items, veggie_toppings ); else UI::ChangeWidget(`toppings, `Items, all_toppings ); } } while ( button != `ok ); list<string> order = (list<string>) UI::QueryWidget(`toppings, `SelectedItems ); UI::CloseDialog(); // // Show the result // UI::OpenDialog(`VBox( `Label( sformat( "Your order: %1", mergestring( order, ", " ) ) ), `PushButton(`opt(`default), "&OK" ) ) ); UI::UserInput(); UI::CloseDialog(); }
PackageSelector — Complete software package selection
PackageSelector
( | ) ; |
youMode
start in YOU (YaST Online Update) mode
updateMode
start in update mode
searchMode
start with the "search" filter view
summaryMode
start with the "installation summary" filter view
repoMode
start with the "repositories" filter view
repoMgr
enable "Repository Manager" menu item
confirmUnsupported
user has to confirm all unsupported (non-L3) packages
A very complex widget that handles software package selection completely transparently. Set up the package manager (the backend) before creating this widget and let the package manager and the package selector handle all the rest. The result of all this are the data stored in the package manager.
Use UI::RunPkgSelection() after creating a dialog with this widget. The result of UI::UserInput() in a dialog with such a widget is undefined - it may or may not return.
This widget gets the (best) floppy device as a parameter since the UI has no general way of finding out by itself what device can be used for saving or loading pacakge lists etc. - this is best done outside and passed here as a parameter.
// Package Selector example { // Pkg::SourceCreate( "http://dist.suse.de/install/SLP/SUSE-10.1-Beta7/i386/CD1/", "" ); // Pkg::SourceCreate( "http://dist.suse.de/install/SLP/SUSE-10.0-RC4/i386/CD1/", "" ); // Pkg::SourceCreate( "file:/srv/10.1-i386/CD1/", "" ); Pkg::SourceCreate( "file:/srv/10.1-i386/DVD1/", "" ); // Pkg::SourceCreate( "file:/srv/sles-10-i386/CD1/", "" ); if ( true ) { Pkg::TargetInit( "/", // installed system false ); // don't create a new RPM database } UI::OpenDialog(`opt(`defaultsize), `PackageSelector(`id(`selector), "/dev/fd0" ) ); any input = UI::RunPkgSelection(`id(`selector) ); UI::CloseDialog(); y2milestone( "Input: %1", input ); }
PartitionSplitter — Hard disk partition splitter tool (optional widget)
PartitionSplitter
( | usedSize, | |
totalFreeSize, | ||
newPartSize, | ||
minNewPartSize, | ||
minFreeSize, | ||
usedLabel, | ||
freeLabel, | ||
newPartLabel, | ||
freeFieldLabel, | ||
newPartFieldLabel) ; |
integer
usedSize
;
integer
totalFreeSize
;
integer
newPartSize
;
integer
minNewPartSize
;
integer
minFreeSize
;
string
usedLabel
;
string
freeLabel
;
string
newPartLabel
;
string
freeFieldLabel
;
string
newPartFieldLabel
;usedSize
size of the used part of the partition
totalFreeSize
total size of the free part of the partition (before the split)
newPartSize
suggested size of the new partition
minNewPartSize
minimum size of the new partition
minFreeSize
minimum free size of the old partition
usedLabel
BarGraph label for the used part of the old partition
freeLabel
BarGraph label for the free part of the old partition
newPartLabel
BarGraph label for the new partition
freeFieldLabel
label for the remaining free space field
newPartFieldLabel
label for the new size field
A very specialized widget to allow a user to comfortably split an existing hard disk partition in two parts. Shows a bar graph that displays the used space of the partition, the remaining free space (before the split) of the partition and the space of the new partition (as suggested). Below the bar graph is a slider with an input fields to the left and right where the user can either input the desired remaining free space or the desired size of the new partition or drag the slider to do this.
The total size is usedSize+freeSize
.
The user can resize the new partition between minNewPartSize
and totalFreeSize-minFreeSize
.
![]() | Note |
---|---|
This is a "special" widget, i.e. not all UIs necessarily support it. Check for availability with |
if ( HasSpecialWidget( `PartitionSplitter ) {... `PartitionSplitter( 600, 1200, 800, 300, 50, "Windows used\n%1 MB", "Windows used\n%1 MB", "Linux\n%1 MB", "Linux ( MB )" )
{ if ( ! UI::HasSpecialWidget(`PartitionSplitter) ) { UI::OpenDialog( `VBox( `Label("Error: This UI doesn't support the PartitionSplitter widget!"), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); return; } string unit = "MB"; integer win_used = 350; integer total_free = 1500; integer min_free = 50; integer linux_min = 300; integer linux_size = 800; UI::OpenDialog( `VBox( `HSpacing( 60 ), // wider default size `PartitionSplitter( win_used, total_free, linux_size, linux_min, min_free, "Windows\nused\n%1 " + unit, "Windows\nfree\n%1 " + unit, "Linux\n%1 " + unit, "Windows free (" + unit + ")", "Linux (" + unit + ")" ), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ if ( ! UI::HasSpecialWidget(`Slider) || ! UI::HasSpecialWidget(`BarGraph ) ) { UI::OpenDialog( `VBox( `Label("Error: This UI doesn't support the required special widgets!"), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); return; } string unit = "MB"; integer win_used = 350; integer total_free = 1500; integer min_free = 50; integer linux_min = 300; integer linux_size = 800; UI::OpenDialog( `VBox( `HSpacing( 60 ), // wider default size `Left( `Label( "Now:") ), `BarGraph( `opt(`vstretch), [ win_used, total_free ], [ "Windows\nused\n%1 " + unit, "Windows\nfree\n%1 " + unit ] ), `VSpacing(1), `Left( `Label( "After installation:" ) ), `PartitionSplitter( win_used, total_free, linux_size, linux_min, min_free, "Windows\nused\n%1 " + unit, "Windows\nfree\n%1 " + unit, "Linux\n%1 " + unit, "Windows free (" + unit + ")", "Linux (" + unit + ")" ), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
PatternSelector — High-level widget to select software patterns (selections)
PatternSelector
( | ) ; |
This widget is similar to the PackageSelector in its semantics: It is a very high-level widget that lets the user select software, but unlike the PackageSelector it works on software patterns (selections).
if ( UI::HasSpecialWidget( `PatternSelector) {... `PatternSelector()... UI::RunPkgSelection();
// Simple example for PatternSelector { if ( ! UI::HasSpecialWidget(`PatternSelector ) ) { UI::OpenDialog( `VBox( `Label("Error: This UI doesn't support the PatternSelector widget!"), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); return; } Pkg::TargetInit( "/", // installed system false ); // don't create a new RPM database UI::OpenDialog(`opt(`defaultsize), `PatternSelector(`id(`selector) ) ); any input = UI::RunPkgSelection(`id(`selector) ); UI::CloseDialog(); y2milestone( "Input: %1", input ); }
// Full-fledged pattern selection { textdomain "bogus"; // Pkg::SourceCreate( "http://dist.suse.de/install/SLP/SUSE-10.1-Beta3/i386/CD1/", "" ); Pkg::SourceCreate( "file:/srv/sles-10-i386/CD1/", "" ); void detailedSelection() { // Open empty dialog for instant feedback UI::OpenDialog(`opt(`defaultsize), `ReplacePoint(`id( `rep), `Label( "Reading package database..." ) ) ); // This will take a while: Detailed package data are retrieved // while the package manager is initialized UI::ReplaceWidget(`rep, `PackageSelector(`id(`packages ), "/dev/fd0" ) ); symbol input = (symbol) UI::RunPkgSelection(`id(`packages ) ); y2milestone( "Package selector returned %1", input ); UI::CloseDialog(); } if ( ! UI::HasSpecialWidget(`PatternSelector ) ) { detailedSelection(); // Fallback: Do detailed selection right away return; } UI::OpenDialog(`opt(`defaultsize ), `Wizard(`back, "", `cancel, "&Cancel", `ok, "&OK" ) ); string help_text = _( "<p>" "The available software for this system is shown by category in the left " "column. To view a description for an item, select it in the list." "</p>" ) + _( "<p>" "Change the status of items by clicking on their status icon " "or right-click on any icon for a context menu. " "With the context menu you can also change the status of all items." "</p>" ) + _( "<p>" "<b>Details</b> opens the detailed software package selection " "where you can view and select individual software packages." "</p>" ) + _( "<p>" "The <b>disk usage</b> display in the lower right corner shows the remaining disk space " "after all requested changes will have been performed. " "Please notice that hard disk partitions that are full or nearly full can degrade " "system performance and in some cases even cause serious problems. " "The system needs some available disk space to run properly." "</p>" ); UI::WizardCommand(`SetDialogIcon( "/usr/share/YaST2/theme/current/icons/22x22/apps/YaST.png" ) ); UI::WizardCommand(`SetDialogHeading( "Software Selection" ) ); UI::WizardCommand(`SetHelpText( help_text ) ); Pkg::TargetInit( "/", // installed system false ); // don't create a new RPM database UI::ReplaceWidget(`id(`contents), `PatternSelector(`id(`patterns ) ) ); symbol button = nil; repeat { button = (symbol) UI::RunPkgSelection(`id(`patterns ) ); y2milestone( "Pattern selector returned %1", button ); if ( button == `details ) detailedSelection(); } until ( button == `cancel || button == `accept ); UI::CloseDialog(); }
ProgressBar — Graphical progress indicator
ProgressBar
( | label, | |
maxvalue, | ||
progress) ; |
string
label
;
integer
maxvalue
;
integer
progress
;maxvalue
the maximum value of the bar
progress
the current progress value of the bar
A progress bar is a horizontal bar with a label that shows a progress value. If you omit the optional parameter maxvalue
, the maximum value will be 100. If you omit the optional parameter progress
, the progress bar will set to 0 initially.
If you don't know the number of total steps you might want to use the BusyIndicator widget instead of ProgressBar.
// Simple ProgressBar example { integer max_progress = 7; integer progress = 0; UI::OpenDialog( `VBox( `ProgressBar(`id(`pr), "Sample progress bar", max_progress, progress ), `PushButton(`id(`next), "Next"), `Right(`PushButton(`id(`close), "&Close" ) ) ) ); while ( progress < max_progress ) { symbol button = (symbol) UI::UserInput(); if ( button == `next ) { progress = progress + 1; UI::ChangeWidget(`id(`pr), `Value, progress); UI::ChangeWidget(`id(`pr), `Label, sformat("Progress %1 of %2", progress, max_progress )); } else if ( button == `close ) break; } UI::CloseDialog(); }
// ProgressBar example { UI::OpenDialog( `VBox( `Heading("Adjust the volume"), `ProgressBar(`id(`vol), "Volume", 100, 50), `HBox( `PushButton(`id(`down), "<<"), `PushButton(`id(`up ), ">>"), `HStretch(), `HSpacing(3), `PushButton(`id(`cancel), "&Close" ) ) ) ); while (true) { symbol button = (symbol) UI::UserInput(); if (button == `cancel) break; else if ( button == `down || button == `up ) { integer volume = (integer) UI::QueryWidget(`id(`vol), `Value); if ( button == `down ) volume = volume - 5; if ( button == `up ) volume = volume + 5; y2milestone( "Volume: %1", volume ); UI::ChangeWidget(`id(`vol), `Value, volume ); } } UI::CloseDialog(); }
PushButton, IconButton — Perform action on click
PushButton
( | iconName, | |
label) ; |
string
iconName
;
string
label
;
IconButton
( | iconName, | |
label) ; |
string
iconName
;
string
label
;default
makes this button the dialogs default button
helpButton
automatically shows topmost `HelpText
okButton
assign the [OK] role to this button (see ButtonBox)
cancelButton
assign the [Cancel] role to this button (see ButtonBox)
applyButton
assign the [Apply] role to this button (see ButtonBox)
customButton
override any other button role assigned to this button
A PushButton
is a button with a text label the user can press in order to activate some action. If you call UserInput()
and the user presses the button, UserInput()
returns with the id of the pressed button.
You can (and should) provide keybord shortcuts along with the button label. For example "& Apply" as a button label will allow the user to activate the button with Alt-A, even if it currently doesn't have keyboard focus. This is important for UIs that don't support using a mouse.
An IconButton
is pretty much the same, but it has an icon in addition to the text. If the UI cannot handle icons, it displays only the text, and the icon is silently omitted.
Icons are (at the time of this writing) loaded from the theme directory, /usr/share/YaST2/theme/current.
If a button has `opt(`helpButton) set, it is the official help button of this dialog. When activated, this will open a new dialog with the topmost help text in this dialog (the topmost widget that has a property `HelpText) in a pop-up dialog with a local event loop. Note that this is not done during UI::PollInput() to prevent the application from blocking as long as the help dialog is open.
Since a help button is handled internally by the UI, UI::UserInput() and related will never return this button's ID.
{ // Build a dialog with one button. // Wait until that button is clicked, // then close the dialog and terminate. UI::OpenDialog( `PushButton( "&OK" ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Build dialog with three buttons. // "Cancel" is the default button, i.e. pressing "Return" will // activate it. UI::OpenDialog( `HBox( `PushButton( `id(`ok), "&OK" ), `PushButton( `id(`cancel), `opt(`default), "&Cancel" ), `PushButton( `id(`help), "&Help" ) ) ); // Wait for user input. The value returned is the ID of the widget // that makes UI::UserInput() terminate, i.e. the respective button ID. any button_id = UI::UserInput(); // Close the dialog. UI::CloseDialog(); // Process the input. string button_name = ""; if ( button_id == `ok ) button_name = "OK"; else if ( button_id == `cancel ) button_name = "Cancel"; else if ( button_id == `help ) button_name = "Help"; // Pop up a new dialog to display what button was clicked. UI::OpenDialog( `VBox( `Label( "You clicked button \"" + button_name + "\"."), `PushButton( `opt(`default), "&OK" ) ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Build a dialog with one icon button. // Wait until that button is clicked, // then close the dialog and terminate. UI::OpenDialog( `IconButton( "icons/22x22/apps/yast-users.png", "&OK" ) ); UI::UserInput(); UI::CloseDialog(); }
RadioButton — Clickable on/off toggle button for radio boxes
RadioButton
( | label, | |
selected) ; |
string
label
;
boolean
selected
; A radio button is not usefull alone. Radio buttons are group such that the user can select one radio button of a group. It is much like a selection box, but radio buttons can be dispersed over the dialog. Radio buttons must be contained in a RadioButtonGroup
.
{ UI::OpenDialog( `RadioButtonGroup(`id(`rb), `VBox( `Label("How do you want to crash?"), `Left(`RadioButton(`id(0), "No&w")), `Left(`RadioButton(`id(1), "&Every now and then" )), `Left(`RadioButton(`id(2), "Every &five minutes", true)), `Left(`RadioButton(`id(3), `opt(`boldFont), "Ne&ver", true )), `HBox( `PushButton(`id(`next), "&Next"), `PushButton("&OK") ) ) ) ); while (true) { any ret = UI::UserInput(); if (ret == `next) { integer current = (integer) UI::QueryWidget(`id(`rb), `CurrentButton); current = (current + 1) % 4; UI::ChangeWidget(`id(`rb), `CurrentButton, current); } else break; } UI::CloseDialog(); }
{ UI::OpenDialog( `RadioButtonGroup(`id(`rb), `VBox( `Label("How do you want to crash?"), `Left(`RadioButton(`id(0), `opt(`notify), "No&w")), `Left(`RadioButton(`id(1), `opt(`notify), "&Every now an then" )), `Left(`RadioButton(`id(2), `opt(`notify), "Every &five minutes")), `Left(`RadioButton(`id(3), `opt(`notify), "Ne&ver", true )), `HBox( `PushButton(`id(`next), "&Next"), `PushButton(`id(`close), "&Close") ) ) ) ); while (true) { any ret = UI::UserInput(); if ( ret == `close ) break; else if (ret == `next) { // y2milestone("Hit next"); integer current = (integer) UI::QueryWidget(`id(`rb), `CurrentButton); current = (current + 1) % 4; UI::ChangeWidget(`id(`rb), `CurrentButton, current); } else { y2milestone("Hit RadioButton #%1", ret); } } y2milestone("Terminating."); UI::CloseDialog(); }
{ UI::OpenDialog( `VBox( `Frame ( "CPU &Speed", `RadioButtonGroup( `VBox( `Left(`RadioButton("Normal" )), `Left(`RadioButton("Overclocked" )), `Left(`RadioButton("Red Hot" )), `Left(`RadioButton("Melting", true )) ) ) ), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Demo for shortcut checking: // Deliberately generate a shortcut conflict. UI::OpenDialog( `VBox( `RadioButtonGroup( `VBox( `Left( `RadioButton( "Instant &Play", true ) ), `Left( `RadioButton( "Demo &Only" ) ) ) ), `Left( `ComboBox( "Game &Type:", [ "Boring", "Full automatic", "Unplayable" ] ) ), `InputField( "Nickname for &Player 1:" ), `InputField( "Nickname for &Player 2:" ), `IntField( "Maximum &Time:", 0, 100, 20 ), `PushButton( "&OK" ) ) ); UI::UserInput(); UI::CloseDialog(); }
RadioButtonGroup — Radio box - select one of many radio buttons
RadioButtonGroup
( | child) ; |
term
child
; A RadioButtonGroup
is a container widget that has neither impact on the layout nor has it a graphical representation. It is just used to logically group RadioButtons together so the one-out-of-many selection strategy can be ensured.
Radio button groups may be nested. Looking bottom up we can say that a radio button belongs to the radio button group that is nearest to it. If you give the RadioButtonGroup
widget an id, you can use it to query and set which radio button is currently selected.
{ UI::OpenDialog( `RadioButtonGroup(`id(`rb), `VBox( `Label("How do you want to crash?"), `Left(`RadioButton(`id(0), "No&w")), `Left(`RadioButton(`id(1), "&Every now and then" )), `Left(`RadioButton(`id(2), "Every &five minutes", true)), `Left(`RadioButton(`id(3), `opt(`boldFont), "Ne&ver", true )), `HBox( `PushButton(`id(`next), "&Next"), `PushButton("&OK") ) ) ) ); while (true) { any ret = UI::UserInput(); if (ret == `next) { integer current = (integer) UI::QueryWidget(`id(`rb), `CurrentButton); current = (current + 1) % 4; UI::ChangeWidget(`id(`rb), `CurrentButton, current); } else break; } UI::CloseDialog(); }
{ UI::OpenDialog( `VBox( `Frame ( "CPU &Speed", `RadioButtonGroup( `VBox( `Left(`RadioButton("Normal" )), `Left(`RadioButton("Overclocked" )), `Left(`RadioButton("Red Hot" )), `Left(`RadioButton("Melting", true )) ) ) ), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
ReplacePoint — Pseudo widget to replace parts of a dialog
ReplacePoint
( | child) ; |
term
child
; A ReplacePoint can be used to dynamically change parts of a dialog. It contains one widget. This widget can be replaced by another widget by calling ReplaceWidget( `id( id ), newchild )
, where id
is the the id of the new child widget of the replace point. The ReplacePoint widget itself has no further effect and no optical representation.
{ UI::OpenDialog( `VBox( `ReplacePoint(`id(`rp), `Label("This is a label")), `PushButton(`id(`change), "Change"))); UI::UserInput(); UI::ReplaceWidget(`id(`rp), `PushButton("This is a PushButton")); UI::UserInput(); UI::ReplaceWidget(`id(`rp), `CheckBox("This is a CheckBox")); UI::UserInput(); UI::ReplaceWidget(`id(`rp), `HBox(`PushButton("Button1"), `PushButton("Button2"))); UI::UserInput(); UI::CloseDialog(); }
// Typical usage example for tab widget { term address_page = `VBox( `Left( `Heading( "Address" ) ), `VSpacing(), `HCenter( `HSquash( `VBox( `HSpacing( 50 ), `InputField( "Name" ), `InputField( "E-Mail" ), `InputField( "Phone" ), `VSpacing(), `MultiLineEdit( "Comments" ), `VStretch() ) ) ) ); term overview_page = `VBox( `Left( `Heading( "DumbTab Widget Overview" ) ), `VSpacing(), `Label( "This kind of tab is pretty dumb - hence the name DumbTab.\n" + "You need to do most everything yourself.\n" + "Each tab behaves very much like a push button;\n" + "the YCP application is notified when the user clicks on a tab.\n" + "The application must take care to exchange the tab contents.\n" + "\n" + "Note: That means changes made in on tab are lost when switching\n" + "the tabs, e.g. text entered here in the address tab.") ); term style_hints_page = `VBox( `Left( `Heading( "GUI Style Hints" ) ), `VSpacing(), `Heading( "Using tabs is usually a result of poor dialog design." ), `VSpacing(), `Left( `Label( "Tabs hide complexity, they do not resolve it.\n" + "The problem remains just as complex as before,\n" + "only the user can no longer see it." ) ) ); UI::OpenDialog(`opt(`defaultsize), `VBox( `DumbTab( [ `item(`id(`address ), "&Address" ), `item(`id(`overview ), "&Overview" ), `item(`id(`style ), "GUI &Style Hints", true ) // true: selected ], `Left( `Top( `HVSquash( `VBox( `VSpacing(0.3), `HBox( `HSpacing(1), `ReplacePoint(`id(`tabContents ), style_hints_page ) ) ) ) ) ) ), `Right(`PushButton(`id(`close), "Cl&ose" ) ) ) ); while ( true ) { symbol widget = (symbol) UI::UserInput(); if ( widget == `close ) break; else if ( widget == `address ) UI::ReplaceWidget(`tabContents, address_page ); else if ( widget == `overview ) UI::ReplaceWidget(`tabContents, overview_page ); else if ( widget == `style ) UI::ReplaceWidget(`tabContents, style_hints_page ); } UI::CloseDialog(); }
{ // Demo for postponed shortcut checking term b_kilroy = `PushButton(`opt(`hstretch), "&Kilroy"); term b_was = `PushButton(`opt(`hstretch), "&was"); term b_here = `PushButton(`opt(`hstretch), "&here"); UI::OpenDialog( `VBox( `VSpacing( 0.3 ), `HBox( `HSpacing(), `Label("Click on one of the upper buttons\n" + "to exchange them." ), `HSpacing() ), `VSpacing( 0.3 ), `ReplacePoint(`id(1), b_kilroy ), `ReplacePoint(`id(2), b_was ), `ReplacePoint(`id(3), b_here ), `VSpacing(`opt(`vstretch) ), `Right( `PushButton( `id(`cancel), "&Quit" ) ) ) ); any button = nil; y2milestone( "Initial state: 'Kilroy was here'" ); button = UI::UserInput(); if ( button == `cancel ) return; UI::PostponeShortcutCheck(); // "&Kilroy" "&was" "&here" UI::ReplaceWidget(`id(1), b_was ); // "&was" "&was" "&here" UI::ReplaceWidget(`id(2), b_kilroy ); // "&was" "&Kilroy" "&here" UI::CheckShortcuts(); y2milestone( "After change: 'was Kilroy here'" ); button = UI::UserInput(); if ( button == `cancel ) return; UI::PostponeShortcutCheck(); // "&was" "&Kilroy" "&here" UI::ReplaceWidget(`id(1), b_here ); // "&here" "&Kilroy" "&here" UI::ReplaceWidget(`id(2), b_was ); // "&here" "&was" "&here" UI::ReplaceWidget(`id(3), b_kilroy ); // "&here" "&was" "&Kilroy" UI::CheckShortcuts(); y2milestone( "After change: 'here was Kilroy'" ); button = UI::UserInput(); if ( button == `cancel ) return; UI::PostponeShortcutCheck(); // "&here" "&was" "&Kilroy" UI::ReplaceWidget(`id(1), b_kilroy ); // "&Kilroy" "&was" "&Kilroy" UI::ReplaceWidget(`id(3), b_here ); // "&Kilroy" "&was" "&here" // Omitting UI::CheckShortcuts(); y2milestone( "After change: back to 'Kilroy was here'" ); y2milestone( "Expect complaint about missing UI::CheckShortcuts()" ); button = UI::UserInput(); if ( button == `cancel ) return; y2milestone( "Expect shortcut conflict: '&was' vs. '&was'" ); UI::ReplaceWidget(`id(1), b_was ); // "&was" "&was" "&here" UI::ReplaceWidget(`id(2), b_kilroy ); // "&was" "&Kilroy" "&here" y2milestone( "Expect complaint about excess UI::CheckShortcuts()" ); UI::CheckShortcuts(); y2milestone( "After change: 'was Kilroy here'" ); button = UI::UserInput(); if ( button == `cancel ) return; UI::CloseDialog(); }
{ /** * Return a text summary of existing buttons. **/ define string Summary() ``{ string summary = ( UI::WidgetExists(`id(`top ) ) ? "Top button exists" : "No top button" ) + ( UI::WidgetExists(`id(`center ) ) ? "\nCenter button exists" : "\nNo center button" ) + ( UI::WidgetExists(`id(`bottom ) ) ? "\nBottom button exists" : "\nNo bottom button" ); return summary; }; /** * Remove button with given id and update summary. **/ define void RemoveButton( term id ) ``{ UI::ReplaceWidget( id, `Empty() ); UI::ChangeWidget( `id(`summary), `Value, Summary() ); UI::RecalcLayout(); } /** * main **/ UI::OpenDialog( `VBox( `ReplacePoint( `id(`rp_top ), `PushButton(`id(`top ), "Top Button" ) ), `ReplacePoint( `id(`rp_center ), `PushButton(`id(`center ), "Center Button" ) ), `ReplacePoint( `id(`rp_bottom ), `PushButton(`id(`bottom ), "Bottom Button" ) ), `VSpacing( 1 ), `Label(`id(`summary), "" ), `VSpacing( 1 ), `PushButton(`id(`close), "&Close") ) ); // Better wait until now before doing the summary - it isn't much use before UI::OpenDialog() // since of course all UI::WidgetExists() calls return false until then. UI::ChangeWidget( `id(`summary), `Value, Summary() ); UI::RecalcLayout(); any button = nil; repeat { button = UI::UserInput(); if ( button == `top ) RemoveButton(`id(`rp_top ) ); else if ( button == `center ) RemoveButton(`id(`rp_center ) ); else if ( button == `bottom ) RemoveButton(`id(`rp_bottom ) ); } until ( button == `close ); UI::CloseDialog(); }
RichText — Static text with HTML-like formatting
RichText
( | text) ; |
string
text
;plainText
don't interpret text as HTML
autoScrollDown
automatically scroll down for each text change
shrinkable
make the widget very small
A RichText
is a text area with two major differences to a Label
: The amount of data it can contain is not restricted by the layout and a number of control sequences are allowed, which control the layout of the text.
// Example for a RichText widget { UI::OpenDialog( `opt(`defaultsize), `VBox( `RichText( "<h3>RichText example</h3>" + "<p>This is a <i>RichText</i> widget.</p>" + "<p>It's very much like <i>HTML</i>, but not quite as powerful.</p>" + "<p><b>bold</b> and <i>italic</i> you can rely on.</p>" + "<p>" + "<font color=blue>colored </font>" + "<font color=red> text </font>" + "<font color=green> might </font>" + "<font color=magenta> or </font>" + "<font color=cyan> might </font>" + "<font color=blue> not </font>" + "<font color=red> be </font>" + "<font color=green> available,</font>" + "<font color=magenta> depending </font>" + "<font color=cyan> on </font>" + "<font color=blue> the </font>" + "<font color=red> UI. </font>" + "</p>" + "<p>The product name is automatically replaced by the UI. " + "Use the special macro <b>&product;</b> for that." + "</p><p>" + "The current product name is <b>&product;</b>." + "</p>" ), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Example for a RichText widget { UI::OpenDialog( `opt(`defaultsize), `VBox( `RichText(`opt(`plainText), "This is a RichText in plainText mode.\n" +"No HTML tags are supported here, but line breaks\n" +"are output literally - as are HTML tags like <b> or <i> or &product;." ), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Example for a RichText widget { UI::OpenDialog( `opt(`defaultsize), `VBox( `RichText( "<h3>RichText example</h3>" + "<p>This is a <i>RichText</i> widget. The ncurses UI wraps lines automatically - Qt breaks lines and uses a vertical scroll bar.</p>" + "<p>It's very much like <i>HTML</i>, but not quite as powerful.</p>" + "<p>Text in <b>pre</b> tags preserves newlines and spaces.</p>" + "<pre>" + " host ip adress host ip adress\n" + " sturm 10.10.0.159 sturm 10.10.0.159\n" + "Lines are not wrapped; in text mode HTML tags are <b>not</b> removed if the pre tag is used.\n" + "</pre>" + "<p>After <b>/pre</b> the text is <i>HTML</i> text like before.</p>" + "<p>Much much more text .............. continous" + " much much more text .... follows " + " much much more text .................</p>" + "<p>" + "<pre>another pre t t </pre>" + "and much more text hopefully now displayed correctly" + "</p>" + "<pre>" + " host ip adress host ip adress\n" + " sturm 10.10.0.159 sturm 10.10.0.159\n" + "Lines are not wrapped; in text mode HTML tags are <b>not</b> removed if the pre tag is used.\n" + "</pre>" + "<p>and even much more text but now not wrapped at old place but with new layout of the pad widget which is underlying the richtext widget</p>" + "<p><pre>And another short pre</pre></p>" ), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Example for a RichText widget with hyperlinks { UI::OpenDialog( `opt(`defaultsize), `VBox( `RichText( "<h3>RichText example</h3>" + "<p>RichText may contain <a href=\"hyper\">hyperlinks</a>," + "very much like HTML.</p>" + "<p>Clicking on a <a href=\"link\">link</a> will make " + "<a href=\"user_input\">UI::UserInput()</a> return " + "with the <i>href</i> part of the hyperlink " + "as a (string) return value.</p>" + "<h3>Known (HTML-like) entities</h3>" + "<ul>" + "<li><b>&product;</b> for the product name (<b>&product;</b>)" + "<li><b>&amp;</b> for <b>&</b>" + "<li><b>&lt;</b> for <b><</b>" + "<li><b>&gt;</b> for <b>></b>" + "</ul>" ), `HBox( `Label( "You clicked: " ), `Label(`id(`val), `opt(`outputField, `hstretch), "" ) ), `PushButton(`id(`close), `opt(`default), "&Close") ) ); any ret = nil; repeat { ret = UI::UserInput(); if ( ret != nil ) { string val = sformat( "%1", ret ); UI::ChangeWidget(`id(`val), `Value, val ); } } until ( ret == `close ); UI::CloseDialog(); }
SelectionBox — Scrollable list selection
SelectionBox
( | label, | |
items) ; |
string
label
;
list
items
;shrinkable
make the widget very small
immediate
make `notify trigger immediately when the selected item changes
A selection box offers the user to select an item out of a list. Each item has a label and an optional id. When constructing the list of items, you have two way of specifying an item. Either you give a plain string, in which case the string is used both for the id and the label of the item. Or you specify a term `item( term id, string label )
or `item( term id, string label, boolean selected )
, where you give an id of the form `id( any v )
where you can store an aribtrary value as id. The third argument controls whether the item is the selected item.
`SelectionBox( `id( `pizza ), "select your Pizza:", [ "Margarita", `item( `id( `na ), "Napoli" ) ] )
{ UI::OpenDialog( `VBox( `SelectionBox( "Select your Pizza:", [ "Napoli", "Funghi", "Salami" ] ), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Simple SelectionBox example { // Create a selection box with three entries. // All entries have IDs to identify them independent of locale // (The texts might have to be translated!). // Entry "Funghi" will be selected by default. UI::OpenDialog( `VBox( `SelectionBox(`id(`pizza), "Select your Pizza:", [ `item(`id(`nap), "Napoli" ), `item(`id(`fun), "Funghi", true ), `item(`id(`sal), "Salami" ) ] ), `PushButton("&OK") ) ); UI::UserInput(); // Get the input from the selection box. // // Notice: The return value of UI::UserInput() does NOT return this value! // Rather, it returns the ID of the widget (normally the PushButton) // that caused UI::UserInput() to return. any pizza = UI::QueryWidget(`id(`pizza), `CurrentItem); y2milestone( "Selected pizza: %1", pizza ); // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); // Evaluate selection string toppings = "nothing"; if ( pizza == `nap ) toppings = "Tomatoes, Cheese"; else if ( pizza == `fun ) toppings = "Tomatoes, Cheese, Mushrooms"; else if ( pizza == `sal ) toppings = "Tomatoes, Cheese, Sausage"; // Pop up a new dialog to echo the selection. UI::OpenDialog( `VBox( `Label("You will get a pizza with:"), `Label(toppings), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Create a selection box with three entries. // All entries have IDs to identify them independent of locale // (The texts might have to be translated!). // Entry "Funghi" will be selected by default. // // There are two buttons to select a "Today's special" and a // "veggie" pizza to demonstrate how to select list entries // programmatically. UI::OpenDialog( `VBox( `SelectionBox(`id(`pizza), "Select your Pizza:", [ `item(`id(`nap), "Napoli" ), `item(`id(`fun), "Funghi", true ), `item(`id(`sal), "Salami" ) ] ), `HBox( `PushButton(`id(`todays_special), `opt(`hstretch), "&Today's special" ), `PushButton(`id(`veggie), `opt(`hstretch), "&Veggie" ), `PushButton(`id(`none), `opt(`hstretch), "&Nothing" ) ), `PushButton(`id(`ok), `opt(`default), "&OK") ) ); any id = nil; repeat { id = UI::UserInput(); if ( id == `todays_special ) UI::ChangeWidget( `pizza, `CurrentItem, `nap ); else if ( id == `veggie ) UI::ChangeWidget( `pizza, `CurrentItem, `fun ); else if ( id == `none ) UI::ChangeWidget( `pizza, `CurrentItem, nil ); } until ( id == `ok ); // Get the input from the selection box. // // Notice: The return value of UI::UserInput() does NOT return this value! // Rather, it returns the ID of the widget (normally the PushButton) // that caused UI::UserInput() to return. any pizza = UI::QueryWidget(`id(`pizza), `CurrentItem); // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); // Evaluate selection string toppings = "nothing"; if ( pizza == `nap ) toppings = "Tomatoes, Cheese"; else if ( pizza == `fun ) toppings = "Tomatoes, Cheese, Mushrooms"; else if ( pizza == `sal ) toppings = "Tomatoes, Cheese, Sausage"; // Pop up a new dialog to echo the selection. UI::OpenDialog( `VBox( `Label("You will get a pizza with:"), `Label(toppings), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Create a selection box with three entries. // All entries have IDs to identify them independent of locale // (The texts might have to be translated!). // Entry "Funghi" will be selected by default. // // There are two buttons to select a "Today's special" and a // "veggie" pizza to demonstrate how to select list entries // from within a YCP script - even without having to use item IDs. UI::OpenDialog( `VBox( `SelectionBox(`id(`pizza), "Select your Pizza:", [ "Napoli", "Funghi", "Salami", "Quattro Stagioni (a pizza which is devided into 4 parts each with a different topping)", "Caprese", "Speciale", "Hawaii" ] ), `HBox( `PushButton(`id(`todays_special), `opt(`hstretch), "&Today's special" ), `PushButton(`id(`veggie), `opt(`hstretch), "&Veggie" ) ), `PushButton(`id(`ok), `opt(`default), "&OK") ) ); any id = nil; repeat { id = UI::UserInput(); if ( id == `todays_special ) UI::ChangeWidget( `id( `pizza ), `CurrentItem, "Napoli" ); else if ( id == `veggie ) UI::ChangeWidget( `id( `pizza ), `CurrentItem, "Funghi" ); } until ( id == `ok ); // Get the input from the selection box. // // Notice: The return value of UI::UserInput() does NOT return this value! // Rather, it returns the ID of the widget (normally the PushButton) // that caused UI::UserInput() to return. string pizza = (string) UI::QueryWidget(`id(`pizza), `CurrentItem); // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); // Pop up a new dialog to echo the selection. UI::OpenDialog( `VBox( `Label("Pizza " + pizza + " coming right up"), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Selection box with icons { UI::OpenDialog( `VBox( `Heading( "YaST2 Mini Control Center" ), `SelectionBox(`id(`mod ), "Modules", [ `item(`id( "keyboard" ), `icon( "yast-keyboard.png" ), "Keyboard" ), `item(`id( "mouse" ), `icon( "yast-mouse.png" ), "Mouse" ), `item(`id( "timezone" ), `icon( "yast-timezone.png" ), "Time zone" ), `item(`id( "lan" ), `icon( "yast-lan.png" ), "Network" ), `item(`id( "sw_single" ), `icon( "yast-software.png" ), "Software" ) ] ), `PushButton("&OK") ) ); UI::UserInput(); // Get the input from the selection box. // // Notice: The return value of UI::UserInput() does NOT return this value! // Rather, it returns the ID of the widget (normally the PushButton) // that caused UI::UserInput() to return. string mod = (string) UI::QueryWidget(`id(`mod ), `CurrentItem); // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); }
// Example showing how to replace SelectionBox items { list pizza_list = [ "Pizza Napoli", "Pizza Funghi", "Pizza Salami", "Pizza Hawaii" ]; list pasta_list = [ "Spaghetti", "Rigatoni", "Tortellini" ]; UI::OpenDialog( `VBox( `SelectionBox(`id(`menu), "Daily &Specials:", pizza_list ), `HBox( `PushButton(`id(`pizza), "Pi&zza" ), `PushButton(`id(`pasta), "&Pasta" ), `PushButton(`id(`diet ), "Strict &Diet" ) ), `PushButton(`id(`ok), "&OK" ) ) ); symbol button = nil; do { button = (symbol) UI::UserInput(); if ( button == `pizza ) UI::ChangeWidget(`menu, `Items, pizza_list ); if ( button == `pasta ) UI::ChangeWidget(`menu, `Items, pasta_list ); if ( button == `diet ) UI::ChangeWidget(`menu, `Items, [] ); } while ( button != `ok ); string order = (string) UI::QueryWidget(`menu, `CurrentItem ); UI::CloseDialog(); // // Show the result // UI::OpenDialog(`VBox( `Label( sformat( "Your order: %1", order ) ), `PushButton(`opt(`default), "&OK" ) ) ); UI::UserInput(); UI::CloseDialog(); }
// Example showing how to replace SelectionBox items { list pizza_list = [ `item(`id("Pizza #01"), "Pizza Napoli" ), `item(`id("Pizza #02"), "Pizza Funghi" ), `item(`id("Pizza #03"), "Pizza Salami", true ), `item(`id("Pizza #04"), "Pizza Hawaii" ) ]; list pasta_list = [ `item(`id("Pasta #11"), "Spaghetti" ), `item(`id("Pasta #12"), "Rigatoni", true ), `item(`id("Pasta #13"), "Tortellini" ) ]; UI::OpenDialog( `VBox( `SelectionBox(`id(`menu), "Daily &Specials:", pizza_list ), `HBox( `PushButton(`id(`pizza), "Pi&zza" ), `PushButton(`id(`pasta), "&Pasta" ) ), `PushButton(`id(`ok), "&OK" ) ) ); symbol button = nil; do { button = (symbol) UI::UserInput(); if ( button == `pizza ) UI::ChangeWidget(`menu, `Items, pizza_list ); if ( button == `pasta ) UI::ChangeWidget(`menu, `Items, pasta_list ); } while ( button != `ok ); string order = (string) UI::QueryWidget(`menu, `CurrentItem ); UI::CloseDialog(); // // Show the result // UI::OpenDialog(`VBox( `Label( sformat( "Your order: %1", order ) ), `PushButton(`opt(`default), "&OK" ) ) ); UI::UserInput(); UI::CloseDialog(); }
SimplePatchSelector — Simplified approach to patch selection
SimplePatchSelector
( | ) ; |
This is a stripped-down version of the PackageSelector widget in "online update" ("patches") mode. It provides a very simplistic view on patches. It does not give access to handling packages by itself, but it contains a "Details..." button that lets the application open a full-fledged PackageSelector (in "online update" / "patches" mode).
Be advised that only this widget alone without access to the full PackageSelector might easily lead the user to a dead end: If dependency problems arise that cannot easily be solved from within the dependency problems dialog or by deselecting one or several patches, it might be necessary for the user to solve the problem on the package level. If he cannot do that, he might be unable to continue his update task.
This widget is similar in many ways to the PatternSelector widget: It gives a higher-level, more abstract access to package management at the cost of omitting details and fine control that more advanced users will want or need. The SimplePatchSelector should be used in ways similarl to the PatternSelector widget.
if ( UI::HasSpecialWidget( `SimplePatchSelector) {... `SimplePatchSelector()... UI::RunPkgSelection();
// Simple example for SimplePatchSelector { if ( ! UI::HasSpecialWidget(`SimplePatchSelector ) ) { UI::OpenDialog( `VBox( `Label("Error: This UI doesn't support the SimplePatchSelector widget!"), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); return; } UI::OpenDialog(`opt(`defaultsize), `SimplePatchSelector(`id(`selector) ) ); any input = UI::RunPkgSelection(`id(`selector) ); UI::CloseDialog(); y2milestone( "Input: %1", input ); }
// Full-fledged simple patch selection { textdomain "bogus"; // Initialize RPM DB as pkg src Pkg::TargetInit( "/", // installed system false ); // don't create a new RPM database // Pkg::SourceCreate( "file:/mounts/dist/install/stable-x86/", "" ); Pkg::SourceCreate( "ftp://ftp.gwdg.de/pub/suse/update/10.2", "" ); void detailedSelection() { // Open empty dialog for instant feedback UI::OpenDialog(`opt(`defaultsize), `ReplacePoint(`id( `rep), `Label( "Reading package database..." ) ) ); // This will take a while: Detailed package data are retrieved // while the package manager is initialized UI::ReplaceWidget(`rep, `PackageSelector(`id(`packages ), `opt(`youMode), "/dev/fd0" ) ); symbol input = (symbol) UI::RunPkgSelection(`id(`packages ) ); y2milestone( "Package selector returned %1", input ); UI::CloseDialog(); } if ( ! UI::HasSpecialWidget(`SimplePatchSelector ) ) { detailedSelection(); // Fallback: Do detailed selection right away return; } UI::OpenDialog(`opt(`defaultsize), `SimplePatchSelector(`id(`selector) ) ); symbol button = nil; repeat { button = (symbol) UI::RunPkgSelection(`id(`selector) ); y2milestone( "SimplePatchSelector selector returned %1", button ); if ( button == `details ) detailedSelection(); } until ( button == `cancel || button == `accept ); UI::CloseDialog(); }
Slider — Numeric limited range input (optional widget)
Slider
( | label, | |
minValue, | ||
maxValue, | ||
initialValue) ; |
string
label
;
integer
minValue
;
integer
maxValue
;
integer
initialValue
;label
Explanatory label above the slider
minValue
minimum value
maxValue
maximum value
initialValue
initial value
A horizontal slider with (numeric) input field that allows input of an integer value in a given range. The user can either drag the slider or simply enter a value in the input field.
Remember you can use `opt( `notify )
in order to get instant response when the user changes the value - if this is desired.
![]() | Note |
---|---|
This is a "special" widget, i.e. not all UIs necessarily support it. Check for availability with |
{ if ( ! UI::HasSpecialWidget(`Slider) ) { UI::OpenDialog( `VBox( `Label("Error: This UI doesn't support the Slider widget!"), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); return; } UI::OpenDialog( `VBox( `Slider( "Percentage", 0, 100, 50), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Advanced Slider + BarGraph example: // // Display a dialog with a bar graph for RGB color percentages // and 3 sliders for the RGB percentage. // Update the bar graph while the user adjusts the RGB values. // // Unfortunately the colors don't match any more in the BarGraph widget - they // used to be red, blue and green. You need to use a bit of imagination // here. ;-) { // Check for availability of required widgets if ( ! UI::HasSpecialWidget(`Slider) || ! UI::HasSpecialWidget(`BarGraph ) ) { UI::OpenDialog( `VBox( `Label("Error: This UI doesn't support the required special widgets!"), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); return; } // Initialize RGB values integer red = 128; integer blue = 128; integer green = 128; // Create the dialog UI::OpenDialog( `VBox( `HSpacing(50), // force width `BarGraph( `id(`graph), [ red, green, blue ], [ "Red\n%1", "Green\n%1", "Blue\n%1" ] ), `Slider( `id(`red), `opt(`notify), "Red", 0, 255, red ), `Slider( `id(`green), `opt(`notify), "Green", 0, 255, green ), `Slider( `id(`blue), `opt(`notify), "Blue", 0, 255, blue ), `PushButton(`id(`close), `opt(`default), "&Close") ) ); // Event processing loop - left only via the "close" button // or the window manager close button / function. any widget = nil; do { widget = UI::UserInput(); if ( widget == `red || // any of the sliders? widget == `blue || widget == `green ) { // Get all slider values red = (integer) UI::QueryWidget(`id(`red), `Value ); green = (integer) UI::QueryWidget(`id(`green), `Value ); blue = (integer) UI::QueryWidget(`id(`blue), `Value ); // Update bar graph UI::ChangeWidget(`id(`graph), `Values, [ red, green, blue ] ); } } while ( widget != `close && // the real "Close" button widget != `cancel ); // the window manager close function/button UI::CloseDialog(); }
Table — Multicolumn table widget
Table
( | header, | |
items) ; |
term
header
;
list
items
;immediate
make `notify trigger immediately when the selected item changes
keepSorting
keep the insertion order - don't let the user sort manually by clicking
multiSelection
user can select multiple items (rows) at once (shift-click, ctrl-click)
The Table widget is a selection list with multiple columns. By default, the user can select exactly one row (with all its columns) from that list. With `opt(`multiSelection), the user can select one or more rows (with all their columns) from that list (In that case, use the `SelectedItems property, not `Value).
Each cell (each column within each row) has a label text and an optional icon.
(Note: Not all UIs (in particular not text-based UIs) support displaying icons, so an icon should never be an exclusive means to display any kind of information).
This widget is similar to SelectionBox, but it has several columns for each item (each row). If just one column is desired, consider using SelectionBox instead.
Note: This is not something like a spread sheet, and it doesn't pretend or want to be. Actions are performed on rows, not on individual cells (columns within one row).
The first argument (after `opt() and `id() which both are optional) is `header() which specifies the column headers (and implicitly the number of columns) and optionally the alignment for each column. Default alignment is left.
In the list of items, an ID is specified for each item. Each item can have less cells (columns) than the table has columns (from `header()), in which case any missing cells are assumed to be empty. If an item has more cells than the table has columns, any extra cells are ignored.
Each cell has a text label (which might also be an empty string) and optionally an icon. If a cell has an icon, it has to be specified with `cell(`icon("myiconname.png", "Label text")).
A simple table is specified like this:
`Table(`id(`players), `header("Nick", "Age", "Role"), [ `item(`id("Bluebird"), "Bluebird, 18, "Scout" ), `item(`id("Ozzz" ), "Ozzz", 23, "Wizard" ), `item(`id("Wannabe" ), "Wannabe", 17 ), `item(`id("Coxxan" ), "Coxxan", 26, "Warrior") ] )
This will create a 3-column table. The first column ("Nick") and the third column ("Role") will be left aligned. The second column ("Age") will be right aligned. note that "Wannabe" doesn't have a Role. This field will be empty.
A table that uses icons is specified like this:
`Table(`id(`players), `header("Nick", "Age", "Role"), [ `item(`id("Bluebird"), "Bluebird, 18, `cell(`icon("scout.png"), "Scout" ) ), `item(`id("Ozzz" ), "Ozzz", 23, "Wizard" ), `item(`id("Wannabe" ), "Wannabe", `cell(`icon("underage.png", 17 ) ), `item(`id("Coxxan" ), "Coxxan", `cell(`icon("oldman.png", 26 ), "Warrior" ) ] )
In this example, "Bluebird" has an additional icon in his "Role" column, and "Wannabe" and "Coxxan" both have additional icons in their "Age" columns.
{ UI::OpenDialog( `VBox( `Heading("Today's menu"), `MinSize( 25, 7, `Table( `header("Name", "Price"), [ `item(`id(1), "Chili", 6), `item(`id(2), "Salami Baguette", nil), `item(`id(3), "Spaghetti", 8), `item(`id(4), "Steak Sandwich", 12) ] ) ), `PushButton("&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ UI::OpenDialog( `VBox( `Heading("Today's menu"), `MinSize( 30, 7, `Table( `id(`table), `opt(`keepSorting), `header("Name", `Right("Price"), `Center("Rating")), [ `item(`id(0), "Steak Sandwich", 12, "+++"), `item(`id(1), "Salami Baguette", nil, "-" ), `item(`id(2), "Chili", 6, "--" ), `item(`id(3), "Spaghetti", 8, "+" ) ] ) ), `Right( `HBox( `PushButton(`id(`next), "&Next"), `PushButton(`id(`cancel), "&Close") ) ) ) ); UI::ChangeWidget(`id(`table), `CurrentItem, 2); while (UI::UserInput() != `cancel) { UI::ChangeWidget(`id(`table), `CurrentItem, ((integer) UI::QueryWidget(`id(`table), `CurrentItem) + 1) % 4); } UI::CloseDialog(); }
// Table example: Exchange complete table content { list foodItems = [ `item(`id(3), "Spaghetti", 8), `item(`id(4), "Steak Sandwich", 12), `item(`id(1), "Chili", 6), `item(`id(2), "Salami Baguette", nil) ]; list carItems = [ `item(`id(0), "Mercedes", 60000), `item(`id(1), "Audi", 50000), `item(`id(2), "VW", 40000), `item(`id(3), "BMW", 60000), `item(`id(3), "Porsche", 80000) ]; list itemLists = [ foodItems, carItems ]; integer listNo = 0; UI::OpenDialog( `VBox( `Heading("Prices"), `MinSize( 30, 10, `Table(`id(`table), `header("Name", "Price"), foodItems ) ), `Right( `HBox( `PushButton(`id(`next), "Change &Table Contents"), `PushButton(`id(`cancel), "&Close") ) ) ) ); while (UI::UserInput() != `cancel) { // Change table contents listNo = 1 - listNo; UI::ChangeWidget(`table, `Items, itemLists[ listNo ]:nil ); // Double check: Retrieve contents and dump to log y2milestone( "New table content:\n%1", UI::QueryWidget(`table, `Items ) ); } UI::CloseDialog(); }
{ UI::OpenDialog( `VBox( `Heading("Today's menu"), `MinSize( 25, 7, `Table(`id(`table), `header("Name", "Price"), [ `item(`id(1), "Chili", 6), `item(`id(2), "Salami Baguette", nil), `item(`id(3), "Spaghetti", 8), `item(`id(4), "Steak Sandwich", 12) ] ) ), `Right(`HBox( `PushButton("&Lookup"), `PushButton(`id(`cancel), "&Close" ) ) ) ) ); while (UI::UserInput() != `cancel) { any id = UI::QueryWidget(`id(`table), `CurrentItem); if (is(id, integer)) { string text = sformat("Line: %1", UI::QueryWidget(`id(`table), `Item(id))); UI::OpenDialog( `MarginBox( 1, 0.2, `VBox( `Left( `Label( "Current Table Item" ) ), `Label(`opt(`outputField), text), `PushButton("&OK") ) ) ); UI::UserInput(); UI::CloseDialog(); } } UI::CloseDialog(); }
{ UI::OpenDialog( `VBox( `MinSize( 25, 8, `Table(`id(`table), `opt(`notify), `header("Name", "Amount"), [ `item(`id(1), "Chili", 0), `item(`id(2), "Salami Baguette", 0), `item(`id(3), "Spaghetti", 0), `item(`id(4), "Steak Sandwich", 0) ] ) ), `Label("Double-click any item to increase the number"), `Right( `PushButton(`id(`cancel), "&Close") ) ) ); while ( UI::UserInput() != `cancel) { integer current_item_id = (integer) UI::QueryWidget(`id(`table), `CurrentItem); integer amount = tointeger( (string) UI::QueryWidget(`table, `Cell( current_item_id, 1 ) ) ); amount = amount+1; y2debug( "amount: %1", amount ); UI::ChangeWidget(`id(`table), `Cell( current_item_id, 1 ), amount ); } UI::CloseDialog(); }
TimeField — Time input field
TimeField
( | label, | |
initialTime) ; |
string
label
;
string
initialTime
;TimezoneSelector — Timezone selector map
TimezoneSelector
( | pixmap, | |
timezones) ; |
string
pixmap
;
map
timezones
;pixmap
path to a jpg or png of a world map - with 0°0° being the middle of the picture
timezones
a map of timezones. The map should be between e.g. Europe/London and the tooltip to be displayed ("United Kingdom")
Tree — Scrollable tree selection
Tree
( | label, | |
items) ; |
string
label
;
itemList
items
;items
the items contained in the tree
itemList ::= [ item [ , item ] [ , item ] ... ] item ::= string | `item( [ `id( string ),] string [ , true | false ] [ , itemList ] )
The boolean parameter inside `item() indicates whether or not the respective tree item should be opened by default - if it has any subitems and if the respective UI is capable of closing and opening subtrees. If the UI cannot handle this, all subtrees will always be open.
A tree widget provides a selection from a hierarchical tree structure. The semantics are very much like those of a SelectionBox. Unlike the SelectionBox, however, tree items may have subitems that in turn may have subitems etc.
Each item has a label string, optionally preceded by an ID. If the item has subitems, they are specified as a list of items after the string.
The tree widget will not perform any sorting on its own: The items are always sorted by insertion order. The application needs to handle sorting itself, if desired.
Note: The Qt version of the Wizard widget also provides a built-in tree with an API that is (sometimes) easier to use.
// Simple tree example { UI::OpenDialog( `VBox( `Tree(`id(`dest_dir), "Select destination directory:", [ `item(`id(`root), "/" , true, [ `item(`id(`etc), "etc", [ `item("opt"), `item("SuSEconfig"), `item("X11") ] ), `item("usr", false, [ "bin", "lib", `item("share", [ "man", "info", "emacs" ] ), `item(`id(`usr_local),"local"), `item("X11R6", [ "bin", "lib", "share", "man", "etc" ] ) ] ), `item(`id(`opt), "opt", true, [ "kde", "netscape", "Office51" ] ), `item("home"), "work", `item(`id(`other), "<other>") ] ) ] ), `HBox( `PushButton(`id(`sel_opt), `opt(`hstretch), "/&opt" ), `PushButton(`id(`sel_usr), `opt(`hstretch), "/&usr" ), `PushButton(`id(`sel_usr_local), `opt(`hstretch), "/usr/&local" ) ), `PushButton(`id(`ok), `opt(`default), "&OK") ) ); any id = nil; repeat { id = UI::UserInput(); if ( id == `sel_usr) UI::ChangeWidget(`dest_dir, `CurrentItem, "usr" ); else if ( id == `sel_usr_local) UI::ChangeWidget(`dest_dir, `CurrentItem, `usr_local ); else if ( id == `sel_opt) UI::ChangeWidget(`dest_dir, `CurrentItem, `opt ); } until ( id == `ok ); // Get the input from the tree. // // Notice: The return value of UI::UserInput() does NOT return this value! // Rather, it returns the ID of the widget (normally the PushButton) // that caused UI::UserInput() to return. any dest_dir = UI::QueryWidget(`dest_dir, `CurrentItem); y2debug( "Selected: %1", dest_dir ); if ( dest_dir == nil ) dest_dir = ""; // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); // Pop up a new dialog to echo the selection. UI::OpenDialog( `VBox( `Label( sformat( "Selected destination directory: %1", dest_dir ) ), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
{ // Build a dialog with a tree for directory selection, three // buttons with common values and a label that directly echoes any // selected directory. // // The tree in this example uses the `notify option that makes // UI::UserInput() return immediately as soon as the user selects a // tree item rather than the default behaviour which waits for the // user to activate a button. UI::OpenDialog( `VBox( `MinHeight( 14, `Tree(`id(`dest_dir), `opt(`notify), "Select destination directory:", [ `item(`id(`root), "/" , true, [ `item(`id(`etc), "etc", [ `item("opt"), `item("SuSEconfig"), `item("X11") ] ), `item("usr", false, [ "bin", "lib", `item("share", [ "man", "info", "emacs" ] ), `item(`id(`usr_local),"local"), `item("X11R6", [ "bin", "lib", "share", "man", "etc" ] ) ] ), `item(`id(`opt), "opt", true, [ "kde", "netscape", "Office51" ] ), `item("home"), "work", `item(`id(`other), "<other>") ] ) ] ) ), `HBox( `PushButton(`id(`sel_opt ), `opt(`hstretch), "/&opt" ), `PushButton(`id(`sel_usr ), `opt(`hstretch), "/&usr" ), `PushButton(`id(`sel_usr_local ), `opt(`hstretch), "/usr/&local" ), `PushButton(`id(`none ), `opt(`hstretch), "none" ) ), `HBox( `Label("Current Value:"), `Label(`id(`echo), `opt(`outputField, `hstretch), "?????????") ), `PushButton(`id(`ok), `opt(`default), "&OK") ) ); any id = nil; repeat { id = UI::UserInput(); if ( id == `sel_usr ) UI::ChangeWidget(`dest_dir, `CurrentItem, "usr" ); else if ( id == `sel_usr_local ) UI::ChangeWidget(`dest_dir, `CurrentItem, `usr_local ); else if ( id == `sel_opt ) UI::ChangeWidget(`dest_dir, `CurrentItem, `opt ); else if ( id == `none ) UI::ChangeWidget(`dest_dir, `CurrentItem, nil ); UI::ChangeWidget(`echo, `Value, ""); any current_dir = UI::QueryWidget(`dest_dir, `CurrentItem); if ( current_dir != nil ) UI::ChangeWidget(`echo, `Value, sformat( "%1", current_dir ) ); y2milestone( "Items:\n%1", UI::QueryWidget(`dest_dir, `Items ) ); y2milestone( "OpenItems: %1", UI::QueryWidget(`dest_dir, `OpenItems ) ); y2milestone( "Current Branch: %1", UI::QueryWidget(`dest_dir, `CurrentBranch ) ); } until ( id == `ok ); // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); }
{ // Build a dialog with a tree for directory selection, three // buttons with common values and a label that directly echoes any // selected directory. // // The tree in this example uses the `notify option that makes // UI::UserInput() return immediately as soon as the user selects a // tree item rather than the default behaviour which waits for the // user to activate a button. UI::OpenDialog( `MinWidth( 50, `VBox( `Tree(`id(`dest_dir), `opt(`notify), "Select destination directory:", [ `item( "/" , true, [ `item( "etc", [ `item("opt"), `item("SuSEconfig"), `item("X11") ] ), `item( "usr", false, [ "bin", "lib", `item("share", [ "man", "info", "emacs" ] ), `item( "local" ), `item("X11R6", [ "bin", "lib", "share", "man", "etc" ] ) ] ), `item( "opt", true, [ "kde", "netscape", "Office51" ] ), `item("home"), "work", `item( "<other>") ] ) ] ), `HBox( `PushButton(`id(`sel_opt), `opt(`hstretch), "/&opt" ), `PushButton(`id(`sel_usr), `opt(`hstretch), "/&usr" ), `PushButton(`id(`sel_usr_local), `opt(`hstretch), "/usr/&local" ) ), `HBox( `HWeight( 2, `Label("Current Item:") ), `HWeight( 5, `Label(`id(`echoItem), `opt(`outputField, `hstretch), "") ) ), `HBox( `HWeight( 2, `Label("Current Branch:") ), `HWeight( 5, `Label(`id(`echoBranch), `opt(`outputField, `hstretch), "") ) ), `HBox( `HWeight( 2, `Label("Current Path:") ), `HWeight( 5, `Label(`id(`echoPath), `opt(`outputField, `hstretch), "") ) ), `PushButton(`id(`ok), `opt(`default), "&OK") ) ) ); any id = nil; repeat { id = UI::UserInput(); if ( id == `sel_usr) UI::ChangeWidget( `id( `dest_dir ), `CurrentItem, "usr" ); else if ( id == `sel_usr_local) UI::ChangeWidget( `id( `dest_dir ), `CurrentItem, "local" ); else if ( id == `sel_opt) UI::ChangeWidget( `id( `dest_dir ), `CurrentItem, "opt" ); else if ( id == `dest_dir) { any current_dir = UI::QueryWidget(`dest_dir, `CurrentItem ); if ( current_dir != nil ) UI::ChangeWidget( `id( `echoItem ), `Value, sformat( "%1", current_dir ) ); list<string> current_branch = (list<string>) UI::QueryWidget(`dest_dir, `CurrentBranch ); if ( current_branch != nil ) { UI::ChangeWidget(`echoBranch, `Value, sformat( "%1", current_branch) ); string current_path = mergestring( current_branch, "/" ); if ( size( current_path ) > 2 ) // Remove duplicate "/" at start current_path = substring( current_path, 1, size( current_path )-1 ); UI::ChangeWidget(`echoPath, `Value, sformat( "%1", current_path ) ); } } } until ( id == `ok ); // Close the dialog. // Remember to read values from the dialog's widgets BEFORE closing it! UI::CloseDialog(); }
// Tree with icons { UI::OpenDialog( `VBox( `Heading( "YaST2 Mini Control Center" ), `Tree(`id(`mod), "Modules", [ `item(`id( "country" ), `icon( "yast-yast-language.png" ), "Localization", true, [ `item(`id( "keyboard" ), `icon( "yast-keyboard.png" ), "Keyboard" ), `item(`id( "timezone" ), `icon( "yast-timezone.png" ), "Time zone" ) ] ), `item(`id( "mouse" ), `icon( "yast-mouse.png" ), "Mouse" ), `item(`id( "lan" ), `icon( "yast-lan.png" ), "Network" ), `item(`id( "sw_single" ), `icon( "yast-software.png" ), "Software" ) ] ), `PushButton(`id(`ok), `opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); }
// Example showing how to replace Tree items { list pizza_list = [ "Pizza Napoli", "Pizza Funghi", "Pizza Salami", "Pizza Hawaii" ]; list pasta_list = [ "Spaghetti", "Rigatoni", "Tortellini" ]; list veggie_toppings = [ "Cheese", "Mushrooms", "Pepperoni", "Rucola", "Tomatoes" ]; list meat_toppings = [ "Ham", "Salami", "Tuna" ]; list menu = [ `item( `id ( `pizza_branch ), "Pizza", true, pizza_list ), `item( `id ( `pasta_branch ), "Pasta", true, pasta_list) ]; list toppings = [ `item( `id ( `meat_branch ), "Meat", true, meat_toppings ), `item( `id ( `veggie_branch ), "Veggie", true, veggie_toppings) ]; UI::OpenDialog( `VBox( `Tree(`id(`listing), "Daily &Specials:", menu ), `HBox( `PushButton(`id(`menu), "&Menu" ), `PushButton(`id(`toppings), "&Toppings" ), `PushButton(`id(`empty), "&None" ) ), `PushButton(`id(`ok), "&OK" ) ) ); symbol button = nil; do { button = (symbol) UI::UserInput(); if ( button == `menu ) UI::ChangeWidget(`listing, `Items, menu); if ( button == `toppings ) UI::ChangeWidget(`listing, `Items, toppings ); if ( button == `empty ) UI::ChangeWidget(`listing, `Items, [] ); } while ( button != `ok ); string order = (string) UI::QueryWidget(`listing, `CurrentItem ); UI::CloseDialog(); // // Show the result // UI::OpenDialog(`VBox( `Label( sformat( "Your order: %1", order ) ), `PushButton(`opt(`default), "&OK" ) ) ); UI::UserInput(); UI::CloseDialog(); }
// Advanced example of using the Wizard widget. // // Note: YCP applications are discouraged from using the Wizard widget directly. // Use the Wizard module instead. { if ( ! UI::HasSpecialWidget(`Wizard) ) { y2error( "This works only with UIs that provide the wizard widget!" ); return; } string help_text = "<p>This is a help text.</p>" + "<p>It should be helpful.</p>" + "<p>If it isn't helpful, it should rather not be called a <i>help text</i>.</p>"; UI::OpenDialog(`opt(`defaultsize ), `Wizard(`opt(`treeEnabled), `back, "&Back", `abort, "Ab&ort", `next, "&Next" ) ); // UI::DumpWidgetTree(); UI::WizardCommand(`SetDialogIcon( "/usr/share/YaST2/theme/current/icons/22x22/apps/YaST.png" ) ); UI::WizardCommand(`SetDialogHeading( "Welcome to the YaST2 installation" ) ); UI::WizardCommand(`SetHelpText( help_text ) ); UI::WizardCommand(`AddTreeItem( "", "First Toplevel Item" , "tl1" ) ); UI::WizardCommand(`AddTreeItem( "", "Second Toplevel Item", "tl2" ) ); UI::WizardCommand(`AddTreeItem( "", "Third Toplevel Item" , "tl3" ) ); UI::WizardCommand(`AddTreeItem( "tl1", "First Sublevel" , "1-1" ) ); UI::WizardCommand(`AddTreeItem( "tl1", "Second Sublevel" , "1-2" ) ); UI::WizardCommand(`AddTreeItem( "tl1", "Third Sublevel" , "1-3" ) ); UI::WizardCommand(`AddTreeItem( "tl2", "First Sublevel" , "2-1" ) ); UI::WizardCommand(`AddTreeItem( "tl2", "Second Sublevel" , "2-2" ) ); UI::WizardCommand(`AddTreeItem( "tl2", "Third Sublevel" , "2-3" ) ); UI::WizardCommand(`AddTreeItem( "1-2", "First 3rd level " , "3rd 1" ) ); UI::WizardCommand(`AddTreeItem( "1-2", "Second 3rd level " , "3rd 2" ) ); UI::WizardCommand(`AddTreeItem( "1-2", "Item without ID" , "" ) ); UI::WizardCommand(`SelectTreeItem( "3rd 1" ) ); UI::WizardCommand(`AddMenu( "&File", "file-menu" ) ); UI::WizardCommand(`AddMenu( "&Edit", "edit-menu" ) ); UI::WizardCommand(`AddMenu( "&Options", "opt-menu" ) ); UI::WizardCommand(`AddMenuEntry ( "file-menu", "&New", "file-new" ) ); UI::WizardCommand(`AddMenuEntry ( "file-menu", "&Open", "file-open" ) ); UI::WizardCommand(`AddSubMenu ( "file-menu", "Open &Recent", "file-recent" ) ); UI::WizardCommand(`AddMenuEntry ( "file-menu", "&Save", "file-save" ) ); UI::WizardCommand(`AddMenuEntry ( "file-menu", "Save &As", "file-save-as" ) ); UI::WizardCommand(`AddMenuEntry ( "file-recent", "/tmp/test1", "recent-test1" ) ); UI::WizardCommand(`AddMenuEntry ( "file-recent", "/tmp/test2", "recent-test2" ) ); UI::WizardCommand(`AddMenuEntry ( "edit-menu", "C&ut", "edit-cut" ) ); UI::WizardCommand(`AddMenuEntry ( "edit-menu", "C&opy", "edit-copy" ) ); UI::WizardCommand(`AddMenuEntry ( "edit-menu", "&Paste", "edit-paste" ) ); UI::WizardCommand(`AddMenuEntry ( "opt-menu", "&Settings", "opt-settings" ) ); UI::WizardCommand(`AddMenuSeparator ( "opt-menu" ) ); UI::WizardCommand(`AddMenuEntry ( "opt-menu", "Activate &Hypersonic Transducer", "frank-n-furter" ) ); while ( true ) { map event = UI::WaitForEvent(); y2milestone( "Got event: %1", event ); if ( event[ "ID" ]:nil == `abort ) break; y2milestone( "Tree selection: %1", UI::QueryWidget(`id(`wizard), `CurrentItem ) ); } UI::CloseDialog(); }
VMultiProgressMeter, HMultiProgressMeter — Progress bar with multiple segments (optional widget)
VMultiProgressMeter
( | maxValues) ; |
List<integer>
maxValues
;
HMultiProgressMeter
( | maxValues) ; |
List<integer>
maxValues
;A vertical (VMultiProgressMeter) or horizontal (HMultiProgressMeter) progress display with multiple segments. The numbers passed on widget creation are the maximum numbers of each individual segment. Segments sizes will be displayed proportionally to these numbers.
This widget is intended for applications like showing the progress of installing from multiple CDs while giving the user a hint how much will be installed from each individual CD.
Set actual values later with UI::ChangeWidget(`id(...), `Values, [ 1, 2, ...] );
The widget may choose to reserve a minimum amount of space for each segment even if that means that some segments will be shown slightly out of proportion.
![]() | Note |
---|---|
This is a "special" widget, i.e. not all UIs necessarily support it. Check for availability with |
if ( HasSpecialWidget( `MultiProgressMeter ) {... `MultiProgressMeter( "Percentage", 1, 100, 50 )
// Simple example for MultiProgressMeter { if ( ! UI::HasSpecialWidget(`HMultiProgressMeter ) ) { UI::OpenDialog( `VBox( `Label("Error: This UI doesn't support the MultiProgressMeter widget!"), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); return; } UI::OpenDialog( `VBox( `VMultiProgressMeter(`id(`prog), [ 1000, 200, 500, 20, 100 ] ), `PushButton(`opt(`default), "&Ok" ) ) ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 100, 0, 0, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 200, 0, 0, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 300, 0, 0, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 400, 0, 0, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 500, 0, 0, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 600, 0, 0, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 700, 0, 0, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 800, 0, 0, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 900, 0, 0, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 1000, 0, 0, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 1000, 50, 0, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 1000, 100, 0, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 1000, 150, 0, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 1000, 200, 0, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 1000, 200, 100, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 1000, 200, 200, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 1000, 200, 300, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 1000, 200, 400, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 1000, 200, 500, 0, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 1000, 200, 500, 10, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 1000, 200, 500, 20, 0 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 1000, 200, 500, 20, 20 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 1000, 200, 500, 20, 40 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 1000, 200, 500, 20, 80 ] ); UI::UserInput(); UI::ChangeWidget(`prog, `Values, [ 1000, 200, 500, 20, 100 ] ); UI::UserInput(); UI::UserInput(); UI::CloseDialog(); }
// // Advanced MultiProgressMeter example: // Change values interactively with sliders // and allow tests with huge numbers // { // // Global variables // list<integer> maxValueList = [ 950, 200, 500, 20, 100 ]; list<integer> valueList = [ 100, 30, 400, 0, 0 ]; integer unit = 0; // exponent: powers of 2 /** * Return a VBox term with Slider widgets for each list value. **/ term slidersVBox( list<integer> maxValuesList, list<integer> currentValuesList ) { term vbox = `VBox(); integer i=0; foreach ( integer maxVal, maxValuesList, { vbox = add( vbox, `Slider(`id(`slider(i) ), `opt(`notify), "", // label 0, // minVal maxVal, currentValuesList[i]:0 // currentVal ) ); i = i+1; }); return vbox; } /** * Apply unit to a list of values. Return the scaled list. **/ list<integer> scaleList( integer unit, list<integer> values ) { list<integer> scaledValues = []; foreach ( integer val, values, { scaledValues = add( scaledValues, val << unit ); }); // y2debug( "Values: %1 unit: %2 scaled: %3", values, unit, scaledValues ); return scaledValues; } /** * Get the current values from all sliders and return them as a list. **/ list<integer> getValues() { list<integer> values = []; integer i = 0; while ( true ) { term sliderID = `slider(i); // y2debug( "Looking for %1", sliderID ); if ( ! UI::WidgetExists(`id( sliderID) ) ) break; values = add( values, (integer) UI::QueryWidget(`id(sliderID), `Value ) ); i = i+1; } // y2debug( "Values: %1", values ); return values; } /** * Update progress meters with values from sliders. **/ void updateProgress() { list<integer> values = scaleList( unit, getValues() ); UI::ChangeWidget(`vProgress, `Values, values ); UI::ChangeWidget(`hProgress, `Values, values ); } // // Check if required special widgets are available // if ( ! UI::HasSpecialWidget(`HMultiProgressMeter ) || ! UI::HasSpecialWidget(`Slider ) ) { UI::OpenDialog( `VBox( `Label("Error: This UI doesn't support the required widgets!"), `PushButton(`opt(`default), "&OK") ) ); UI::UserInput(); UI::CloseDialog(); return; } // // Create dialog // term radioBox = `Frame( "Unit", `RadioButtonGroup(`id(`unit), `opt(`notify), `HBox( `HSpacing(0.5), `RadioButton(`id(`unit( 0)), `opt(`notify), "&Bytes"), `HSpacing(1.5), `RadioButton(`id(`unit(10)), `opt(`notify), "&kB"), `HSpacing(1.5), `RadioButton(`id(`unit(20)), `opt(`notify), "&MB"), `HSpacing(1.5), `RadioButton(`id(`unit(30)), `opt(`notify), "&GB"), `HSpacing(0.5) ) ) ); UI::OpenDialog( `VBox( `HBox( `VBox( `Heading( "MultiProgressMeter Example" ), `VSpacing( 1 ), slidersVBox( maxValueList, valueList ), radioBox, `VStretch() ), `HSpacing( 1 ), `ReplacePoint(`id(`rep_vProgress), `VMultiProgressMeter(`id(`vProgress), scaleList( unit, maxValueList ) ) ) ), `HBox( `ReplacePoint(`id(`rep_hProgress), `HMultiProgressMeter(`id(`hProgress), scaleList( unit, maxValueList ) ) ), `HSpacing( 0.5 ), `PushButton(`id(`cancel), "&Close" ) ) ) ); UI::ChangeWidget(`id(`unit), `Value, `unit( unit ) ); updateProgress(); // // Event loop // while ( true ) { map<string, any> event = UI::WaitForEvent(); // y2debug( "Event: %1", event ); any id = event["ID"]:nil; symbol widgetClass = (symbol) event["WidgetClass"]:nil; if ( widgetClass == `RadioButton ) { any currentUnitID = UI::QueryWidget(`unit, `CurrentButton ); if ( is( currentUnitID, term ) ) { unit = ( (term) currentUnitID )[0]:0; y2milestone( "New unit: 2^%1", unit ); UI::ReplaceWidget(`rep_vProgress, `VMultiProgressMeter(`id(`vProgress), scaleList( unit, maxValueList ) ) ); UI::ReplaceWidget(`rep_hProgress, `HMultiProgressMeter(`id(`hProgress), scaleList( unit, maxValueList ) ) ); updateProgress(); } } if ( widgetClass == `Slider ) { updateProgress(); list<integer> values = scaleList( unit, getValues() ); UI::ChangeWidget(`vProgress, `Values, values ); UI::ChangeWidget(`hProgress, `Values, values ); } if ( id == `cancel ) break; } // // Clean up // UI::CloseDialog(); }
Wizard — Wizard frame - not for general use, use the Wizard:: module instead!
Wizard
( | backButtonId, | |
backButtonLabel, | ||
abortButtonId, | ||
abortButtonLabel, | ||
nextButtonId, | ||
nextButtonLabel) ; |
any
backButtonId
;
string
backButtonLabel
;
any
abortButtonId
;
string
abortButtonLabel
;
any
nextButtonId
;
string
nextButtonLabel
;backButtonId
ID to return when the user presses the "Back" button
backButtonLabel
Label of the "Back" button
abortButtonId
ID to return when the user presses the "Abort" button
abortButtonLabel
Label of the "Abort" button
nextButtonId
ID to return when the user presses the "Next" button
nextButtonLabel
Label of the "Next" button
stepsEnabled
Enable showing wizard steps (use UI::WizardCommand() to set them).
treeEnabled
Enable showing a selection tree in the left panel. Disables stepsEnabled.
This is the UI-specific technical implementation of a wizard dialog's main widget. This is not intended for general use - use the Wizard:: module instead which will use this widget properly.
A wizard widget always has ID `wizard. The ID of the single replace point within the wizard is always `contents.
![]() | Note |
---|---|
This is a "special" widget, i.e. not all UIs necessarily support it. Check for availability with |
The RichText widget in the Qt UI currently supports the tags listed below. Note that not all of them will make sense for use within YaST2 (Most notably all those which refer to files)
<qt>...</qt> - A Qt rich text document. It understands the following attributes
title - the caption of the document. This attribute is easily accessible with [25]QTextView::documentTitle()
type - The type of the document. The default type is page . It indicates that the document is displayed in a page of its own. Another style is detail. It can be used to explain certain expressions more detailed in a few sentences. The QTextBrowser will then keep the current page and display the new document in a small popup similar to QWhatsThis. Note that links will not work in documents with <qt type="detail" >...</qt>
bgcolor - The background color, for example bgcolor="yellow" or bgcolor="#0000FF"
background - The background pixmap, for example background="granit.xpm". The pixmap name will be resolved by a [26]QMimeSourceFactory().
text - The default text color, for example text="red"
link - The link color, for example link="green"
<a>...</a> - An anchor or link. The reference target is defined in the href attribute of the tag as in \c<a href="target.qml">...</a>. You can also specify an additional anchor within the specified target document, for example <a href="target.qml#123">...</a>. If a is meant to be an anchor, the reference source is given in the name attribute.
<font>...</font> - Customizes the font size and text color. The tag understands two attributes:
color - the text color, for example color="red" or color="#FF0000".
size - the logical size of the font. Logical sizes 1 to 7 are supported. The value may either be absolute, for example size=3, or relative. In the latter case, the sizes are simply added.
<em>...</em> - Emphasized. As default, this is the same as <i>...</i> (Italic)
<strong>...</strong> - Strong. As default, this is the same as <bold>...</bold> (bold)
<big>...</big> - A larger font size.
<small>...</small> - A smaller font size.
<code>...</code> - Indicates Code. As default, this is the same as <tt>...</tt> (typewriter)
<pre>...</pre> - For larger junks of code. Whitespaces in the contents are preserved.
<large>...</large> - Large font size.
<b>...</b> - Bold font style.
<h1>...</h1> - A top-level heading.
<h2>...</h2> - A sub-level heading.
<h3>...</h3> - A sub-sub-level heading.
<p>...</p> - A paragraph.
<center>...</center> - A centered paragraph.
<blockquote>...</blockquote> - An indented paragraph, useful for quotes.
<multicol cols=n >...</multicol> - Multicol display with n columns
<twocolumn>...</twocolumn> - Two-column display.
<ul>...</ul> - An un-ordered list. You can also pass a type argument to define the bullet style. The default is type=disc, other types are circle and square.
<ol>...</ol> - An ordered list. You can also pass a type argument to define the enumeration label style. The default is type="1", other types are "a" and "A".
<li>...</li> - A list item.
<img> - An image. The image name for the mime source factory is given in the source attribute, for example <img source="qt.xpm"/> The image tag also understands the attributes width and height that determine the size of the image. If the pixmap does not fit to the specified size, it will be scaled automatically.
<br> - A line break
<hr> - A horizonal line