Command Line User Interface

Warning

This library is very experimental. Handle with care and expect interface changes with the next update.

The Command Line User Interface (CLUI) is an unicode based “graphical” user interface library. It provides some primitive UI elements documented in this section of the documentation.

digraph hierarchy { size="5,8" node[shape=record,style=filled,fillcolor=gray95] edge[dir=back, arrowtail=empty] Text [label = "{Text||}"] Frame [label = "{Frame||+ Draw()\l}"] Pane [label = "{Pane||+ Draw()\l}"] ButtonView [label = "{ButtonView||+ Draw()\l}"] ListView [label = "{ListView||+ Draw()\l+ HandleKey()\l# onDraw()\l# onAction()\l}"] Dialog [label = "{Dialog||/+ Draw()\l/+ HandleKey()\l}"] TabGroup [label = "{TabGroup||+ HandleKey()\l}"] TextInput [label = "{TextInput||+ Draw()\l+ HandleKey\l}"] NoTextInput [label = "{NoTextInput||\l/+ HandleKey\l}"] BoolInput [label = "{BoolInput||+ Draw()\l+ HandleKey\l}"] Text -> Frame Text -> TextInput Text -> BoolInput Text -> ButtonView TextInput -> NoTextInput Frame -> Pane Pane -> ListView Pane -> Group ListView -> Dialog ButtonView -> Dialog TabGroup }

Text

class lib.clui.text.Text[source]

This class handles the shell output. It sets color and cursor settings and handles the screen.

The default color is gray text on black background (0;37;40).

Whenever a parameter is a color, the ANSI escape format for colors as strings were expected. These strings were sent to the terminal as shown in the following code example:

def SetColor(color):
    print("\033[%sm" % color, end="")

SetColor("1;32")    # bright green text
SetColor("44")      # blue background
ClearScreen()[source]

This method clears the screen using the ANSI escape squence [2J.

Returns

Nothing

FlushScreen()[source]

This method forces the shell to flush the stdout buffer. After calling this method, everything written into the output buffer will be visible on the screen.

Returns

Nothing

GetKey()[source]

This method returns the name of the pressed key. The key input gets not printed to the screen.

Until now, the following keys will be recognized:

  • The usual prinable keys

  • escape (Must be hit twice)

  • enter, backspace, delete

  • backtab (shift+tab)

  • up, down, left, right, end, home

  • page down, page up

  • Ctrl-D (Quit), Ctrl-W (Delete Word), Ctrl-U (Delete Line)

In case you want to get other keys, it is easy to add them by editing the code of this method. I just implemented the one I need for the ListView UI element.

Returns

The pressed key. If it is a special key, then the name of the key gets returnd as string. None will be returnd for unknown keys.

GetRawKey()[source]

This method reads a raw key input from the input buffer without printing it on the screen.

Usually you want to use the more abstract method GetKey()

Returns

The key from the input buffer

GetScreenSize()[source]

This method returns the screen size as columns and rows. For detecting the screen size, the tool stty gets called:

stty size
Returns

columns, rows (as integer)

PrintText(text)[source]

This method simply prints text to the screen. It does nothing more than:

print(text, end="")

So instead of the “normal” print, there will be no line break at the end.

Returns

Nothing

SetBGColor(color=None)[source]

This method sets the background color. If the color argument is None, then the default background color gets used. If the color argument is a color, then a new default backgound color gets defined and set. Next time this method gets called without an argument, this new color will be used as default.

Parameters

color (str) – New background color

Returns

Nothing

Raises

TypeError – When argument is set and not a string

SetColor(fgcolor=None, bgcolor=None)[source]

This method calls SetFGColor() and SetBGColor().

SetCursor(x, y)[source]

This method sets the position of the curser. The top left corner is at position x=0; y=0. y represents the rows and x the columns.

For setting the cursor the ANSI escape sequence [y;xH gets used.

Parameters
  • x (int) – Column position of the cursor

  • y (int) – Row position of the cursor

Returns

Nothing

Raises
  • TypeError – When x or y are not of type integer

  • ValueError – When x or y are negative

SetFGColor(color=None)[source]

This method sets the foreground (text) color. If the color argument is None, then the default foreground color gets used. If the color argument is a color, then a new default foreground color gets defined and set. Next time this method gets called without an argument, this new color will be used as default.

Parameters

color (str) – New foreground (text) color

Returns

Nothing

Raises

TypeError – When argument is set and not a string

ShowCursor(show=True)[source]

This method makes the cursor visible or invisible.

Therefore the ANSI escape seqneces [?25h and [?25l will be used.

Parameters

show (bool) – True: Make cursor visible, False: Hide cursor

Returns

Nothing

Frame

class lib.clui.frame.Frame(linestyle=0)[source]

This class provides an unicode based frame that gets printed around a certain area.

There are the following styles available:

LINESTYLE_NORMAL:

┌─┐
│ │
└─┘

LINESTYLE_BOLD:

┏━┓
┃ ┃
┗━┛

LINESTYLE_ASCII:

+-+
| |
+-+

LINESTYLE_DOUBLE:

╔═╗
║ ║
╚═╝

LINESTYLE_ROUND:

╭─╮
│ │
╰─╯
Parameters

linestyle – The style used to draw the lines. The default linestyle is LINESTYLE_NORMAL.

Draw(x, y, w, h)[source]

This method draws the frame around a certain area.

The upper left corner of the frame will be at (x;y), the bottom right corner at (x+w;y+h)

Only the frame characters gets drawn. Every character inside the frame stays. So this method can also be called to draw a frame around already printed content.

Parameters
  • x (int) – Start position of the frame (top left corner)

  • y (int) – Start position of the frame (top left corner)

  • w (int) – width of the frame

  • h (int) – height of the frame

Returns

Nothing

Raises
  • TypeError – When one of the arguments is not of type integer.

  • ValueError – When x or y is negative.

SetLineStyle(linestyle)[source]

This method can be used to set a different line style. You may want to call Draw() to redraw the frame with the new style.

Parameters

linestyle – The new style

Returns

Nothing

Raises

ValueError – When the argument does not address a valid style

Pane

class lib.clui.pane.Pane(title=None, x=0, y=0, w=0, h=0)[source]

This class can be used to define a whole, filled are in the shell. The area consist of spaces in the defined color. Around the area, a Frame gets printed.

If a title is set, then this title gets printed in the top left corner of the frame. The title gets surrounded by a space character.

The coordinates define the placement of the frame, the usable area starts with an offset of one.

Parameters
  • title (str) – Optional title for the area.

  • x (int) – Start position of the frame (top left corner) of this area

  • y (int) – Start position of the frame (top left corner) of this area

  • w (int) – width of the frame

  • h (int) – height of the frame

Draw()[source]

This method prints a whole area in the shell.

Returns

Nothing

ButtonView

class lib.clui.buttonview.ButtonView(align='left')[source]

This class provides a button view. It is a line where a key event is described. For example: [a:Add element][r:Remove element]

Example

cli  = Text()
maxw, maxh = cli.GetScreenSize()

# Place a button view in the middle of the line before the last line of the screen
buttons = ButtonView(0, maxh-2, maxw, align="middle")
buttons.AddButton("a", "Add new tag")
buttons.AddButton("e", "Edit tag")
buttons.AddButton("r", "Remove tag")
Parameters

align (str) – If the buttons are "left", "right" or "center" shall be aligned over the span of the View width.

AddButton(key, name)[source]

This method adds a new button-description to the ButtonView.

Parameters
  • key (str) – A single Unicode character key

  • name (str) – What pressing that key will do

Returns

Nothing

Raises
  • TypeError – When key or name are not a string

  • ValueError – When key is larger than one character

Draw(x, y, w)[source]

This method prints the ButtonView. It always uses the colors red for the [:]-characters, light blue for the key and blue for the description.

Parameters
  • x (int) – Position where to start printing the ButtonView

  • y (int) – Position where to start printing the ButtonView

  • w (int) – Width of the ButtonView

RemoveButton(name)[source]

This method removes a button from the button view. The button gets identified by the name.

Parameters

name (str) – Name of the button to remove

Returns

Nothing

ListView

class lib.clui.listview.ListView(title=None, x=0, y=0, w=0, h=0)[source]

This class provides a ListView that shows a list the user can scroll through using the arrow keys.

The data shown in the list must be a string. For derived classes, they can be considered abstract and contain any kind of type as long as you adopt the onDrawElement() method.

An example on how to use this class with complex data structures:

class MusicView(ListView):
    def __init__(self, title):
        ListView.__init__(self, title)

    def SetData(self, artists, albums, songs):
        artistdata = [("artist", name) for name in artists]
        albumdata  = [("album",  name) for name in albums ]
        songdata   = [("song",   name) for name in songs  ]

        data = []
        data.extend(artistdata)
        data.extend(albumdata )
        data.extend(songdata  )
        ListView.SetData(self, data)

    def onDrawElement(self, element, number, maxwidth):
        tag  = element[0]
        path = element[1]
        maxnamelen = maxwidth - 6   # - "[xxx] "

        string = "\033[1;30m["
        if tag == "artist":
            string += "art"
        elif tag == "album":
            string += "alb"
        elif tag == "song":
            string += "sng"
        else:
            string += "INV"
        string += "] \033[1;34m"
        string += path[:(maxnamelen)].ljust(maxpathlen)
        return string

artists = ["Artist A", "Brtist B"]
albums  = ["An album", "And Another Album"]
songs   = ["This is a song name"]
sv  = MusicView("Music", 2, 2, 10, 5)
sv.SetData(artists, albums, songs)
sv.Draw()

The elements in the list are stored in a variable elements. This list can be maintained using SetData().

A second variable linemarker points (in form of an index) to the selected element in this list. The element the linemarker points to can be accessed via GetSelectedData() and SetSelectedData()

When the list of element exceeds the height of the list, a scroll bar will be printed in place of the right frame edge.

Parameters
  • title (str) – Title of the list view

  • x (int) – Position of the list view

  • y (int) – Position of the list view

  • w (int) – Width and height

  • h (int) – Width and height

Draw()[source]

This method draws the list of elements. If the list exceeds the height of the list view, a scroll bar gets added and the user can scroll though the data.

The background of the list view will be black. The surrounding frame is red. The lines in the list will be printed in blue. The selected line gets a cyan background color.

Returns

Nothing

GetData()[source]

Returns the list of elements.

Returns

The list of elements of the list view

GetSelectedData()[source]

This method returns the selected element from the list.

Returns

The selected element

HandleKey(key)[source]

This method must be called whenever a key was pressed and this list view is “active”. It expects the key name returned by lib.clui.text.Text.GetKey().

If the key is "up" the element above the current selected element gets selected. If the key is "down" the one below will be selected. Any other key gets passed to the onAction() method. Read the documentation of the onAction() method to see how the return value of that method gets processed.

After handling an key, the Draw() method gets called to refresh the view.

Parameters

key (str) – Key name that shall be handled

Returns

Nothing

SetData(elements)[source]

This method can be used to initialize the list of data that will be shown in the list view If the onDrawElement() method is not overloaded, the list must contain strings.

After setting the new elements list, the linemarker gets set to the begin of the list.

Parameters

elements (list) – A list of data

Returns

Nothing

SetSelectedData(element)[source]

Replaces the selected element with a new one. This method can be used to interact with the list without triggering the onAction() callback.

Parameters

element – An element that replaces the selected element

Returns

Nothing

onAction(element, key)[source]

This method gets called when a key gets passed to the HandleKey() method. It is supposed to process the selected line in the list of elements. In this class, nothing will be done with the data.

This function must return the processed element back to the list. It is also possible to return a new element to replace the old one. When a line shall not be updated, return None. A case where this may be useful will be presented in the following example:

def onAction(self, element, key):
    # this method manipulates a list of numbers

    if key == "r":      # remove element
        self.elements.remove(element)
        return None

    elif key == "a":    # append number to list
        self.elements.append("0")

    elif key == "0":    # reset number
        return "0"

    elif key == "+":    # increment number
        element = str(int(element) + 1)

    elif key == "-":    # decrement number
        element = str(int(element) - 1)

    return element

When the last element gets removed, the linemarker variable gets moved to the previous line. Otherwise it stays at its position that will now be the element next after the removed one.

Parameters
  • element – The selected element in the list

  • key (str) – The key that was pressed while the element was selected

Returns

The modified element that will be put back in the list of elements.

onDrawElement(element, number, maxwidth)[source]

This method gets called when an element gets printed into the list view. To customize how elements will appear in the list, overload this method.

The returned string can contain ANSI Escape Sequences for coloring. The number of printable character should not exceed the maximum width maxwidth. Furthermore the string should be exact maxwidth wide.

An example how to implement a custom onDrawElement method can be seen in the following code example:

def onDrawElement(self, element, number, maxwidth):
    # Print the element number in front of each entry.
    # Alternate the text color with each line. Odd lines in white, even in blue.

    num = "%2d: "%(number)      # start list entry with the index of the element
    val = element[:maxwidth-4]  # Limit string to max width. 4 Characters are consumed for the num variable

    # Alternating colors with each line
    # Colors do not count to the width of the string because they are not printable
    if num % 2:
        color = "\033[1;37m"    # Set color to white for odd lines
    else:
        color = "\033[1;34m"    # Set color to blue for even lines

    return color + num + val
Parameters
  • element – The element that shall be printed.

  • number (int) – The index of the element in the list of all elements

  • maxwidth (int) – The maximum number of characters that can be printed in one line of the list view

Returns

The string that will be printed in one line of the list view

Dialog

class lib.clui.dialog.Dialog(title=None, x=0, y=0, w=0, h=0)[source]

This class provides a dialog box that is organized like a list view.

It provides one input element per line over several lines. The lines can be selected using the up (↑) and down (↓) key. Pressing the space bar on an BoolInput toggles its value. On enter (↵), the changes shoud be committed, on escape (␛) they should be rejected.

Beside the list of input elements a ButtonView gets printed at the bottom of the dialog view telling the user about the key mentioned above.

The dialog works on a list with each element beeing a tuple of a label, the control itself and a hint for the user.

Example on using a Dialog:

dialog     = Dialog("Add Unicode Icon", 1, 1, 40, 10)
nameinput  = TextInput()
iconinput  = TextInput()
varselector= BoolInput()
dialog.AddInput("Name:",  self.nameinput,   "Visibly for user")
dialog.AddInput("Icon:",  self.iconinput,   "Unicode char")
dialog.AddInput("U+FE0E:",self.varselector, "Do not replace with emoji")
Parameters
  • title (str) – Title of the list view

  • x (int) – Position of the list view

  • y (int) – Position of the list view

  • w (int) – Width and height

  • h (int) – Width and height

AddInput(label, control, hint=None)[source]

This method adds a new control to the list of controls. The input control element must provide a Draw and a HandleKey method. Position and width of the control element will be calculated when they get printed. The label gets printed in front of the control element, the hint at the end of the list entry.

The following inputs are suitable for the dialog:
Parameters
  • label (str) – Label of the input element

  • control – The input element itself

  • hint (str) – An additional hint to the input element. None for no hint.

Returns

Nothing

Draw()[source]

This method draws the list of control elements.

GetSelectedData()[source]
Raises

NotImplementedErroralway

HandleKey(key)[source]

This method must be called whenever a key was pressed and this dialog is “active”. It expects the key name returned by lib.clui.text.Text.GetKey().

If the key is "up" the element above the current selected element gets selected. If the key is "down" the one below will be selected. This will be done by passing the up and down key to the HandleKey method of the base class.

Any other key gets passed to the onAction() method of this class.

After handling an key, the Draw() method gets called to refresh the view.

Parameters

key (str) – Key name that shall be handled

Returns

Nothing

SetData(elements)[source]
Raises

NotImplementedErroralway

SetSelectedData(element)[source]
Raises

NotImplementedErroralway

onAction(element, key)[source]

This method gets called when a key gets passed to the HandleKey() method. It is supposed to get passed to the selected control element.

Parameters
  • element – The selected element in the list

  • key (str) – The key that was pressed while the element was selected

Returns

Nothing

onDrawElement(element, number, maxwidth)[source]

This method gets called when an element gets printed into the dialog view. Keep in mind that the dialog element is not able to scroll.

The element is a tuple containing the label, the control element and the hint. If the number of characters is too high, the hints will not be printed. The number of printable characters gets calculated by the with of the label and hint of each row. It should be at least 10 characters be left for the control, and 2 for spaces around the control.

Parameters
  • element – The element that shall be printed.

  • number (int) – The index of the element in the list of all elements and at the same time the y coordinate

  • maxwidth (int) – Will be ignored

Returns

Nothing

Raises

ValueError – In case the with of the dialog is to small to print at least the label and the input control

TextInput

class lib.clui.textinput.TextInput(x=0, y=0, w=0)[source]

This class provides a simple one-line text input element.

The cursor mentioned in this class documentation is not related to the cursor of the terminal! Each TextInput maintain its own cursor.

Parameters
  • x (int) – Position of the list view

  • y (int) – Position of the list view

  • w (int) – Width

Draw()[source]

This method draws the input control and the cursor. The input elements color are white text on blue background. The element the cursor points to has a cyan background.

Returns

Nothing

GetData()[source]

Returns the string from the input element

Returns

The input as string

HandleKey(key)[source]

This method handled the users input. The keys are expected as the method lib.clui.text.Text.GetKey() returns. With the "right" and "left" key, the user can navigate through the text. With "backspace" the character left to the cursor gets removed. With "delete" the character right below the cursor. Each printable character gets inserted left to the cursor.

Parameters

key (str) – Key name that shall be handled

Returns

Nothing

SetData(string)[source]

This method sets the data of the input element. The cursor points to the end of the input data.

Note

The string gets normalized (NFC). All further manipulations of the string won’t be normalized.

Parameters

string (str) – String that will be used as the input

Returns

Nothing

Raises

TypeError – When string is not of type string

class lib.clui.textinput.NoTextInput(x=0, y=0, w=0)[source]

This is a read-only variation of the TextInput element.

Parameters
  • x (int) – Position of the list view

  • y (int) – Position of the list view

  • w (int) – Width

HandleKey(key=None)[source]

This method does nothing. This is a read-only control element.

Parameters

keywill be ignored

Returns

Nothing

BoolInput

class lib.clui.boolinput.BoolInput(x=0, y=0, data=None)[source]

This class provides a simple boolean input element. None is also allowed!

Default representation is:

  • [✔] True

  • [✘] False

  • [❓] None

Parameters
  • x (int) – Position of the list view

  • y (int) – Position of the list view

  • data (bool) – True, False or None representing the input of the BoolInput element

Draw()[source]

This method draws the input control.

GetData()[source]

Returns the string from the input element

Returns

The input as bool or None

HandleKey(key)[source]

Space key toggles the value of the input element. When the previos value was None, True will be the next. Then it alters between True and False

Parameters

key (str) – Key name that shall be handled

Returns

Nothing

SetData(data)[source]

This method sets the data of the input element

Parameters

data (bool) – True, False or None representing the input of the BoolInput element

Returns

Nothing

SetRepresentation(true=None, false=None, none=None)[source]

Define the representation of True, False and None in form of an Unicode character.

Parameters
  • true (str) – Unicode to represent one of the three states

  • false (str) – Unicode to represent one of the three states

  • none (str) – Unicode to represent one of the three states

Returns

Nothing

Raises
  • TypeError – when an argument is not None and not of type string

  • ValueError – when the string is larger than one character

TabGroup

class lib.clui.tabgroup.TabGroup[source]

This class can be used to group multiple UI elements. They can be selected by pressing tab (" "). All other keys get passed to the selected UI element. The selected element gets highlighted by setting its frame line-style to Frame.LINESTYLE_BOLD.

AddPane(pane)[source]

Add a pane to the list of UI elements. They must provide the HandleKey, Draw and SetLineStyle method!

Parameters

pane – A UI element that shall be added.

Returns

Nothing

HandleKey(key)[source]

Pass the key to the current selected pane. If the key is "       ", then the next pane in the list gets selected.

Parameters

key (str) – A key name as returned by lib.clui.text.GetKey().

Returns

Nothing