Many command-line programs are more than just one liners.
They often need to display data and allow users to interface by both viewing data and making selections.
In this post I look at the Gambas component gb.ncurses and attempt to make an interactive display.
There are almost 100 Gambas components available covering everything from scanner management to web development, from text editors to databases. Using components allows the Gambas developer to draw on the power of other technologies.
Unfortunately, component documentation sometimes lags behind the availability of the component itself. And since the creator of Gambas (Benoît Minisini) is not a native English speaker, I often struggle to understand what documentation there is. Although I should point out that the ncurses component is actually the work of Tobias Boege, and I often struggle with software documentation anyway, simply because the author is normally light-years ahead of me!
Curses! ...what is ncurses
Ncurses is a programming library which allows you to create programs which look like alsamixer. The Gambas component gb.ncurses wraps ncurses functionality.
To use it, create a new Gambas cli project and add gb.ncurses from the list at menu Project > Properties > Components.
Also go to Project > Properties > Options and set "Use a terminal emulator" to "Yes".
Here is a very basic "Hello World!" example:-
Public Sub Main()
With Window
.Border = Border.ACS 'continuous line border
.Caption = "a window on the world"
.Print("\nHello world!") 'line-feed + text
End With
End
We can add some colour, underline the message and move it within the window;
Public Sub Main()
With Window
.Border = Border.ACS
.Background = Color.Green
.Caption = "a window on the world"
.Print("Hello world!", 5, 5, Attr.Underline, Pair[Color.Red, Color.Green])
End With
End
In the print statement, the two numbers are x & y positions, and the Pair[ ] controls the foreground and background colours.
a terminal is a terminal, is a terminal
Light-weight operating systems like Lubuntu often include their own terminal programs. As Lubuntu uses LXDE, the default terminal is LXTerminal, and this does not seem to have full functionality when compared to alternatives such as Xterm.
This can be illustrated by modifying the Hello World! example to make the text blink.
Public Sub Main()
With Window
.Border = Border.ACS
.Background = Color.Green
.Caption = "a window on the world"
.Print("Hello world!", 5, 5, Attr.Blink, Pair[Color.Red, Color.Green])
End With
End
By creating an executable of this code, it is then possible to run it in an LXTerminal (the text does not blink) and an Xterm (the text does blink). Differences like this may be important when you are writing code (especially if you distribute to others).
using events
As in an earlier Gambas cli post, we can use an event timer. In this case a timer is used to update the window containing a clock display.
Public hTimerScreen As Timer 'declare screen update timer
Public Sub Main()
'this timer re-writes the screen at 1s intervals
hTimerScreen = New Timer As "tmrScreenUpdate"
hTimerScreen.Delay = 1000
hTimerScreen.start
End
Public Sub tmrScreenUpdate_Timer()
With Window
.Border = Border.ACS
.Background = Color.Green
.Caption = " clock \r" '\r returns cursor to start of line
.PrintCenter(Format(Now, "hh:mm:ss"), Attr.Bold, Pair[Color.Black, Color.Green])
End With
End
As you can see, this program just keeps printing the time each time the window is updated by the 1 second timer...
...but the principle shows that you can display updating information (e.g. temperature, data rate & so on).
what about pressing buttons?
The final example illustrates user interaction;
This requires us to create a new instance of "window", give it a name ("qWindow"), and then use Read events to respond to key-presses.
Public hTimerScreen As Timer 'screen update timer
Public hUserInterface As Window 'our user interface window
Public lngCount As Long 'just a trivial counter
'
Public Sub Main()
'this timer re-writes the screen at 1s intervals
hTimerScreen = New Timer As "tmrScreenUpdate"
hUserInterface = New Window(True, 0, 0, 200, 15) As "qWindow"
hTimerScreen.Delay = 1000
hTimerScreen.start
End
Public Sub tmrScreenUpdate_Timer()
Inc lngCount
Screen.Echo = False 'don't display key press
With hUserInterface
.Border = Border.ACS
.Background = Color.Green
.Caption = "clock\r"
.Print("Counter: " & lngCount, 2, 2, Attr.Blink, Pair[Color.Blue, Color.Green])
.PrintCenter(Format(Now, "hh:mm:ss"), Attr.Bold, Pair[Color.Black, Color.Green])
.Print("Press
key to quit", 20, 13, Attr.Underline, Pair[Color.Blue, Color.Green])
.SetFocus()
End With
End
Public Sub qWindow_Read()
Dim iKey As Integer = Window.Read()
If iKey = Key["r"] Then
lngCount = 0
Endif
If iKey = Key["q"] Then
Quit
Endif
hUserInterface.Raise()
End
The .SetFocus method is required in order that our Window instance can raise the _Read event.
The .Raise method is required to keep the window on top (visible).
other weird things
The documentation does not give information on some of the Window methods including .Lower (probably the opposite of .Raise), .Drain (I've no idea) and .SetFullScreen.
Using \n (new-line) or \r (like carriage return) may over-write x/y arguments used in the same command (e.g. Print).
Leaving x/y argument blank generally defaults them to zero. Leaving height/width parameters blank normally defaults them to maximum.
Also have no idea at the moment regarding input constants Cooked, Raw & CBreak.
I've just worked out that you can hide the flashing cursor by adding:-
Screen.Cursor = Cursor.Hidden
...to the timer event in the code above.
I worked out most of this stuff from this:-
http://gambaswiki.org/wiki/comp/gb.ncurses
...and this: http://gambaswiki.org/wiki/tutorial/ncursestut
Hopefully the information in this post may get you started, and it will certainly remind me how it works 6 months from now!
I tried your code but ran into strange error messages. As I used a general 'mess about with' project I had several components selected. These, I discovered, were causing the errors. So make sure you only have 'gb' and 'gb.ncurses' components selected if you get errors.
ReplyDeleteYes, I think the easy way is to start with the new project wizard and select "command-line application" as project type, then add gb.ncurses from the Components list.
DeleteHi, nice tutorial, thank you! One question, how can i move the cursor?
ReplyDeleteVery sorry that its taken so long to get back to you.
ReplyDeleteYou can set the cursor position to (say) x = 5, y = 7 by using:-
Window.cursorX = 5
Window.cursorY = 7
If you do this followed by a window.print command you will see that this has worked by the position of the printed text.
However, I can't seem to display the actual cursor by setting:-
Screen.Cursor = Cursor.Visible
Thanks, but i need move the current cursor. I can't move it.
ReplyDelete