YaST2 Documentation

Novell Inc.

YaST2 Novell Inc. Team

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

Preface
1. Intro
2. What's Inside
3. Style Conventions
4. Introducing YaST
YaST - The Big Picture (overview)
1. Overview
1.1. Access To The System
1.2. Reasonable Suggestions
1.3. Workflows
1.4. Modules And The YCP-Language
1.5. User Interface
1.6. Summary
2. YaST Architecture
2.1. The SCR (System Configuration Repository)
2.2. The UI (User Interface)
2.3. YaST Core Engine
2.4. External Programs
The YaST Programming Language - YCP
1. The First YCP Program
1.1. YCP Source
1.2. The YCP compiler
1.3. Running YCP
2. YCP Data Types
2.1. Data Type void (nil)
2.2. Data Type symbol
2.3. Data Type boolean
2.4. Data Type integer
2.5. Data Type float
2.6. Data Type string
2.7. Data Type byteblock
2.8. Data Type list
2.9. Data Type map
2.10. Data Type path
2.11. Data Type term
2.12. Data Type any
3. More YCP types
3.1. Data Type block
4. YCP Type System
4.1. Data Type any And Type Checking
5. YCP Expression Evaluation
5.1. Evaluation Of Blocks
5.2. Evaluation Of Basic Data Types
6. YCP Operators
6.1. Comparison Operators
6.2. Boolean Operators
6.3. Bit Operators
6.4. Math Operators
6.5. Triple Operator
6.6. Operators Precedence
6.7. The bracket operator
6.7.1. Introduction
6.7.2. Access variant
6.7.2.1. Accessing lists
6.7.2.2. Accessing maps
6.7.2.3. Mixed map/list access
6.7.3. Assign variant
7. Data Type locale
8. YCP Program Structure
8.1. Comments
8.2. Variable Declaration
8.3. Variable Assignment
8.4. Conditional Branch
8.5. while() Loop
8.6. do..while() Loop
8.7. repeat..until() Loop
8.8. break Statement
8.9. continue Statement
8.10. return Statement
8.11. Function definition
8.12. Function declaration
8.13. include Statement
8.14. import Statement
8.15. Variable Scopes and blocks
8.16. Applying Expressions To Lists And Maps
8.16.1. foreach() Statement
8.16.2. listmap() Statement
8.16.3. maplist() Statement
8.16.4. mapmap() Statement
9. Controlling The User Interface
10. The YaST Wizard
11. Running y2base Stand-Alone
Access to the System (SCR in General)
1. SCR Agents
2. SCR Tree
3. Accessing SCR
4. Using SCR From Within YCP
5. Using SCR From The Command Line
6. Useful SCR Agents
YCP Modules in General
1. YCP Modules Overview
2. True YCP Modules
2.1. Included Modules
2.2. True Modules (Imported Modules)
2.3. True Modules And Constructors
3. Some Rules
3.1. Usability
4. Module Layout
4.1. Module Skeleton
4.2. Module Example
Source Code Documentation HowTo
1. YCP Module and Client Documentation
1.1. Documentation-Generation Tool
1.1.1. HTML Output
1.1.2. XML Output
1.2. YCP File Header
1.2.1. Authors AKA Author Attribute
1.2.2. Depends Attribute
1.2.3. File Attribute
1.2.4. Flags Attribute
1.2.5. Internal Attribute
1.2.6. Package AKA Module Attribute
1.2.7. Summary Attribute
1.3. YCP Functions
1.3.1. Description tag @deprecated
1.3.2. Description tag @descr
1.3.3. Description tag @example
1.3.4. Description tag @param
1.3.5. Description tag @ref
1.3.6. Description tag @return
1.3.7. Description tag @short
1.3.8. Description tag @since
1.3.9. Description tag @stable
1.3.10. Description tag @struct
1.3.11. Description tag @unstable
1.4. YCP Variables
2. Perl Module Documentation
3. SCR Agent Documentation
Common Modules and Libraries
1. Modules and Libraries
1.1. Packages Manipulation
1.1.1. Modules
1.1.2. Functions
1.1.2.1. Package Installation (GUI)
1.1.2.2. Package Installation (GUI, custom message)
1.1.2.3. Conditional Package Installation
1.1.2.4. Packages Installation (No GUI)
1.1.2.5. Testing
1.1.2.6. Other
1.2. Common Popup Dialogs
1.2.1. Simple and Expert Version
1.2.2. Headlines, Yes or No?
1.2.3. Predefined Messages
1.2.4. When to use which Popup
1.2.4.1. Decision Popups - two buttons, return true or false
1.2.4.2. Info Popups - just an "OK" button
1.3. Advanced YaST2 command line parsing
1.3.1. Important features
1.3.2. Basic usage of module CommandLine
1.3.3. Internally handled commands
1.3.4. Specification of the supported commands in YaST module
1.3.5. Advanced API
1.3.6. Example usage in YaST module
1.4. Runlevel Editor Library
1.4.1. Enabling/Disabling Service
1.4.2. Specifying Runlevels for Service
1.4.3. Init Script Actions
1.4.4. Service Info
1.4.5. Is Service Enabled?
1.4.6. Example of Usage
1.4.7. What Should You Know?
UI Layout and Events
1. YaST UI
1.1. YaST2 Layout
1.1.1. Summary: What's This All About?
1.1.2. Basics and Terms
1.1.2.1. The UI
1.1.2.2. Widgets
1.1.2.3. UI Independence and the libyui
1.1.2.4. The Nice Size
1.1.2.5. Initial Dialog Sizes
1.1.2.6. Full Screen Dialogs: `opt(`defaultsize)
1.1.3. Layout Building Blocks
1.1.3.1. Layout Boxes: ??? and ???
1.1.3.2. Specifying Proportions: ??? and ???
1.1.3.3. Rubber Bands: ??? and ???
1.1.3.4. Making Common Widgets Stretchable: `opt(`hstretch) and `opt(`vstretch)
1.1.3.5. Spacings: HSpacing and VSpacing
1.1.3.6. Alignments: ???, ???, ???, ???, ???, ???, ???
1.1.3.7. Compressing Excess Space: ???, ???, ???
1.1.3.8. Optical Grouping: ???
1.1.3.9. Grouping RadioButtons: ???
1.1.3.10. The Esoterics: ???
1.1.3.11. Obsolete: Split
1.1.4. Common Layout Techniques
1.1.4.1. Creating Widgets of Equal Size
1.1.4.2. Creating Widgets of Equal Size that don't Grow
1.1.4.3. Creating Widgets of Equal Size that don't Grow - with Spacings in between
1.1.4.4. Specifying the Size of Scrollable Widgets
1.1.5. Hints and Tips
1.1.5.1. Debugging Aids: The Log File
1.1.5.2. Keep it Simple - Do not Overcrowd Dialogs!
1.1.5.3. Always Keep Other UIs in Mind - What does it Look Like with NCurses?
1.1.5.4. Do not Neglect Mouseless Users - Always Provide Keyboard Shortcuts!
1.1.6. The Layout Algorithm - How the Layout Engine Works Internally
1.1.6.1. Primary and Secondary Dimensions
1.1.6.2. Calculating the Nice Size
1.1.6.2.1. Secondary Nice Size
1.1.6.2.2. Primary Nice Size
1.1.6.3. Setting the Size of a Layout - SetSize()
1.1.6.3.1. Running out of Space - the Pathological Cases
1.1.6.3.2. Centering in the Secondary Dimension
1.2. UI Events
1.2.1. Introduction
1.2.1.1. The YaST2 Event Model
1.2.1.1.1. Classic GUI Event Loops
1.2.1.1.2. The YaST2 Approach
1.2.1.1.3. Simplicity vs. Features
1.2.1.1.4. The notify Option
1.2.1.1.5. Downsides and Discussions
1.2.1.1.6. Design Alternatives
1.2.1.2. Event Delivery
1.2.1.2.1. Event Queues vs. One Single Pending Event
1.2.1.2.2. Event Reliability
1.2.1.2.3. Defensive Programming
1.2.2. Event-related UI Builtin Functions
1.2.3. Event Reference
1.2.3.1. Event Maps in General
1.2.3.2. Event Types
1.2.3.2.1. WidgetEvent
1.2.3.2.2. Activated WidgetEvent
1.2.3.2.3. ValueChanged WidgetEvent
1.2.3.2.4. SelectionChanged WidgetEvent
1.2.3.2.5. MenuEvent
1.2.3.2.6. TimeoutEvent
1.2.3.2.7. CancelEvent
1.2.3.2.8. KeyEvent
1.2.3.2.9. DebugEvent
Installation — Frameworks and Installation Process
1. Installation in General
1.1. Product Installation Control
1.1.1. Functionality
1.1.2. Implementation
1.1.3. Configuration
1.1.3.1. Workflows
1.1.3.2. Proposals
1.1.3.3. Installation and Product Variables
1.1.3.4. Special Installation and Product Variables
1.1.3.5. Installation helpers
1.1.3.6. Importing Files from Previous Installation
1.1.3.7. Automatic Configuration
1.1.3.8. Software
1.1.3.9. Supported Desktops
1.1.3.10. System Scenarios
1.1.3.11. Partitioning
1.1.3.11.1. Algorithm for space allocation
1.1.3.11.2. Configuration Options
1.1.3.12. Hooks
1.1.3.13. Texts
1.1.4. Add-on Product Installation Workflow Specification
1.1.4.1. Introduction
1.1.4.1.1. Product dependency
1.1.4.1.2. Order of updates of the workflow/wizard
1.1.4.1.3. Steps/Proposal Items Naming
1.1.4.1.4. Update possibilities
1.1.4.1.4.1. Insert an item into proposal
1.1.4.1.4.2. Remove an item from proposal
1.1.4.1.4.3. Replace an item in proposal
1.1.4.1.4.4. Insert steps to installation sequence
1.1.4.1.4.5. Append steps to installation sequence
1.1.4.1.4.6. Remove and replace steps in installation sequence
1.1.4.1.4.7. Add, remove, replace items in inst_finish.ycp
1.1.4.1.4.8. Replace whole second-stage workflow
1.1.4.1.4.9. Adding a new proposal
1.1.4.1.4.10. Replace or remove whole proposal
1.1.4.1.5. File layout
1.1.4.1.5.1. Add-on Product CD
1.1.4.1.5.2. Workflow Adaptation
1.1.4.1.6. Diff File Format
1.1.4.1.7. Setting a text domain
1.1.4.1.8. Defining proposals and workflow for standalone installation
1.1.4.1.9. Proposal modification
1.1.4.1.10. Appending an item at the end of proposal
1.1.4.1.11. Removing an item from a proposal
1.1.4.1.12. Replacing an item of a proposal
1.1.4.1.13. Workflow updates
1.1.4.1.14. Append steps to the end of installation sequence
1.1.4.1.15. Insert steps to installation sequence
1.1.4.1.16. Remove steps from installation sequence
1.1.4.1.17. Replace steps in installation sequence
1.1.4.1.18. Add items in inst_finish.ycp
1.1.4.1.18.1. Before chroot
1.1.4.1.18.2. Running in chroot
1.1.4.1.18.3. Before unmounting the system
1.1.4.1.19. Replace whole second-stage workflow
1.1.4.1.20. Algorith for Adapting Workflow
1.1.4.1.21. Product Features
1.1.4.1.22. AutoYaST profile generation
1.1.4.1.23. Example of OES 1.0
1.2. Firstboot Configuration
1.2.1. Enabling Firstboot
1.2.2. Customizing YaST Firstboot
1.2.2.1. Customizing Messages
1.2.2.2. License Action
1.2.2.3. Release Notes
1.2.2.4. Customizing Workflow Components
1.2.2.5. Using Automatic Configuration
1.2.3. Scripting
1.2.4. Firstboot and AutoYaST
1.3. Installation Features
1.3.1. Additional Products Automatically Added with Installation Repository
1.3.1.1. Configuration file add_on_products.xml
1.3.1.2. Configuration file add_on_products
2. Installation Proposal
2.1. API for YaST2 installation proposal
2.1.1. Motivation
2.1.2. Overview
2.1.3. The Dispatcher Interface
2.1.4. API functions
2.1.5. Dummy Proposal
2.2. Proposal API Reference
2.2.1. MakeProposal
2.2.1.1. Parameters
2.2.1.2. Return Values
2.2.2. AskUser
2.2.2.1. Parameters
2.2.2.2. Description
2.2.2.3. Return Values
2.2.3. Description
2.2.3.1. Return Values
2.2.4. Write
2.2.4.1. Description
2.2.4.2. Return Values
YaST Development And Tools
1. Development And Tools
1.1. YaST2 Development Tools
1.1.1. Quick Start
1.1.2. What is it?
1.1.3. Migration
1.1.4. Translation (po) Modules)
1.1.5. create-spec: Automatic creation of the .spec file
1.1.6. Overview of Paths
1.1.7. Toplevel make Targets in Detail
1.1.7.1. make package-local
1.1.7.2. make package
1.1.7.3. make check-tagversion
1.1.7.4. make check-up-to-date
1.1.7.5. make checkin-stable
1.1.7.6. make stable
1.2. YaST2 Logging
1.2.1. Introduction
1.2.2. Quick start
1.2.3. Logging levels
1.2.4. Logging functions
1.2.5. Additional functions
1.2.5.1. Setting the logfile name
1.2.5.2. Universal logging functions:
1.2.6. Components
1.2.7. Logfiles
1.2.8. Log entries
1.2.9. Logging control
1.2.10. Environment control
1.3. Check YCP Syntax
1.3.1. Quick Start
1.3.2. Why this Document?
1.3.3. Header Comment Checks
1.3.4. Filename Check
1.3.5. Author / Maintainer Entry Check
1.3.6. CVS Id: Marker Check
1.3.7. Translatable Messages Checks
1.3.7.1. textdomain Check
1.3.8. RichText / HTML Sanity Check
1.3.8.1. Completeness of <p> / </p> Paragraph Tags
1.3.8.2. Text Before, After, Between Paragraphs
1.3.8.3. No More Than One Paragraph per Message
1.3.8.4. Excess Forced Line Breaks <br> after Paragraphs
1.3.9. Widget / UI Function Parameter Checks
1.3.9.1. Keyboard Shortcut Check
1.3.9.2. Translatable Messages Check
1.3.10. Standardized Lib Function Checks
1.3.10.1. Duplicate Definitions of Wizard Lib Functions
1.3.10.2. Definitions and Usage of Obsolete Functions
1.3.10.3. Usage of Predefined Messages
1.3.11. Alternative Variable Declarations
1.3.12. Checking YCP Examples
1.3.13. check_ycp and Emacs
1.3.14. Extending check_ycp
1.3.14.1. Adding new Widgets / UI Functions
1.3.14.2. Other Extensions
1.4. The YaST2 Macro Recorder
1.4.1. Introduction
1.4.2. Quick Start
1.4.3. Purpose
1.4.4. What it is not
1.4.5. Quirks and Limitations
1.4.6. Anatomy of a Macro
1.5. YaST Desktop Files
1.5.1. Desktop File Rules
1.5.2. Desktop File Entries
1.5.2.1. Mandatory Desktop File Entries
1.5.2.2. YaST-Specific Desktop File Entries
1.5.2.3. AutoYaST-Specific Desktop File Entries
1.5.3. Desktop File Example
Reference — YCP Language
I. WFM Builtins
Args — Returns the arguments with which the module was called.
ClientExists — Checks whether a YCP client exists
Execute — Special interface to the system agent. Not for general use.
GetEncoding — Returns the current encoding code
GetEnvironmentEncoding — Returns the encoding code of the environment where YaST is started
GetLanguage — Returns the current language code (without modifiers !)
Read — Special interface to the system agent. Not for general use.
SCRClose — Closes a scr instance.
SCRGetDefault — Gets the default scr instance.
SCRGetName — Get the name of a scr instance.
SCROpen — Create a new scr instance.
SCRSetDefault — Sets the default scr instance.
SetLanguage — Selects the language for translate()
Write — Special interface to the system agent. Not for general use.
call — Executes a YCP client or a Y2 client component.
II. YCP Byteblock Builtins
size — Returns a size of a byteblock in bytes.
tobyteblock — Converts a value to a byteblock.
III. YCP Float Builtins
float::abs — absolute value
float::ceil — round upwards to integer
float::floor — round downwards to integer
float::pow — power function
float::trunc — round to integer, towards zero
tofloat — Converts a value to a floating point number.
tostring — Converts a floating point number to a string
IV. YCP Integer Builtins
tointeger — Converts a value to an integer.
V. YCP List Builtins
add — Create a new list with a new element
change — Changes a list. Deprecated, use LIST[size(LIST)] = value.
contains — Checks if a list contains an element
filter — Filters a List
find — Searches for the first occurence of a certain element in a list
flatten — Flattens List
foreach — Processes the content of a list
list::reduce — Reduces a list to a single value.
list::reduce — Reduces a list to a single value.
listmap — Maps an operation onto all elements of a list and thus creates a map.
lsort — Sort A List respecting locale
maplist — Maps an operation onto all elements of a list and thus creates a new list.
merge — Merges two lists into one
prepend — Prepends a list with a new element
remove — Removes element from a list
select — Selects a list element (deprecated, use LIST[INDEX]:DEFAULT)
setcontains — Checks if a sorted list contains an element
size — Returns size of list
sort — Sorts a List according to the YCP builtin predicate
sort — Sort list using an expression
splitstring — Split a string by delimiter
sublist — Extracts a sublist
sublist — Extracts a sublist
tolist — Converts a value to a list (deprecated, use (list)VAR).
toset — Sorts list and removes duplicates
union — Unions of lists
VI. Map Builtins
add — Add a key/value pair to a map
change — Change element pair in a map. Deprecated, use MAP[KEY] = VALUE.
filter — Filter a Map
foreach — Process the content of a map
haskey — Check if map has a certain key
lookup — Select a map element (deprecated, use MAP[KEY]:DEFAULT)
maplist — Maps an operation onto all elements key/value and create a list
mapmap — Maps an operation onto all key/value pairs of a map
remove — Remove key/value pair from a map
size — Size of a map
tomap — Converts a value to a map.
union — Union of 2 maps
VII. Miscellaneous YCP Builtins
eval — Evaluate a YCP value.
getenv — Change or add an environment variable
is — Checks whether a value is of a certain type
random — Random number generator.
setenv — Change or add an environment variable
setenv — Change or add an environment variable
sformat — Format a String
sleep — Sleeps a number of milliseconds.
srandom — Initialize random number generator
srandom — Initialize random number generator.
time — Return the number of seconds since 1.1.1970.
y2debug — Log a message to the y2log.
y2error — Log an error to the y2log.
y2internal — Log an internal message to the y2log.
y2milestone — Log a milestone to the y2log.
y2security — Log a security message to the y2log.
y2useritem — Log an user-level system message to the y2changes
y2usernote — Log an user-level addional message to the y2changes
y2warning — Log a warning to the y2log.
VIII. YCP Path Builtins
add — Add a path element to existing path
size — Returns the number of path elements
topath — Converts a value to a path.
IX. YCP String Builtins
crypt — Encrypts a string
cryptbigcrypt — Encrypts a string using bigcrypt
cryptblowfish — Encrypts a string with blowfish
cryptmd5 — Encrypts a string using md5
deletechars — Removes all characters from a string
dgettext — Translates the text using the given text domain
dngettext — Translates the text using a locale-aware plural form handling
dpgettext — Translates the text using the given text domain and path
filterchars — Filters characters out of a String
find — Returns position of a substring
findfirstnotof — Searches string for the first non matching chars
findfirstof — Finds position of the first matching characters in string
findlastnotof — Searches the last element of string that doesn't match
findlastof — Searches string for the last match
issubstring — searches for a specific string within another string
lsubstring — Extracts a substring in UTF-8 encoded string
lsubstring — Extracts a substring in UTF-8 encoded string
mergestring — Joins list elements with a string
regexpmatch — Searches a string for a POSIX Extended Regular Expression match.
regexppos — Returns a pair with position and length of the first match.
regexpsub — Regex Substitution
regexptokenize — Regex tokenize
search — Returns position of a substring
size — Returns the number of characters of the string s
substring — Returns part of a string
substring — Extracts a substring
timestring — Returns time string
toascii — Returns characters below 0x7F included in STRING
tohexstring — Converts an integer to a hexadecimal string.
tohexstring — Converts an integer to a hexadecimal string.
tolower — Makes a string lowercase
tostring — Converts a value to a string.
toupper — Makes a string uppercase
X. YCP Term Builtins
add — Add value to term
argsof — Returns the arguments of a term.
remove — Remove item from term
select — Select item from term
size — Returns the number of arguments of the term TERM.
symbolof — Returns the symbol of the term TERM.
toterm — Converts a value to a term.
toterm — Constructs a term from a symbol and a list.
Reference — User Interface
I. Event-related UI Builtin Functions
UI::UserInput — Waits for user input and returns a widget ID.
UI::PollInput — Checks for pending user input. Does not wait. Returns a widget ID or nil if no input is available.
UI::TimeoutUserInput — Waits for user input and returns a widget ID. Returns ID `timeout if no input is available for timeout milliseconds.
UI::WaitForEvent — Waits for user input and returns an event map. Returns ID `timeout if no input is available for timeout milliseconds.
II.
AskForExistingDirectory — Ask user for existing directory
AskForExistingFile — Ask user for existing file
AskForSaveFileName — Ask user for a file to save data to.
Beep — Beeps the system bell
BusyCursor — Sets the mouse cursor to the busy cursor
ChangeWidget — Changes widget contents
CheckShortcuts — Performs an explicit shortcut check after postponing shortcut checks.
CloseDialog() — Closes an open dialog
DumpWidgetTree — Debugging function
FakeUserInput — Fakes User Input
GetDisplayInfo — Gets Display Info
GetLanguage — Gets Language
GetProductName — Gets Product Name
Glyph — Returns a special character (a 'glyph')
HasSpecialWidget — Checks for support of a special widget type.
MakeScreenShot — Makes Screen Shot
NormalCursor — Sets the mouse cursor to the normal cursor
OpenDialog — Opens a Dialog with options
PlayMacro — Plays a recorded macro
PollInput — Poll Input
PostponeShortcutCheck — Postpones Shortcut Check
QueryWidget — Queries Widget contents
RecalcLayout — Recalculates Layout
Recode — Recodes encoding of string from or to "UTF-8" encoding.
RecordMacro — Records Macro into a file
RedrawScreen — Redraws the screen
ReplaceWidget
RunInTerminal — runs external program in the same terminal
RunPkgSelection — Initializes and run the PackageSelector widget
SetConsoleFont — Sets Console Font
SetFocus — Sets Focus to the specified widget
SetFunctionKeys — Sets the (default) function keys for a number of buttons.
SetKeyboard — Sets Keyboard
SetLanguage — Sets the language of the UI
SetProductName — Sets Product Name
StopRecordingMacro — Stops recording macro
TextMode — Check if the UI is running in text (NCurses) mode.
TimeoutUserInput — User Input with Timeout
UserInput — User Input
WFM/SCR — callback
WaitForEvent — Waits for Event
WidgetExists — Checks whether or not a widget with the given ID currently exists
WizardCommand — Runs a wizard command
III.
AAA_All-Widgets — Generic options for all widgets
BarGraph — Horizontal bar graph (optional widget)
BusyIndicator — Graphical busy indicator
ButtonBox — Layout for push buttons that takes button order into account
CheckBox — Clickable on/off toggle button
CheckBoxFrame — Frame with clickable on/off toggle button
ComboBox — drop-down list selection (optionally editable)
DateField — Date input field
DownloadProgress — Self-polling file growth progress indicator (optional widget)
DumbTab — Simplistic tab widget that behaves like push buttons
Empty — Placeholder widget
Frame — Frame with label
HBox — Generic layout: Arrange widgets horizontally or vertically
HSpacing — Fixed size empty space for layout
HSquash — Layout aid: Minimize widget to its preferred size
HWeight — Control relative size of layouts
Image — Pixmap image
InputField — Input field
IntField — Numeric limited range input field
Label — Simple static text
Left — Layout alignment
LogView — scrollable log lines like "tail -f"
MarginBox — Margins around one child widget
MenuButton — Button with popup menu
MinWidth — Layout minimum size
MultiLineEdit — multiple line text edit field
MultiSelectionBox — Selection box that allows selecton of multiple items
PackageSelector — Complete software package selection
PartitionSplitter — Hard disk partition splitter tool (optional widget)
PatternSelector — High-level widget to select software patterns (selections)
PkgSpecial — Package selection special - DON'T USE IT
ProgressBar — Graphical progress indicator
PushButton — Perform action on click
RadioButton — Clickable on/off toggle button for radio boxes
RadioButtonGroup — Radio box - select one of many radio buttons
ReplacePoint — Pseudo widget to replace parts of a dialog
RichText — Static text with HTML-like formatting
SelectionBox — Scrollable list selection
SimplePatchSelector — Simplified approach to patch selection
Slider — Numeric limited range input (optional widget)
Table — Multicolumn table widget
TimeField — Time input field
TimezoneSelector — Timezone selector map
Tree — Scrollable tree selection
VMultiProgressMeter — Progress bar with multiple segments (optional widget)
Wizard — Wizard frame - not for general use, use the Wizard:: module instead!
A. UI Richtext

Preface


Chapter 1. Intro

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 “officialYaST 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

Chapter 2. What's Inside

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.

Chapter 3. Style Conventions

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.

Chapter 4. Introducing YaST

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.

YaST - The Big Picture (overview)


List of Figures

2.1. The YaST Architecture

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.

Chapter 1. Overview

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.

1.1. Access To The System

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.

1.2. Reasonable Suggestions

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.

1.3. Workflows

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.

1.4. Modules And The YCP-Language

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.

1.5. User Interface

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.

1.6. Summary

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.

Chapter 2. YaST Architecture

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:

Figure 2.1. The YaST Architecture

The YaST Architecture

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.

2.1. The SCR (System Configuration Repository)

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.

2.2. The UI (User Interface)

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.

2.3. YaST Core Engine

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.

2.4. External Programs

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.

The YaST Programming Language - YCP


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.

Chapter 1. The First YCP Program

Probably the best way to get into the matter is by means of a simple example.

1.1. YCP Source

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.

1.2. The YCP compiler

Section not written yet...

1.3. Running YCP

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.

Figure 1.1. Output of the “Hello, World!”-program

Output of the Hello, World!-program

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.

Chapter 2. YCP Data Types

Just like any other high-level programming language YCP has typed variables to hold data of different kinds:

2.1. Data Type void (nil)

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.

2.2. Data Type symbol

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.

Example 2.1. Symbol constants

`literal
`next

2.3. Data Type boolean

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.

2.4. Data Type integer

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++).

Example 2.2. Integer constants

1
-17049349
0x9fa0
0xDEADBEEF
0233

2.5. Data Type float

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.

Example 2.3. Float constants

1.0
-0.0035
1e30
-0.128e-17

2.6. Data Type string

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:

RepresentationMeaning
\nNewline (ASCII 10)
\tTabulator
\rCarriage Return (ASCII 13)
\bBackspace
\fForm Feed
\abc ASCII character represented by the octal value abc. Note that unlike in C, there must be exactly 3 octal digits!
\XThe 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

2.7. Data Type byteblock

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.

Example 2.5. Byteblock constants

#[ ]
#[42]
#[0111fE]
#[03A6f298B5]

2.8. Data Type list

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.

2.9. Data Type map

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.

2.10. Data Type path

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:

RepresentationMeaning
\nNewline (ASCII 10)
\tTabulator
\rCarriage Return (ASCII 13)
\bBackspace
\fForm Feed
\xXX ASCII character represented by the hexadecimal value XX.
\XThe character X itself.

Example 2.8. Path constants

.
.17
.etc.fstab
."\nHello !\n".World

."\xff" == ."\xFF"
."\x41" == ."A"
."" != .

2.11. Data Type term

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"))

2.12. Data Type any

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.

Chapter 3. More YCP types

Table of Contents

3.1. Data Type block

Section not written yet...

3.1. Data Type block

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.

Example 3.1. Block constants

{ return 17; }
{ integer a = 5; return a + 8; }

Chapter 4. YCP Type System

Section not written yet...

4.1. Data Type any And Type Checking

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.

Chapter 5. YCP Expression Evaluation

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.

5.1. Evaluation Of Blocks

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
        

5.2. Evaluation Of Basic Data Types

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.

Chapter 6. YCP Operators

As any other programming language YCP knows a lot of operators that can be used to act on data.

6.1. Comparison Operators

These are binary operators for comparison of two values. The result is always boolean.

OperatorDatatypeDescription
==almost allTrue if operands are equal, otherwise false.
<almost allTrue if left operand is smaller than the right one, otherwise false.
>almost allTrue if left operand is greater than the right one, otherwise false.
<=almost allTrue if left operand is smaller or equal to the right one, otherwise false.
>=almost allTrue if left operand is greater or equal to the right one, otherwise false.
!=almost allTrue if operands are not equal, otherwise false.

6.2. Boolean Operators

These are logical operators, that works with boolean datatype, two are binary one is unary. The result is always boolean.

OperatorDatatypeDescription
&&booleanTrue if both operands are true, otherwise false (logical and).
||booleanTrue if at least one of the operands is true, otherwise false (logical or).
!booleanTrue if the operand if false, otherwise false (logical not).

6.3. Bit Operators

These are bit operators that works with integer, two are binary one is unary. The result is always integer.

OperatorDatatypeDescription
&integerBits of the result number are product of the bits of the operands (bit and).
|integerBits of the result number are count of the bits of the operands (bit or).
~integerBits of the result number are reverted bits of operand (bit not).
<<integerBits of the result number are left shifted bits of the operands (bit shift left).
>>integerBits of the result number are right shifted bits of the operands (bit shift right).

6.4. Math Operators

There math operators works with numeric data types (integer and float) and also with string. All are binary (except unary minus).

OperatorDatatypeDescription
+integer, float, stringThe result is sum of the numbers or concatenation of the strings.
-integer, floatThe result is difference of the numbers.
*integer, floatThe result is product of the numbers.
/integer, floatThe result is quotient of the numbers (number class is preserved, thus quotient of integers produce integer, etc).
%integerThe result is modulo.
unary -integer, floatThe result is negative number.

6.5. Triple Operator

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.

CodeResultComment
(3 > 2) ? true : falsetrueThe 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-1The expression size ([]) > 0 evaluates to false, the result is -1
[Note]Note

Using brackets makes code cleaner, but is not necessary (according to operators precedence).

[Note]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.

6.6. Operators Precedence

The table of operators precedence (from lowest to highest).

 Direction  Operators 
right=
left?:
left||
left&&
left== !=
left< <= > >=
left+ -
left* / %
left<< >>
left|
left&
prefix! ~ -

6.7. The bracket operator

6.7.1. Introduction

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.

6.7.2. Access variant

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.

6.7.2.1. Accessing lists

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]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 is 3
    integer three = list_of_numbers[2]:0;

    // evaluates to the default value 0,
    // because list element with index 42 doesn't exist
    integer zero = list_of_numbers[42]:0;

    // evaluates to 4
    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 to 3
    // just as the right side does (three == 3)
    return (list_of_lists[1,0]:0 == three);
}

6.7.2.2. Accessing maps

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]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);
}

6.7.2.3. Mixed map/list access

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);
}

6.7.3. Assign variant

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);
}

Chapter 7. Data Type locale

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)

Chapter 8. YCP Program Structure

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.

8.1. Comments

Despite not being “reallystatements, 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");
}

8.2. Variable Declaration

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);
}

8.3. Variable Assignment

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!!!
}

8.4. Conditional Branch

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);
}

8.5. while() Loop

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;
    }
}

8.6. do..while() Loop

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);
}

8.7. repeat..until() Loop

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);
}
	  

8.8. break Statement

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);
}

[Note]Note

The break statement can be used for all loop statments and also statments: listmap, mapmap, list-based maplist, map-based maplist, list-based foreach, map-based foreach, list-based filter, map-based filter.

8.9. continue Statement

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]Note
The usage is similar to the break's one.

8.10. return Statement

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.17. return statement 2

{
    // This block evaluates to 18
    while (true)
    {
	return 18;
    }
}

Example 8.18. return statement 3

{
    // This program evaluates to 3:
    integer a = 1 + { return 2; };

    return a;
}

8.11. Function definition

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());
}

8.12. Function declaration

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");
    }
}

8.13. include Statement

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";

8.14. import Statement

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.

8.15. Variable Scopes and blocks

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);
}

8.16. Applying Expressions To Lists And Maps

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.

8.16.1. foreach() Statement

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]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);
}

8.16.2. listmap() Statement

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);
}

8.16.3. maplist() Statement

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]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);
}

8.16.4. mapmap() Statement

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);
}
            

Chapter 9. Controlling The User Interface

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.

Chapter 10. The YaST Wizard

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.

Chapter 11. Running y2base Stand-Alone

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.

Access to the System (SCR in General)


List of Figures

2.1. SCR Hierarchy Tree

List of Tables

2.1. SCR Node Types
3.1. The SCR-commands

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.

Chapter 1. SCR Agents

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.

Chapter 2. 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.

Figure 2.1. SCR Hierarchy Tree

SCR Hierarchy Tree

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".

Chapter 3. Accessing SCR

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

FunctionWhat 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.

Chapter 4. Using SCR From Within YCP

FIXME: To be done... (Examples?)

Chapter 5. Using SCR From The Command Line

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 “simulateYaST-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]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.

Chapter 6. Useful SCR Agents

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/.

YCP Modules in General


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 .

Chapter 1. YCP Modules Overview

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.

Chapter 2. True YCP Modules

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.

2.1. Included Modules

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.

2.2. True Modules (Imported Modules)

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]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]Note

The first encounter of the statement import "Sample"; triggers the loading of “Sample.ycp”. Subsequent import statements are ignored, because “Sample” is already defined. Consequently you can't replace a module during runtime !

2.3. True Modules And Constructors

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]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 !
}
	

Chapter 3. Some Rules

Table of Contents

3.1. Usability

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.

3.1. Usability

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]Important

If you want to create an interactive YaST module you should try to heed those rules to ease the users life and to assure your module fits smoothly into the surrounding YaST environment which (hopefully) follows them too.

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

Chapter 4. Module Layout

FIXME: To be done...

4.1. Module Skeleton

FIXME: To be done...

4.2. Module Example

FIXME: To be done...

Source Code Documentation HowTo

Auto-Generated Documentation


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.

Chapter 1. YCP Module and Client Documentation

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.

1.1. Documentation-Generation Tool

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]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.

1.1.1. HTML Output

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.

1.1.2. XML Output

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]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>

1.2. YCP File Header

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

1.2.1. Authors AKA Author Attribute

Defines the author(s) of the file.

Example 1.3. Single-line Authors tag

Authors: Wendy Graceful <grace@example.com>

Example 1.4. Multi-line Authors tag

Authors: Wendy Graceful <grace@example.com>
         Lars Kralewski <lars@example.com>

1.2.2. Depends Attribute

What does this file / module depend on.

Example 1.5. Depends tag

Depends: Language

1.2.3. File Attribute

Relative path where is this file located under the base YaST /usr/share/YaST2/ path.

Example 1.6. File tag

File: modules/HTTP.ycp

1.2.4. Flags Attribute

Defines the default value for the module stability (read: stability of module API).

Possible values are:

  • Stable

  • Unstable

Example 1.7. Flags tag

Flags: Stable

1.2.5. Internal Attribute

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.

Example 1.8. Package tag

 *
 * Author: Joe Example <joe@example.com>
 * Internal
 *

1.2.6. Package AKA Module Attribute

Textual description of the package (YaST module) that this file belongs to. This tag is obsolete.

Example 1.9. Package tag

Package: SSHD Configuration Module

1.2.7. Summary Attribute

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.
 */

1.3. YCP Functions

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

1.3.1. Description tag @deprecated

Defines that the function has been deprecated. Additionaly defines another function as the replacement.

Example 1.13. @deprecated tag

@deprecated AnotherFunction()

1.3.2. Description tag @descr

Complete description of the function. It might describe the environment or detailed behavior.

Example 1.14. Standard description

@descr This is a standard multi-line description
       of a function.

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).
*/

1.3.3. Description tag @example

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.");

1.3.4. Description tag @param

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;
}

1.3.5. Description tag @ref

Single-line version of @see tag.

Example 1.18. @ref tag

@ref AddNewNSServer()

1.3.6. Description tag @return

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;
}

1.3.7. Description tag @short

Short single-line description of the function.

Example 1.20. Standard description

@short This function does nothing valuable

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.

1.3.8. Description tag @since

Since which version thix function exists. This is not a commonly used tag.

Example 1.22. @since tag

@since: 2.12.65

1.3.9. Description tag @stable

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).

Example 1.23. @stable tag

@stable

1.3.10. Description tag @struct

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 ],
]

1.3.11. Description tag @unstable

Defines that the function has unknown of unstable API and we plan (or might) to change it in the future.

See the @stable tag.

Example 1.25. @unstable tag

@unstable

1.4. YCP Variables

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.

Chapter 2. Perl Module Documentation

...

Chapter 3. SCR Agent Documentation

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(
    ...
)

Common Modules and Libraries


Chapter 1. Modules and Libraries

1.1. Packages Manipulation

This is a set of basic routines for manipulating packages.

1.1.1. Modules

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

1.1.2. Functions

The function names should be self-descriptive, so there are no comments here. Feel free to ask if you are in doubts. (FIXME)

1.1.2.1. Package Installation (GUI)

  • boolean Package::Install(<string> package);

  • boolean Package::InstallAll(list<string> packages);

  • boolean Package::InstallAny(list<string> packages);

  • boolean Package::Remove(<string> package);

  • boolean Package::RemoveAll(list<string> packages);

1.1.2.2. Package Installation (GUI, custom message)

  • 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);

1.1.2.3. Conditional Package Installation

[Note]Note

GUI based, do not install if Mode::config is defined, only in PackageSystem)

  • boolean Package::CheckAndInstallPackages (list<string> packages);

  • boolean Package::CheckAndInstallPackagesInteractive (list<string> packages); // with error handling

1.1.2.4. Packages Installation (No GUI)

  • boolean Package::DoInstall(list<string> packages);

  • boolean Package::DoRemove(list<string> packages);

  • boolean Package::DoInstallAndRemove(list<string> toinstall, list<string> toremove);

1.1.2.5. Testing

  • boolean Package::Available(<string> package);

  • boolean Package::AvailableAll(list<string> packages);

  • boolean Package::AvailableAny(list<string> packages);

  • boolean Package::Installed(<string> package);

  • boolean Package::InstalledAll(list<string> packages);

  • boolean Package::InstalledAny(list<string> packages);

1.1.2.6. Other

  • void RunSUSEconfig();

  • boolean Package::LastOperationCanceled();

  • boolean Package::InstallKernel(list<lt;string> kernel_modules);

1.2. Common Popup Dialogs

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).

1.2.1. Simple and Expert Version

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.

1.2.2. Headlines, Yes or No?

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]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.

1.2.3. Predefined Messages

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).

1.2.4. When to use which Popup

1.2.4.1. Decision Popups - two buttons, return true or false

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]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" 
}

1.2.4.2. Info Popups - just an "OK" button

If you can classify a simple message accordingly, use one of

  • Popup::Error

  • Popup::Warning

  • Popup::Notify

they all have a headline that indicates the type (Error, Warning or Notification).

If you can't classify your message, use the Popup::Message.

Use Popup::LongText to display large amounts of text that might need scrolling.

1.3. Advanced YaST2 command line parsing

1.3.1. Important features

  • simple specification in the YaST module

  • automatic help

  • automatic checking of arguments (types, format)

  • interactive session without UI

1.3.2. Basic usage of module CommandLine

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:

  1. standard UI start of a module - CommandLine::StartGUI will return true in this case

  2. command given as an argument - the inner while loop will be done only once

  3. interactive controling of a module - the while loop will be done as long as the user does not enter "exit" or "quit"

1.3.3. Internally handled commands

help

shows the help text for the command

interactive

starts interactive session without UI

<command> help

shows the command-specific help

<command> quiet

option to supress the progress messages

These are available in interactive mode only:

quit

quits interactive session, sets CommandLine::Aborted() flag to true

exit

exits interactive session, sets CommandLine::Aborted() flag to false

1.3.4. Specification of the supported commands in YaST module

[Note]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:

non_strict

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:

regex

In this case, you need to specify "typespec" key containing the regular expression the argument should be matched against.

enum

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" ]
                        ]

        

1.3.5. Advanced API

If you need to write your own event loop, this is a part of the CommandLine API useful for this:

boolean CommandLine::StartGUI()

returns true, if the user asked to start up the module GUI

list CommandLine::Scan()

reads a new command line in interactive mode, splits the arguments into a list

map CommandLine::Command()

parse (and scan if needed) next command and return its map

map CommandLine::Parse( list commandline )

lower-level function to parse the command line, check the validity

boolean CommandLine::Done()

returns true, if the last command was already returned

boolean CommandLine::Aborted()

returns true, if the user asked to cancel the changes

void CommandLine::Error(string)

prints the string and then a message how to obtain the help

void CommandLine::Print(string)

prints the string

void CommandLine::PrintVerbose(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]Note

In interactive mode, the communication uses /dev/tty. In non-interactive commandline mode it prints everything to standard error output.

1.3.6. Example usage in YaST module

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 */
}

1.4. Runlevel Editor Library

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.

[Note]Note

You do not need to run Service::Read if you want to use any of them.

1.4.1. Enabling/Disabling Service

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");

1.4.2. Specifying Runlevels for Service

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" ]);

1.4.3. Init Script Actions

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");

1.4.4. Service Info

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.

1.4.5. Is Service Enabled?

Service::Enabled (string name) returns true if service is set to run in any runlevel. False otherwise.

1.4.6. Example of Usage

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");
        }
}

1.4.7. What Should You Know?

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

UI Layout and Events


Table of Contents

1. YaST UI
1.1. YaST2 Layout
1.1.1. Summary: What's This All About?
1.1.2. Basics and Terms
1.1.2.1. The UI
1.1.2.2. Widgets
1.1.2.3. UI Independence and the libyui
1.1.2.4. The Nice Size
1.1.2.5. Initial Dialog Sizes
1.1.2.6. Full Screen Dialogs: `opt(`defaultsize)
1.1.3. Layout Building Blocks
1.1.3.1. Layout Boxes: ??? and ???
1.1.3.2. Specifying Proportions: ??? and ???
1.1.3.3. Rubber Bands: ??? and ???
1.1.3.4. Making Common Widgets Stretchable: `opt(`hstretch) and `opt(`vstretch)
1.1.3.5. Spacings: HSpacing and VSpacing
1.1.3.6. Alignments: ???, ???, ???, ???, ???, ???, ???
1.1.3.7. Compressing Excess Space: ???, ???, ???
1.1.3.8. Optical Grouping: ???
1.1.3.9. Grouping RadioButtons: ???
1.1.3.10. The Esoterics: ???
1.1.3.11. Obsolete: Split
1.1.4. Common Layout Techniques
1.1.4.1. Creating Widgets of Equal Size
1.1.4.2. Creating Widgets of Equal Size that don't Grow
1.1.4.3. Creating Widgets of Equal Size that don't Grow - with Spacings in between
1.1.4.4. Specifying the Size of Scrollable Widgets
1.1.5. Hints and Tips
1.1.5.1. Debugging Aids: The Log File
1.1.5.2. Keep it Simple - Do not Overcrowd Dialogs!
1.1.5.3. Always Keep Other UIs in Mind - What does it Look Like with NCurses?
1.1.5.4. Do not Neglect Mouseless Users - Always Provide Keyboard Shortcuts!
1.1.6. The Layout Algorithm - How the Layout Engine Works Internally
1.1.6.1. Primary and Secondary Dimensions
1.1.6.2. Calculating the Nice Size
1.1.6.2.1. Secondary Nice Size
1.1.6.2.2. Primary Nice Size
1.1.6.3. Setting the Size of a Layout - SetSize()
1.1.6.3.1. Running out of Space - the Pathological Cases
1.1.6.3.2. Centering in the Secondary Dimension
1.2. UI Events
1.2.1. Introduction
1.2.1.1. The YaST2 Event Model
1.2.1.1.1. Classic GUI Event Loops
1.2.1.1.2. The YaST2 Approach
1.2.1.1.3. Simplicity vs. Features
1.2.1.1.4. The notify Option
1.2.1.1.5. Downsides and Discussions
1.2.1.1.6. Design Alternatives
1.2.1.2. Event Delivery
1.2.1.2.1. Event Queues vs. One Single Pending Event
1.2.1.2.2. Event Reliability
1.2.1.2.3. Defensive Programming
1.2.2. Event-related UI Builtin Functions
1.2.3. Event Reference
1.2.3.1. Event Maps in General
1.2.3.2. Event Types
1.2.3.2.1. WidgetEvent
1.2.3.2.2. Activated WidgetEvent
1.2.3.2.3. ValueChanged WidgetEvent
1.2.3.2.4. SelectionChanged WidgetEvent
1.2.3.2.5. MenuEvent
1.2.3.2.6. TimeoutEvent
1.2.3.2.7. CancelEvent
1.2.3.2.8. KeyEvent
1.2.3.2.9. DebugEvent

Chapter 1. YaST UI

Table of Contents

1.1. YaST2 Layout
1.1.1. Summary: What's This All About?
1.1.2. Basics and Terms
1.1.2.1. The UI
1.1.2.2. Widgets
1.1.2.3. UI Independence and the libyui
1.1.2.4. The Nice Size
1.1.2.5. Initial Dialog Sizes
1.1.2.6. Full Screen Dialogs: `opt(`defaultsize)
1.1.3. Layout Building Blocks
1.1.3.1. Layout Boxes: ??? and ???
1.1.3.2. Specifying Proportions: ??? and ???
1.1.3.3. Rubber Bands: ??? and ???
1.1.3.4. Making Common Widgets Stretchable: `opt(`hstretch) and `opt(`vstretch)
1.1.3.5. Spacings: HSpacing and VSpacing
1.1.3.6. Alignments: ???, ???, ???, ???, ???, ???, ???
1.1.3.7. Compressing Excess Space: ???, ???, ???
1.1.3.8. Optical Grouping: ???
1.1.3.9. Grouping RadioButtons: ???
1.1.3.10. The Esoterics: ???
1.1.3.11. Obsolete: Split
1.1.4. Common Layout Techniques
1.1.4.1. Creating Widgets of Equal Size
1.1.4.2. Creating Widgets of Equal Size that don't Grow
1.1.4.3. Creating Widgets of Equal Size that don't Grow - with Spacings in between
1.1.4.4. Specifying the Size of Scrollable Widgets
1.1.5. Hints and Tips
1.1.5.1. Debugging Aids: The Log File
1.1.5.2. Keep it Simple - Do not Overcrowd Dialogs!
1.1.5.3. Always Keep Other UIs in Mind - What does it Look Like with NCurses?
1.1.5.4. Do not Neglect Mouseless Users - Always Provide Keyboard Shortcuts!
1.1.6. The Layout Algorithm - How the Layout Engine Works Internally
1.1.6.1. Primary and Secondary Dimensions
1.1.6.2. Calculating the Nice Size
1.1.6.2.1. Secondary Nice Size
1.1.6.2.2. Primary Nice Size
1.1.6.3. Setting the Size of a Layout - SetSize()
1.1.6.3.1. Running out of Space - the Pathological Cases
1.1.6.3.2. Centering in the Secondary Dimension
1.2. UI Events
1.2.1. Introduction
1.2.1.1. The YaST2 Event Model
1.2.1.1.1. Classic GUI Event Loops
1.2.1.1.2. The YaST2 Approach
1.2.1.1.3. Simplicity vs. Features
1.2.1.1.4. The notify Option
1.2.1.1.5. Downsides and Discussions
1.2.1.1.6. Design Alternatives
1.2.1.2. Event Delivery
1.2.1.2.1. Event Queues vs. One Single Pending Event
1.2.1.2.2. Event Reliability
1.2.1.2.3. Defensive Programming
1.2.2. Event-related UI Builtin Functions
1.2.3. Event Reference
1.2.3.1. Event Maps in General
1.2.3.2. Event Types
1.2.3.2.1. WidgetEvent
1.2.3.2.2. Activated WidgetEvent
1.2.3.2.3. ValueChanged WidgetEvent
1.2.3.2.4. SelectionChanged WidgetEvent
1.2.3.2.5. MenuEvent
1.2.3.2.6. TimeoutEvent
1.2.3.2.7. CancelEvent
1.2.3.2.8. KeyEvent
1.2.3.2.9. DebugEvent

1.1. YaST2 Layout

1.1.1. Summary: What's This All About?

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.

1.1.2. Basics and Terms

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. ;-)

1.1.2.1. The UI

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.

1.1.2.2. Widgets

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.

1.1.2.3. UI Independence and the libyui

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).

1.1.2.4. The Nice Size

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.

1.1.2.5. Initial Dialog Sizes

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).

1.1.2.6. Full Screen Dialogs: `opt(`defaultsize)

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.

1.1.3. Layout Building Blocks

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.

1.1.3.1. Layout Boxes: HBox and VBox

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.

1.1.3.2. Specifying Proportions: HWeight and VWeight

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.


1.1.3.3. Rubber Bands: HStretch and VStretch

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).

1.1.3.4. Making Common Widgets Stretchable: `opt(`hstretch) and `opt(`vstretch)

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.

1.1.3.5. Spacings: HSpacing and VSpacing

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.


1.1.3.6. Alignments: Left, Right, HCenter, Top, Bottom, VCenter, HVCenter

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.

1.1.3.7. Compressing Excess Space: HSquash, VSquash, HVSquash

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(
                                 ...
                                )
                          )
                   )
         )

1.1.3.8. Optical Grouping: Frame

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.

1.1.3.9. Grouping RadioButtons: RadioButtonGroup

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(...)
                                                  )
                                            )
                          )
                   )
         )

1.1.3.10. The Esoterics: ReplacePoint

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.

1.1.3.11. Obsolete: Split

This is not used any more. If you know anything about it, forget it. If you don't, don't bother. It's old and obsolete and nobody used it anyway.

1.1.4. Common Layout Techniques

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.

1.1.4.1. Creating Widgets of Equal Size

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.

1.1.4.2. Creating Widgets of Equal Size that don't Grow

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.

1.1.4.3. Creating Widgets of Equal Size that don't Grow - with Spacings in between

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.

1.1.4.4. Specifying the Size of Scrollable 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.


1.1.5. Hints and Tips

1.1.5.1. Debugging Aids: The Log File

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.

1.1.5.2. Keep it Simple - Do not Overcrowd Dialogs!

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.

1.1.5.3. Always Keep Other UIs in Mind - What does it Look Like with NCurses?

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.

1.1.5.4. Do not Neglect Mouseless Users - Always Provide Keyboard Shortcuts!

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.

1.1.6. The Layout Algorithm - How the Layout Engine Works Internally

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.

1.1.6.1. Primary and Secondary Dimensions

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).

1.1.6.2. Calculating the Nice Size

1.1.6.2.1. Secondary Nice Size

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.

1.1.6.2.2. Primary Nice Size

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.

1.1.6.3. Setting the Size of a Layout - SetSize()

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.

1.1.6.3.1. Running out of Space - the Pathological Cases

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.

1.1.6.3.2. Centering in the Secondary Dimension
stretchable

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.

1.2. UI Events

1.2.1. Introduction

1.2.1.1. The YaST2 Event Model

1.2.1.1.1. Classic GUI Event Loops

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

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

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

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

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

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

1.2.1.1.2. The YaST2 Approach

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

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

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

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

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

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

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

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

1.2.1.1.3. Simplicity vs. Features

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

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

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

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

    symbol button_id = UI::UserInput();

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

    UI::CloseDialog();
}
                

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

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

1.2.1.1.4. The notify Option

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

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.


1.2.1.1.5. Downsides and Discussions

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

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

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

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

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

OpenDialog(...);

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

CloseDialog();

                

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

1.2.1.1.6. Design Alternatives

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

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

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

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

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

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

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

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

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

UI::RemoveCallback()

It might look about like this:

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

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

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

                

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

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

  • Widget creation with UI::OpenDialog()

  • Adding callbacks with UI::AddCallback()

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

Bottom line: Having callbacks is not really an improvement.

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

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

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

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

1.2.1.2. Event Delivery

1.2.1.2.1. Event Queues vs. One Single Pending Event

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

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

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

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

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

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

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

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

  • The user clicks "OK".

  • The background initialization takes some more time.

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

  • The background initialization still is not done.

  • The user clicks "OK" again.

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

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

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

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

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

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

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

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

Argh. What a mess.

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

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

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

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

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

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

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

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

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

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

1.2.1.2.2. Event Reliability

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

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

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

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

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

1.2.1.2.3. Defensive Programming

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

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

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

{
    // Example how NOT to do things

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

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

    map event = $[];

    repeat
    {
          event = UI::WaitForEvent();

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

                
{
    // Fixed the broken logic in the example above

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

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

    map event = $[];

    repeat
    {
        event = UI::WaitForEvent();

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

                

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

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

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

1.2.2. Event-related UI Builtin Functions

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

The Event-related UI Builtin are available in the reference

1.2.3. Event Reference

1.2.3.1. Event Maps in General

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

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

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

  • Use this for general event classification.

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

1.2.3.2. Event Types

1.2.3.2.1. WidgetEvent

All WidgetEvents have these map fields in common:

Map KeyValue TypeValid ValuesDescription
EventTypestringWidgetEvent(constant)
EventReasonstring The reason for this event. This is something like an event sub-type. Use this to find out what the user really did with the widget.
IDany The ID of the widget that caused the event. This is what UserInput() returns.
WidgetIDany 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.
WidgetClassstringPushButton SelectionBox Table CheckBox ...The class (type) of the widget that caused the event.
WidgetDebugLabelstring 

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

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

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

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

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

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

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

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

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

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

  • Press space on the button.

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

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

  • Press space on an item.

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

  • Press space on an item.

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

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

  • Press space on an item.

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

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

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

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

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

  • Press space on an item.

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

  • Press space on the widget.

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

  • Press space on the widget.

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

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

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

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

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

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

  • Click on the up button (Qt).

  • Click on the down button (Qt).

  • Press cursor up in the widget (NCurses).

  • Press cursor down in the widget (NCurses).

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

  • Enter a number in the embedded IntField.

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

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

  • Enter a number in one of the embedded IntFields.

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

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

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

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

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

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

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

  • Press cursor up in the widget.

  • Press cursor down in the widget.

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

  • Press cursor up in the widget.

  • Press cursor down in the widget.

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

  • Press cursor up in the widget.

  • Press cursor down in the widget.

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

  • Press cursor up in the widget.

  • Press cursor down in the widget.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1.2.3.2.8. KeyEvent

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

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

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

Map KeyValue TypeValid ValuesDescription
EventTypestringKeyEvent(constant)
IDstring


                                    CursorRight
                                    CursorDown
                                    F1
                                    a
                                    A
                                    ...
                                

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


                                    CursorRight
                                    CursorDown
                                    F1
                                    a
                                    A
                                    ...
                                

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

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

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

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

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

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

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

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

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

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

Installation — Frameworks and Installation Process


Table of Contents

1. Installation in General
1.1. Product Installation Control
1.1.1. Functionality
1.1.2. Implementation
1.1.3. Configuration
1.1.3.1. Workflows
1.1.3.2. Proposals
1.1.3.3. Installation and Product Variables
1.1.3.4. Special Installation and Product Variables
1.1.3.5. Installation helpers
1.1.3.6. Importing Files from Previous Installation
1.1.3.7. Automatic Configuration
1.1.3.8. Software
1.1.3.9. Supported Desktops
1.1.3.10. System Scenarios
1.1.3.11. Partitioning
1.1.3.11.1. Algorithm for space allocation
1.1.3.11.2. Configuration Options
1.1.3.12. Hooks
1.1.3.13. Texts
1.1.4. Add-on Product Installation Workflow Specification
1.1.4.1. Introduction
1.1.4.1.1. Product dependency
1.1.4.1.2. Order of updates of the workflow/wizard
1.1.4.1.3. Steps/Proposal Items Naming
1.1.4.1.4. Update possibilities
1.1.4.1.4.1. Insert an item into proposal
1.1.4.1.4.2. Remove an item from proposal
1.1.4.1.4.3. Replace an item in proposal
1.1.4.1.4.4. Insert steps to installation sequence
1.1.4.1.4.5. Append steps to installation sequence
1.1.4.1.4.6. Remove and replace steps in installation sequence
1.1.4.1.4.7. Add, remove, replace items in inst_finish.ycp
1.1.4.1.4.8. Replace whole second-stage workflow
1.1.4.1.4.9. Adding a new proposal
1.1.4.1.4.10. Replace or remove whole proposal
1.1.4.1.5. File layout
1.1.4.1.5.1. Add-on Product CD
1.1.4.1.5.2. Workflow Adaptation
1.1.4.1.6. Diff File Format
1.1.4.1.7. Setting a text domain
1.1.4.1.8. Defining proposals and workflow for standalone installation
1.1.4.1.9. Proposal modification
1.1.4.1.10. Appending an item at the end of proposal
1.1.4.1.11. Removing an item from a proposal
1.1.4.1.12. Replacing an item of a proposal
1.1.4.1.13. Workflow updates
1.1.4.1.14. Append steps to the end of installation sequence
1.1.4.1.15. Insert steps to installation sequence
1.1.4.1.16. Remove steps from installation sequence
1.1.4.1.17. Replace steps in installation sequence
1.1.4.1.18. Add items in inst_finish.ycp
1.1.4.1.18.1. Before chroot
1.1.4.1.18.2. Running in chroot
1.1.4.1.18.3. Before unmounting the system
1.1.4.1.19. Replace whole second-stage workflow
1.1.4.1.20. Algorith for Adapting Workflow
1.1.4.1.21. Product Features
1.1.4.1.22. AutoYaST profile generation
1.1.4.1.23. Example of OES 1.0
1.2. Firstboot Configuration
1.2.1. Enabling Firstboot
1.2.2. Customizing YaST Firstboot
1.2.2.1. Customizing Messages
1.2.2.2. License Action
1.2.2.3. Release Notes
1.2.2.4. Customizing Workflow Components
1.2.2.5. Using Automatic Configuration
1.2.3. Scripting
1.2.4. Firstboot and AutoYaST
1.3. Installation Features
1.3.1. Additional Products Automatically Added with Installation Repository
1.3.1.1. Configuration file add_on_products.xml
1.3.1.2. Configuration file add_on_products
2. Installation Proposal
2.1. API for YaST2 installation proposal
2.1.1. Motivation
2.1.2. Overview
2.1.3. The Dispatcher Interface
2.1.4. API functions
2.1.5. Dummy Proposal
2.2. Proposal API Reference
2.2.1. MakeProposal
2.2.1.1. Parameters
2.2.1.2. Return Values
2.2.2. AskUser
2.2.2.1. Parameters
2.2.2.2. Description
2.2.2.3. Return Values
2.2.3. Description
2.2.3.1. Return Values
2.2.4. Write
2.2.4.1. Description
2.2.4.2. Return Values

List of Examples

1.1. Flexible Partitioning
2.1. Proposal Example

Chapter 1. Installation in General

Table of Contents

1.1. Product Installation Control
1.1.1. Functionality
1.1.2. Implementation
1.1.3. Configuration
1.1.3.1. Workflows
1.1.3.2. Proposals
1.1.3.3. Installation and Product Variables
1.1.3.4. Special Installation and Product Variables
1.1.3.5. Installation helpers
1.1.3.6. Importing Files from Previous Installation
1.1.3.7. Automatic Configuration
1.1.3.8. Software
1.1.3.9. Supported Desktops
1.1.3.10. System Scenarios
1.1.3.11. Partitioning
1.1.3.11.1. Algorithm for space allocation
1.1.3.11.2. Configuration Options
1.1.3.12. Hooks
1.1.3.13. Texts
1.1.4. Add-on Product Installation Workflow Specification
1.1.4.1. Introduction
1.1.4.1.1. Product dependency
1.1.4.1.2. Order of updates of the workflow/wizard
1.1.4.1.3. Steps/Proposal Items Naming
1.1.4.1.4. Update possibilities
1.1.4.1.4.1. Insert an item into proposal
1.1.4.1.4.2. Remove an item from proposal
1.1.4.1.4.3. Replace an item in proposal
1.1.4.1.4.4. Insert steps to installation sequence
1.1.4.1.4.5. Append steps to installation sequence
1.1.4.1.4.6. Remove and replace steps in installation sequence
1.1.4.1.4.7. Add, remove, replace items in inst_finish.ycp
1.1.4.1.4.8. Replace whole second-stage workflow
1.1.4.1.4.9. Adding a new proposal
1.1.4.1.4.10. Replace or remove whole proposal
1.1.4.1.5. File layout
1.1.4.1.5.1. Add-on Product CD
1.1.4.1.5.2. Workflow Adaptation
1.1.4.1.6. Diff File Format
1.1.4.1.7. Setting a text domain
1.1.4.1.8. Defining proposals and workflow for standalone installation
1.1.4.1.9. Proposal modification
1.1.4.1.10. Appending an item at the end of proposal
1.1.4.1.11. Removing an item from a proposal
1.1.4.1.12. Replacing an item of a proposal
1.1.4.1.13. Workflow updates
1.1.4.1.14. Append steps to the end of installation sequence
1.1.4.1.15. Insert steps to installation sequence
1.1.4.1.16. Remove steps from installation sequence
1.1.4.1.17. Replace steps in installation sequence
1.1.4.1.18. Add items in inst_finish.ycp
1.1.4.1.18.1. Before chroot
1.1.4.1.18.2. Running in chroot
1.1.4.1.18.3. Before unmounting the system
1.1.4.1.19. Replace whole second-stage workflow
1.1.4.1.20. Algorith for Adapting Workflow
1.1.4.1.21. Product Features
1.1.4.1.22. AutoYaST profile generation
1.1.4.1.23. Example of OES 1.0
1.2. Firstboot Configuration
1.2.1. Enabling Firstboot
1.2.2. Customizing YaST Firstboot
1.2.2.1. Customizing Messages
1.2.2.2. License Action
1.2.2.3. Release Notes
1.2.2.4. Customizing Workflow Components
1.2.2.5. Using Automatic Configuration
1.2.3. Scripting
1.2.4. Firstboot and AutoYaST
1.3. Installation Features
1.3.1. Additional Products Automatically Added with Installation Repository
1.3.1.1. Configuration file add_on_products.xml
1.3.1.2. Configuration file add_on_products

1.1. Product Installation Control

1.1.1. Functionality

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.

1.1.2. Implementation

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

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 /usr/share/YaST2/control/control.xml.

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.

1.1.3. Configuration

1.1.3.1. Workflows

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>

            

1.1.3.2. Proposals

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>

            

1.1.3.3. Installation and Product Variables

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>


            

1.1.3.4. Special Installation and Product Variables

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.

1.1.3.5. Installation helpers

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.

1.1.3.6. Importing Files from Previous Installation

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

1.1.3.7. Automatic Configuration

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.

1.1.3.8. Software

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:

  • default_desktop - defines a desktop selected by default by the installation.

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.

1.1.3.9. Supported Desktops

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.

1.1.3.10. System Scenarios

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.

1.1.3.11. Partitioning

If present, the partition proposal will be based on the data provided in the control file.

1.1.3.11.1. Algorithm for space allocation

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.

1.1.3.11.2. Configuration Options

The following elements are global to all disks and partitions:

prefer_remove

Possible values

true|false

Default value

true

Description

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

Possible values

true|false

Default value

false

Description

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

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

Possible values

comma separated list of reiser, xfs, fat, vfat, ext2, ext3, jfs, ntfs, swap

Default value

Empty list

Description

Partitions that contain filesystems in that list are not deleted even if prefer_remove is set to true.

keep_partition_id

Possible values

comma separated list of possible partition ids

Default value

Empty list

Description

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

Possible values

comma separated list of possible partition numbers

Default value

Empty list

Description

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.

[Note]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

Example

<mount>swap</mount>

Description

This entry describes the mount point of the partition. For a swap partition the special value "swap" has to be used.

fsys

Example

<fsys>reiser</fsys>

Description

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

Example

<formatopt>reiser<formatopt>

Description

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

Example

<fstopt>acl,user_xattr<fstopt>

Description

This entry describes the options written to /etc/fstab. Multiple options have to be separated by comma. This entry is optional.

label

Example

<label>emil<label>

Description

If the filesystem can have a label, the value of the label is set to this value.

id

Example

<id>0x8E<id>

Description

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

Example

<size>2G<size>

Description

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

Example

<percent>30<percent>

Description

This keyword determines that a partition should be allocated a certain percentage of the available space for installation on a disk.

maxsize

Example

<maxsize>4G<maxsize>

Description

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

Example

<increasable config:type="boolean">true<increasable>

Default

false

Description

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

Example

<disk>2<disk>

Description

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>

                

1.1.3.12. Hooks

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>


            

1.1.3.13. Texts

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: &lt;b&gt;bold &lt;/b&gt;.</label>
    </some_text_id>

    <congratulate>
	<label>&lt;p&gt;&lt;b&gt;Congratulations!&lt;/b&gt;&lt;/p&gt;</label>
    </congratulate>
</texts>

	    

Translated texts can be got using ProductControl::GetTranslatedText (text_id) call.

1.1.4. Add-on Product Installation Workflow Specification

1.1.4.1. Introduction

1.1.4.1.1. Product dependency

Everywhere, product B depends on product A, there is no dependency related to product C. A, B and C are add-on products.

1.1.4.1.2. Order of updates of the workflow/wizard

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.

1.1.4.1.3. Steps/Proposal Items Naming

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.

1.1.4.1.4. Update possibilities
1.1.4.1.4.1. Insert an item into proposal

Item is always added at the end of the proposal. Multiple items are possible.

1.1.4.1.4.2. Remove an item from proposal

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.

1.1.4.1.4.3. Replace an item in proposal

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.

1.1.4.1.4.4. Insert steps to installation sequence

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.

1.1.4.1.4.5. Append steps to installation sequence

The steps can be appended at the end of installation sequence.

1.1.4.1.4.6. Remove and replace steps in installation sequence

The same rules for removing and replacing steps of the installation workflow as for proposal items will be applied.

1.1.4.1.4.7. Add, remove, replace items in inst_finish.ycp

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).

1.1.4.1.4.8. Replace whole second-stage workflow

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.

1.1.4.1.4.9. Adding a new proposal

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

1.1.4.1.4.10. Replace or remove whole proposal

Is possible as replacing or removing a step of the installation workflow.

1.1.4.1.5. File layout
1.1.4.1.5.1. Add-on Product CD

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

1.1.4.1.5.2. Workflow Adaptation

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.

1.1.4.1.6. Diff File Format

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>

	    
1.1.4.1.7. Setting a text domain

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>
	    
1.1.4.1.8. Defining proposals and workflow for standalone installation

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>
	    
1.1.4.1.9. Proposal modification

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>
	    
1.1.4.1.10. Appending an item at the end of 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>
	    
1.1.4.1.11. Removing an item from a proposal
		<remove_modules config:type="list">
		    <remove_module>module_3</remove_module>
		    <remove_module>module_4</remove_module>
		</remove_modules>
	    
1.1.4.1.12. Replacing an item of a proposal

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>
	    
1.1.4.1.13. Workflow updates

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>
	    
1.1.4.1.14. Append steps to the end of installation sequence
		<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>
	    
1.1.4.1.15. Insert steps to installation sequence
		<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>
	    
1.1.4.1.16. Remove steps from installation sequence
		<remove_modules config:type="list">
		    <remove_module>finish</remove_module>
		    [...]
		</remove_modules>
	    
1.1.4.1.17. Replace steps in installation sequence
		<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>
	    
1.1.4.1.18. Add items in inst_finish.ycp

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

1.1.4.1.18.1. Before chroot
		    <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>
		
1.1.4.1.18.2. Running in chroot
		    <inst_finish_stages config:type="list">
			<chroot>
			    <label>Update Configuration</label>
			    <steps config:type="list">
				<step>pkg</step>
				[...]
			    </steps>
			</chroot>
		    </inst_finish_stages>
		
1.1.4.1.18.3. Before unmounting the system
		    <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.

1.1.4.1.19. Replace whole second-stage workflow

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.

1.1.4.1.20. Algorith for Adapting Workflow

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.

1.1.4.1.21. Product Features

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>
	    
1.1.4.1.22. AutoYaST profile generation

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>
	    
1.1.4.1.23. Example of OES 1.0

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>

	    

1.2. Firstboot Configuration

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:

  1. The Welcome screen

  2. The License Agreement

  3. Date & Time

  4. Network

  5. Root Password

  6. User Account

  7. Hardware

  8. Finish

During firstboot, two additional dialogs are shown for writing the data and running SuSEconfig which require no user interaction.

1.2.1. Enabling Firstboot

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.

  1. Install the product on a master box, making sure that you install the firstboot package. Configure whole system including system services, hardware components,...

  2. 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.

  1. Boot the master box using the rescue boot option.

  2. Configure network in the rescue system.

  3. Mount an NFS exported directory to /mnt.

  4. 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:

  1. Boot a user's machine using the rescue boot option.

  2. Configure network in the rescue system.

  3. Mount the NFS exported directory to /mnt.

  4. Run dd if=/mnt/image.bin of=/dev/hda count=4000000.

  5. Remove the boot media and boot the user's machine.

  6. 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.

1.2.2. Customizing YaST Firstboot

1.2.2.1. Customizing Messages

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.

1.2.2.2. License Action

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.

1.2.2.3. Release Notes

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.

1.2.2.4. Customizing Workflow Components

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.

1.2.2.5. Using Automatic Configuration

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.

1.2.3. Scripting

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.

1.2.4. Firstboot and AutoYaST

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.

1.3. Installation Features

This section contains description of several installation features.

1.3.1. Additional Products Automatically Added with Installation Repository

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.

1.3.1.1. Configuration file add_on_products.xml

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.

1.3.1.2. Configuration file add_on_products

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.

Chapter 2. Installation Proposal

2.1.  API for YaST2 installation proposal

2.1.1. Motivation

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

2.1.2. Overview

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

2.1.3. The Dispatcher Interface

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 ] ) );
      

2.1.4. API functions

[Note]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:

  • MakeProposal
  • AskUser
  • Description
  • Write

2.1.5. Dummy Proposal

      /**
 * 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;
}


    

2.2. Proposal API Reference

2.2.1. MakeProposal

Make proposal for installation.

map MakeProposal( boolean force_reset, boolean language_changed )

2.2.1.1. Parameters

  • 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.

2.2.1.2. Return Values

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]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:

    • `notice
    • `warning (default)
    • `error
    • `blocker
    • `fatal

    `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)

    Helptext for this module which appears in the standard dialog help (particular helps for modules sorted by presentation order).

2.2.2. AskUser

Run an interactive workflow

map AskUser( boolean has_next, string chosen_id )

2.2.2.1. Parameters

  • 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.

2.2.2.2. Description

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.

2.2.2.3. Return Values

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.

2.2.3. Description

Return human readable titles both for the RichText (HTML) widget and for menuentries.

map Description()

2.2.3.1. Return Values

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]Note

In this case, all other proposal functions must return a useful success value so they can be called without problems.

2.2.4. Write

Write the proposed (and probably modified) settings to the system.

map Write()

2.2.4.1. Description

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.

2.2.4.2. Return Values

Returns a map containing:

  • boolean success

    Returns true, if the settings were written successfully.

YaST Development And Tools


Table of Contents

1. Development And Tools
1.1. YaST2 Development Tools
1.1.1. Quick Start
1.1.2. What is it?
1.1.3. Migration
1.1.4. Translation (po) Modules)
1.1.5. create-spec: Automatic creation of the .spec file
1.1.6. Overview of Paths
1.1.7. Toplevel make Targets in Detail
1.1.7.1. make package-local
1.1.7.2. make package
1.1.7.3. make check-tagversion
1.1.7.4. make check-up-to-date
1.1.7.5. make checkin-stable
1.1.7.6. make stable
1.2. YaST2 Logging
1.2.1. Introduction
1.2.2. Quick start
1.2.3. Logging levels
1.2.4. Logging functions
1.2.5. Additional functions
1.2.5.1. Setting the logfile name
1.2.5.2. Universal logging functions:
1.2.6. Components
1.2.7. Logfiles
1.2.8. Log entries
1.2.9. Logging control
1.2.10. Environment control
1.3. Check YCP Syntax
1.3.1. Quick Start
1.3.2. Why this Document?
1.3.3. Header Comment Checks
1.3.4. Filename Check
1.3.5. Author / Maintainer Entry Check
1.3.6. CVS Id: Marker Check
1.3.7. Translatable Messages Checks
1.3.7.1. textdomain Check
1.3.8. RichText / HTML Sanity Check
1.3.8.1. Completeness of <p> / </p> Paragraph Tags
1.3.8.2. Text Before, After, Between Paragraphs
1.3.8.3. No More Than One Paragraph per Message
1.3.8.4. Excess Forced Line Breaks <br> after Paragraphs
1.3.9. Widget / UI Function Parameter Checks
1.3.9.1. Keyboard Shortcut Check
1.3.9.2. Translatable Messages Check
1.3.10. Standardized Lib Function Checks
1.3.10.1. Duplicate Definitions of Wizard Lib Functions
1.3.10.2. Definitions and Usage of Obsolete Functions
1.3.10.3. Usage of Predefined Messages
1.3.11. Alternative Variable Declarations
1.3.12. Checking YCP Examples
1.3.13. check_ycp and Emacs
1.3.14. Extending check_ycp
1.3.14.1. Adding new Widgets / UI Functions
1.3.14.2. Other Extensions
1.4. The YaST2 Macro Recorder
1.4.1. Introduction
1.4.2. Quick Start
1.4.3. Purpose
1.4.4. What it is not
1.4.5. Quirks and Limitations
1.4.6. Anatomy of a Macro
1.5. YaST Desktop Files
1.5.1. Desktop File Rules
1.5.2. Desktop File Entries
1.5.2.1. Mandatory Desktop File Entries
1.5.2.2. YaST-Specific Desktop File Entries
1.5.2.3. AutoYaST-Specific Desktop File Entries
1.5.3. Desktop File Example

List of Tables

1.1.
1.2.

List of Examples

1.1. YaST2 UI macro file

Chapter 1. Development And Tools

Table of Contents

1.1. YaST2 Development Tools
1.1.1. Quick Start
1.1.2. What is it?
1.1.3. Migration
1.1.4. Translation (po) Modules)
1.1.5. create-spec: Automatic creation of the .spec file
1.1.6. Overview of Paths
1.1.7. Toplevel make Targets in Detail
1.1.7.1. make package-local
1.1.7.2. make package
1.1.7.3. make check-tagversion
1.1.7.4. make check-up-to-date
1.1.7.5. make checkin-stable
1.1.7.6. make stable
1.2. YaST2 Logging
1.2.1. Introduction
1.2.2. Quick start
1.2.3. Logging levels
1.2.4. Logging functions
1.2.5. Additional functions
1.2.5.1. Setting the logfile name
1.2.5.2. Universal logging functions:
1.2.6. Components
1.2.7. Logfiles
1.2.8. Log entries
1.2.9. Logging control
1.2.10. Environment control
1.3. Check YCP Syntax
1.3.1. Quick Start
1.3.2. Why this Document?
1.3.3. Header Comment Checks
1.3.4. Filename Check
1.3.5. Author / Maintainer Entry Check
1.3.6. CVS Id: Marker Check
1.3.7. Translatable Messages Checks
1.3.7.1. textdomain Check
1.3.8. RichText / HTML Sanity Check
1.3.8.1. Completeness of <p> / </p> Paragraph Tags
1.3.8.2. Text Before, After, Between Paragraphs
1.3.8.3. No More Than One Paragraph per Message
1.3.8.4. Excess Forced Line Breaks <br> after Paragraphs
1.3.9. Widget / UI Function Parameter Checks
1.3.9.1. Keyboard Shortcut Check
1.3.9.2. Translatable Messages Check
1.3.10. Standardized Lib Function Checks
1.3.10.1. Duplicate Definitions of Wizard Lib Functions
1.3.10.2. Definitions and Usage of Obsolete Functions
1.3.10.3. Usage of Predefined Messages
1.3.11. Alternative Variable Declarations
1.3.12. Checking YCP Examples
1.3.13. check_ycp and Emacs
1.3.14. Extending check_ycp
1.3.14.1. Adding new Widgets / UI Functions
1.3.14.2. Other Extensions
1.4. The YaST2 Macro Recorder
1.4.1. Introduction
1.4.2. Quick Start
1.4.3. Purpose
1.4.4. What it is not
1.4.5. Quirks and Limitations
1.4.6. Anatomy of a Macro
1.5. YaST Desktop Files
1.5.1. Desktop File Rules
1.5.2. Desktop File Entries
1.5.2.1. Mandatory Desktop File Entries
1.5.2.2. YaST-Specific Desktop File Entries
1.5.2.3. AutoYaST-Specific Desktop File Entries
1.5.3. Desktop File Example

1.1. YaST2 Development Tools

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.

1.1.1. Quick Start

  • 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!

1.1.2. What is it?

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.

1.1.3. Migration

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]Note

    You can do without that SUBDIRS file if you want to include all subdirectories that have a Makefile.am in alphabetical order anyway):

    grep 'SUBDIRS' Makefile.am | sed -e 's/SUBDIRS *= *//' >SUBDIRS
    

    Getting rid of the "SUBDIRS = " prefix is not exactly mandatory (the devtools are forgiving enough to handle that), but recommended.

  • 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.

1.1.4. Translation (po) Modules)

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.

1.1.5. create-spec: Automatic creation of the .spec file

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:

@HEADER-COMMENT@

writes the SuSE .spec comment

@HEADER@

writes the usual header except BuildArch:, Requires:, Summary:

@PREP@

writes %prep with %setup

@BUILD-YCP@

writes %build with usual make

@INSTALL-YCP@

writes %install with usual YCP make install

@CLEAN@

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
/...

1.1.6. Overview of Paths

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.

yast2dir=${prefix}/share/YaST2

not for direct use

docdir=${prefix}/share/doc/packages/\$RPMNAME

for documentation

ybindir=${prefix}/lib/YaST2/bin

for all yast2 programs not be started by the user.

plugindir=${libdir}/YaST2/plugin

for loadable plugins

includedir=${prefix}/include/YaST2

for c header files

localedir=${yast2dir}/locale

for translations files

clientdir=${yast2dir}/clients

for ycp clients

moduledir=${yast2dir}/modules

for ycp modules

schemadir=${yast2dir}/schema

for schema files (autoyast, control file)

yncludedir=${yast2dir}/include

for ycp includes

scrconfdir=${yast2dir}/scrconf

for scr files

desktopdir=${prefix}/share/applications/YaST2/modules

for .desktop files (former *.y2cc)

execcompdir=${prefix}/lib/YaST2

for external programs that are yast2 components. here you have to append servers, servers_non_y2, clients or clients_non_y2.

ydatadir=${yast2dir}/data

for general data

imagedir=${yast2dir}/images

for non theme-able images

themedir=${yast2dir}/theme

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.

1.1.7. Toplevel make Targets in Detail

1.1.7.1. make package-local

Create a tarball from your module and put it into the package/ directory. This also creates a spec file from the .spec.in file.

1.1.7.2. make package

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.

1.1.7.3. make check-tagversion

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]Note

You will very likely never call this manually.

1.1.7.4. make check-up-to-date

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]Note

You will very likely never call this manually.

1.1.7.5. make checkin-stable

This makes a package (i.e. it does everything "make package" does and checks it into the correct SuSE Linux distribution.

[Note]Note

This requires /work/src/done to be mounted via NFS.

1.1.7.6.  make stable

Just an alias for "make checkin-stable".

1.2. YaST2 Logging

1.2.1. Introduction

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.

1.2.2. Quick start

  • 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.

1.2.3. Logging levels

There exist six different log levels denoting incidents of different importance:

0: DEBUG

Debug messages, which help the programmers.

1: MILESTONE

Normal log messages. Some important actions are logged. For example each time a YaST2 module is started, a log entry is created.

2: WARNING

Some error has occured, but the execution could be continued.

3: ERROR

Some major error has occured. The execution may be continued, but probably more errors will occur.

4: SECURITY

Some security relevant incident has occured.

5: INTERNAL

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.

1.2.4. Logging functions

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)

1.2.5. Additional functions

1.2.5.1. Setting the logfile name

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("-");

1.2.5.2. Universal logging functions:

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.

1.2.6. Components

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>

1.2.7. Logfiles

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.

1.2.8. Log entries

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

1.2.9. Logging control

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); }; 

1.2.10. Environment control

Additionally to the usual logfile control you can control some logging feature by the environment variables.

Y2DEBUG

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.

Y2DEBUGALL

By setting this variable to an arbitrary value you turn on the debug log output. Everything will be logged.

Y2DEBUGSHELL

By setting this variable to an arbitrary value you turn on the debug log output for the bash_background processes.

Y2MAXLOGSIZE

By this variable you can control the size of logfiles. See Logfiles for details.

Y2MAXLOGNUM

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

1.3. Check YCP Syntax

1.3.1. Quick Start

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.

1.3.2. Why this Document?

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.

1.3.3. Header Comment Checks

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:

  • /*
  • */
  • //

1.3.4. Filename Check

What

If present, the contents of a Module field is checked against the current file name.

Why

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.

How

This file name is particularly easy to check (plus, it's the only one that can reliably be checked), so check_ycp checks it: It compares the base name (not the complete path) of the current file to what you specified in Module: in the header.

1.3.5. Author / Maintainer Entry Check

What

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.

Why

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.

How

The fields are checked for presence of something like somebody@somewhere.domain - in fact only for something before the at sign "@" and something with a period "." behind it.

1.3.6. CVS Id: Marker Check

What

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 
	

Why

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.

How

Presence of

Id:
	

is checked. There may be more characters before the closing dollar sign "$", but the exact contents is not checked.

[Note]Note

When creating a new file, it is absolutely sufficient to include the unexpanded string ("Id:") somewhere in the file. CVS or RCS will automatically expand this to the full ID string.

1.3.7. Translatable Messages Checks

1.3.7.1. textdomain Check

What

If there is any message that is marked for translation with _("..."), there must be a textdomain statement.

Why

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.

How

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]Note

Theoretically the "_(" sequence contained in a literal string (i.e. within double quotes "...") could falsely trigger this error, too. But if you do that, you are very likely to run into trouble with other tools as well - most likely even the original getext tools regularly used to extract the messages for translation. Bottom line: Don't do that.

1.3.8. RichText / HTML Sanity Check

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.

1.3.8.1. Completeness of <p> / </p> Paragraph Tags

What

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.

Why

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.

How

See the intro of this section.

1.3.8.2. Text Before, After, Between Paragraphs

What

For each portion of HTML text:

  • No text before the first <p> tag is permitted.
  • No text after the last </p> tag is permitted.
  • No text between a closing </p> and the next opening <p> tag is permitted.

Why

Each of those cases is a simple yet common HTML syntax error.

How

See the intro of this section.

1.3.8.3. No More Than One Paragraph per Message

What

Each single portion of HTML text may contain exactly one paragraph, i.e. one <p> ... </p> pair.

Why

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.

Workaround

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.

How

See the intro of this section.

1.3.8.4. Excess Forced Line Breaks <br> after Paragraphs

What

Forced line break tags <br> are discouraged, especially after a paragraph end tag </p>.

Why

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.

How

<br> after </p> (maybe with anything in between) is rejected. All other <br> tags are silently ignored.

1.3.9. Widget / UI Function Parameter Checks

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 );
    

1.3.9.1. Keyboard Shortcut Check

What

Widgets that can have a keyboard shortcut (one character marked with an ampersand "&") are checked for presence of a keyboard shortcut.

[Note]Note

Consistency of the keyboard shortcuts is not checked, only presence. check_ycp cannot know which widgets will be on-screen at the same time, thus it cannot find out whether the same keyboard shortcut has been assigned twice to different widgets.

Why

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.

How

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.

1.3.9.2. Translatable Messages Check

What

Widget parameters that are displayed literally as text are checked for translation markers ("_("...")").

Why

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.

How

See the intro of this section.

1.3.10. Standardized Lib Function Checks

1.3.10.1. Duplicate Definitions of Wizard Lib Functions

What

Presence of definitions of functions from the wizard lib (Package yast2) outside the wizard library itself is checked such as Wizard::SetContents() etc.

Why

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).

How

If "define" followed by one of the known function names of the wizard lib is found outside the file where this is supposed to be, a warning is issued. Both function names and file names are hardwired within check_ycp.

1.3.10.2. Definitions and Usage of Obsolete Functions

What

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.

Why

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.

How

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.

1.3.10.3. Usage of Predefined Messages

What

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.

Why

  • 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.

How

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 ("_("...")").

Limitations

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.

1.3.11. Alternative Variable Declarations

What

Alternative variable declarations are rejected, e.g.

string|void value = anything();
symbol|term result = UI::UserInput();
integer|string x = 42;

	  

Why

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.

How

The entire YCP code, stripped of comments, is checked for occurences of one of the primitive YCP types (string, integer, boolean, map, list, any, void etc.) followed by a pipe sign | (maybe with whitespace before or after it) and another primitive YCP type.

1.3.12. Checking YCP Examples

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.

1.3.13. check_ycp and Emacs

check_ycp and Emacs go together well:

  • Load a YCP file into Emacs.
  • 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
    		

  • Hit Return
  • 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")
      

1.3.14. Extending check_ycp

1.3.14.1. Adding new Widgets / UI Functions

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:

  1. Locate the check_widget_params() function.

  2. 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) |

  3. 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.

1.3.14.2. Other Extensions

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.

1.4. The YaST2 Macro Recorder

Target Audience:

  • YaST2 power users
  • Quality assurance (Testers)
  • Technical writers

1.4.1. Introduction

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.

1.4.2. Quick Start

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:

  • Start a YaST2 module in Qt (grahpical) mode - from the YaST2 control center, from the KDE control center, from the desktop menu or via command line.
  • To record a macro, press Alt-Ctrl-Shift M The sequence of first Alt, then Ctrl, then Shift is important! A file selection box opens prompting you to enter a file name for the macro. Make sure you have write permission to the directory you select.
  • Work with the YaST2 module as usual.
  • Press Alt-Ctrl-Shift M again to stop recording. When the module is finished of course recording stops automatically.
  • Start the same module again.
  • To play a macro, press Alt-Ctrl-Shift P.

    A file selection box opens. Select the macro file you recorded earlier.

  • Watch the YaST2 module replay the same you did when recording the macro.

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).

1.4.3. Purpose

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.

1.4.4. What it is not

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.

1.4.5. Quirks and Limitations

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.

1.4.6. Anatomy of a Macro

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.

1.5. YaST Desktop Files

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.

1.5.1. Desktop File Rules

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).

1.5.2. Desktop File Entries

There are some mandatory entries that are required by the desktop file definition. Additionally we use a YaST-specific and AutoYaST-specific entries.

1.5.2.1. Mandatory Desktop File 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

1.5.2.2. YaST-Specific Desktop File Entries

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

1.5.2.3. AutoYaST-Specific Desktop File Entries

These entries are described in the AutoYaST Configuration Management System in section Configuration file

1.5.3. Desktop File Example

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

Reference — YCP Language


Table of Contents

I. WFM Builtins
Args — Returns the arguments with which the module was called.
ClientExists — Checks whether a YCP client exists
Execute — Special interface to the system agent. Not for general use.
GetEncoding — Returns the current encoding code
GetEnvironmentEncoding — Returns the encoding code of the environment where YaST is started
GetLanguage — Returns the current language code (without modifiers !)
Read — Special interface to the system agent. Not for general use.
SCRClose — Closes a scr instance.
SCRGetDefault — Gets the default scr instance.
SCRGetName — Get the name of a scr instance.
SCROpen — Create a new scr instance.
SCRSetDefault — Sets the default scr instance.
SetLanguage — Selects the language for translate()
Write — Special interface to the system agent. Not for general use.
call — Executes a YCP client or a Y2 client component.
II. YCP Byteblock Builtins
size — Returns a size of a byteblock in bytes.
tobyteblock — Converts a value to a byteblock.
III. YCP Float Builtins
float::abs — absolute value
float::ceil — round upwards to integer
float::floor — round downwards to integer
float::pow — power function
float::trunc — round to integer, towards zero
tofloat — Converts a value to a floating point number.
tostring — Converts a floating point number to a string
IV. YCP Integer Builtins
tointeger — Converts a value to an integer.
V. YCP List Builtins
add — Create a new list with a new element
change — Changes a list. Deprecated, use LIST[size(LIST)] = value.
contains — Checks if a list contains an element
filter — Filters a List
find — Searches for the first occurence of a certain element in a list
flatten — Flattens List
foreach — Processes the content of a list
list::reduce — Reduces a list to a single value.
list::reduce — Reduces a list to a single value.
listmap — Maps an operation onto all elements of a list and thus creates a map.
lsort — Sort A List respecting locale
maplist — Maps an operation onto all elements of a list and thus creates a new list.
merge — Merges two lists into one
prepend — Prepends a list with a new element
remove — Removes element from a list
select — Selects a list element (deprecated, use LIST[INDEX]:DEFAULT)
setcontains — Checks if a sorted list contains an element
size — Returns size of list
sort — Sorts a List according to the YCP builtin predicate
sort — Sort list using an expression
splitstring — Split a string by delimiter
sublist — Extracts a sublist
sublist — Extracts a sublist
tolist — Converts a value to a list (deprecated, use (list)VAR).
toset — Sorts list and removes duplicates
union — Unions of lists
VI. Map Builtins
add — Add a key/value pair to a map
change — Change element pair in a map. Deprecated, use MAP[KEY] = VALUE.
filter — Filter a Map
foreach — Process the content of a map
haskey — Check if map has a certain key
lookup — Select a map element (deprecated, use MAP[KEY]:DEFAULT)
maplist — Maps an operation onto all elements key/value and create a list
mapmap — Maps an operation onto all key/value pairs of a map
remove — Remove key/value pair from a map
size — Size of a map
tomap — Converts a value to a map.
union — Union of 2 maps
VII. Miscellaneous YCP Builtins
eval — Evaluate a YCP value.
getenv — Change or add an environment variable
is — Checks whether a value is of a certain type
random — Random number generator.
setenv — Change or add an environment variable
setenv — Change or add an environment variable
sformat — Format a String
sleep — Sleeps a number of milliseconds.
srandom — Initialize random number generator
srandom — Initialize random number generator.
time — Return the number of seconds since 1.1.1970.
y2debug — Log a message to the y2log.
y2error — Log an error to the y2log.
y2internal — Log an internal message to the y2log.
y2milestone — Log a milestone to the y2log.
y2security — Log a security message to the y2log.
y2useritem — Log an user-level system message to the y2changes
y2usernote — Log an user-level addional message to the y2changes
y2warning — Log a warning to the y2log.
VIII. YCP Path Builtins
add — Add a path element to existing path
size — Returns the number of path elements
topath — Converts a value to a path.
IX. YCP String Builtins
crypt — Encrypts a string
cryptbigcrypt — Encrypts a string using bigcrypt
cryptblowfish — Encrypts a string with blowfish
cryptmd5 — Encrypts a string using md5
deletechars — Removes all characters from a string
dgettext — Translates the text using the given text domain
dngettext — Translates the text using a locale-aware plural form handling
dpgettext — Translates the text using the given text domain and path
filterchars — Filters characters out of a String
find — Returns position of a substring
findfirstnotof — Searches string for the first non matching chars
findfirstof — Finds position of the first matching characters in string
findlastnotof — Searches the last element of string that doesn't match
findlastof — Searches string for the last match
issubstring — searches for a specific string within another string
lsubstring — Extracts a substring in UTF-8 encoded string
lsubstring — Extracts a substring in UTF-8 encoded string
mergestring — Joins list elements with a string
regexpmatch — Searches a string for a POSIX Extended Regular Expression match.
regexppos — Returns a pair with position and length of the first match.
regexpsub — Regex Substitution
regexptokenize — Regex tokenize
search — Returns position of a substring
size — Returns the number of characters of the string s
substring — Returns part of a string
substring — Extracts a substring
timestring — Returns time string
toascii — Returns characters below 0x7F included in STRING
tohexstring — Converts an integer to a hexadecimal string.
tohexstring — Converts an integer to a hexadecimal string.
tolower — Makes a string lowercase
tostring — Converts a value to a string.
toupper — Makes a string uppercase
X. YCP Term Builtins
add — Add value to term
argsof — Returns the arguments of a term.
remove — Remove item from term
select — Select item from term
size — Returns the number of arguments of the term TERM.
symbolof — Returns the symbol of the term TERM.
toterm — Converts a value to a term.
toterm — Constructs a term from a symbol and a list.

WFM Builtins


Table of Contents

Args — Returns the arguments with which the module was called.
ClientExists — Checks whether a YCP client exists
Execute — Special interface to the system agent. Not for general use.
GetEncoding — Returns the current encoding code
GetEnvironmentEncoding — Returns the encoding code of the environment where YaST is started
GetLanguage — Returns the current language code (without modifiers !)
Read — Special interface to the system agent. Not for general use.
SCRClose — Closes a scr instance.
SCRGetDefault — Gets the default scr instance.
SCRGetName — Get the name of a scr instance.
SCROpen — Create a new scr instance.
SCRSetDefault — Sets the default scr instance.
SetLanguage — Selects the language for translate()
Write — Special interface to the system agent. Not for general use.
call — Executes a YCP client or a Y2 client component.

Name

Args — Returns the arguments with which the module was called.

Synopsis

list Args (); 
 

Return

list

List of arguments

Description

The result is a list whose arguments are the module's arguments. If the module was called with CallFunction("my_mod", [17,true]), Args() will return [ 17, true ].


Name

ClientExists — Checks whether a YCP client exists

Synopsis

boolean ClientExists (name); 
string name ;
 

Parameters

string name

client name

Return

boolean

whether client exists

Description

This is similar to 'call' or 'CallFunction' but client is only checked for existence and not executed. If client exists 'true' is returned, otherwise 'false'.

Usage

  ClientExists ("inst_mouse") -> true
  ClientExists ("missing_client") -> false

Name

Execute — Special interface to the system agent. Not for general use.

Synopsis

any Execute (path,  
 options); 
path path ;
any options ;
 

Parameters

path path

Path

Optional Arguments

any options

Return

any

Description

WFM::Execute (.local.foo, ... ) works like SCR::Execute (.target.foo, ...) but in the inst-sys rather than on the system being installed. It only works for paths starting with .local


Name

GetEncoding — Returns the current encoding code

Synopsis

string GetEncoding (); 
 

Return

string

Encoding


Name

GetEnvironmentEncoding — Returns the encoding code of the environment where YaST is started

Synopsis

string GetEnvironmentEncoding (); 
 

Return

string

encoding code of the environment


Name

GetLanguage — Returns the current language code (without modifiers !)

Synopsis

string GetLanguage (); 
 

Return

string

Language


Name

Read — Special interface to the system agent. Not for general use.

Synopsis

any Read (path,  
 options); 
path path ;
any options ;
 

Parameters

path path

Path

Optional Arguments

any options

Return

any

Description

WFM::Read (.local.foo, ... ) works like SCR::Read (.target.foo, ...) but in the inst-sys rather than on the system being installed. It only works for paths starting with .local


Name

SCRClose — Closes a scr instance.

Synopsis

void SCRClose (handle); 
integer handle ;
 

Parameters

integer handle

SCR handle

Return

void


Name

SCRGetDefault — Gets the default scr instance.

Synopsis

integer SCRGetDefault (); 
 

Return

integer

Default SCR handle


Name

SCRGetName — Get the name of a scr instance.

Synopsis

string SCRGetName (handle); 
integer handle ;
 

Parameters

integer handle

SCR handle

Return

string

Name


Name

SCROpen — Create a new scr instance.

Synopsis

integer SCROpen (name,  
 check_version); 
string name ;
boolean check_version ;
 

Parameters

string name

a valid y2component name

boolean check_version

determines whether the SuSE Version should be checked

Return

integer

On error a negative value is returned.

Description

Creates a new scr instance. The name must be a valid y2component name (e.g. "scr", "chroot=/mnt:scr"). The component is created immediately. The parameter check_version determines whether the SuSE Version should be checked. On error a negative value is returned.


Name

SCRSetDefault — Sets the default scr instance.

Synopsis

void SCRSetDefault (handle); 
integer handle ;
 

Parameters

integer handle

SCR handle

Return

void


Name

SetLanguage — Selects the language for translate()

Synopsis

string SetLanguage (language,  
 encoding); 
string language ;
string encoding ;
 

Parameters

string language

Optional Arguments

string encoding

Return

string

proposed encoding have fun

Description

The "<proposed encoding>" is the output of 'nl_langinfo (CODESET)' and only given if SetLanguage() is called with a single argument.

Usage

  SetLanguage("de_DE", "UTF-8") -> ""
  SetLanguage("de_DE@euro") -> "ISO-8859-15"
 

Name

Write — Special interface to the system agent. Not for general use.

Synopsis

boolean Write (path,  
 options); 
path path ;
any options ;
 

Parameters

path path

Path

Optional Arguments

any options

Return

boolean

Description

WFM::Write (.local.foo, ... ) works like SCR::Write (.target.foo, ...) but in the inst-sys rather than on the system being installed. It only works for paths starting with .local


Name

call — Executes a YCP client or a Y2 client component.

Synopsis

any call (name,  
 arguments); 
string name ;
list arguments ;
 

Parameters

string name

client name

list arguments

list of arguments

Return

any

Description

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.

Usage

  call ("inst_mouse", [true, false]) -> ....

YCP Byteblock Builtins


Table of Contents

size — Returns a size of a byteblock in bytes.
tobyteblock — Converts a value to a byteblock.

Name

size — Returns a size of a byteblock in bytes.

Synopsis

integer size (VALUE); 
byteblock VALUE ;
 

Parameters

byteblock VALUE

Return

integer

Usage

  size(#[03A6f298B5]) -> 5
 
 

Name

tobyteblock — Converts a value to a byteblock.

Synopsis

byteblock tobyteblock (VALUE); 
any VALUE ;
 

Parameters

any VALUE

Return

byteblock

Description

If the value can't be converted to a byteblock, nilbyteblock is returned.

YCP Float Builtins


Table of Contents

float::abs — absolute value
float::ceil — round upwards to integer
float::floor — round downwards to integer
float::pow — power function
float::trunc — round to integer, towards zero
tofloat — Converts a value to a floating point number.
tostring — Converts a floating point number to a string

Name

float::abs — absolute value

Synopsis

FLOAT float::abs (FLOAT); 
f FLOAT ;
 

Parameters

f FLOAT

Return

FLOAT

Usage

  trunc (+1.6) -> +1.6
  trunc (-1.6) -> +1.6
 

Name

float::ceil — round upwards to integer

Synopsis

FLOAT float::ceil (FLOAT); 
f FLOAT ;
 

Parameters

f FLOAT

Return

FLOAT

Usage

  trunc (+1.6) -> +2.0
  trunc (-1.6) -> -1.0
 

Name

float::floor — round downwards to integer

Synopsis

FLOAT float::floor (FLOAT); 
f FLOAT ;
 

Parameters

f FLOAT

Return

FLOAT

Usage

  trunc (+1.6) -> +1.0
  trunc (-1.6) -> -2.0
 

Name

float::pow — power function

Synopsis

FLOAT float::pow (FLOAT,  
 FLOAT); 
f1 FLOAT ;
f2 FLOAT ;
 

Parameters

f1 FLOAT

f2 FLOAT

Return

FLOAT

Usage

  pow (10.0, 3.0) -> 1000.0
 

Name

float::trunc — round to integer, towards zero

Synopsis

FLOAT float::trunc (FLOAT); 
f FLOAT ;
 

Parameters

f FLOAT

Return

FLOAT

Usage

  trunc (+1.6) -> +1.0
  trunc (-1.6) -> -1.0
 

Name

tofloat — Converts a value to a floating point number.

Synopsis

float tofloat (VALUE); 
any VALUE ;
 

Parameters

any VALUE

Return

float

Usage

  tofloat (4) -> 4.0
  tofloat ("42") -> 42.0
  tofloat ("3.14") -> 3.14
 

Name

tostring — Converts a floating point number to a string

Synopsis

string tostring (FLOAT,  
 PRECISION); 
float FLOAT ;
integer PRECISION ;
 

Parameters

float FLOAT

integer PRECISION

Return

string

Usage

  tostring (0.12345, 4) -> 0.1235
 

YCP Integer Builtins


Table of Contents

tointeger — Converts a value to an integer.

Name

tointeger — Converts a value to an integer.

Synopsis

integer tointeger (VALUE); 
any VALUE ;
 

Parameters

any VALUE

Return

integer

Usage

  tointeger (4.03) -> 4
  tointeger ("42") -> 42
  tointeger ("0x42") -> 66
  tointeger ("042") -> 34
 

YCP List Builtins


Table of Contents

add — Create a new list with a new element
change — Changes a list. Deprecated, use LIST[size(LIST)] = value.
contains — Checks if a list contains an element
filter — Filters a List
find — Searches for the first occurence of a certain element in a list
flatten — Flattens List
foreach — Processes the content of a list
list::reduce — Reduces a list to a single value.
list::reduce — Reduces a list to a single value.
listmap — Maps an operation onto all elements of a list and thus creates a map.
lsort — Sort A List respecting locale
maplist — Maps an operation onto all elements of a list and thus creates a new list.
merge — Merges two lists into one
prepend — Prepends a list with a new element
remove — Removes element from a list
select — Selects a list element (deprecated, use LIST[INDEX]:DEFAULT)
setcontains — Checks if a sorted list contains an element
size — Returns size of list
sort — Sorts a List according to the YCP builtin predicate
sort — Sort list using an expression
splitstring — Split a string by delimiter
sublist — Extracts a sublist
sublist — Extracts a sublist
tolist — Converts a value to a list (deprecated, use (list)VAR).
toset — Sorts list and removes duplicates
union — Unions of lists

Name

add — Create a new list with a new element

Synopsis

list add (LIST,  
 VAR); 
list LIST ;
any VAR ;
 

Parameters

list LIST

any VAR

Return

list

The new list

Description

Creates a new list that is identical to the list LIST but has the value VAR appended as additional element.

Usage

  add ([1, 4], 8) -> [1, 4, 8]
 

Name

change — Changes a list. Deprecated, use LIST[size(LIST)] = value.

Synopsis

list change (LIST,  
 value); 
list LIST ;
any value ;
 

Parameters

list LIST

any value

Return

list

Description

Before Code 9, this was used to change a list directly without creating a copy. Now it is a synonym for add.

Usage

  change ([1, 4], 8) -> [1, 4, 8]
 

Name

contains — Checks if a list contains an element

Synopsis

boolean contains (LIST,  
 ELEMENT); 
list LIST ;
any ELEMENT ;
 

Parameters

list LIST

List

any ELEMENT

Element

Return

boolean

True if element is in the list.

Description

Determines, if a certain value ELEMENT is contained in a list LIST.

Usage

  contains ([1, 2, 5], 2) -> true
 

Name

filter — Filters a List

Synopsis

list filter (VAR,  
 LIST,  
 EXPR); 
any VAR ;
list LIST ;
block<boolean> EXPR ;
 

Parameters

any VAR

Variable

list LIST

List to be filtered

block<boolean> EXPR

Block

Return

list

Description

For each element of the list LIST the expression expr is executed in a new block, where the variable VAR is assigned to that value. If the expression evaluates to true under this circumstances, the value is appended to the result list.

Usage

  filter (integer v, [1, 2, 3, 5], { return (v > 2); }) -> [3, 5]
 

Name

find — Searches for the first occurence of a certain element in a list

Synopsis

any find (VAR,  
 LIST,  
 EXPR); 
any VAR ;
list LIST ;
block EXPR ;
 

Parameters

any VAR

list LIST

block EXPR

Return

any

Returns nil, if nothing is found.

Description

Searches for a certain item in the list. It applies the expression EXPR to each element in the list and returns the first element the makes the expression evaluate to true, if VAR is bound to that element.

Usage

  find (integer n, [3,5,6,4], ``(n >= 5)) -> 5
 

Name

flatten — Flattens List

Synopsis

list flatten (LIST); 
list<list> LIST ;
 

Parameters

list<list> LIST

Return

list

Description

Gets a list of lists LIST and creates a single list that is the concatenation of those lists in LIST.

Usage

  flatten ([ [1, 2], [3, 4] ]) -> [1, 2, 3, 4]
  flatten ([ [1, 2], [6, 7], [3, 4] ]) -> [1, 2, 6, 7, 3, 4]
 

Name

foreach — Processes the content of a list

Synopsis

any foreach (VAR,  
 LIST,  
 EXPR); 
any VAR ;
list LIST ;
block EXPR ;
 

Parameters

any VAR

list LIST

block EXPR

Return

any

return value of last execution of EXPR

Description

For each element of the list LIST the expression EXPR is executed in a new context, where the variable VAR is assigned to that value. The return value of the last execution of EXPR is the value of the foreach construct.

Usage

  foreach (integer v, [1,2,3], { return v + 10; }) -> 13
 

Name

list::reduce — Reduces a list to a single value.

Synopsis

flex1 list::reduce (x,  
 y,  
 list,  
 expression); 
flex1 x ;
flex1 y ;
list<flex1> list ;
block<flex1> expression ;
 

Parameters

flex1 x

flex1 y

list<flex1> list

block<flex1> expression

Return

flex1

Description

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.

Usage

  list::reduce (integer x, integer y, [2, 4, 6], { return x < y ? x : y; }) -> 2
  list::reduce (integer x, integer y, [2, 4, 6], { return x < y ? x : y; }) -> 6
 

Name

list::reduce — Reduces a list to a single value.

Synopsis

flex1 list::reduce (x,  
 y,  
 value,  
 list,  
 expression); 
flex1 x ;
flex2 y ;
flex1 value ;
list<flex2> list ;
block<flex1> expression ;
 

Parameters

flex1 x

flex2 y

flex1 value

list<flex2> list

block<flex1> expression

Return

flex1

Description

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.

Usage

  list::reduce (integer x, integer y, 0, [2, 4, 6], { return x + y; }) -> 12
  list::reduce (integer x, integer y, 1, [2, 4, 6], { return x * y; }) -> 48
 
  list::reduce (term t, float f, `item(`id(`dummy)), [3.14, 2.71], { return add(t, tostring(f)); }) -> `item (`id (`dummy), "3.14", "2.71")
 

Name

listmap — Maps an operation onto all elements of a list and thus creates a map.

Synopsis

map listmap (VAR,  
 LIST,  
 EXPR); 
any VAR ;
list LIST ;
block EXPR ;
 

Parameters

any VAR

list LIST

block EXPR

Return

map

Description

For each element VAR of the list LIST in the expression EXPR is evaluated in a new block. 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.

Usage

  listmap (integer k, [1,2,3], { return $[k:"xy"]; })  -> $[1:"xy", 2:"xy"]
  listmap (integer k, [1,2,3], { integer a = k+10;  any b = sformat ("x%1", k); return $[a:b]; }) -> $[11:"x1", 12:"x2", 13:"x3"]
 

Name

lsort — Sort A List respecting locale

Synopsis

list lsort (LIST); 
list LIST ;
 

Parameters

list LIST

Return

list

Sorted list

Description

Sort the list LIST according to the YCP builtin predicate >. Strings are compared using the current locale. Duplicates are not removed.

Usage

  lsort (["česky", "slovensky", "německy", 2, 1]) -> [1,
 2, "česky", "německy", "slovensky"]
 

Name

maplist — Maps an operation onto all elements of a list and thus creates a new list.

Synopsis

list<any> maplist (VAR,  
 LIST,  
 EXPR); 
any VAR ;
list<any> LIST ;
block EXPR ;
 

Parameters

any VAR

list<any> LIST

block EXPR

Return

list<any>

Description

For each element of the list LIST the expression EXPR is evaluated in a new block, where the variable VAR is assigned to that value. The result is the list of those evaluations.

Usage

  maplist (integer v, [1, 2, 3, 5], { return (v + 1); }) -> [2, 3, 4, 6]
 

Name

merge — Merges two lists into one

Synopsis

list merge (LIST1,  
 LIST2); 
list LIST1 ;
list LIST2 ;
 

Parameters

list LIST1

First List

list LIST2

Second List

Return

list

Description

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.

Usage

  merge ([1, 2], [3, 4]) -> [1, 2, 3, 4]
  merge ([1, 2, 3], [2, 3, 4]) -> [1, 2, 3, 2, 3, 4]
 

Name

prepend — Prepends a list with a new element

Synopsis

list prepend (LIST,  
 ELEMENT); 
list LIST ;
any ELEMENT ;
 

Parameters

list LIST

List

any ELEMENT

Element to prepend

Return

list

Description

Creates a new list that is identical to the list LIST but has the value ELEMENT prepended as additional element.

Usage

  prepend ([1, 4], 8) -> [8, 1, 4]
 

Name

remove — Removes element from a list

Synopsis

list remove (LIST,  
 e); 
list LIST ;
integer e ;
 

Parameters

list LIST

integer e

element index

Return

list

Returns unchanged list if the index is invalid.

Description

Removes the i'th value from a list. The first value has the index 0. The call remove ([1,2,3], 1) thus returns [1,3].

The yast2-core version < 2.17.16 returns nil if the key is invalid. This behavior has changed in version 2.17.16 to return unchanged list.

Usage

  remove ([1, 2], 0) -> [2]
 

Name

select — Selects a list element (deprecated, use LIST[INDEX]:DEFAULT)

Synopsis

any select (LIST,  
 INDEX,  
 DEFAULT); 
list LIST ;
integer INDEX ;
any DEFAULT ;
 

Parameters

list LIST

integer INDEX

any DEFAULT

Return

any

Description

 Gets the INDEX'th value of a list. The first value has the index 0. The call select([1,2,3], 1) thus returns 2. Returns DEFAULT 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

Usage

  select ([1, 2], 22, 0) -> 0
  select ([1, "two"], 0, "no") -> "no"
 

Name

setcontains — Checks if a sorted list contains an element

Synopsis

boolean setcontains (LIST,  
 ELEMENT); 
list LIST ;
any ELEMENT ;
 

Parameters

list LIST

List

any ELEMENT

Element

Return

boolean

True if element is in the list.

Description

Determines, if a certain value ELEMENT is contained in a list LIST, but assumes that LIST is sorted. If LIST is not sorted, the result is undefined.

Usage

  setcontains ([1, 2, 5], 2) -> true
 

Name

size — Returns size of list

Synopsis

integer size (LIST); 
list LIST ;
 

Parameters

list LIST

Return

integer

size of the list

Description

Returns the number of elements of the list LIST

Usage

  size(["A", 1, true, "3", false]) -> 5
 

Name

sort — Sorts a List according to the YCP builtin predicate

Synopsis

list sort (LIST); 
list LIST ;
 

Parameters

list LIST

Return

list

Sorted list

Description

Sorts the list LIST according to the YCP builtin predicate. Duplicates are not removed.

Usage

  sort ([2, 1, true, 1]) -> [true, 1, 1, 2]
 

Name

sort — Sort list using an expression

Synopsis

list sort (x,  
 y,  
 LIST,  
 EXPR); 
any x ;
any y ;
list LIST ;
block EXPR ;
 

Parameters

any x

any y

list LIST

block EXPR

Return

list

Description

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

Usage

  sort (integer x, integer y, [ 3,6,2,8 ], ``(x < y)) -> [ 2, 3, 6, 8 ]
  sort (string x, string y, [ "A","C","B" ], ``(x > y)) -> ["C", "B", "A"]
 

Name

splitstring — Split a string by delimiter

Synopsis

list<string> splitstring (STR,  
 DELIM); 
string STR ;
string DELIM ;
 

Parameters

string STR

string DELIM

Return

list<string>

Description

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.

Usage

  splitstring ("/abc/dev/ghi", "/") -> ["", "abc", "dev", "ghi" ]
  splitstring ("abc/dev/ghi/", "/") -> ["abc", "dev", "ghi", "" ]
  splitstring ("abc/dev/ghi/", ".") -> ["abc/dev/ghi/" ]
  splitstring ("text/with:different/separators", "/:") -> ["text", "with", "different", "separators"]
 

Name

sublist — Extracts a sublist

Synopsis

list sublist (LIST,  
 OFFSET); 
list LIST ;
integer OFFSET ;
 

Parameters

list LIST

integer OFFSET

Return

list

Description

Extracts a sublist of the list LIST starting at OFFSET. The OFFSET starts with 0.

Usage

  sublist ([ "a", "b", "c"], 0) -> [ "a", "b", "c" ]
  sublist ([ "a", "b", "c"], 2) -> [ "c" ]
 

Name

sublist — Extracts a sublist

Synopsis

list sublist (LIST,  
 OFFSET,  
 LENGTH); 
list LIST ;
integer OFFSET ;
integer LENGTH ;
 

Parameters

list LIST

integer OFFSET

integer LENGTH

Return

list

Description

Extracts a sublist of the list LIST starting at OFFSET with length LENGTH. The OFFSET starts with 0.

Usage

  sublist ([ "a", "b", "c"], 0, 2) -> [ "a", "b" ]
  sublist ([ "a", "b", "c"], 1, 1) -> [ "b" ]
 

Name

tolist — Converts a value to a list (deprecated, use (list)VAR).

Synopsis

list tolist (VAR); 
any VAR ;
 

Parameters

any VAR

Return

list

Description

 If the value can't be converted to a list, nillist is returned.
Functionality replaced by retyping: any l_1 = [1, 2, 3]; list <integer> l_2 = (list<integer>) l_1;


Name

toset — Sorts list and removes duplicates

Synopsis

list toset (LIST); 
list LIST ;
 

Parameters

list LIST

Return

list

Sorted list with unique items

Description

Scans a list for duplicates, removes them and sorts the list.

Usage

  toset ([1, 5, 3, 2, 3, true, false, true]) -> [false, true, 1, 2, 3, 5]
 

Name

union — Unions of lists

Synopsis

list union (LIST1,  
 LIST2); 
list LIST1 ;
list LIST2 ;
 

Parameters

list LIST1

First List

list LIST2

Second List

Return

list

Description

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

Usage

  union ([1, 2], [3, 4]) -> [1, 2, 3, 4]
  union ([1, 2, 3], [2, 3, 4]) -> [1, 2, 3, 4]
  union ([1, 3, 5], [1, 2, 4, 6]) -> [1, 3, 5, 2, 4, 6]
 

Map Builtins


Table of Contents

add — Add a key/value pair to a map
change — Change element pair in a map. Deprecated, use MAP[KEY] = VALUE.
filter — Filter a Map
foreach — Process the content of a map
haskey — Check if map has a certain key
lookup — Select a map element (deprecated, use MAP[KEY]:DEFAULT)
maplist — Maps an operation onto all elements key/value and create a list
mapmap — Maps an operation onto all key/value pairs of a map
remove — Remove key/value pair from a map
size — Size of a map
tomap — Converts a value to a map.
union — Union of 2 maps

Name

add — Add a key/value pair to a map

Synopsis

map add (MAP,  
 KEY,  
 VALUE); 
map MAP ;
any KEY ;
any VALUE ;
 

Parameters

map MAP

any KEY

any VALUE

Return

map

Description

 Adds the key/value pair k : v to the map MAP and returns the newly Created map. If the key KEY exists in KEY, the old key/value pair is replaced with the new one.
Functionality partly replaced with syntax: map map m = $["a":1]; m["b"] = 2; -> $["a":1, "b":2]

Usage

  add ($["a": 17, "b": 11], "c", 2) -> $["a":17, "b":11, "c":2]
  add ($["a": 17, "b": 11], "b", 2) -> $["a":17, "b":2]
 

Name

change — Change element pair in a map. Deprecated, use MAP[KEY] = VALUE.

Synopsis

change (MAP,  
 KEY,  
 VALUE); 
map MAP ;
any KEY ;
any VALUE ;
 

Parameters

map MAP

any KEY

any VALUE

Description

Before Code 9, this was used to change a map directly without creating a copy. Now it is a synonym for add.

Usage

  change ($[.a: 17, .b: 11], .b, nil) -> $[.a:17, .b:nil].
 

Name

filter — Filter a Map

Synopsis

map filter (KEY,  
 VALUE,  
 MAP,  
 EXPR); 
any KEY ;
any VALUE ;
map MAP ;
blocl EXPR ;
 

Parameters

any KEY

any VALUE

map MAP

blocl EXPR

Return

map

Description

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. If the expression evaluates to true, the key/value pair is appended to the returned map.

Usage

  filter (`k, `v, $[1:"a", 2:"b", 3:3, 5:5], { return (k == v); }) -> $[3:3, 5:5]
 

Name

foreach — Process the content of a map

Synopsis

map foreach (KEY,  
 VALUE,  
 MAP,  
 EXPR); 
any KEY ;
any VALUE ;
map MAP ;
any EXPR ;
 

Parameters

any KEY

any VALUE

map MAP

any EXPR

Return

map

Description

For each key:value pair of the map MAP the expression EXPR is executed in a new block, where the variables KEY is bound to the key and VALUE is bound to the value. The return value of the last execution of exp is the value of the foreach construct.

Usage

  foreach (integer k, integer v, $[1:1,2:4,3:9], { y2debug("v = %1", v); return v; }) -> 9
 

Name

haskey — Check if map has a certain key

Synopsis

boolean haskey (MAP,  
 KEY); 
map MAP ;
any KEY ;
 

Parameters

map MAP

any KEY

Return

boolean

Description

Determines whether the map MAP contains a pair with the key KEY. Returns true if this is true.

Usage

  haskey($["a":1, "b":2], "a") -> true
  haskey($["a":1, "b":2], "c") -> false
 

Name

lookup — Select a map element (deprecated, use MAP[KEY]:DEFAULT)

Synopsis

any lookup (MAP,  
 KEY,  
 DEFAULT); 
map MAP ;
any KEY ;
any DEFAULT ;
 

Parameters

map MAP

any KEY

any DEFAULT

Return

any

Description

 Gets the KEY's value of a map. Returns DEFAULT if the key does not exist. Returns nil if the found entry has a different type than the default value.
Functionality replaced with syntax: map m = $["a":1, "b":2];
m["a"]:100 -> 1; m["c"]:100 -> 100;

Usage

  lookup ($["a":42], "b", 0) -> 0
 

Name

maplist — Maps an operation onto all elements key/value and create a list

Synopsis

list maplist (KEY,  
 VALUE,  
 MAP,  
 EXPR); 
any KEY ;
any VALUE ;
map MAP ;
block EXPR ;
 

Parameters

any KEY

any VALUE

map MAP

block EXPR

Return

list

Description

Maps an operation onto all elements key/value pairs of a map and thus creates a list.

Usage

  maplist (`k, `v, $[1:"a", 2:"b"], { return [k+10, v+"x"]; }) -> [ [11, "ax"], [ 12, "bx" ] ]
 

Name

mapmap — Maps an operation onto all key/value pairs of a map

Synopsis

map mapmap (KEY,  
 VALUE,  
 MAP,  
 EXPR); 
any KEY ;
any VALUE ;
map MAP ;
block EXPR ;
 

Parameters

any KEY

any VALUE

map MAP

block EXPR

Return

map

Description

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.

Usage

  mapmap (integer k, string v, $[1:"a", 2:"b"], { return ($[k+10 : v+"x"]); }) -> $[ 11:"ax", 12:"bx" ]
  mapmap (integer k, string v, $[1:"a", 2:"b"], { integer a = k + 10; string b = v + "x"; return $[a:b]; }) -> $[ 11:"ax", 12:"bx" ]
 

Name

remove — Remove key/value pair from a map

Synopsis

map remove (MAP,  
 KEY); 
map MAP ;
any KEY ;
 

Parameters

map MAP

any KEY

Return

map

Description

Remove the value with the key KEY from a map. Returns unchanged map if the key is invalid.

The yast2-core version < 2.17.16 returns nil if the key is invalid. This behavior has changed in version 2.17.16 to return unchanged map.

Usage

  remove($[1:2], 0) -> $[1:2]
  remove($[1:2], 1) -> $[]
  remove ($[1:2, 3:4], 1) -> $[3:4]
 

Name

size — Size of a map

Synopsis

integer size (MAP); 
map MAP ;
 

Parameters

map MAP

Return

integer

Description

Returns the number of key/value pairs in the map MAP.

Usage

  size($["a":1, "aa":2, "b":3]) -> 3
 

Name

tomap — Converts a value to a map.

Synopsis

map tomap (VALUE); 
any VALUE ;
 

Parameters

any VALUE

Return

map

Description

 If the value can't be converted to a map, nilmap is returned.
Functionality partly replaced with retyping: any a = $[1:1, 2:2]; map m = (map) a;


Name

union — Union of 2 maps

Synopsis

map union (MAP1,  
 MAP2); 
map MAP1 ;
map MAP2 ;
 

Parameters

map MAP1

map MAP2

Return

map

Description

Interprets two maps as sets and returns a new map that has all elements of the first map MAP1and all of the second map MAP2. If elements have identical keys, values from MAP2 overwrite elements from MAP1.

Usage

  union($["a":1, "b":2], $[1:"a", 2:"b"]) -> $[1:"a", 2:"b", "a":1, "b":2]
  union($["a":1, "b":2], $["b":10, "c":20]) -> $["a":1, "b":10, "c":20]
 

Miscellaneous YCP Builtins


Table of Contents

eval — Evaluate a YCP value.
getenv — Change or add an environment variable
is — Checks whether a value is of a certain type
random — Random number generator.
setenv — Change or add an environment variable
setenv — Change or add an environment variable
sformat — Format a String
sleep — Sleeps a number of milliseconds.
srandom — Initialize random number generator
srandom — Initialize random number generator.
time — Return the number of seconds since 1.1.1970.
y2debug — Log a message to the y2log.
y2error — Log an error to the y2log.
y2internal — Log an internal message to the y2log.
y2milestone — Log a milestone to the y2log.
y2security — Log a security message to the y2log.
y2useritem — Log an user-level system message to the y2changes
y2usernote — Log an user-level addional message to the y2changes
y2warning — Log a warning to the y2log.

Name

eval — Evaluate a YCP value.

Synopsis

eval (); 
 

Description

See also the builtin ``, which is kind of the counterpart to eval.

Usage

  eval (``(1+2)) -> 3
 

Name

getenv — Change or add an environment variable

Synopsis

string getenv (name); 
string name ;
 

Parameters

string name

Return

string

value

Description

The getenv(variable) function returns the value of variable from environment. If variable doesn't exist the value is NULL.

Usage

  getenv ("USER") -> "root"
  getenv ("LC_CTYPE") -> "en_US.UTF-8"
 

Name

is — Checks whether a value is of a certain type

Synopsis

boolean is (value,  
 type); 
any value ;
type type ;
 

Parameters

any value

a value whose type is checked

type type

type to check

Return

boolean

Usage

 
 any ui = UI::UserInput();
 if (is (ui, string)) {
     foo ("Hello, " + (string) ui);
 }
 else if (is (ui, symbol)) {
     bar ((symbol) ui);
 }
 

Name

random — Random number generator.

Synopsis

integer random (MAX); 
integer MAX ;
 

Parameters

integer MAX

Return

integer

Returns integer in the interval [0,MAX).

Description

Returns a random integer in the interval [0,MAX). srandom must be activated to get really random numbers.

Usage

  random(100) -> 82
  random(100) -> 36
 

Name

setenv — Change or add an environment variable

Synopsis

boolean setenv (variable,  
 value,  
 overwrite); 
string variable ;
string value ;
boolean overwrite ;
 

Parameters

string variable

string value

boolean overwrite

Return

boolean

Description

The setenv() function adds the variable to the environment with the value. If variable exist the value is changed.

Usage

  setenv("PATH", "/home/user", true)
 

Name

setenv — Change or add an environment variable

Synopsis

boolean setenv (variable,  
 value); 
string variable ;
string value ;
 

Parameters

string variable

string value

Return

boolean

Description

The setenv() function adds the variable to the environment with the value. If variable exist the value is changed.

Usage

  setenv("PATH", "/home/user")
 

Name

sformat — Format a String

Synopsis

string sformat (FORM,  
 PAR1,  
 PAR2,  
 ...); 
string FORM ;
any PAR1 ;
any PAR2 ;
any ... ;
 

Parameters

string FORM

any PAR1

any PAR2

any ...

Return

string

Description

FORM is a string that may contains placeholders %1, %2, ... Each placeholder is substituted with the argument converted to string whose number is after the %. Only 1-9 are allowed by now. The percentage sign is donated with %%.

Usage

  sformat ("%2 is greater %% than %1", 3, "five") -> "five is greater % than 3"
 

Name

sleep — Sleeps a number of milliseconds.

Synopsis

void sleep (MILLISECONDS); 
integer MILLISECONDS ;
 

Parameters

integer MILLISECONDS

Time in milliseconds

Return

void

Usage

  sleep(3000) -> sleeps 3 sec.
 

Name

srandom — Initialize random number generator

Synopsis

integer srandom (); 
 

Return

integer

Description

Initialize random number generator with current date and time and returns the seed.

Usage

  srandom()
 

Name

srandom — Initialize random number generator.

Synopsis

void srandom (SEED); 
integer SEED ;
 

Parameters

integer SEED

Return

void

Usage

  srandom(3355)
 

Name

time — Return the number of seconds since 1.1.1970.

Synopsis

integer time (); 
 

Return

integer

Usage

  time() -> 1111207439
 

Name

y2debug — Log a message to the y2log.

Synopsis

void y2debug (FORMAT,  
 PAR1,  
 PAR2,  
 ...); 
string FORMAT ;
any PAR1 ;
any PAR2 ;
any ... ;
 

Parameters

string FORMAT

any PAR1

any PAR2

any ...

Return

void

Description

Arguments are same as for sformat() builtin. The y2log component is "YCP", so you can control these messages the same way as other y2log messages.

Usage

  y2debug ("%1 is smaller than %2", 7, "13");
 

Name

y2error — Log an error to the y2log.

Synopsis

void y2error (FORMAT,  
 PAR1,  
 PAR2,  
 ...); 
string FORMAT ;
any PAR1 ;
any PAR2 ;
any ... ;
 

Parameters

string FORMAT

any PAR1

any PAR2

any ...

Return

void

Usage

  y2error ("Invalid format of IPv4 '%1'.", "333.10.20.1") -> "Invalid format of IPv4 '333.10.20.1'"
 

Name

y2internal — Log an internal message to the y2log.

Synopsis

void y2internal (FORMAT,  
 PAR1,  
 PAR2,  
 ...); 
string FORMAT ;
any PAR1 ;
any PAR2 ;
any ... ;
 

Parameters

string FORMAT

any PAR1

any PAR2

any ...

Return

void

Usage

  y2internal("This is a robbery!") -> "This is a robbery!"
 

Name

y2milestone — Log a milestone to the y2log.

Synopsis

void y2milestone (FORMAT,  
 PAR1,  
 PAR2,  
 ...); 
string FORMAT ;
any PAR1 ;
any PAR2 ;
any ... ;
 

Parameters

string FORMAT

any PAR1

any PAR2

any ...

Return

void

Usage

  y2milestone("%1 - Humans detected!", "2038-02-12") -> "2038-02-12 - Humans detected!"
 

Name

y2security — Log a security message to the y2log.

Synopsis

void y2security (FORMAT,  
 PAR1,  
 PAR2,  
 ...); 
string FORMAT ;
any PAR1 ;
any PAR2 ;
any ... ;
 

Parameters

string FORMAT

any PAR1

any PAR2

any ...

Return

void

Usage

  y2security ("Users on vacations: %1", ["josh", "joe", "pete"]) -> "Users on vacations: ["josh", "joe", "pete"]"
 

Name

y2useritem — Log an user-level system message to the y2changes

Synopsis

void y2useritem (FORMAT,  
 PAR1,  
 PAR2,  
 ...); 
string FORMAT ;
any PAR1 ;
any PAR2 ;
any ... ;
 

Parameters

string FORMAT

any PAR1

any PAR2

any ...

Return

void

Usage

  y2useritem("Executing reboot")
 

Name

y2usernote — Log an user-level addional message to the y2changes

Synopsis

void y2usernote (FORMAT,  
 PAR1,  
 PAR2,  
 ...); 
string FORMAT ;
any PAR1 ;
any PAR2 ;
any ... ;
 

Parameters

string FORMAT

any PAR1

any PAR2

any ...

Return

void

Usage

  y2usernote("Starting module Bee")
 

Name

y2warning — Log a warning to the y2log.

Synopsis

void y2warning (FORMAT,  
 PAR1,  
 PAR2,  
 ...); 
string FORMAT ;
any PAR1 ;
any PAR2 ;
any ... ;
 

Parameters

string FORMAT

any PAR1

any PAR2

any ...

Return

void

Usage

  y2warning ("Breakers don't work!") -> "Breakers don't work!"
  y2warning ("%1 %2 packets have been lost", 12, "UDP") -> "12 UDP packets have been lost"
 

YCP Path Builtins


Table of Contents

add — Add a path element to existing path
size — Returns the number of path elements
topath — Converts a value to a path.

Name

add — Add a path element to existing path

Synopsis

path add (PATH,  
 or); 
path PATH ;
string or ;
 

Parameters

path PATH

string or

path STR

Return

path

Usage

  add (.aaa, "anypath...\n\"") -> .aaa."anypath...\n\""
  add (.sysconfig, .safety) -> .sysconfig.safety
 

Name

size — Returns the number of path elements

Synopsis

integer size (PATH); 
path PATH ;
 

Parameters

path PATH

Return

integer

Number of elements in the path

Usage

  size (.hello.world) -> 2
  size (.) -> 0
 

Name

topath — Converts a value to a path.

Synopsis

path topath (STR); 
string STR ;
 

Parameters

string STR

Return

path

Usage

  topath ("path") -> .path
  topath (".some.path") -> .some.path
 

YCP String Builtins


Table of Contents

crypt — Encrypts a string
cryptbigcrypt — Encrypts a string using bigcrypt
cryptblowfish — Encrypts a string with blowfish
cryptmd5 — Encrypts a string using md5
deletechars — Removes all characters from a string
dgettext — Translates the text using the given text domain
dngettext — Translates the text using a locale-aware plural form handling
dpgettext — Translates the text using the given text domain and path
filterchars — Filters characters out of a String
find — Returns position of a substring
findfirstnotof — Searches string for the first non matching chars
findfirstof — Finds position of the first matching characters in string
findlastnotof — Searches the last element of string that doesn't match
findlastof — Searches string for the last match
issubstring — searches for a specific string within another string
lsubstring — Extracts a substring in UTF-8 encoded string
lsubstring — Extracts a substring in UTF-8 encoded string
mergestring — Joins list elements with a string
regexpmatch — Searches a string for a POSIX Extended Regular Expression match.
regexppos — Returns a pair with position and length of the first match.
regexpsub — Regex Substitution
regexptokenize — Regex tokenize
search — Returns position of a substring
size — Returns the number of characters of the string s
substring — Returns part of a string
substring — Extracts a substring
timestring — Returns time string
toascii — Returns characters below 0x7F included in STRING
tohexstring — Converts an integer to a hexadecimal string.
tohexstring — Converts an integer to a hexadecimal string.
tolower — Makes a string lowercase
tostring — Converts a value to a string.
toupper — Makes a string uppercase

Name

crypt — Encrypts a string

Synopsis

string crypt (UNENCRYPTED); 
string UNENCRYPTED ;
 

Parameters

string UNENCRYPTED

Return

string

Usage

  crypt ("readable") -> "Y2PEyAiaeaFy6"
 

Name

cryptbigcrypt — Encrypts a string using bigcrypt

Synopsis

string cryptbigcrypt (UNENCRYPTED); 
string UNENCRYPTED ;
 

Parameters

string UNENCRYPTED

Return

string

Usage

  cryptbigcrypt ("readable") -> "d4brTQmcVbtNg"
 

Name

cryptblowfish — Encrypts a string with blowfish

Synopsis

string cryptblowfish (UNENCRYPTED); 
string UNENCRYPTED ;
 

Parameters

string UNENCRYPTED

Return

string

Description

Encrypts the string UNENCRYPTED using blowfish password encryption. The password is not truncated.

Usage

  cryptblowfish ("readable") -> "$2a$05$B3lAUExB.Bqpy8Pq0TpZt.s7EydrmxJRuhOZR04YG01ptwOUR147C"
 

Name

cryptmd5 — Encrypts a string using md5

Synopsis

string cryptmd5 (UNENCRYPTED); 
string UNENCRYPTED ;
 

Parameters

string UNENCRYPTED

Return

string

Usage

  cryptmd5 ("readable") -> "$1$BBtzrzzz$zc2vEB7XnA3Iq7pOgDsxD0"
 

Name

deletechars — Removes all characters from a string

Synopsis

string deletechars (STRING,  
 REMOVE); 
string STRING ;
string REMOVE ;
 

Parameters

string STRING

string REMOVE

Characters to be removed from STRING

Return

string

Description

Returns a string that results from string STRING by removing all characters that occur in string REMOVE.

Usage

  deletechars ("a", "abcdefghijklmnopqrstuvwxyz") -> ""
  deletechars ("abc","cde") -> "ab"
 

Name

dgettext — Translates the text using the given text domain

Synopsis

string dgettext (textdomain,  
 text); 
string textdomain ;
string text ;
 

Parameters

string textdomain

string text

Return

string

Description

Translates the text using the given text domain into the current language.

This is a special case builtin not intended for general use. See _() instead.

Usage

  dgettext ("base", "No") -> "Nie"
 

Name

dngettext — Translates the text using a locale-aware plural form handling

Synopsis

string dngettext (textdomain,  
 singular,  
 plural,  
 value); 
string textdomain ;
string singular ;
string plural ;
integer value ;
 

Parameters

string textdomain

string singular

string plural

integer value

Return

string

Description

Translates the text using a locale-aware plural form handling using the given textdomain.

The chosen form of the translation depend on the value.

This is a special case builtin not intended for general use. See _() instead.

Usage

  dngettext ("base", "%1 File", "%1 Files", 2) -> "%1 soubory"
 

Name

dpgettext — Translates the text using the given text domain and path

Synopsis

string dpgettext (textdomain,  
 dirname,  
 text); 
string textdomain ;
string dirname ;
string text ;
 

Parameters

string textdomain

string dirname

string text

Return

string

Description

Translates the text using the given text domain into the current language and path of localization . Path of localization is same than dirname in function bindtextdomain()

This is a special case builtin not intended for general use. See _() instead.

Usage

  dpgettext ("base", "/texdomain/path", "No") -> "Nie"
 

Name

filterchars — Filters characters out of a String

Synopsis

string filterchars (STRING,  
 CHARS); 
string STRING ;
string CHARS ;
 

Parameters

string STRING

string CHARS

chars to be included

Return

string

Description

Returns a string that results from string STRING by removing all characters that do not occur in CHARS.

Usage

  filterchars ("a", "abcdefghijklmnopqrstuvwxyz") -> "a"
  filterchars ("abc","cde") -> "c"
 

Name

find — Returns position of a substring

Synopsis

integer find (STRING1,  
 STRING2); 
string STRING1 ;
string STRING2 ;
 

Parameters

string STRING1

String

string STRING2

Substring

Return

integer

OFFSET If substring is not found find returns `-1'.

Description

The find function searches string for the first occurency of a specified substring (possibly a single character) and returns its starting position.

Returns the first position in STRING1 where the string STRING2 is contained in STRING1. OFFSET starts with 0.

Usage

  find ("abcdefghi", "efg") -> 4
  find ("aaaaa", "z") -> -1
 

Name

findfirstnotof — Searches string for the first non matching chars

Synopsis

integer findfirstnotof (STRING,  
 CHARS); 
string STRING ;
string CHARS ;
 

Parameters

string STRING

string CHARS

Return

integer

the position of the first character in STRING that is not contained in CHARS.

Description

The findfirstnotof function searches the first element of string that doesn't match any character stored in chars and returns its position.

Usage

  findfirstnotof ("abcdefghi", "abcefghi") -> 3
  findfirstnotof ("aaaaa", "a") -> nil
 

Name

findfirstof — Finds position of the first matching characters in string

Synopsis

integer findfirstof (STRING,  
 CHARS); 
string STRING ;
string CHARS ;
 

Parameters

string STRING

string CHARS

Characters to find

Return

integer

the position of the first character in STRING that is contained in CHARS.

Description

The findfirstof function searches string for the first match of any character stored in chars and returns its position.

If no match is found findfirstof returns `nil'.

Usage

  findfirstof ("abcdefghi", "cxdv") -> 2
  findfirstof ("aaaaa", "z") -> nil
 

Name

findlastnotof — Searches the last element of string that doesn't match

Synopsis

integer findlastnotof (STRING,  
 CHARS); 
string STRING ;
string CHARS ;
 

Parameters

string STRING

string CHARS

Characters

Return

integer

The position of the last character in STRING that is NOT contained in CHARS.

Description

The `findlastnotof' function searches the last element of string that doesn't match any character stored in chars and returns its position.

If no match is found the function returns `nil'.

Usage

  findlastnotof( "abcdefghi", "abcefghi" ) -> 3 ('d')
  findlastnotof("aaaaa", "a") -> nil
 

Name

findlastof — Searches string for the last match

Synopsis

integer findlastof (STRING,  
 CHARS); 
string STRING ;
string CHARS ;
 

Parameters

string STRING

String

string CHARS

Characters to find

Return

integer

the position of the last character in STRING that is contained in CHARS.

Description

The `findlastof' function searches string for the last match of any character stored in chars and returns its position.

Usage

  findlastof ("abcdecfghi", "cxdv") -> 5
  findlastof ("aaaaa", "z") -> nil
 

Name

issubstring — searches for a specific string within another string

Synopsis

boolean issubstring (s,  
 substring); 
string s ;
string substring ;
 

Parameters

string s

String to be searched

string substring

Pattern to be searched for

Return

boolean

Description

Return true, if substring is a substring of s.

Usage

  issubstring ("some text", "tex") -> true
 

Name

lsubstring — Extracts a substring in UTF-8 encoded string

Synopsis

string lsubstring (STRING,  
 OFFSET,  
 LENGTH); 
string STRING ;
integer OFFSET ;
integer LENGTH ;
 

Parameters

string STRING

integer OFFSET

integer LENGTH

Return

string

Description

Extracts a substring of the string STRING, starting at OFFSET after the first one with length of at most LENGTH. OFFSET starts with 0. This method uses UTF-8 encoding.

Usage

  substring ("some text", 5) -> "text"
  substring ("some text", 42) -> ""
 

Name

lsubstring — Extracts a substring in UTF-8 encoded string

Synopsis

string lsubstring (STRING,  
 OFFSET,  
 LENGTH); 
string STRING ;
integer OFFSET ;
integer LENGTH ;
 

Parameters

string STRING

integer OFFSET

integer LENGTH

Return

string

Description

Extracts a substring of the string STRING, starting at OFFSET after the first one with length of at most LENGTH. OFFSET starts with 0. This method uses UTF-8 encoding.

Usage

  lsubstring ("some text", 5, 2) -> "te"
  lsubstring ("some text", 42, 2) -> ""
  lsubstring("123456789", 2, 3) -> "345"
 

Name

mergestring — Joins list elements with a string

Synopsis

string mergestring (PIECES,  
 GLUE); 
list<string> PIECES ;
string GLUE ;
 

Parameters

list<string> PIECES

A List of strings

string GLUE

Return

string

Description

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.

Usage

  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"
 

Name

regexpmatch — Searches a string for a POSIX Extended Regular Expression match.

Synopsis

boolean regexpmatch (INPUT,  
 PATTERN); 
string INPUT ;
string PATTERN ;
 

Parameters

string INPUT

string PATTERN

Return

boolean

Usage

  regexpmatch ("aaabbbccc", "ab") -> true
  regexpmatch ("aaabbbccc", "^ab") -> false
  regexpmatch ("aaabbbccc", "ab+c") -> true
  regexpmatch ("aaa(bbb)ccc", "\\(.*\\)") -> true
 

Name

regexppos — Returns a pair with position and length of the first match.

Synopsis

list regexppos (INPUT,  
 PATTERN); 
string INPUT ;
string PATTERN ;
 

Parameters

string INPUT

string PATTERN

Return

list

Description

If no match is found it returns an empty list.

Usage

  regexppos ("abcd012efgh345", "[0-9]+") -> [4, 3]
  ("aaabbb", "[0-9]+") -> []
 

Name

regexpsub — Regex Substitution

Synopsis

string regexpsub (INPUT,  
 PATTERN,  
 OUTPUT); 
string INPUT ;
string PATTERN ;
string OUTPUT ;
 

Parameters

string INPUT

string PATTERN

string OUTPUT

Return

string

Description

Searches a string for a POSIX Extended Regular Expression match and returns OUTPUT with the matched subexpressions substituted or nil if no match was found.

Usage

  regexpsub ("aaabbb", "(.*ab)", "s_\\1_e") -> "s_aaab_e"
  regexpsub ("aaabbb", "(.*ba)", "s_\\1_e") -> nil
 

Name

regexptokenize — Regex tokenize

Synopsis

list regexptokenize (INPUT,  
 PATTERN); 
string INPUT ;
string PATTERN ;
 

Parameters

string INPUT

string PATTERN

Return

list

Description

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.

Usage

 
 Examples:
 // e ==  [ "aaabbB" ]
 list e = regexptokenize ("aaabbBb", "(.*[A-Z]).*");
 
 // h == [ "aaab", "bb" ]
 list h = regexptokenize ("aaabbb", "(.*ab)(.*)");
 
 // h == []
 list h = regexptokenize ("aaabbb", "(.*ba).*");
 
 // h == nil
 list h = regexptokenize ("aaabbb", "(.*ba).*(");
 

Name

search — Returns position of a substring

Synopsis

integer search (STRING1,  
 STRING2); 
string STRING1 ;
string STRING2 ;
 

Parameters

string STRING1

String

string STRING2

Substring

Return

integer

OFFSET If substring is not found find returns `nil'.

Description

The search function searches string for the first occurency of a specified substring (possibly a single character) and returns its starting position.

Returns the first position in STRING1 where the string STRING2 is contained in STRING1. OFFSET starts with 0.

Usage

  search ("abcdefghi", "efg") -> 4
  search ("aaaaa", "z") -> nil
 

Name

size — Returns the number of characters of the string s

Synopsis

integer size (s); 
string s ;
 

Parameters

string s

String

Return

integer

Size of string s

Description

Notice, that size(nil) -> nil

Usage

  size("size") -> 4
 

Name

substring — Returns part of a string

Synopsis

string substring (STRING,  
 OFFSET,  
 LENGTH); 
string STRING ;
integer OFFSET ;
integer LENGTH ;
 

Parameters

string STRING

Original String

integer OFFSET

Start position

Optional Arguments

integer LENGTH

Length of new string

Return

string

Description

Returns the portion of STRING specified by the OFFSET and LENGHT parameters. OFFSET starts with 0.

Usage

  substring ("some text", 5) -> "text"
  substring ("some text", 42) -> ""
  substring ("some text", 5, 2) -> "te"
  substring ("some text", 42, 2) -> ""
  substring("123456789", 2, 3) -> "345"
 

Name

substring — Extracts a substring

Synopsis

string substring (STRING,  
 OFFSET,  
 LENGTH); 
string STRING ;
integer OFFSET ;
integer LENGTH ;
 

Parameters

string STRING

integer OFFSET

integer LENGTH

Return

string

Description

Extracts a substring of the string STRING, starting at OFFSET after the first one with length of at most LENGTH. OFFSET starts with 0.

Usage

  substring ("some text", 5, 2) -> "te"
  substring ("some text", 42, 2) -> ""
  substring("123456789", 2, 3) -> "345"
 

Name

timestring — Returns time string

Synopsis

string timestring (FORMAT,  
 TIME,  
 UTC); 
string FORMAT ;
integer TIME ;
boolean UTC ;
 

Parameters

string FORMAT

integer TIME

boolean UTC

Return

string

Description

Combination of standard libc functions gmtime or localtime and strftime.

Usage

  timestring ("%F %T %Z", time (), false) -> "2004-08-24 14:55:05 CEST"
 

Name

toascii — Returns characters below 0x7F included in STRING

Synopsis

string toascii (STRING); 
string STRING ;
 

Parameters

string STRING

Return

string

Description

Returns a string that results from string STRING by copying each character that is below 0x7F (127).

Usage

  toascii ("aBë") -> "aB"
  toascii ("123+-abcABCöëä") -> "123+-abcABC"
 

Name

tohexstring — Converts an integer to a hexadecimal string.

Synopsis

string tohexstring (number); 
integer number ;
 

Parameters

integer number

Number

Return

string

number in Hex

Usage

  tohexstring (31) -> "0x1f"
 

Name

tohexstring — Converts an integer to a hexadecimal string.

Synopsis

string tohexstring (number,  
 width); 
integer number ;
integer width ;
 

Parameters

integer number

Number

integer width

Width

Return

string

number in Hex

Usage

  tohexstring (31, 1) -> "0x1f"
  tohexstring (31, 4) -> "0x001f"
 

Name

tolower — Makes a string lowercase

Synopsis

string tolower (s); 
string s ;
 

Parameters

string s

String

Return

string

String in lower case

Description

Returns string with all alphabetic characters converted to lowercase. Notice: national characters are left unchanged.

Usage

  tolower ("aBcDeF") -> "abcdef"
  tolower ("ABCÁÄÖČ") -> "abcÁÄÖČ"
 

Name

tostring — Converts a value to a string.

Synopsis

string tostring (VALUE); 
any VALUE ;
 

Parameters

any VALUE

Return

string

Usage

  tostring(.path) -> ".path"
  tostring([1,2,3]) -> "[1, 2, 3]"
  tostring($[1:1,2:2,3:3]) -> "$[1:1, 2:2, 3:3]"
  tostring(`Empty()) -> "`Empty ()"
 

Name

toupper — Makes a string uppercase

Synopsis

toupper (); 
 

Description

Returns string with all alphabetic characters converted to uppercase.

Usage

  tolower ("aBcDeF") -> "ABCDEF"
  toupper ("abcáäöč") -> "ABCáäöč"
 

YCP Term Builtins


Table of Contents

add — Add value to term
argsof — Returns the arguments of a term.
remove — Remove item from term
select — Select item from term
size — Returns the number of arguments of the term TERM.
symbolof — Returns the symbol of the term TERM.
toterm — Converts a value to a term.
toterm — Constructs a term from a symbol and a list.

Name

add — Add value to term

Synopsis

term add (TERM,  
 VALUE); 
term TERM ;
any VALUE ;
 

Parameters

term TERM

any VALUE

Return

term

Description

Adds the value VALUE to the term TERM and returns the newly created term. As always in YCP, TERM is not modified.

Usage

  add (`VBox(`Empty()), `Label("a")) -> `VBox (`Empty (), `Label ("a"))
  add (`VBox(`Empty()), "a") -> `VBox (`Empty (), "a")
 

Name

argsof — Returns the arguments of a term.

Synopsis

list argsof (TERM); 
term TERM ;
 

Parameters

term TERM

Return

list

Usage

  argsof (`fun(1, 2)) -> [1, 2]
  argsof(`TextEntry(`id("text"), "Label", "value")) -> [`id ("text"), "Label", "value"]
 

Name

remove — Remove item from term

Synopsis

term remove (TERM,  
 i); 
term TERM ;
integer i ;
 

Parameters

term TERM

integer i

Return

term

Description

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.

Usage

  remove (`fun(1, 2), 1) -> `fun(2)
  remove (`VBox(`Label("a"), `Label("b")), 2) -> `VBox (`Label ("a"))
 

Name

select, (deprecated,, use, TERM[ITEM]:DEFAULT) — Select item from term

Synopsis

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 ;
 

Parameters

term TERM

integer ITEM

any DEFAULT

Description

 Gets the i'th value of the term t. The first value has the index 0. The call select ([1, 2, 3], 1) thus returns 2. Returns the default if the index is invalid or the found value has a diffetent type that default.
Functionality replaced with syntax: term a = `VBox(`VSpacing(2), `Label("string"), `VSpacing(2));
a[1]:`Empty() -> `Label ("string") a[9]:`Empty() -> `Empty ()

Usage

  select (`hirn (true, false), 33, true) -> true
 

Name

size — Returns the number of arguments of the term TERM.

Synopsis

integer size (TERM); 
term TERM ;
 

Parameters

term TERM

Return

integer

Size of the TERM

Usage

  size (`VBox(`Empty(),`Empty(),`Empty())) -> 3
 

Name

symbolof — Returns the symbol of the term TERM.

Synopsis

symbol symbolof (TERM); 
term TERM ;
 

Parameters

term TERM

Return

symbol

Usage

  symbolof (`hrombuch (18, false)) -> `hrombuch
  symbolof (`Label("string")) -> `Label
 

Name

toterm — Converts a value to a term.

Synopsis

term toterm (VALUE); 
any VALUE ;
 

Parameters

any VALUE

Return

term

Description

If the value can't be converted to a term, nilterm is returned.

Usage

  toterm ("VBox") -> `VBox ()
  toterm (`VBox) -> `VBox ()
 

Name

toterm — Constructs a term from a symbol and a list.

Synopsis

term toterm (s,  
 l); 
symbol s ;
list l ;
 

Parameters

symbol s

list l

Return

term

Description

Constructs a term from a symbol and a list. Thus complement to symbolof and argsof.

Usage

  toterm (`RadioButton, [ `id (`test), "Test" ]) -> `RadioButton (`id (`test), "Test")
 

Reference — User Interface


Table of Contents

I. Event-related UI Builtin Functions
UI::UserInput — Waits for user input and returns a widget ID.
UI::PollInput — Checks for pending user input. Does not wait. Returns a widget ID or nil if no input is available.
UI::TimeoutUserInput — Waits for user input and returns a widget ID. Returns ID `timeout if no input is available for timeout milliseconds.
UI::WaitForEvent — Waits for user input and returns an event map. Returns ID `timeout if no input is available for timeout milliseconds.
II.
AskForExistingDirectory — Ask user for existing directory
AskForExistingFile — Ask user for existing file
AskForSaveFileName — Ask user for a file to save data to.
Beep — Beeps the system bell
BusyCursor — Sets the mouse cursor to the busy cursor
ChangeWidget — Changes widget contents
CheckShortcuts — Performs an explicit shortcut check after postponing shortcut checks.
CloseDialog() — Closes an open dialog
DumpWidgetTree — Debugging function
FakeUserInput — Fakes User Input
GetDisplayInfo — Gets Display Info
GetLanguage — Gets Language
GetProductName — Gets Product Name
Glyph — Returns a special character (a 'glyph')
HasSpecialWidget — Checks for support of a special widget type.
MakeScreenShot — Makes Screen Shot
NormalCursor — Sets the mouse cursor to the normal cursor
OpenDialog — Opens a Dialog with options
PlayMacro — Plays a recorded macro
PollInput — Poll Input
PostponeShortcutCheck — Postpones Shortcut Check
QueryWidget — Queries Widget contents
RecalcLayout — Recalculates Layout
Recode — Recodes encoding of string from or to "UTF-8" encoding.
RecordMacro — Records Macro into a file
RedrawScreen — Redraws the screen
ReplaceWidget
RunInTerminal — runs external program in the same terminal
RunPkgSelection — Initializes and run the PackageSelector widget
SetConsoleFont — Sets Console Font
SetFocus — Sets Focus to the specified widget
SetFunctionKeys — Sets the (default) function keys for a number of buttons.
SetKeyboard — Sets Keyboard
SetLanguage — Sets the language of the UI
SetProductName — Sets Product Name
StopRecordingMacro — Stops recording macro
TextMode — Check if the UI is running in text (NCurses) mode.
TimeoutUserInput — User Input with Timeout
UserInput — User Input
WFM/SCR — callback
WaitForEvent — Waits for Event
WidgetExists — Checks whether or not a widget with the given ID currently exists
WizardCommand — Runs a wizard command
III.
AAA_All-Widgets — Generic options for all widgets
BarGraph — Horizontal bar graph (optional widget)
BusyIndicator — Graphical busy indicator
ButtonBox — Layout for push buttons that takes button order into account
CheckBox — Clickable on/off toggle button
CheckBoxFrame — Frame with clickable on/off toggle button
ComboBox — drop-down list selection (optionally editable)
DateField — Date input field
DownloadProgress — Self-polling file growth progress indicator (optional widget)
DumbTab — Simplistic tab widget that behaves like push buttons
Empty — Placeholder widget
Frame — Frame with label
HBox — Generic layout: Arrange widgets horizontally or vertically
HSpacing — Fixed size empty space for layout
HSquash — Layout aid: Minimize widget to its preferred size
HWeight — Control relative size of layouts
Image — Pixmap image
InputField — Input field
IntField — Numeric limited range input field
Label — Simple static text
Left — Layout alignment
LogView — scrollable log lines like "tail -f"
MarginBox — Margins around one child widget
MenuButton — Button with popup menu
MinWidth — Layout minimum size
MultiLineEdit — multiple line text edit field
MultiSelectionBox — Selection box that allows selecton of multiple items
PackageSelector — Complete software package selection
PartitionSplitter — Hard disk partition splitter tool (optional widget)
PatternSelector — High-level widget to select software patterns (selections)
PkgSpecial — Package selection special - DON'T USE IT
ProgressBar — Graphical progress indicator
PushButton — Perform action on click
RadioButton — Clickable on/off toggle button for radio boxes
RadioButtonGroup — Radio box - select one of many radio buttons
ReplacePoint — Pseudo widget to replace parts of a dialog
RichText — Static text with HTML-like formatting
SelectionBox — Scrollable list selection
SimplePatchSelector — Simplified approach to patch selection
Slider — Numeric limited range input (optional widget)
Table — Multicolumn table widget
TimeField — Time input field
TimezoneSelector — Timezone selector map
Tree — Scrollable tree selection
VMultiProgressMeter — Progress bar with multiple segments (optional widget)
Wizard — Wizard frame - not for general use, use the Wizard:: module instead!
A. UI Richtext

Event-related UI Builtin Functions


Table of Contents

UI::UserInput — Waits for user input and returns a widget ID.
UI::PollInput — Checks for pending user input. Does not wait. Returns a widget ID or nil if no input is available.
UI::TimeoutUserInput — Waits for user input and returns a widget ID. Returns ID `timeout if no input is available for timeout milliseconds.
UI::WaitForEvent — Waits for user input and returns an event map. Returns ID `timeout if no input is available for timeout milliseconds.

Name

UI::UserInput — Waits for user input and returns a widget ID.

Description

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).

Usage:

any widget_id = UI::UserInput();

Example:

// 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 );
}


Name

UI::PollInput — Checks for pending user input. Does not wait. Returns a widget ID or nil if no input is available.

Description

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.

Usage:

any widget_id = UI::PollInput();

Example:

// 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();
}


Name

UI::TimeoutUserInput — Waits for user input and returns a widget ID. Returns ID `timeout if no input is available for timeout milliseconds.

Description

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.

Usage:

any widget_id = UI::TimeoutUserInput( integer timeout_millisec );

Example

// 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();
}


Name

UI::WaitForEvent — Waits for user input and returns an event map. Returns ID `timeout if no input is available for timeout milliseconds.

Description

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.

Usage

map event = UI::WaitForEvent();

map event = UI::WaitForEvent( integer timeout_millisec );

Example

// 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
AskForExistingFile — Ask user for existing file
AskForSaveFileName — Ask user for a file to save data to.
Beep — Beeps the system bell
BusyCursor — Sets the mouse cursor to the busy cursor
ChangeWidget — Changes widget contents
CheckShortcuts — Performs an explicit shortcut check after postponing shortcut checks.
CloseDialog() — Closes an open dialog
DumpWidgetTree — Debugging function
FakeUserInput — Fakes User Input
GetDisplayInfo — Gets Display Info
GetLanguage — Gets Language
GetProductName — Gets Product Name
Glyph — Returns a special character (a 'glyph')
HasSpecialWidget — Checks for support of a special widget type.
MakeScreenShot — Makes Screen Shot
NormalCursor — Sets the mouse cursor to the normal cursor
OpenDialog — Opens a Dialog with options
PlayMacro — Plays a recorded macro
PollInput — Poll Input
PostponeShortcutCheck — Postpones Shortcut Check
QueryWidget — Queries Widget contents
RecalcLayout — Recalculates Layout
Recode — Recodes encoding of string from or to "UTF-8" encoding.
RecordMacro — Records Macro into a file
RedrawScreen — Redraws the screen
ReplaceWidget
RunInTerminal — runs external program in the same terminal
RunPkgSelection — Initializes and run the PackageSelector widget
SetConsoleFont — Sets Console Font
SetFocus — Sets Focus to the specified widget
SetFunctionKeys — Sets the (default) function keys for a number of buttons.
SetKeyboard — Sets Keyboard
SetLanguage — Sets the language of the UI
SetProductName — Sets Product Name
StopRecordingMacro — Stops recording macro
TextMode — Check if the UI is running in text (NCurses) mode.
TimeoutUserInput — User Input with Timeout
UserInput — User Input
WFM/SCR — callback
WaitForEvent — Waits for Event
WidgetExists — Checks whether or not a widget with the given ID currently exists
WizardCommand — Runs a wizard command

Name

AskForExistingDirectory — Ask user for existing directory

Synopsis

string AskForExistingDirectory (startDir,  
 headline); 
string startDir ;
string headline ;
 

Parameters

string startDir

is the initial directory that is displayed.

string headline

is an explanatory text for the directory selection box. Graphical UIs may omit that if no window manager is running.

Return

string

Returns the selected directory name or nil if the user canceled the operation.

Description

Opens a directory selection box and prompt the user for an existing directory.


Name

AskForExistingFile — Ask user for existing file

Synopsis

string AskForExistingFile (startWith,  
 filter,  
 headline); 
string startWith ;
string filter ;
string headline ;
 

Parameters

string startWith

is the initial directory or file.

string filter

is one or more blank-separated file patterns, e.g. "*.png *.jpg"

string headline

is an explanatory text for the file selection box. Graphical UIs may omit that if no window manager is running.

Return

string

Returns the selected file name or nil if the user canceled the operation.

Description

Opens a file selection box and prompt the user for an existing file.


Name

AskForSaveFileName — Ask user for a file to save data to.

Synopsis

string AskForSaveFileName (startWith,  
 filter,  
 headline); 
string startWith ;
string filter ;
string headline ;
 

Parameters

string startWith

is the initial directory or file.

string filter

is one or more blank-separated file patterns, e.g. "*.png *.jpg"

string headline

is an explanatory text for the file selection box. Graphical UIs may omit that if no window manager is running.

Return

string

Returns the selected file name or nil if the user canceled the operation.

Description

Opens a file selection box and prompt the user for a file to save data to. Automatically asks for confirmation if the user selects an existing file.


Name

Beep — Beeps the system bell

Synopsis

void Beep (); 
 

Return

void

Description

Beeps the system bell. This is implemented by the frontend, which may do a visual beep if the system is set up that way (eg. for accessiblity purposes).


Name

BusyCursor — Sets the mouse cursor to the busy cursor

Synopsis

void BusyCursor (); 
 

Return

void

Description

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.


Name

ChangeWidget — Changes widget contents

Synopsis

boolean ChangeWidget (widgetId,  
 property,  
 newValue); 
symbol widgetId ;
symbol property ;
any newValue ;
 

Parameters

symbol widgetId

Can also be specified as `id( any widgetId )

symbol property

any newValue

Return

boolean

Returns true on success.

Description

Changes a property of a widget of the topmost dialog. id specified the widget to change, property specifies the property that should be changed, newvalue gives the new value.


Name

CheckShortcuts — Performs an explicit shortcut check after postponing shortcut checks.

Synopsis

void CheckShortcuts (); 
 

Return

void

Description

Performs an explicit shortcut check after postponing shortcut checks. Use this after calling PostponeShortcutCheck().

The normal sequence looks like this:

PostponeShortcutChecks();
ReplaceWidget( ... );
ReplaceWidget( ... );
...
ReplaceWidget( ... );
CheckShortcuts();
...
UserInput(); 


Name

CloseDialog() — Closes an open dialog

Synopsis

boolean CloseDialog() (); 
 

Return

boolean

Returns true on success.

Description

Closes the most recently opened dialog. It is an error to call CloseDialog if no dialog is open.


Name

DumpWidgetTree — Debugging function

Synopsis

void DumpWidgetTree (); 
 

Return

void

Description

Debugging function: Dumps the widget tree of the current dialog to the log file.


Name

FakeUserInput — Fakes User Input

Synopsis

void FakeUserInput (); 
 

Optional Arguments

any nextUserInput

Return

void

Description

Prepares a fake value for the next call to UserInput() - i.e. the next UserInput() will return exactly this value. This is only useful in connection with macros.

If called without a parameter, the next call to UserInput() will return "nil".


Name

GetDisplayInfo — Gets Display Info

Synopsis

map GetDisplayInfo (); 
 

Return

map

<string any>

Description

Gets information about the current display and the UI's capabilities.

Function output might differ according to the system where called.


Name

GetLanguage — Gets Language

Synopsis

string GetLanguage (strip_encoding); 
boolean strip_encoding ;
 

Parameters

boolean strip_encoding

Return

string

Description

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).


Name

GetProductName — Gets Product Name

Synopsis

string GetProductName (); 
 

Return

string

Product Name

Description

Returns the current product name ("SuSE Linux", "United Linux", etc.) for display in dialogs. This can be set with SetProductName().

Note: In help texts in RichText widgets, a predefined macro &amp;product; can be used for the same purpose.

Usage

  sformat( "Welcome to %1", GetProductName() );
 

Name

Glyph — Returns a special character (a 'glyph')

Synopsis

string Glyph (glyph); 
symbol glyph ;
 

Parameters

symbol glyph

Return

string

Description

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 "-&gt;".

If an unknown glyph symbol is specified, 'nil' is returned.


Name

HasSpecialWidget — Checks for support of a special widget type.

Synopsis

HasSpecialWidget (); 
 

Description

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.


Name

MakeScreenShot — Makes Screen Shot

Synopsis

void MakeScreenShot (filename); 
string filename ;
 

Parameters

string filename

Return

void

Description

Makes a screen shot if the specific UI supports that. The Qt UI opens a file selection box if filename is empty.


Name

NormalCursor — Sets the mouse cursor to the normal cursor

Synopsis

void NormalCursor (); 
 

Return

void

Description

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.


Name

OpenDialog — Opens a Dialog with options

Synopsis

boolean OpenDialog (options,  
 widget); 
term options ;
term widget ;
 

Parameters

term options

term widget

Return

boolean

true if success, false if error

Description

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.

Usage

  OpenDialog( `opt( `defaultsize ), `Label( "Hello, World!" ) )
 

Name

PlayMacro — Plays a recorded macro

Synopsis

void PlayMacro (macroFileName); 
string macroFileName ;
 

Parameters

string macroFileName

Return

void

Description

Executes everything in macro file "macroFileName". Any errors are sent to the log file only. The macro can be terminated only from within the macro file.


Name

PollInput — Poll Input

Synopsis

any PollInput (); 
 

Return

any

Description

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.


Name

PostponeShortcutCheck — Postpones Shortcut Check

Synopsis

void PostponeShortcutCheck (); 
 

Return

void

Description

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(); 


Name

QueryWidget — Queries Widget contents

Synopsis

any QueryWidget (widgetId,  
 property); 
symbol widgetId ;
symbol|term property ;
 

Parameters

symbol widgetId

Can also be specified as `id( any id )

symbol|term property

Return

any

Description

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.


Name

RecalcLayout — Recalculates Layout

Synopsis

void RecalcLayout (); 
 

Return

void

Description

Recompute the layout of the current dialog.

This is a very expensive operation.

Use this after changing widget properties that might affect their size - like the a Label widget's value. Call this once (!) after changing all such widget properties.


Name

Recode — Recodes encoding of string from or to "UTF-8" encoding.

Synopsis

any Recode (fromEncoding,  
 toEncoding,  
 text); 
string fromEncoding ;
string toEncoding ;
string text ;
 

Parameters

string fromEncoding

string toEncoding

string text

Return

any

Description

Recodes encoding of string from or to "UTF-8" encoding. One of from/to must be "UTF-8", the other should be an iso encoding specifier (i.e. "ISO-8859-1" for western languages, "ISO-8859-2" for eastern languages, etc. )


Name

RecordMacro — Records Macro into a file

Synopsis

void RecordMacro (macroFileName); 
string macroFileName ;
 

Parameters

string macroFileName

Return

void


Name

RedrawScreen — Redraws the screen

Synopsis

void RedrawScreen (); 
 

Return

void

Description

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.


Name

ReplaceWidget

Synopsis

true ReplaceWidget (id,  
 newWidget); 
symbol id ;
term newWidget ;
 

Parameters

symbol id

term newWidget

Return

true

if success, false if failed

Description

Replaces a complete widget (or widget subtree) with an other widget (or widget tree). You can only replace the widget contained in a ReplacePoint. As parameters to ReplaceWidget specify the id of the ReplacePoint and the new widget.


Name

RunInTerminal — runs external program in the same terminal

Synopsis

RunInTerminal (external_program); 
string external_program ;
 

Parameters

string external_program

return integer

Usage

  RunInTerminal("/bin/bash")
 

Name

RunPkgSelection — Initializes and run the PackageSelector widget

Synopsis

any RunPkgSelection (pkgSelId); 
any pkgSelId ;
 

Parameters

any pkgSelId

Return

any

Returns `cancel if the user wishes to cancel his selections.

Description

Not to be used outside the package selection

Initialize and run the PackageSelector widget identified by 'pkgSelId'.

Black magic to everybody outside. ;- )


Name

SetConsoleFont — Sets Console Font

Synopsis

void SetConsoleFont (console_magic,  
 font,  
 screen_map,  
 unicode_map,  
 language); 
string console_magic ;
string font ;
string screen_map ;
string unicode_map ;
string language ;
 

Parameters

string console_magic

string font

string screen_map

string unicode_map

string language

Return

void

Usage

  SetConsoleFont( "( K", "lat2u-16.psf", "latin2u.scrnmap", "lat2u.uni", "latin1" )
 

Name

SetFocus — Sets Focus to the specified widget

Synopsis

boolean SetFocus (widgetId); 
symbol widgetId ;
 

Parameters

symbol widgetId

Return

boolean

Returns true on success (i.e. the widget accepted the focus).


Name

SetFunctionKeys — Sets the (default) function keys for a number of buttons.

Synopsis

void SetFunctionKeys (fkeys); 
map fkeys ;
 

Parameters

map fkeys

Return

void

Description

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.

Usage

  SetFunctionKeys( $[ "Back": 8, "Next": 10, ... ] );
 

Name

SetKeyboard — Sets Keyboard

Synopsis

void SetKeyboard (); 
 

Return

void

Usage

  SetKeyboard()
 

Name

SetLanguage — Sets the language of the UI

Synopsis

void SetLanguage (lang,  
 encoding); 
string lang ;
string encoding ;
 

Parameters

string lang

Language selected by user

Optional Arguments

string encoding

Return

void

Usage

  SetLanguage( "de_DE@euro" )
  SetLanguage( "en_GB" )
 

Name

SetProductName — Sets Product Name

Synopsis

void SetProductName (prod); 
string prod ;
 

Parameters

string prod

Return

void

Description

Sets the current product name ("SuSE Linux", "United Linux", etc.) for displaying in dialogs and in RichText widgets (for help text) with the RichText &amp;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.

Usage

  SetProductName( "SuSE HyperWall" );
 

Name

StopRecordingMacro — Stops recording macro

Synopsis

void StopRecordingMacro (); 
 

Return

void

Description

Stops macro recording. This is only necessary if you don't wish to record everything until the program terminates.


Name

TextMode — Check if the UI is running in text (NCurses) mode.

Synopsis

true TextMode (); 
 

Return

true

if text mode, false if GUI (Qt / Gtk).

Description

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.


Name

TimeoutUserInput — User Input with Timeout

Synopsis

any TimeoutUserInput (timeout_millisec); 
integer timeout_millisec ;
 

Parameters

integer timeout_millisec

Return

any

Description

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.


Name

UserInput — User Input

Synopsis

any UserInput (); 
 

Return

any

Description

Waits for the user to click some button, close the window or activate some widget that has the `notify option set. 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).


Name

WFM/SCR — callback

Synopsis

any WFM/SCR (expression); 
block expression ;
 

Parameters

block expression

Return

any

Description

This is used for a callback mechanism. The expression will be sent to the WFM interpreter and evaluated there. USE WITH CAUTION.


Name

WaitForEvent — Waits for Event

Synopsis

map WaitForEvent (); 
 

Optional Arguments

timeout_millisec

Return

map

Description

Extended event handling - very much like UserInput(), but returns much more detailed information about the event that occured in a map.


Name

WidgetExists — Checks whether or not a widget with the given ID currently exists

Synopsis

boolean WidgetExists (widgetId); 
symbol widgetId ;
 

Parameters

symbol widgetId

Return

boolean

Description

Checks whether or not a widget with the given ID currently exists in the current dialog. Use this to avoid errors in the log file before changing the properties of widgets that might or might not be there.


Name

WizardCommand — Runs a wizard command

Synopsis

boolean WizardCommand (wizardCommand); 
term wizardCommand ;
 

Parameters

term wizardCommand

Return

boolean

Returns true on success.

Description

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
BarGraph — Horizontal bar graph (optional widget)
BusyIndicator — Graphical busy indicator
ButtonBox — Layout for push buttons that takes button order into account
CheckBox — Clickable on/off toggle button
CheckBoxFrame — Frame with clickable on/off toggle button
ComboBox — drop-down list selection (optionally editable)
DateField — Date input field
DownloadProgress — Self-polling file growth progress indicator (optional widget)
DumbTab — Simplistic tab widget that behaves like push buttons
Empty — Placeholder widget
Frame — Frame with label
HBox — Generic layout: Arrange widgets horizontally or vertically
HSpacing — Fixed size empty space for layout
HSquash — Layout aid: Minimize widget to its preferred size
HWeight — Control relative size of layouts
Image — Pixmap image
InputField — Input field
IntField — Numeric limited range input field
Label — Simple static text
Left — Layout alignment
LogView — scrollable log lines like "tail -f"
MarginBox — Margins around one child widget
MenuButton — Button with popup menu
MinWidth — Layout minimum size
MultiLineEdit — multiple line text edit field
MultiSelectionBox — Selection box that allows selecton of multiple items
PackageSelector — Complete software package selection
PartitionSplitter — Hard disk partition splitter tool (optional widget)
PatternSelector — High-level widget to select software patterns (selections)
PkgSpecial — Package selection special - DON'T USE IT
ProgressBar — Graphical progress indicator
PushButton — Perform action on click
RadioButton — Clickable on/off toggle button for radio boxes
RadioButtonGroup — Radio box - select one of many radio buttons
ReplacePoint — Pseudo widget to replace parts of a dialog
RichText — Static text with HTML-like formatting
SelectionBox — Scrollable list selection
SimplePatchSelector — Simplified approach to patch selection
Slider — Numeric limited range input (optional widget)
Table — Multicolumn table widget
TimeField — Time input field
TimezoneSelector — Timezone selector map
Tree — Scrollable tree selection
VMultiProgressMeter — Progress bar with multiple segments (optional widget)
Wizard — Wizard frame - not for general use, use the Wizard:: module instead!

Name

AAA_All-Widgets — Generic options for all widgets

Synopsis

AAA_All-Widgets (); 
 

Options

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.

Description

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.

Usage

 	---

Examples

          {
    // (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();
}


        

Name

BarGraph — Horizontal bar graph (optional widget)

Synopsis

BarGraph (values,  
 labels); 
list values ;
list labels ;
 

Parameters

list values

the initial values (integer numbers)

Optional Arguments

list labels

the labels for each part; use "%1" to include the current numeric value. May include newlines.

Usage

 	if ( HasSpecialWidget( `BarGraph ) {...
 	`BarGraph( [ 450, 100, 700 ],
 	[ "Windows used\n%1 MB", "Windows free\n%1 MB", "Linux\n%1 MB" ] )
 

Examples

          // 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();
}

          

Name

BusyIndicator — Graphical busy indicator

Synopsis

BusyIndicator (label,  
 timeout); 
string label ;
integer timeout ;
 

Parameters

string label

the label describing the bar

Optional Arguments

integer timeout

the timeout in milliseconds until busy indicator changes to stalled state, 1000ms by default

Description

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.

Usage

 	`BusyIndicator(`id(`busy), "background action", 2000 ),

Examples

          // 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();
}

        

Name

ButtonBox — Layout for push buttons that takes button order into account

Synopsis

ButtonBox (button1,  
 button2); 
term button1 ;
term button2 ;
 

Parameters

term button1

the first button

Options

relaxSanityCheck

less stringent requirements for button roles

Optional Arguments

term button2

the second button (etc.)

Description

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.

Usage

 	`ButtonBox(`PushButton( `id( `ok ), "OK" ), `PushButton( `id( `cancel ), "Cancel" ) )
 

Examples

          // 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();
}

        

Name

CheckBox — Clickable on/off toggle button

Synopsis

CheckBox (label,  
 checked); 
string label ;
boolean|nil checked ;
 

Parameters

string label

the text describing the check box

Options

boldFont

use a bold font

Optional Arguments

boolean|nil checked

whether the check box should start checked - nil means tristate condition, i.e. neither on nor off

Description

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.

Usage

 	`CheckBox( `id( `cheese ), "&amp; Extra cheese" )

Examples

          {
    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();
}

	       

        

Name

CheckBoxFrame — Frame with clickable on/off toggle button

Synopsis

CheckBoxFrame (label,  
 checked,  
 child); 
string label ;
boolean checked ;
term child ;
 

Parameters

string label

the text describing the check box

boolean checked

whether the check box should start checked

term child

the child widgets for frame content - typically `VBox(...) or `HBox(...)

Options

noAutoEnable

do not enable/disable frame children upon status change

invertAutoAnable

disable frame children if check box is checked

Description

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.

Usage

 	`CheckBoxFrame( `id( `custom), "&Custom", true, `VBox(`InputField(...), ... )

Examples

          // 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();
}

        

Name

ComboBox — drop-down list selection (optionally editable)

Synopsis

ComboBox (label,  
 items); 
string label ;
list items ;
 

Parameters

string label

Options

editable

the user can enter any value.

Optional Arguments

list items

the items contained in the combo box

Description

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]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.

Usage

 	`ComboBox( `id( `pizza ), "select your Pizza:", [ "Margarita", `item( `id( `na ), "Napoli" ) ] )

Examples

          {
    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();


}

        

Name

DateField — Date input field

Synopsis

DateField (label,  
 initialDate); 
string label ;
string initialDate ;
 

Parameters

string label

Optional Arguments

string initialDate

Description

An input field for entering a date.

[Note]Note

This is a "special" widget, i.e. not all UIs necessarily support it. Check for availability with HasSpecialWidget( `TimeField) before using it.

Usage

 	if ( HasSpecialWidget( `DateField ) {...
 	    `DateField( "Date:", "2004-10-12" )
 

Name

DownloadProgress — Self-polling file growth progress indicator (optional widget)

Synopsis

DownloadProgress (label,  
 filename,  
 expectedSize); 
string label ;
string filename ;
integer expectedSize ;
 

Parameters

string label

label above the indicator

string filename

file name with full path of the file to poll

integer expectedSize

expected final size of the file in bytes

Description

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]Note

This is a "special" widget, i.e. not all UIs necessarily support it. Check for availability with HasSpecialWidget( `DownloadProgress ) before using it.

Usage

 	if ( HasSpecialWidget( `DownloadProgress ) {...
 	`DownloadProgress( "Base system (230k)", "/tmp/aaa_base.rpm", 230*1024 );
 

Examples

          {
    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();
}

          

Name

DumbTab — Simplistic tab widget that behaves like push buttons

Synopsis

DumbTab (tabs,  
 contents); 
list tabs ;
term contents ;
 

Parameters

list tabs

page headers

term contents

page contents - usually a ReplacePoint

Description

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]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.

Usage

 	if ( HasSpecialWidget( `DumbTab) {...
 	`DumbTab( [ `item(`id(`page1), "Page &1" ), `item(`id(`page2), "Page &2" ) ], contents; }
 

Examples

          // 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();
}

        

Name

Empty — Placeholder widget

Synopsis

Empty (); 
 

Description

The Empty widget does nothing and has a default size of zero in both dimensions. It is useful as a placeholder, for example as the initial child of a ReplacePoint.

Usage

 	`Empty()
 

Name

Frame — Frame with label

Synopsis

Frame (label,  
 child); 
string label ;
term child ;
 

Parameters

string label

title to be displayed on the top left edge

term child

the contained child widget

Description

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.

Usage

 	`Frame( `RadioButtonGroup( `id( rb ), `VBox( ... ) ) );

Examples

          {
    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();
}

        

Name

HBox, VBox — Generic layout: Arrange widgets horizontally or vertically

Synopsis

HBox (); 
 
VBox (); 
 

Options

debugLayout

verbose logging

Optional Arguments

term child1

the first child widget

term child2

the second child widget

term child3

the third child widget

term child4

the fourth child widget ( and so on... )

Description

The layout boxes are used to split up the dialog and layout a number of widgets horizontally ( HBox ) or vertically ( VBox ).

Usage

 	HBox( `PushButton( `id( `ok ), "OK" ), `PushButton( `id( `cancel ), "Cancel" ) )
 

Examples

          {
    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();
}


          

Name

HSpacing, VSpacing, HStretch, VStretch — Fixed size empty space for layout

Synopsis

HSpacing (); 
 
VSpacing (); 
 
HStretch (); 
 
VStretch (); 
 

Optional Arguments

integer|float size

Description

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.

Usage

 	`HSpacing( 0.3 ) or `HStretch() or `HStretch( 0.7 )

Examples

          {
    // 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();
}


          

Name

HSquash, VSquash, HVSquash — Layout aid: Minimize widget to its preferred size

Synopsis

HSquash (child); 
term child ;
 
VSquash (child); 
term child ;
 
HVSquash (child); 
term child ;
 

Parameters

term child

the child widget

Description

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( ... ).

Usage

 	HSquash( `InputField( "Name:" ) )

Examples

          {
    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();
}

        

Name

HWeight, VWeight — Control relative size of layouts

Synopsis

HWeight (weight,  
 child); 
integer weight ;
term child ;
 
VWeight (weight,  
 child); 
integer weight ;
term child ;
 

Parameters

integer weight

the new weight of the child widget

term child

the child widget

Description

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.

Usage

 	`HWeight( 2, `SelectionBox( "Language" ) )

Examples

          {
    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();
}


          

Name

Image — Pixmap image

Synopsis

Image (imageFileName); 
string imageFileName ;
 

Parameters

string imageFileName

file name (with path) of the image to display

Options

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

Description

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.

Examples

          // 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();
}

          

Name

InputField, TextEntry, Password — Input field

Synopsis

InputField (label,  
 defaulttext); 
string label ;
string defaulttext ;
 
TextEntry (label,  
 defaulttext); 
string label ;
string defaulttext ;
 
Password (label,  
 defaulttext); 
string label ;
string defaulttext ;
 

Parameters

string label

the label describing the meaning of the entry

Options

shrinkable

make the input field very small

Optional Arguments

string defaulttext

The text contained in the text entry

Description

This widget is a one line text entry field with a label above it. An initial text can be provided.

[Note]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.

Usage

 	`InputField( `id( `name ), "Enter your name:", "Kilroy" )
 

Examples

          {
    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();


}

        

Name

IntField — Numeric limited range input field

Synopsis

IntField (label,  
 minValue,  
 maxValue,  
 initialValue); 
string label ;
integer minValue ;
integer maxValue ;
integer initialValue ;
 

Parameters

string label

Explanatory label above the input field

integer minValue

minimum value

integer maxValue

maximum value

integer initialValue

initial value

Description

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.

Usage

 	`IntField( "Percentage", 1, 100, 50 )
 

Examples

          {
    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();
}

          

Name

Label, Heading — Simple static text

Synopsis

Label (label); 
string label ;
 
Heading (label); 
string label ;
 

Parameters

string label

Options

outputField

make the label look like an input field in read-only mode

boldFont

use a bold font

Description

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.

Usage

 	`Label( "Here goes some text\nsecond line" )
 

Examples

          {
    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 );
}

        

Name

Left, Right, Top, Bottom, HCenter, VCenter, HVCenter — Layout alignment

Synopsis

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" ;
 

Parameters

term child

The contained child widget

Optional Arguments

`BackgroundPixmap( "dir/pixmap.png"

) background pixmap

Description

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.

Usage

 	`Left( `CheckBox( "Don't ask this again" ) )

Examples

          {
    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();
}

        

Name

LogView — scrollable log lines like "tail -f"

Synopsis

LogView (label,  
 visibleLines,  
 maxLines); 
string label ;
integer visibleLines ;
integer maxLines ;
 

Parameters

string label

(above the log lines)

integer visibleLines

number of visible lines (without scrolling)

integer maxLines

number of log lines to store (use 0 for "all")

Description

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.

Usage

 	`LogView( "Log file", 4, 200 );

Examples

          {
    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();
}
   

          

Name

MarginBox — Margins around one child widget

Synopsis

MarginBox (horMargin,  
 vertMargin,  
 child); 
float horMargin ;
float vertMargin ;
term child ;
 

Parameters

float horMargin

margin left and right of the child widget

float vertMargin

margin above and below the child widget

term child

The contained child widget

Description

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.

Usage

 	`MarginBox( 0.2, 0.3, `Label( "Hello" ) );
 	`MarginBox( `leftMargin( 0.7,), `rightMargin( 2.0 ), `topMargin( 0.3 ), `bottomMargin( 0.8 ), `Label( "Hello" ) );

Examples

          {
    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();
}

        

Name

MenuButton — Button with popup menu

Synopsis

MenuButton (label,  
 menu); 
string label ;
itemList menu ;
 

Parameters

string label

itemList menu

items

Description

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.

Usage

 	`MenuButton( "button label", [ `item( `id( `doit ), "&amp; Do it" ), `item( `id( `something ), "&amp; Something" ) ] );

Examples

          // 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 );
}

        

Name

MinWidth, MinHeight, MinSize — Layout minimum size

Synopsis

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 ;
 

Parameters

float|integer size

minimum width (for MinWidth or MinSize) or minimum heigh (for MinHeight)

term child

The contained child widget

Optional Arguments

float|integer height

(only for MinSize)

Description

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.

Usage

 	`MinWidth( 30, InputField(`id(`name), "Name" ) );

Examples

          // 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();
}

        

Name

MultiLineEdit — multiple line text edit field

Synopsis

MultiLineEdit (label,  
 initialValue); 
string label ;
string initialValue ;
 

Parameters

string label

label above the field

Optional Arguments

string initialValue

the initial contents of the field

Description

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.

Usage

 	`MultiLineEdit( `id( `descr ), "Enter problem &amp; description:", "No problem here." )
 

Examples

          {
    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();


}

        

Name

MultiSelectionBox — Selection box that allows selecton of multiple items

Synopsis

MultiSelectionBox (label,  
 items); 
string label ;
list items ;
 

Parameters

string label

Options

shrinkable

make the widget very small

Optional Arguments

list items

the items initially contained in the selection box

Description

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).

Usage

 	`MultiSelectionBox( `id( `topping ), "select pizza toppings:", [ "Salami", `item( `id( `cheese ), "Cheese", true ) ] )

Examples

          {
    // 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();
}

        

Name

PackageSelector — Complete software package selection

Synopsis

PackageSelector (); 
 

Options

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

Optional Arguments

string floppyDevice

Description

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.

Usage

 	`PackageSelector()
 

Examples

          // 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 );
}

        

Name

PartitionSplitter — Hard disk partition splitter tool (optional widget)

Synopsis

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 ;
 

Parameters

integer usedSize

size of the used part of the partition

integer totalFreeSize

total size of the free part of the partition (before the split)

integer newPartSize

suggested size of the new partition

integer minNewPartSize

minimum size of the new partition

integer minFreeSize

minimum free size of the old partition

string usedLabel

BarGraph label for the used part of the old partition

string freeLabel

BarGraph label for the free part of the old partition

string newPartLabel

BarGraph label for the new partition

string freeFieldLabel

label for the remaining free space field

string newPartFieldLabel

label for the new size field

Description

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]Note

This is a "special" widget, i.e. not all UIs necessarily support it. Check for availability with HasSpecialWidget( `PartitionSplitter ) before using it.

Usage

 	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 )" )
 

Examples

          {
    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();
}

          

Name

PatternSelector — High-level widget to select software patterns (selections)

Synopsis

PatternSelector (); 
 

Description

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).

Usage

 	if ( UI::HasSpecialWidget( `PatternSelector) {...
 	`PatternSelector()...
 	UI::RunPkgSelection();
 

Examples

          // 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();
}

        

Name

PkgSpecial — Package selection special - DON'T USE IT

Synopsis

PkgSpecial (); 
 

Description

Use only if you know what you are doing - that is, DON'T USE IT.

Usage

 	`PkgSpecial( "subwidget_name" )
 

Name

ProgressBar — Graphical progress indicator

Synopsis

ProgressBar (label,  
 maxvalue,  
 progress); 
string label ;
integer maxvalue ;
integer progress ;
 

Parameters

string label

the label describing the bar

Optional Arguments

integer maxvalue

the maximum value of the bar

integer progress

the current progress value of the bar

Description

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.

Usage

 	`ProgressBar( `id( `pb ), "17 of 42 Packages installed", 42, 17 )

Examples

          // 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();
}

        

Name

PushButton, IconButton — Perform action on click

Synopsis

PushButton (iconName,  
 label); 
string iconName ;
string label ;
 
IconButton (iconName,  
 label); 
string iconName ;
string label ;
 

Parameters

string iconName

(IconButton only)

string label

Options

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

Description

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 "&amp; 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.

Usage

 	`PushButton( `id( `click ), `opt( `default, `hstretch ), "Click me" )

Examples

          {
    // 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();
}

        

Name

RadioButton — Clickable on/off toggle button for radio boxes

Synopsis

RadioButton (label,  
 selected); 
string label ;
boolean selected ;
 

Parameters

string label

Options

boldFont

use a bold font

Optional Arguments

boolean selected

Description

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.

Usage

 	`RadioButton( `id( `now ), "Crash now", true )

Examples

          {
    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();
}


        

Name

RadioButtonGroup — Radio box - select one of many radio buttons

Synopsis

RadioButtonGroup (child); 
term child ;
 

Parameters

term child

the child widget

Description

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.

Usage

 	`RadioButtonGroup( `id( rb ), `VBox( ... ) )

Examples

          {
    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();
}

        

Name

ReplacePoint — Pseudo widget to replace parts of a dialog

Synopsis

ReplacePoint (child); 
term child ;
 

Parameters

term child

the child widget

Description

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.

Usage

 	`ReplacePoint( `id( `rp ), `Empty() )

Examples

          {
    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();
}

        

Name

RichText — Static text with HTML-like formatting

Synopsis

RichText (text); 
string text ;
 

Parameters

string text

Options

plainText

don't interpret text as HTML

autoScrollDown

automatically scroll down for each text change

shrinkable

make the widget very small

Description

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.

Usage

 	`RichText( "This is a bold text" )

Examples

          // 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>&amp;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>&amp;product;</b> for the product name (<b>&product;</b>)"
				 + "<li><b>&amp;amp;</b> for <b>&amp;</b>"
				 + "<li><b>&amp;lt;</b> for <b>&lt;</b>"
				 + "<li><b>&amp;gt;</b> for <b>&gt;</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();
}

        

Name

SelectionBox — Scrollable list selection

Synopsis

SelectionBox (label,  
 items); 
string label ;
list items ;
 

Parameters

string label

Options

shrinkable

make the widget very small

immediate

make `notify trigger immediately when the selected item changes

Optional Arguments

list items

the items contained in the selection box

Description

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.

Usage

 	`SelectionBox( `id( `pizza ), "select your Pizza:", [ "Margarita", `item( `id( `na ), "Napoli" ) ] )

Examples

          {
    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();
}

        

Name

SimplePatchSelector — Simplified approach to patch selection

Synopsis

SimplePatchSelector (); 
 

Description

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.

Usage

 	if ( UI::HasSpecialWidget( `SimplePatchSelector) {...
 	`SimplePatchSelector()...
 	UI::RunPkgSelection();
 

Examples

          // 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();
}

        

Name

Slider — Numeric limited range input (optional widget)

Synopsis

Slider (label,  
 minValue,  
 maxValue,  
 initialValue); 
string label ;
integer minValue ;
integer maxValue ;
integer initialValue ;
 

Parameters

string label

Explanatory label above the slider

integer minValue

minimum value

integer maxValue

maximum value

integer initialValue

initial value

Description

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]Note

This is a "special" widget, i.e. not all UIs necessarily support it. Check for availability with HasSpecialWidget( `Slider ) before using it.

Usage

 	if ( HasSpecialWidget( `Slider ) {...
 	`Slider( "Percentage", 1, 100, 50 )
 

Examples

          {
    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();
}

          

Name

Table — Multicolumn table widget

Synopsis

Table (header,  
 items); 
term header ;
list items ;
 

Parameters

term header

the headers of the columns

Options

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)

Optional Arguments

list items

the items contained in the selection box

Description

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.

Usage

 	`Table( `header( "Game", "Highscore" ), [ `item( `id(1), "xkobo", "1708" ) ] )

Examples

          {
    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();
}


          

Name

TimeField — Time input field

Synopsis

TimeField (label,  
 initialTime); 
string label ;
string initialTime ;
 

Parameters

string label

Optional Arguments

string initialTime

Description

An input field for entering a time of day in 24 hour format.

[Note]Note

This is a "special" widget, i.e. not all UIs necessarily support it. Check for availability with HasSpecialWidget( `TimeField) before using it.

Usage

 	if ( HasSpecialWidget( `TimeField ) {...
 		    `TimeField( "Time:" , "20:20:20" )
 

Name

TimezoneSelector — Timezone selector map

Synopsis

TimezoneSelector (pixmap,  
 timezones); 
string pixmap ;
map timezones ;
 

Parameters

string pixmap

path to a jpg or png of a world map - with 0°0° being the middle of the picture

map timezones

a map of timezones. The map should be between e.g. Europe/London and the tooltip to be displayed ("United Kingdom")

Description

An graphical timezone selector map

[Note]Note

This is a "special" widget, i.e. not all UIs necessarily support it. Check for availability with HasSpecialWidget( `TimezoneSelector) before using it.

Usage

 	if ( HasSpecialWidget( `TimezoneSelector ) {...
 		    `TimezoneSelector( "world.jpg", timezones )
 

Name

Tree — Scrollable tree selection

Synopsis

Tree (label,  
 items); 
string label ;
itemList items ;
 

Parameters

string label

Options

immediate

make `notify trigger immediately when the selected item changes

Optional Arguments

itemList 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.

Description

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.

Usage

 	`Tree( `id( `treeID ), "treeLabel", [ "top1", "top2", "top3" ] );

Examples

          // 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();

}

        

Name

VMultiProgressMeter, HMultiProgressMeter — Progress bar with multiple segments (optional widget)

Synopsis

VMultiProgressMeter (maxValues); 
List<integer> maxValues ;
 
HMultiProgressMeter (maxValues); 
List<integer> maxValues ;
 

Parameters

List<integer> maxValues

maximum values

Description

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]Note

This is a "special" widget, i.e. not all UIs necessarily support it. Check for availability with HasSpecialWidget( `MultiProgressMeter ) before using it.

Usage

 	if ( HasSpecialWidget( `MultiProgressMeter ) {...
 	`MultiProgressMeter( "Percentage", 1, 100, 50 )
 

Examples

          // 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();
}

        

Name

Wizard — Wizard frame - not for general use, use the Wizard:: module instead!

Synopsis

Wizard (backButtonId,  
 backButtonLabel,  
 abortButtonId,  
 abortButtonLabel,  
 nextButtonId,  
 nextButtonLabel); 
any backButtonId ;
string backButtonLabel ;
any abortButtonId ;
string abortButtonLabel ;
any nextButtonId ;
string nextButtonLabel ;
 

Parameters

any backButtonId

ID to return when the user presses the "Back" button

string backButtonLabel

Label of the "Back" button

any abortButtonId

ID to return when the user presses the "Abort" button

string abortButtonLabel

Label of the "Abort" button

any nextButtonId

ID to return when the user presses the "Next" button

string nextButtonLabel

Label of the "Next" button

Options

stepsEnabled

Enable showing wizard steps (use UI::WizardCommand() to set them).

treeEnabled

Enable showing a selection tree in the left panel. Disables stepsEnabled.

Description

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]Note

This is a "special" widget, i.e. not all UIs necessarily support it. Check for availability with HasSpecialWidget( `Wizard) before using it.

Usage

 	`Wizard(`id(`back), "&Back", `id(`abort), "Ab&ort", `id(`next), "&Next" )
 	`Wizard(`back, "&Back", `abort, "Ab&ort", `next, "&Next" )
 

Appendix A. UI Richtext

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