Tuesday, December 6, 2022
HomeSoftware DevelopmentEasy methods to Create a Python curses-enabled Utility

Easy methods to Create a Python curses-enabled Utility


Within the first a part of this programming tutorial sequence, we discovered easy methods to set up and setup the Python curses module, which is expounded to the C ncurses library. At present, we’ll proceed that dialogue as we create our first “Good day, World” instance utilizing the curses library.

If you happen to missed the primary a part of this sequence, you may learn it right here: Python curses: Drawing with Textual content.

Making a Good day, World! Utility with Python curses

With all the formalities concluded, it’s now time to create a easy program that can show primary ncurses performance by way of a Python curses-enabled program. The code under will write a customary “Good day, world!” message to the terminal:

# demo-ncurses-hello-world.py

import curses
import sys

def most important(argv):
  # BEGIN ncurses startup/initialization...
  # Initialize the curses object.
  stdscr = curses.initscr()

  # Don't echo keys again to the consumer.
  curses.noecho()

  # Non-blocking or cbreak mode... don't anticipate Enter key to be pressed.
  curses.cbreak()

  # Flip off blinking cursor
  curses.curs_set(False)

  # Allow coloration if we are able to...
  if curses.has_colors():
    curses.start_color()

  # Elective - Allow the keypad. This additionally decodes multi-byte key sequences
  # stdscr.keypad(True)

  # END ncurses startup/initialization...

  caughtExceptions = ""
  attempt:
    # Coordinates begin from prime left, within the format of y, x.
    stdscr.addstr(0, 0, "Good day, world!")
    screenDetailText = "This display is [" + str(curses.LINES) + "] excessive and [" + str(curses.COLS) + "] throughout."
    startingXPos = int ( (curses.COLS - len(screenDetailText))/2 )
    stdscr.addstr(3, startingXPos, screenDetailText)
    stdscr.addstr(5, curses.COLS - len("Press a key to give up."), "Press a key to give up.")

    # Really attracts the textual content above to the positions specified.
    stdscr.refresh()

    # Grabs a worth from the keyboard with out Enter having to be pressed (see cbreak above)
    stdscr.getch()
  besides Exception as err:
   # Simply printing from right here is not going to work, as this system remains to be set to
   # use ncurses.
   # print ("Some error [" + str(err) + "] occurred.")
   caughtExceptions = str(err)

  # BEGIN ncurses shutdown/deinitialization...
  # Flip off cbreak mode...
  curses.nocbreak()

  # Flip echo again on.
  curses.echo()

  # Restore cursor blinking.
  curses.curs_set(True)

  # Flip off the keypad...
  # stdscr.keypad(False)

  # Restore Terminal to authentic state.
  curses.endwin()

  # END ncurses shutdown/deinitialization...

  # Show Errors if any occurred:
  if "" != caughtExceptions:
   print ("Received error(s) [" + caughtExceptions + "]")

if __name__ == "__main__":
  most important(sys.argv[1:])


The primary line in most important, stdscr = curses.initscr(), reveals that the curses treats the display as a curses window object that occurs to cowl your complete display. The entire different capabilities that write textual content to the display are members of the curses object. Nonetheless, stdscr = curses.initscr() goes additional by initializing the ncurses module in order that it may well do its work on the terminal.

Textual content Positioning with curses in Python

The code above makes use of ncurses’ positioning grid to put the textual content on the display. ncurses makes use of a zero-indexed grid system, represented by X and Y values, to place components on the display:

Python ncurses text positioning

The 2 values, curses.COLS and curses.LINES seek advice from the utmost variety of columns within the terminal and the utmost variety of strains within the terminal, respectively.

The “Good day, World!” program above makes use of three totally different coordinate positions within the terminal with the intention to show textual content. The primary place, 0, 0, merely writes “Good day, World!” to the top-left nook of the terminal. Whereas ncurses, typically, may be very delicate to writing textual content outdoors of its containing window, the code makes the belief that the terminal is extensive sufficient to accommodate the textual content. Remember that operating the “Good day, World!” program above with a really slender area (lower than the size of “Good day, World!”) will trigger an exception.

The second place, which is calculated primarily based on the width of a string, is an approximation of the middle of the terminal on a hard and fast line. Word that, not like a very graphical program, the place is all the time going to be both 1 over, 1 much less, or precisely on the width of the terminal. This variance is as a result of the terminal width should be an integer, as cursor positions can’t be fractional. The code even casts the results of the calculation to an integer for this very cause.

The third place right-justifies the textual content literal with the directions to press a (any) key to give up. As with the second place, the beginning X coordinate is calculated relative to the curses.COLS, besides that there isn’t any division by 2.

Word: the exception messages returned by the Python curses module on account of incorrect sizing of strings, home windows, or different objects typically make no point out of a measurement drawback. One strategy to mitigate that is to verify all of those calculations earlier than passing any values into any curses object, and, if the mathematics doesn’t permit for a match, then prematurely increase an exception with an appropriate error message.

Learn: High On-line Programs to Study Python Programming

Easy methods to Draw or Place Textual content with Python curses

Because the remark above the stdscr.refresh() code signifies, that is the place all of the textual content is definitely drawn to the display. This suggests that, ought to alternate textual content have to be positioned at a location wherein textual content already exists, then one other name to stdscr.refresh() is critical. If the textual content that replaces current textual content isn’t lengthy sufficient to fully cowl the present textual content, then areas will have to be appended to the brand new textual content with the intention to cowl up the present textual content. Calls to stdscr.addstr(…) typically don’t overwrite current textual content.

Consumer Enter and Python curses

The stdscr.getch() code works in tandem with the curses.cbreak(), curses.curs_set(False), and curses.noecho() calls above it. With out curses.cbreak(), it will be essential to press Enter after urgent every other key. For this instance, that might not be the specified operation. With out curses.noecho(), the worth of no matter key was pressed could be echoed again to the terminal. That echoing would have the potential to undesirably change the textual content on the display. Lastly, with out curses.curs_set(False), the blinking cursor would nonetheless be displayed on the display, and this could probably confuse customers in purposes with extra advanced interfaces.

Home windows and Python curses

As 99% of the “promoting level” of ncurses is the flexibility to show home windows in a text-based terminal interface it begs the purpose of truly creating some. And, why not make this slightly extra fascinating by including some colours to the output too?

The code under makes additional use of the Python curses.window object, together with the colours outlined within the curses module to create three randomly generated home windows on the terminal. Now, a extra seasoned developer would possibly name out the “non-usage” of object-oriented code right here, as this code could be use case for that, however for the needs of an introductory demonstration, it’s simpler to focus extra on the curses objects themselves, versus how Python calls them, even when it makes for longer code:

# demo-3-windows2.py

# Makes use of the curses library to create 3 randomly sized home windows with totally different coloration
# backgrounds on the display.

# Word that this isn't probably the most environment friendly strategy to code this, however I wish to get away
# the person objects in order that it's simpler to hint what's going on.

import curses
import math
import random
import sys

# A set of layouts, to be randomly chosen.
layouts = ['2 top, 1 bottom', '2 left, 1 right', '1 top, 2 bottom', '1 left, 2 right']

def most important (argv):
  # Initialize the curses object.
  stdscr = curses.initscr()

  # Don't echo keys again to the consumer.
  curses.noecho()

  # Non-blocking or cbreak mode... don't anticipate Enter key to be pressed.
  curses.cbreak()

  # Flip off blinking cursor
  curses.curs_set(False)

  # Allow coloration if we are able to...
  if curses.has_colors():
    curses.start_color()

  # Elective - Allow the keypad. This additionally decodes multi-byte key sequences
  # stdscr.keypad(True)

  # Starting of Program... 
  # Create a listing of all the colours apart from black and white. These will server as 
  # the background colours for the home windows. As a result of these constants are outlined in 
  # ncurses,
  # we won't create the listing till after the curses.initscr name:
  bgColors = [curses.COLOR_BLUE, curses.COLOR_CYAN, curses.COLOR_GREEN, 
   curses.COLOR_MAGENTA, curses.COLOR_RED, curses.COLOR_YELLOW]
  colours = random.pattern(bgColors, 3)

  # Create 3 ncurses coloration pair objects.
  curses.init_pair(1, curses.COLOR_WHITE, colours[0])
  curses.init_pair(2, curses.COLOR_WHITE, colours[1])
  curses.init_pair(3, curses.COLOR_WHITE, colours[2])

  caughtExceptions = ""
  attempt:
   # Word that print statements don't work when utilizing ncurses. If you wish to write
   # to the terminal outdoors of a window, use the stdscr.addstr technique and specify
   # the place the textual content will go. Then use the stdscr.refresh technique to refresh the 
   # show.
   #stdscr.addstr(0, 0, "Gonna make some home windows.")
   #stdscr.refresh()

   # The lists under will ultimately maintain 4 values, the X and Y coordinates of the 
   # top-left nook relative to the display itself, and the variety of characters
   # going proper and down, respectively.
   window1 = []
   window2 = []
   window3 = []

   # The variables under will ultimately include the window objects.
   window1Obj = ""
   window2Obj = ""
   window3Obj = ""

   # The variables under will correspond roughly to the X, Y coordinates of the 
   # of every window.
   window1 = []
   window2 = []
   window3 = []

   # There's going to be a caption on the backside left of the display, nevertheless it must
   # go within the correct window.
   window1Caption = ""
   window2Caption = ""
   window3Caption = ""


   # The randomly sized home windows that do not take up one facet of the display should not 
   # be lower than 1/3 the display measurement, or multiple third of the display measurement on 
   # both edge.
   minWindowWidth = math.flooring(curses.COLS * 1.0/3.0)
   maxWindowWidth = math.flooring(curses.COLS * 2.0/3.0)
   minWindowHeight = math.flooring(curses.LINES * 1.0/3.0)
   maxWindowHeight = math.flooring(curses.LINES * 2.0/3.0)
   # Decide a structure. The random.randrange command will return a worth between 0 and three.
   chosenLayout = layouts[random.randrange(0,4)]
   if '2 prime, 1 backside' == chosenLayout:
    # Home windows 1 and a pair of would be the prime, Window 3 would be the backside.
    window1Width = random.randrange(minWindowWidth, maxWindowWidth)
    window1Height = random.randrange(minWindowHeight, maxWindowHeight)
    window1 = [0, 0, window1Width, window1Height]

    window2Width = curses.COLS - window1Width
    window2Height = window1Height
    window2 = [window1Width, 0, window2Width, window2Height]

    window3 = [0, window1Height, curses.COLS, curses.LINES - window1Height]
    window3Caption = chosenLayout + " - Press a key to give up."

   elif '2 left, 1 proper' == chosenLayout:
    # Home windows 1 and a pair of will probably be on the left, Window 3 will probably be on the precise.
    window1Width = random.randrange(minWindowWidth, maxWindowWidth)
    window1Height = random.randrange(minWindowHeight, maxWindowHeight)
    window1 = [0, 0, window1Width, window1Height]

    window2Width = window1Width
    window2Height = curses.LINES - window1Height
    window2 = [0, window1Height, window2Width, window2Height]
    window2Caption = chosenLayout + " - Press a key to give up."

    window3Width = curses.COLS - window1Width
    window3Height = curses.LINES
    window3 = [window1Width, 0, window3Width, window3Height]

   elif '1 prime, 2 backside' == chosenLayout:
    # Window 1 will probably be on the highest, Home windows 2 and three will probably be on the underside.
    window1Width = curses.COLS
    window1Height = random.randrange(minWindowHeight, maxWindowHeight)
    window1 = [0, 0, window1Width, window1Height]

    window2Width = random.randrange(minWindowWidth, maxWindowWidth)
    window2Height = curses.LINES - window1Height
    window2 = [0, window1Height, window2Width, window2Height]
    window2Caption = chosenLayout + " - Press a key to give up."

    window3Width = curses.COLS - window2Width
    window3Height = window2Height
    window3 = [window2Width, window1Height, window3Width, window3Height]

   elif '1 left, 2 proper' == chosenLayout:
    # Window 1 will probably be on the left, Home windows 2 and three will probably be on the precise.
    window1Width = random.randrange(minWindowWidth, maxWindowWidth)
    window1Height = curses.LINES
    window1 = [0, 0, window1Width, window1Height]
    window1Caption = chosenLayout + " - Press a key to give up."

    window2Width = curses.COLS - window1Width
    window2Height = random.randrange(minWindowHeight, maxWindowHeight)
    window2 = [window1Width, 0, window2Width, window2Height]

    window3Width = window2Width
    window3Height = curses.LINES - window2Height
    window3 = [window1Width, window2Height, window3Width, window3Height]

   # Create and refresh every window. Put the caption 2 strains up from backside
   # in case it wraps. Placing it on the final line with no room to wrap (if
   # the window is just too slender for the textual content) will trigger an exception.

   window1Obj = curses.newwin(window1[3], window1[2], window1[1], window1[0])
   window1Obj.bkgd(' ', curses.color_pair(1))
   # Calculate tough middle...
   window1Center = [math.floor(window1[2]/2.0), math.flooring(window1[3]/2.0)]
   # Add the string to the middle, with BOLD flavoring.
   window1Obj.addstr(window1Center[1], window1Center[0] - 4, "Window 1", 
    curses.color_pair(1) | curses.A_BOLD)
   if "" != window1Caption:
    window1Obj.addstr(curses.LINES - 2, 0, window1Caption, 
     curses.color_pair(1) | curses.A_BOLD)
   window1Obj.refresh()

   window2Obj = curses.newwin(window2[3], window2[2], window2[1], window2[0])
   window2Obj.bkgd(' ', curses.color_pair(2))
   # Calculate tough middle...
   window2Center = [math.floor(window2[2]/2.0), math.flooring(window2[3]/2.0)]
   # Add the string to the middle, with BOLD flavoring.
   window2Obj.addstr(window2Center[1], window2Center[0] - 4, "Window 2", 
    curses.color_pair(2) | curses.A_BOLD)
   if "" != window2Caption:
    # The "Y coordinate" right here is the underside of the *window* and never the display.
    window2Obj.addstr(window2[3] - 2, 0, window2Caption, 
     curses.color_pair(2) | curses.A_BOLD)
   window2Obj.refresh()

   window3Obj = curses.newwin(window3[3], window3[2], window3[1], window3[0])
   window3Obj.bkgd(' ', curses.color_pair(3))
   # Calculate tough middle...
   window3Center = [math.floor(window3[2]/2.0), math.flooring(window3[3]/2.0)]
   # Add the string to the middle, with BOLD flavoring.
   window3Obj.addstr(window3Center[1], window3Center[0] - 4, "Window 3", 
    curses.color_pair(3) | curses.A_BOLD)
   if "" != window3Caption:
    # The "Y coordinate" right here is the underside of the *window* and never the display.
    window3Obj.addstr(window3[3] - 2, 0, window3Caption, 
     curses.color_pair(3) | curses.A_BOLD)
   window3Obj.refresh()

   # Crucial so we are able to "pause" on the window output earlier than quitting.
   window3Obj.getch()

   # Debugging output.
   #stdscr.addstr(0, 0, "Chosen structure is [" + chosenLayout + "]")
   #stdscr.addstr(1, 10, "Window 1 params are [" + str (window1)+ "]")
   #stdscr.addstr(2, 10, "Window 2 params are [" + str(window2) + "]")
   #stdscr.addstr(3, 10, "Window 3 params are [" + str(window3)+ "]")
   #stdscr.addstr(4, 10, "Colours are [" + str(colors) + "]")
   #stdscr.addstr(5, 0, "Press a key to proceed.")
   #stdscr.refresh()
   #stdscr.getch()
  besides Exception as err:
   caughtExceptions = str(err)

  # Finish of Program...
  # Flip off cbreak mode...
  curses.nocbreak()

  # Flip echo again on.
  curses.echo()

  # Restore cursor blinking.
  curses.curs_set(True)

  # Flip off the keypad...
  # stdscr.keypad(False)

  # Restore Terminal to authentic state.
  curses.endwin()

  # Show Errors if any occurred:
  if "" != caughtExceptions:
   print ("Received error(s) [" + caughtExceptions + "]")
  return 0

if __name__ == "__main__":
  most important(sys.argv[1:])

The colour constants, together with the formatting constants for the bolded textual content, are all outlined at curses — Terminal dealing with for character-cell shows — Python 3.10.5 documentation.

There are 4 attainable forms of outputs to this program, as indicated by the layouts listing on the prime of the code:

Learn: The High On-line Programs to Study Linux

The Window Object

Every window within the output above is represented by a definite instantiation of the curses window object. The window object is returned by every name to curses.newwin(…) operate. Word that, whereas every of the three home windows above is instantiated by way of the curses.newwin(…) operate, this operate by itself doesn’t initialize the ncurses module (nor do they de-initialize the identical for return to the immediate). That also must be finished with curses.initscr(), though the stdscr window object returned by this name isn’t going for use on this code.

The curses.newwin(…) operate has two overloads, the second of which is getting used because it permits for the location of the top-left nook anyplace on the display, along with specifying the scale of the window:

curses.newwin(number-of-lines, number-of-columns, starting-column-position, starting-row-position)

All 4 parameters are non-negative integers.

The primary two values number-of-lines and number-of-columns are calculated as “random” integers that vary in between and the peak and width of the terminal window, respectively.

On this specific instance, no matter what the worth of one of many randomly chosen layouts is, the primary window is all the time the one which occupies the top-left nook of the terminal. The opposite two home windows’ sizes and positions are calculated relative to the primary window.

Textual content Inside Home windows

Any textual content that’s positioned inside a window created utilizing the curses.newwin(…) operate should totally match throughout the window. Any textual content that ends outdoors of the bounds of the window will trigger an exception to be raised. The code above creates a caption that’s positioned in no matter window finally ends up occupying the bottom-left nook of the display. Relying on what width is calculated for the scale of this window, it’s attainable that the size of the caption could exceed the width of the window. If this isn’t accounted for, an exception will probably be raised.

Within the above code instance, it may be seen that the caption isn’t on the bottom-most line. It is because this specific implementation of curses will wrap the textual content to the subsequent line ought to it overshoot the sting of the window, as proven under:

Python curses tutorial

For this specific instance, the terminal window was shrunk down considerably.

Nonetheless, if there isn’t any further line to which the rest of the textual content could be wrapped, an exception will probably be raised. Word: this line-wrapping habits will not be constant throughout all implementations of ncurses. In a manufacturing atmosphere, further code must be used to separate the road into separate calls to window.addstr(…).

Colour Pairs

The Python curses module offers with colours in pairs. Every Python color_pair object incorporates a foreground textual content coloration and a background coloration. The explanation for it’s because for any textual content that’s drawn to the display, a foreground and background coloration should be specified, and for the sake of a “good look,” the background coloration of any textual content drawn or positioned in a window ought to match the background coloration of the window.

It might be famous that the examples earlier to this itemizing presumed that the foreground textual content coloration was white and the background coloration was black. Relying on the terminal, and the way meticulous a programmer could also be, this will not be a clever presumption.

Remaining Ideas on Drawing Textual content with Python curses

That’s it for half two of this three-part programming tutorial sequence discussing easy methods to work with the Python curses library to attract textual content in Linux. We’ll wrap up the ultimate half on this sequence in our last piece. Examine again right here for the hyperlink as soon as it’s printed!

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments