Module ui
User Interface.
The ui module lets you create user interfaces. This subject is covered in the user guide.
At the basis of the architecture are widgets. You can create widgets, and you can also access existing widgets created by MC itself. This ability is the one which lets you script the file manager and the editor.
As a quick reference, here’s a snippet that uses some common features:
local function order_pizza() local dlg = ui.Dialog(T"Place an order") local flavor = ui.Radios() flavor.items = { 'Cheese', 'Olive', 'Anchobi', 'Falafel', } local with_pepper = ui.Checkbox(T"With pepper") local with_ketchup = ui.Checkbox{T"With ketchup", checked=true} local send_address = ui.Input() dlg:add( ui.Label(T"Please fill in the details:"), ui.HBox():add( flavor, ui.Groupbox(T"Spices:"):add( with_pepper, with_ketchup ) ), ui.Label(T"Send it to:"), send_address, ui.DefaultButtons() ) flavor.on_change = function(self) -- It's abominable to add ketchup to Anchobi. with_ketchup.enabled = (self.value ~= "Anchobi") end if dlg:run() then alert(T"Great! I'll be delivering the %s pizza to %s!":format( flavor.value, send_address.text)) if with_pepper.checked then alert(T"I too love pepper!") end end end keymap.bind('C-q', order_pizza)
Functions
Button(...) | Creates a Button widget |
Checkbox(...) | Creates a Checkbox widget |
Dialog(...) | Creates a Dialog box |
Editbox(...) | Creates an Editbox widget. |
Gauge(...) | Creates a gauge widget |
Groupbox(...) | Creates a Groupbox widget |
HBox(...) | Creates an HBox container |
HLine(...) | Creates an HLine widget |
Input(...) | Creates a Input widget |
Label(...) | Creates a label widget |
Listbox(...) | Creates a Listbox widget |
Radios(...) | Creates a Radios widget |
Space([cols[, rows]) | Creates a Space widget. |
VBox(...) | Creates a VBox container |
ZLine(...) | Creates a ZLine widget. |
current_widget([widget_type]) | Returns the current widget. |
open() | Enters UI mode. |
queue(fn) | Queues code to run at the next event-loop iteration. |
Widget methods and properties
widget.cols rw | The width of the widget, in characters. |
widget.data rw | Custom user data. |
widget.dialog r | The dialog the widget is in. |
widget.enabled rw | Whether the widget is enabled or disabled. |
widget.expandx rw | Layout control. |
widget.expandy rw | Layout control. |
widget.rows rw | The height of the widget, in lines. |
widget.widget_type r | The name of the widget’s class. |
widget:_send_message(msg[, parm[, sender]) | Low-level message passing. |
widget:command(command_name) | Executes a widget command. |
widget:fixate() | Fixates a widget. |
widget:focus() | Focuses the widget. |
widget:get_canvas() | Returns a canvas object encompassing the widget’s area. |
widget:is_alive() | Whether the widget is alive. |
widget:redraw() | Redraws the widget. |
Button widget
button.result rw | The result value for clicking this button. |
button.text rw | The label shown on the button. |
button.type w | The type of the button. |
button:on_click(self) handler | Click handler. |
Checkbox widget
checkbox.checked rw | The state of the checkbox. |
checkbox.text rw | The label for the checkbox. |
checkbox:on_change(self) handler | Change handler. |
Label widget
label.auto_size w | Whether the text decides the size of the widget. |
label.text rw | The text displayed in the label. |
Input widget
input.cursor_offs rw | The cursor position. |
input.history w | The history bin. |
input.mark rw | The “mark” position. |
input.password rw | Whether to mask the input. |
input.text rw | The text being edited. |
input:insert(s) | Inserts text at the cursor location. |
input:on_change(self) handler | Change handler. |
Groupbox widget
groupbox.padding rw | Horizontal padding. |
groupbox.text rw | Caption. |
Listbox widget
listbox.items rw | The listbox items. |
listbox.selected_index rw | The index of the selected item. |
listbox.value rw | The selected item. |
listbox:on_change(self) handler | Change handler. |
listbox:widest_item() | Calculates the widest item. |
Radios widget
radios.items rw | The radio items. |
radios.selected_index rw | The index of the selected item. |
radios.value rw | The selected item. |
radios:on_change(self) handler | Change handler. |
Gauge widget
gauge.max rw | The maximal value. |
gauge.shown rw | The visibility of the gauge. |
gauge.value rw | The current value. |
HLine widget
hline.text rw | Optional caption. |
Dialog widget
dialog.colorset rw | The colors of the dialog. |
dialog.compact rw | Whether extra space is shown around the dialog’s frame. |
dialog.current r | The focused widget. |
dialog.help_id rw | The help ID. |
dialog.mapped_children r | The child widgets. |
dialog.modal rw | Whether the dialog is modal or modaless. |
dialog.padding rw | Horizontal padding. |
dialog.result rw | Holds the “result” of running the dialog. |
dialog.state r | The state of the dialog. |
dialog.text rw | The dialog window’s title. |
dialog:close() | Closes the dialog. |
dialog:find([wtype,] [predicate,] [from]) | Finds a widget among the children. |
dialog:focus() | Switches to the dialog. |
dialog:gmatch([wtype,] [predicate,] [from]) | Finds widgets among the children. |
dialog:on_draw(self) handler | Frame drawing handler. |
dialog:on_help(self) handler | Help handler. |
dialog:on_idle(self) handler | Idle handler. |
dialog:on_init(self) handler | Initialization handler. |
dialog:on_key(self, keycode) handler | Keypress handler. |
dialog:on_post_key(self, keycode) handler | Keypress handler. |
dialog:on_resize(self) handler | Resize handler. |
dialog:on_title(self) handler | Title handler. |
dialog:on_validate(self) handler | Closing validation handler. |
dialog:popup([lstbx]) | Runs the dialog. |
dialog:redraw() | Redraws the dialog. |
dialog:redraw_cursor() | Positions the cursor at the focused element. |
dialog:refresh([do_redraw]) | Updates the cursor on the physical screen. |
dialog:run() | Runs the dialog. |
dialog:set_dimensions([x], [y], [cols], [rows]) | Explicitly sets the dialog’s dimensions. |
<<draw>> | Triggered after a dialog had been painted. |
<<layout>> | Triggered after a dialog layouts itself. |
<<open>> | Triggered when a dialog is opened. |
<<submit>> | Triggered when a dialog is about to be closed “successfully”. |
<<close>> | Triggered when a dialog is about to be closed. |
Static dialog properties
Dialog.screens r | A list of all the modaless dialogs. |
Dialog.top r | The topmost dialog. |
Containers
container:add(widget[, ...]) | Adds widgets to a container. |
container:preferred_cols() | Calculates the preferred width. |
container:preferred_rows() | Calculates the preferred height. |
HBox container
hbox.gap rw | The horizontal gap between child widgets. |
VBox container
vbox.gap rw | The vertical gap between child widgets. |
Stock buttons
Buttons() | Creates a container for buttons. |
CancelButton([props]) | Creates a “Cancel” button. |
DefaultButtons() | Creates an “Ok” and “Cancel” buttons. |
OkButton([props]) | Creates an “OK” button. |
Static widget functions
bind(keyseq_or_event, function) | Binds functions to keys and events. |
subclass(new_class_name) | Creates a new widget class. |
Functions
- Button(...)
- Creates a Button widget
- Checkbox(...)
- Creates a Checkbox widget
- Dialog(...)
- Creates a Dialog box
- Editbox(...)
-
Creates an Editbox widget.
You'll usually access an already-existing Editbox (as in the editor), but you can create one yourself. Note, however, that since it was not foreseen by the core developers that this widget would be used outside the editor, it has a few problems when used in that fashion. See editbox_instance.mcs.
- Gauge(...)
- Creates a gauge widget
- Groupbox(...)
- Creates a Groupbox widget
- HBox(...)
- Creates an HBox container
- HLine(...)
- Creates an HLine widget
- Input(...)
- Creates a Input widget
- Label(...)
- Creates a label widget
- Listbox(...)
- Creates a Listbox widget
- Radios(...)
- Creates a Radios widget
- Space([cols[, rows])
-
Creates a Space widget.
A Space widget is just an empty rectangle on the screen. It lets us space out other widgets. You can put it between or before/after widgets.
Together with the expandx and expandy properties it can also be used to flush other widgets to the right/bottom/center. (See discussion in the user guide.)
- VBox(...)
- Creates a VBox container
- ZLine(...)
-
Creates a ZLine widget.
A “ZLine” widget is exactly like an HLine widget except that the line stretches all over the way to the dialog’s frame. It’s therefore a bit more aesthetically pleasing than an HLine.
- current_widget([widget_type])
-
Returns the current widget.
The “current widget” is the widget that has the focus, in the active dialog box.
-- A useful debugging aid. keymap.bind('F11', function() devel.view(ui.current_widget()) end) -- An interesting way to close the active dialog. keymap.bind('F12', function() local wgt = ui.current_widget() if wgt then wgt.dialog:close() end -- Or we could just do ui.Dialog.top:close() end)
The function may return nil if there’s no current widget or if the widget doesn’t have a Lua counterpart. For example, if the pull-down menus are active, nil is returned.
The optional string argument widget_type makes the function return the widget only if the widget is of the specified type (otherwise nil is returned). While it’s trivial to do this check in Lua, it’s more efficient to have current_widget do it.
- open()
-
Enters UI mode.
Makes the terminal enter UI mode, where dialogs can be displayed.
This function can be called in standalone mode only. See there for details.
- queue(fn)
-
Queues code to run at the next event-loop iteration.
Sometimes you want to postpone code a bit, till after some other things happen in the current event-loop iteration.
Example:
-- A failed example. -- The "Pause" button, when clicked, should pass focus -- to the "Resume" button. pause_button.on_click = function() interval:stop() -- THIS FAILS: It so happens that MC focuses a button -- *after* its on_click gets called. So right after you -- focus the resume button, MC will take away its focus -- and give it to the pause button. resume_button:focus() end
To fix this example we can postpone the focusing:
-- This works. pause_button.on_click = function() interval:stop() ui.queue(function() resume_button:focus() end) end
(Search our code base for “queue” to see more examples.)
Widget methods and properties
- widget.cols rw
-
The width of the widget, in characters.
You usually don’t need to set this property: for most widgets it’s set according to the widget’s preferred dimensions.
- widget.data rw
-
Custom user data.
A table in which you can store your own data.
See example at dialog:on_validate.
- widget.dialog r
-
The dialog the widget is in.
closeButton.on_click = function(self) self.dialog:close() end
(See another example at command.)
- widget.enabled rw
-
Whether the widget is enabled or disabled.
local ftp = ui.Checkbox(T"Use FTP") local ftp_server = ui.Input("ftp.midnight.org") ftp.on_change = function(self) ftp_server.enabled = self.checked end ui.Dialog():add(ftp, ftp_server):run()
- widget.expandx rw
-
Layout control.
This property helps in laying out a widget, as explained in the user guide.
- widget.expandy rw
-
Layout control.
This property helps in laying out a widget, as explained in the user guide.
- widget.rows rw
- The height of the widget, in lines.
- widget.widget_type r
-
The name of the widget’s class.
This property can aid in debugging. It’s also a way for you to find the type of a widget. E.g.,
if w.widget_type == "Button"
(although you can also doif getmetatable(w) == ui.Button.meta
). - widget:_send_message(msg[, parm[, sender])
-
Low-level message passing.
This method calls the C function send_message().
- widget:command(command_name)
-
Executes a widget command.
Certain widgets respond to certain commands. For example, an Input widget responds to ‘WordLeft’, ‘Paste’, etc. An Editbox responds to ‘Undo’, ‘Redo’, ‘DeleteLine’, etc. Instead of providing a Lua method for every such command, this one method triggers any command by name.
Command names are case insensitive.
To see a list of available commands, check the C source code (e.g., keybind-defaults.c, but that list isn’t exhaustive).
Note that some commands are to be sent to the dialog containing the widget, not the widget itself (see example).
ui.Editbox.bind('C-x e', function(edt) edt:command "DeleteLine" edt.dialog:command "ShowMargin" end) ui.Panel.bind('C-x d', function(pnl) pnl:command "MiddleOnScreen" pnl:redraw() -- Some commands, such as MiddleOnScreen, -- don't automatically redraw the widget. pnl.dialog:command "Find" end)
This method returns true if the command was handled.
- widget:fixate()
-
Fixates a widget.
Introduction
The widgets you interact with in Lua are wrappers around “real” C widgets.
In some situations it can happen that you no longer have a Lua variable referencing a widget you're interested in. In such cases the Lua wrapper gets garbage collected. Usually there’s no problem in that: it’s the expected behavior. A problem may arise, however, when you store some data in the Lua wrapper: this data will be lost too.
The fixate method prevents the Lua wrapper from being garbage collected as long as the underlying C widget is still alive. This means that the next time you get your hands on a wrapper for this widget you'll get the same old wrapper — with your precious data on it.
Example
Let’s have an example. Suppose you want to implement a “read only” feature for the editor. As a first step you make
C-n
mark an editbox as read only:ui.Editbox.bind('C-n', function(edt) alert(T'This editbox is now read-only!') edt.data.is_read_only = true end)
As the next step you reject keys that occur in such marked editboxes:
local ESC = tty.keyname_to_keycode 'esc' ui.Editbox.bind('any', function(edt, kcode) if edt.data.is_read_only and (kcode < 256 and kcode ~= ESC) then tty.beep() else return false -- Let MC handle this key. end end)
Will this work? Not quite. The read-only protection will last for a few seconds only: The Lua wrapper carrying
data.is_read_only
will get garbage collected at some point, and theedt
wrapper seen at the keyboard handler code will be a new one, which doesn’t carrydata.is_read_only
.To fix our code we need to fixate the wrapper:
ui.Editbox.bind('C-n', function(edt) alert(T'This editbox is now read-only!') edt.data.is_read_only = true edt:fixate() end)
Returns:
-
The widget itself.
- widget:focus()
-
Focuses the widget.
That is, moves the keyboard focus (the cursor) to it.
See example at on_validate.
Note that you can only focus a widget that has been "mapped" into a dialog. Mapping happens when you call run, not before. Therefore, a way to select the initial widget to get the focus is to use on_init, as follows:
local function test() local dlg = ui.Dialog() local name = ui.Input() local age = ui.Input() dlg.on_init = function() age:focus() end dlg:add(name, age) dlg:run() end
- widget:get_canvas()
-
Returns a canvas object encompassing the widget’s area.
This lets you draw inside the widget. You'd normally use this method with a ui.Custom widget only.
- widget:is_alive()
-
Whether the widget is alive.
This method tells us whether the C widget associated with this Lua object has been destroyed.
To understand this method, let’s imagine the following code:
local the_editbox = nil ui.Editbox.bind('C-a', function(edt) the_editbox = edt end) keymap.bind('C-b', function() if the_editbox then alert('The editbox edits the file ' .. the_editbox.filename) end end)
We press
C-a
inside the editor. Then we close the editor. This destroys the editbox. Now we pressC-b
. What will happen? An exception will be raised, when we try to access thefilename
property. The error message says “A living widget was expected, but an already destroyed widget was provided”. That’s because the Lua object is now just a shell over a dead body. To fix our code we can change it to:keymap.bind('C-b', function() if the_editbox and the_editbox:is_alive() then alert('The editbox edits the file ' .. the_editbox.filename) end end)
- widget:redraw()
-
Redraws the widget.
Draws (“paints”, if you will) the widget.
You won’t normally need to call this method yourself, because all properties that affect the visual appearance of a widget call :redraw() automatically for you upon setting. For example, if you change the text of an input box or the value of a gauge, you don’t need to call :redraw() afterwards.
A notable case where you do have to call :redraw() yourself is after you change the state of a ui.Custom widget. Only you know what affects the display of your custom widget, so only you can decide when to redraw it.
For further information on the mechanism of updating the screen, see Drawing and Refreshing the Screen.
Button widget
- button.result rw
-
The result value for clicking this button.
Often, after running a dialog, we're interested in knowing which button was clicked.
While you yourself can keep a track of which button was clicked, by using the on_click handler, the “result” property offers a shortcut:
When a button is clicked which has the “result” property set, the dialog’s own “result” property gets set to this value and the dialog is then closed.
Example:
keymap.bind('C-f', function() local dlg = ui.Dialog(T"Open mode") dlg:add(ui.Button{T'Read', result='r'}) :add(ui.Button{T'Write', result='w'}) :add(ui.Button{T'Read/Write', result='rw'}) alert(dlg:run()) -- You'll usually do: --if dlg:run() then -- alert("I'll open the file in " .. dlg.result .. " mode") --end -- Or: --local mode = dlg:run() --if mode then -- alert("I'll open the file in " .. mode .. " mode") --end end)
The result property is just a shortcut for doing:
local btn = ui.Button{T'Read/Write', on_click=function(self) self.dialog.result = 'rw' self.dialog:close() end}
- button.text rw
- The label shown on the button.
- button.type w
-
The type of the button.
Possible types: “normal”, “default”, “narrow”, “hidden”.
- button:on_click(self) handler
-
Click handler.
Called when a button is “clicked”; that is, activated.
btn.on_click = function() alert(T"hi there!") end
You may, of course, close the dialog from the handler:
btn.on_click = function(self) alert(T"hi there!") self.dialog:close() end
Often, however, using the result property is shorter:
local btn = ui.Button{T"Show greeting", result="greet"} dlg:add(btn) if dlg:run() == "greet" then alert(T"hi there!") end
Checkbox widget
- checkbox.checked rw
-
The state of the checkbox.
A checkbox is either checked or not.
See example in widget.enabled.
- checkbox.text rw
- The label for the checkbox.
- checkbox:on_change(self) handler
-
Change handler.
Called when the user changes the state of a checkbox.
Label widget
- label.auto_size w
-
Whether the text decides the size of the widget.
Normally, setting the label.text property sets the size of the label widget. When creating a label that shows a fixed string, this is what you what. If your label changes its text after creation, however, you want to turn off this feature so that the label doesn’t paint over neighboring widgets.
auto_size is initially true. Set it to false to disable it; you'll also want to set cols explicitly to make the label wide enough to display the bulk of its text, and/or to set expandx to true.
local function test() local lst = ui.Listbox {items={"one", "two", "a very loooooong string"}} local lbl = ui.Label {cols=20, auto_size=false} lst.on_change = function() lbl.text = lst.value end ui.Dialog():add(ui.Groupbox():add(lst), lbl):run() end
See another example in ui_filechooser.mcs.
- label.text rw
- The text displayed in the label.
Input widget
- input.cursor_offs rw
- The cursor position.
- input.history w
-
The history bin.
It is a string naming the bin. (As this string isn’t for human consumption, you don’t need to wrap it in T.)
local expr = ui.Input{history="calculator-expression"}
- input.mark rw
-
The “mark” position.
The “mark” is the point where the selection starts. If there’s no selction active, it equals nil.
- input.password rw
-
Whether to mask the input.
If you're inputting a password, set this property to true to show asterisks instead of the actual text.
-- Toggle masking for the current input field. ui.Input.bind('C-r', function(ipt) ipt.password = not ipt.password end)
- input.text rw
- The text being edited.
- input:insert(s)
-
Inserts text at the cursor location.
-- Insert the current date and time into any input line. ui.Input.bind("C-y", function(ipt) ipt:insert(os.date("%Y-%m-%d %H:%M:%S")) end)
- input:on_change(self) handler
-
Change handler.
Called when the user modifies the input box' text.
See example in ui_inputchange.mcs.
Groupbox widget
See example in ui_groupboxes.mcs.
- groupbox.padding rw
-
Horizontal padding.
The amount of screen columns (“spaces”) to reserve on the left and right sides, inside the frame. Defaults to 1. If you want the child widgets to almost “touch” the frame, set it to 0.
- groupbox.text rw
-
Caption.
Caption to print on the groupbox’s frame.
Listbox widget
expandx=true
so the width will
stretch. You may change all this by modifying these properties (and
expandy). There’s also :widest_item() to your help.
- listbox.items rw
-
The listbox items.
In its simplest form, each item is a string:
lstbx.items = { 'apple', 'banana', 'water melon', }
Alternatively, any item may be a list whose first element is the string, plus two optional keyed elements: value and hotkey. The string is meant for humans whereas the value is what your program actually sees. This value can be any complex object; not just strings or numbers. The hotkey, if exists, lets you select the associated item and close the dialog by pressing a key.
local lstbx = ui.Listbox() lstbx.items = { { T'Read only', value='r' }, { T'Write only', value='w' }, { T'Read/write', value='r+', hotkey='C-b' }, } if ui.Dialog():add(lstbx):run() then local f = assert( fs.open('/etc/passwd', lstbx.value) ) -- ... end
- listbox.selected_index rw
- The index of the selected item.
- listbox.value rw
- The selected item.
- listbox:on_change(self) handler
-
Change handler.
Called when the user changes the selection in the listbox.
See example in ui_filechooser.mcs.
- listbox:widest_item()
-
Calculates the widest item.
lstbx.cols = lstbx:widest_item() + 2
Radios widget
- radios.items rw
-
The radio items.
Everything discussed at listbox.items applies here as well except that a hokey element for an item has no effect on radios.
- radios.selected_index rw
- The index of the selected item.
- radios.value rw
- The selected item.
- radios:on_change(self) handler
-
Change handler.
Called when the user changes the selection in radio boxes.
Gauge widget
The cols property (the gauge’s size) is 25 characters by default. You
may change this and/or use expandx=true
.
- gauge.max rw
-
The maximal value.
This number is the 100% value. By default this is 100. You may change it if you think it'd make your calculations easier.
- gauge.shown rw
-
The visibility of the gauge.
Whether the gauge is shown or not. A gauge that is not shown still consumes space on the screen. You'd use this property when, for example, you wish to show the gauge only when some process is running.
A gauge is shown by default.
- gauge.value rw
-
The current value.
As your task progresses, you'd update this property to anything between 0 and max. It is allowed for it to exceed max (in which case it'd be treated as if it were equal to max).
See example at dialog:on_idle.
HLine widget
<hr>
element. You can use it to separate
sections in a dialog.
See also ZLine, which looks nicer.
Dialog widget
- dialog.colorset rw
-
The colors of the dialog.
The set of colors to be used to paint the dialog and the widgets within. Possible values:
-
"normal"
-
"alarm"
(typically red dominated, for error boxes.) -
"pmenu"
(colors of popup menus, like the “User menu”.)
You'd usually set this property in the constructor call:
dlg = ui.Dialog{T"A frightening dialog", colorset="alarm"}
But you can set it anytime afterwards:
local answer = ui.Input() answer.on_change = function(self) if self.text == "" then -- Missing data. self.dialog.colorset = "alarm" else self.dialog.colorset = "normal" end end
You can also read this property:
-- Sound a beep when an error dialog is shown. ui.Dialog.bind("<<open>>", function(dlg) if dlg.colorset == "alarm" then tty.beep() end end)
-
- dialog.compact rw
-
Whether extra space is shown around the dialog’s frame. A boolean flag.
The difference between this and padding is that padding governs the space inside the frame whereas compact governs the space outside the frame.
- dialog.current r
- The focused widget.
- dialog.help_id rw
-
The help ID.
If the dialog has a section in the user manual, this is the name of that section.
- dialog.mapped_children r
-
The child widgets.
Returns a list of all the widgets “mapped” into the dialog. Pseudo widgets (those used for layout: HBox, VBox, Space) aren’t included in the list.
- dialog.modal rw
-
Whether the dialog is modal or modaless.
By default dialogs are modal: the user has to finish interacting with them in order to continue. In other words, dialog:run doesn’t return while the dialog is still open. But a dialog can also be modaless: the user can switch to some other dialog while working with them.
To make a dialog modaless, simply set this property to true before calling dialog:run.
- dialog.padding rw
-
Horizontal padding.
This property behaves just like groupbox.padding.
- dialog.result rw
-
Holds the “result” of running the dialog. This value
has no meaning except the one you yourself give it.
This value is conveniently returned by :run, but you can access it directly any time.
See example at button.result.
- dialog.state r
-
The state of the dialog.
Use this property to inquire about the state of the dialog. Possible states:
- “construct”: The dialog hasn’t been run yet.
- “active”: The dialog is running.
- “suspended”: A modaless dialog has been switched out of.
- “closed”: The dialog has been closed.
This is a read-only property. To actually change the state of the dialog you use other methods; e.g., :run, :close.
- dialog.text rw
- The dialog window’s title.
- dialog:close()
-
Closes the dialog. Normally you'd use this method from click handlers of
buttons.
dlg = ui.Dialog() dlg:add(ui.Button {T"say something nice", on_click = function() alert(T"something nice!") dlg:close() end}) dlg:run()
- dialog:find([wtype,] [predicate,] [from])
-
Finds a widget among the children.
This is a convenience interface for mapped_children.
There are three criteria to search by. Each is optional, and the order doesn’t matter:
wtype – the widget type (a string).
predicate – a function testing a widget and returning true if it matches.
from – either a number, meaning to return the n'th widget found, or a widget, meaning to start searching after this widget (in the tabbing order).
If no widget matches the criteria, nil is returned.
Examples:
dlg:find('Input') -- find the first Input widget. dlg:find('Input', 2) -- find the second one. dlg:find('Checkbox', function(w) return w.text == T'&Fake half tabs' end) -- find the checkbox with that label. dlg:find('Checkbox', dlg:find('Groupbox', 2)) -- find the first checkbox inside the second groupbox.
See also gmatch.
- dialog:focus()
-
Switches to the dialog.
Only works for modaless dialogs.
This method doesn’t return immediately (unless you switch to the filemanager): it starts an event loop. This is not a limitation in our Lua API but the way MC works.
- dialog:gmatch([wtype,] [predicate,] [from])
-
Finds widgets among the children.
Like find, but iterates over all the matched children.
See example at Dialog.screens.
- dialog:on_draw(self) handler
-
Frame drawing handler.
Called to draw the background and frame of the dialog.
You should return true from this handler to signal that you've done the job or else the default frame will then be drawn, overwriting yours.
- dialog:on_help(self) handler
-
Help handler.
Called up when the user presses the help button (usually F1). Example:
dlg.on_help = function() local helpfile = assert(utils.path.module_path('mymodule', 'README.md')) mc.view(helpfile) end
- dialog:on_idle(self) handler
-
Idle handler.
Called while there’s no keyboard input.
You may use this handler, for example, to perform one slice of a lengthy task, repeatedly. This gives the impression of performing in the background: the user is able to interact with the dialog between the invocations of this handler.
keymap.bind('C-q', function() local dlg = ui.Dialog() local gg = ui.Gauge() gg.max = 5000000 dlg.on_idle = function() -- ... imagine we perform a slice of a lengthy calculation here ... gg.value = gg.value + 1 if gg.value > gg.max then gg.value = 0 end dlg:refresh() -- We have to refresh the terminal ourselves. end dlg:add(gg) dlg:add(ui.DefaultButtons()) dlg:run() end)
Comments:
If it happens that you no longer need this handler, set it to
nil
so it won’t get invoked and waste CPU cycles.A alternative to on_idle is to use the timer. But you can’t quite close a dialog (if you need to) from a timed function (because right after MC executes the timers it waits for a key (see tty/key.c:tty_get_event), even if the dialog is closed), which is something you can do from on_idle.
If you wish to close a dialog from on_idle (let’s say when you finish your lengthy “background” task), you first need to set the handler to
nil
(or else MC will keep calling it, since there’s still no keyboard input):
dlg.on_idle = function() do_a_slice_of_some_task() if task_was_completed() then dlg.on_idle = nil dlg:close() end end
- dialog:on_init(self) handler
-
Initialization handler.
Called just before the dialog becomes active.
See usage example at widget:focus.
- dialog:on_key(self, keycode) handler
-
Keypress handler.
Lets you respond to a key before any of the child widgets sees it. Return
true
from this handler to signal that you've consumed the key.See examples at ui.Custom:on_key, which is used similarly.
Parameters:
- self The dialog
- keycode A number
- dialog:on_post_key(self, keycode) handler
-
Keypress handler.
Lets you respond to a key after the child widgets had a chance to respond to it. Return
true
from this handler to signal that you've consumed the key.See examples at ui.Custom:on_key, which is used similarly.
Parameters:
- self The dialog
- keycode A number
- dialog:on_resize(self) handler
-
Resize handler.
Called when the screen changes its size.
Normally you don’t need to implement this handler: The default handler does an adequate job: it will re-center the dialog on the screen (unless it has figured out, by noticing that you've called set_dimensions earlier, that it’s not what you want).
You may implement this handler if you need to do some custom positioning. For example, here’s how to keep a dialog a fixed distance from the screen edges, similar to how the “Directory hotlist” behaves:
local dlg = ui.Dialog() dlg.on_resize = function(self) self:set_dimensions(nil, nil, tty.get_cols() - 10, tty.get_rows() - 2) end dlg:on_resize() -- We have to call this explicitly in the beginning, -- as it only gets called when the screen changes size. dlg:run()
- dialog:on_title(self) handler
-
Title handler.
Called to generate a modaless dialog’s title. It’s not used for modal dialogs. The default handler returns the dialog’s title.
local dlg = ui.Dialog("bobo") dlg.on_title = function() return "Clock: " .. os.date() end dlg.modal = false dlg:add(ui.DefaultButtons()) dlg:run()
- dialog:on_validate(self) handler
-
Closing validation handler.
Lets you decide whether it’s alright to close the dialog. This handler is called whenever an attempt is made to close the dialog. Return
true
from this handler to signal that it’s alright to close the dialog; else, the dialog will stay open.-- Asks the user for his name and age. If the user -- omits either name or age, we nag him. local function test() local dlg = ui.Dialog(T"Tell me about yourself") local name = ui.Input() local age = ui.Input() local occupation = ui.Input() name.data = { required = true, errmsg = T"Missing name!", } age.data = { required = true, errmsg = T"Missing age!", } dlg.on_validate = function() if dlg.result then -- We validate the input only if the -- user pressed some positive button. -- This excludes ESC and "Cancel". for widget in dlg:gmatch() do if widget.data.required and widget.text == "" then alert(widget.data.errmsg) widget:focus() -- Move user to the rogue widget. return false -- Don't let user close the dialog. end end end return true -- Allow closing. end dlg:add( ui.HBox():add(ui.Label(T"Name:"), name), ui.HBox():add(ui.Label(T"Age:"), age), ui.HBox():add(ui.Label(T"Occupation (optional):"), occupation), ui.DefaultButtons() ) dlg:run() end
- dialog:popup([lstbx])
-
Runs the dialog.
This is like run except that the dialog is shown near the cursor, which is where users typically expect “popup” boxes to appear.
Popup dialogs often have a listbox, functioning as a menu. If you pass the optional lstbx argument, this listbox will be resized to show as many of its items as possible while taking care not to make the dialog exceed the screen’s size.
See examples at ui.Editbox.current_word and speller.lua.
- dialog:redraw()
-
Redraws the dialog.
You won’t normally need to call this method.
This method is similar in principle to widget:redraw but works on the whole dialog: the dialog draws itself (frame and background), then its children. It also asks the widget in focus to reposition the cursor.
- dialog:redraw_cursor()
-
Positions the cursor at the focused element.
You won’t normally need to call this method.
This method asks the widget in focus to reposition the cursor.
As to why this method has “redraw” in its name, see the section Drawing and Refreshing the Screen.
- dialog:refresh([do_redraw])
-
Updates the cursor on the physical screen.
This is just a shorthand for calling :redraw_cursor() and then tty.refresh. It is implemented thus:
function ui.Dialog.meta:refresh(do_redraw) if do_redraw then self:redraw() else self:redraw_cursor() end tty.refresh() end
- dialog:run()
-
Runs the dialog.
The dialog is displayed. An “event loop” starts which lets the user interact with the dialog till it’s dismissed.
Returns:
As a convenience, this method returns dialog.result. A nil is returned (and stored in dialog.result) if the user cancels the dialog (e.g., by pressing ESC).
See examples at button.result (and elsewhere on this page).
- dialog:set_dimensions([x], [y], [cols], [rows])
-
Explicitly sets the dialog’s dimensions.
Call this method if you wish to explicitly position or size the dialog. Usually you shouldn’t be interested in this method as the dialog by default will be decently positioned (centered on the screen).
You may omit
x
and/ory
: if you do, they will be calculated such that the dialog will be centered on the screen.You may omit cols and/or rows: if you do, they will be calculated based on the dialog’s contents (therefore, in this case, make sure to call this method after you've already added all the widgets to the dialog.)
local dlg = ui.Dialog() dlg:add(ui.Label('Hi there!')) -- push the dialog to the extreme right of the screen: dlg:set_dimensions(tty.get_cols() - dlg:preferred_cols(), nil) dlg:run()
To “maximize” a dialog, do:
dlg:set_dimensions(nil, nil, tty.get_cols(), tty.get_rows() - 2)
- <<draw>>
-
Triggered after a dialog had been painted.
You may use this event to add decoration to a dialog, like a drop shadow.
ui.Dialog.bind('<<draw>>', function(dlg) local c = dlg:get_canvas() c:set_style(tty.style('yellow, red')) c:goto_xy(0, 0) c:draw_string(T"hello!") end)
- <<layout>>
-
Triggered after a dialog layouts itself.
Triggered after the placement of child widgets has been set. You may use this event to inject your own widgets into a dialog.
See Global events in the user guide.
- <<open>>
-
Triggered when a dialog is opened.
You may use this event to notify the user with sound on alert boxes, text-to-speech the title, etc.
-- Read aloud dialogs' titles. ui.Dialog.bind('<<open>>', function(dlg) -- Note: we run espeak in the background (&) or else we'll be -- blocked till it finishes voicing the text. os.execute(('espeak %q &'):format(dlg.text)) end)
You may also use it to set initial values for widgets of builtin dialogs:
-- Make 'xsel' the default command of 'Paste output of...'. -- -- (This technique isn't very robust because dialog titles may -- change between MC releases.) ui.Dialog.bind('<<open>>', function(dlg) if dlg.text == T'Paste output of external command' then dlg:find('Input').text = 'xsel' end end)
See Global events in the user guide.
- <<submit>>
-
Triggered when a dialog is about to be closed “successfully”.
When a dialog is closed by hitting Enter, or by clicking a positive button (that is, anything except the “Cancel” button), this event is triggered.
This event is not triggered for dialogs that are canceled (e.g., by pressing ESC).
You may use this event to read data from the widgets. E.g., the find_file_title.lua snippet uses this event to read the search parameters from the “Find File” dialog and put them in the title of the progress dialog that follows.
See Global events in the user guide.
- <<close>>
-
Triggered when a dialog is about to be closed.
See Global events in the user guide.
Static dialog properties
- Dialog.screens r
-
A list of all the modaless dialogs.
-- Show all the edited files. local append = table.insert keymap.bind('C-y', function() local edited_files = {} for _, dlg in ipairs(ui.Dialog.screens) do -- A single editor dialog may contain several editboxes (aka "windows"). for edt in dlg:gmatch('Editbox') do append(edited_files, edt.filename) end end devel.view(edited_files) end)
- Dialog.top r
-
The topmost dialog.
This is the “current” dialog.
-- Closes the current dialog. keymap.bind('C-y', function() ui.Dialog.top:close() end)
Containers
Containers can be nested. This way you can set up complex layouts.
- container:add(widget[, ...])
-
Adds widgets to a container.
As a convenience, this method returns the container itself; this lets you save some typing.
dlg:add(w1) dlg:add(w2, w3) dlg:run()
is the same as:
dlg:add(w1, w2, w3):run() dlg:add(w1):add(w2):add(w3):run()
- container:preferred_cols()
-
Calculates the preferred width.
Calculates the width of the container based on its contents.
See example at dialog:set_dimensions.
- container:preferred_rows()
-
Calculates the preferred height.
Calculates the height of the container based on its contents.
HBox container
VBox container
Stock buttons
- Buttons()
-
Creates a container for buttons.
When the default OK/Cancel buttons that DefaultButtons() creates don’t satisfy you, you'll want to create the buttons yourself. You can add them to the dialog in however manner you wish, but for uniformity and conformity it’s recommended that you use this container. It displays the buttons centered horizontally with a line separating them from the preceding widgets. Example:
local dlg = ui.Dialog() dlg:add(ui.Label(T"The target file exists. What to do?")) dlg:add(ui.Buttons():add( ui.Button{T"&Skip", result="skip"}, ui.Button{T"&Overwrite", result="overwrite"}, ui.Button{T"H&elp", on_click=function() alert "hi" end}, ui.CancelButton() )) dlg:run()
- CancelButton([props])
-
Creates a “Cancel” button.
Typically you'd use this function only if DefaultButtons doesn’t suit your needs. See example at Buttons.
- DefaultButtons()
-
Creates an “Ok” and “Cancel” buttons.
You're expected to add this to any normal dialog you create. Example:
local dlg = ui.Dialog() dlg:add(ui.Label(T"Give me the head of Alfredo Garcia!")) dlg:add(ui.DefaultButtons()) dlg:run()
- OkButton([props])
-
Creates an “OK” button.
Typically you'd use this function only if DefaultButtons doesn’t suit your needs.
You can use the optional
props
argument to change the default label:ui.Buttons():add( ui.OkButton(T"G&o!"), ui.CancelButton() )
Static widget functions
ui.Input()
.
But ui.WidgetClass is also a namespace that groups further functions
and properties. E.g., ui.Input.bind()
, ui.Panel.register_field()
,
ui.Editbox.options, etc.
This section describes functions common to all widget classes. These function aren’t methods: they're what called in OOP parlance “static”.
- bind(keyseq_or_event, function)
-
Binds functions to keys and events.
Use this to execute a function when a certain key sequence is pressed in a certain class of widgets:
ui.Panel.bind('C-y', function(pnl) alert("You're standing on " .. pnl.current) end)
Or when a certain event occurs:
ui.Panel.bind('<<load>>', function(pnl) alert("You're browsing " .. pnl.dir) end)
In both cases the bound function is invoked with the widget as its first (and only) argument.
- subclass(new_class_name)
-
Creates a new widget class.
For example, let’s suppose we want to create a widget that shows the current time. We can do it thus:
local clock = ui.Custom() function clock:on_draw() self.canvas:draw_string(os.date("%H:%M:%S")) end ui.Dialog():add(clock):run()
However, this widget isn’t quite reusable. We can instead create is as a class,
local ClockMeta = ui.Custom.subclass("Clock") function ClockMeta:on_draw() self.canvas:draw_string(os.date("%H:%M:%S")) end
…and then re-use this class wherever we want:
ui.Dialog():add(ui.Clock(), ui.Clock(), ui.Clock()):run()
This function, subclass, returns the metatable of the new class. It also creates the namespace ui.NewClassName.