User dialogs¶

Phonometrica allows you to create custom dialogs that you can integrate into your scripts. These can be used to obtain some input from users of your scripts. User dialogs are created with the create_dialog() function, which takes as input a JSON object describing the user dialog, and returns a JSON object containing the user’s choices, if any.

Creating a dialog¶

We will start with a minimalist dialog that only displays some text. A dialog must have at least two attributes: a title, which corresponds to the title of the window, and an array of items, which may be empty. Each item is a JSON object: its type attribute indicates which type of user interface component the object represents, and the other attributes depend on the type of component (see below). Here, we are simply displaying a label with a text attribute.

{
"title": "My first dialog",
"items": [
{ "type": "label", "text": "My first label" }
]
}


There are several optional attributes that can be used to customize our dialog. width and height allow you to set the minimum width and height of the dialog, whereas yes_no can be set to true to use Yes/No buttons instead of the default OK/Cancel.

{
"title": "My first dialog",
"width": 300,
"height": 200,
"yes_no": true,
"items": [
{ "type": "label", "text": "My first label" }
]
}


Once we have created a JSON object describing our dialog’s user interface, we can pass it to the create_dialog() function.

var ui = {
"title": "My first dialog",
"width": 300,
"height": 100,
"yes_no": true,
"items": [
{ "type": "label", "text": "My first label" }
]
}

create_dialog(ui)


The resulting dialog will look something like that:

If the user presses OK (or Yes if yes_no is true), create_dialog() will return a JSON object containing the user’s input (which will be empty in this case); if the user presses Cancel (or No), it will return the value null.

User interface components¶

Each user interface component has its own attributes, but there are a few important commonalities. Components which accept some user input have a name attribute: this will be used as a key in the JSON object returned by create_dialog() to retrieve the corresponding value. In addition, components that accept a default value have an optional default attribute that allows you to explicitly set a default value.

Label¶

A label must have its type attribute set to label. A label has one additional required attribute, text, which corresponds to the text that is displayed inside the label.

{ "type": "label": "text": "This is a label" }


Button¶

A button must have its type attribute set to button. Whenever the button is pressed, the associated action is triggered. You must specify a label attribute, which is the text that will appear on the button, and an action attribute which can be any valid string of Phonometrica code. By default, unless the button is stored in a container, it will fill the whole width of the dialog. You can use the optional position attribute to make it smaller and control its position. Valid values are left, center and right. If the button is inside of a container, the position attribute is ignored.

{ "type": "button", "label": "test", "action": "print('button clicked!')", "position": "left" }


File selector¶

A file selector must have its type attribute set to file_selector. A file selector combines a Choose... button, which lets the user select a file (which may or may not exist) and a field which displays the file’s path. You must specify a name attribute, which is the key that will be used in the result object to retrieve the file selector’s value, and a title which will be displayed in the file selection dialog that opens when the user presses the Choose... button. You can specify a default attribute (as a string): If no default value is provided, the file selector’s field will be empty. Additionally, you can specify a filter attribute that will be used to restrict the type of files that can be selected. For example, The filter "CSV (*.txt *.csv *.tsv)" would accept all CSV files (comma-separated value) that have .txt, .csv or .tsv extension.

{ "type": "file_selector", "name": "path", "title": "Select text file...", "default": "output.txt", "filter": "CSV (*.txt *.csv *.tsv)" }


Field¶

A field must have its type attribute set to field. A field can be used to input a line of text. You must specify a name attribute, which is the key that will be used in the result object to retrieve the field’s value. Additionally, you may specify a default value.

{ "type": "field", "name": "field1", "default": "some default text" }


Check box¶

A check box must have its type attribute set to check_box. It lets you retrieve a Boolean value from the user: when the check box is checked, the value true is returned, whereas false is returned if the box is unchecked. You must specify a name attribute, which is the key that will be used in the result object to retrieve the check box’s value, and a text attribute which is the text that will be displayed next to the check box. In addition, you may specify a default value, which must be Boolean.

{ "type": "check_box", "name": "overwrite", "text": "Overwrite file if it exists", "default": true }


Check list¶

A check list must have its type attribute set to check_list. It is used to display a list of values that can be checked. You must specify a name attribute, which is the key that will be used in the result object to retrieve the check list’s values. You must also provide a list of values as a JSON array of strings: all checked values will be stored in the return value (again, as a JSON array of strings). In addition to the list of values, you may specify a list of labels: in this case, labels will be displayed instead of values and values will be shown as tool tips when the user hovers their mouse over the label. This feature can be used to display shorter values than those we actually want to store.

{ "type": "check_list", "name": "annotations",
"labels": [ "nzdajm1vg.TextGrid", "nzdajm1cg.TextGrid", "nzdajm1fg.TextGrid","nzdajm1tg.TextGrid" ],
"values": [
"/home/julien/PAC/JM/nzdajm1vg.TextGrid",
"/home/julien/PAC/JM/nzdajm1cg.TextGrid",
"/home/julien/PAC/JM/nzdajm1fg.TextGrid",
"/home/julien/PAC/JM/nzdajm1tg.TextGrid",
]
}


A group of radio buttons must have its type attribute set to radio_buttons. It is used to display a number of exclusive options, where only one can be selected at a time. You must specify a name attribute, which is the key that will be used in the result object to retrieve the index of the selected value. You must also provide a list of values as a JSON array of strings. The index of the default value can be specified with the default attribute, which must be an integer. Finally, you can add a title attribute to provide a label for the button group.

{ "type": "radio_buttons", "name": "tough_choice", "values": [ "blue pill", "red pill" ], "title": "Tough choice", "default": 1 }


Combo box¶

A combo box is conceptually similar to a group of radio buttons in that it allows you to select one among several options. The choices are displayed as a list, instead of a group of buttons. A combo box must have its type attribute set to combo_box. You must specify a name attribute, which is the key that will be used in the result object to retrieve the index of the selected value. You must also provide a list of values as a JSON array of strings. The index of the default value can be specified with the default attribute, which must be an integer.

{ "type": "combo_box", "values": [ "blue pill", "red pill" ], "default": 2 }


Container¶

A container must have its type attribute set to containter. A container is not a visible component, but is used to pack components together horizontally (by default) or vertically. A container must have an items attribute, which is an array of components. You can use the orientation attribute to control the container’s packing policy. It accepts two values: horizontal or vertical.

{ "type": "container", "items": [
{ "type": "button", "label": "button 1", "action": "info('A useless button!')", "position": "left" },
{ "type": "button", "label": "button 2", "action": "info('Another useless button!')", "position": "left" }
]}


Stretch¶

A stretch must have its type attribute set to containter. It is a special component that can be put inside a container to fill unused space. It has no attribute beside its type.

{ "type": "container", "items": [
{ "type": "button", "label": "button 1", "action": "info('A useless button!')", "position": "left" },
{ "type": "button", "label": "button 2", "action": "info('Another useless button!')", "position": "left" },
{ "type": "stretch" }
]}


Spacing¶

A spacing must have its type attribute set to spacing. It is a special component that can be put inside a container to separate components. You must specify its size attribute, which is an integer that represents the size of the spacing (in pixels).

{ "type": "container", "items": [
{ "type": "button", "label": "button 1", "action": "info('A useless button!')", "position": "left" },
{ "type": "spacing", "size": 20 },
{ "type": "button", "label": "button 2", "action": "info('Another useless button!')", "position": "left" },
{ "type": "stretch" }
]}


Putting it all together¶

As an illustration, we will show how “Transphon”, the module that allows annotations to be exported to plain text, is implemented. We could store the user interface in a separate JSON file and load it from a Phonometrica script, but since the user interface is relatively simple, we will create it directly in the script. This has two advantages: it allows us to intersperse comments in the user interface, which JSON doesn’t allow, and it makes it unnecessary to surround keys with double quotes, since in Phonometrica, { "key": "value" } and { key: "value" } are equivalent, as long as key doesn’t contain any space.

Here is the part of the script that corresponds to the creation of the user interface:

# Setup user interface as a JSON object.
var ui = {
title: "Transphon",
width: 300,
items: [
{ type: "label", text: "Choose output file:" },
{ type: "file_selector", name: "path", title: "Select text file..." },
{ type: "label", text: "Select layers separated by a comma, or leave empty to process all layers:" },
{ type: "field", name: "layers", default: "1"},
{ type: "label", text: "Choose annotations:"},
# Annotations will be inserted here
{ type: "label", text: "Choose event separator:"},
{ type: "radio_buttons", name: "separator", values: ["space", "new line", "none"] }
]
}

# Insert check list for annotations
var labels = []
var values = []

foreach var annot in get_annotations() do
var path = annot.path
# This is the real value we are interested in
values.append(path)
# This is the label that will be displayed
labels.append(system.get_base_name(path))
end

# Create item and insert it at position 6 in the list of items
var item = { "type": "check_list", "name": "annotations", "labels": labels, "values": values }
ui.items.insert(6, item)

# result will contain a JSON object if the user pressed "OK", or null otherwise.
var result = create_dialog(ui)

if result then
# Process the result. Here we will simply print it.
print(json.stringify(result))
end
`

And here is the dialog that appears when the script is run: