Sunday, December 4, 2022
HomeSoftware DevelopmentPython curses: Working with Windowed Content material

Python curses: Working with Windowed Content material


Python tutorials

Welcome again to the third – and ultimate – installment in our collection on find out how to work with the curses library in Python to attract with textual content. If you happen to missed the primary two elements of this programming tutorial collection – or in case you want to reference the code contained inside them – you possibly can learn them right here:

As soon as reviewed, let’s transfer on to the subsequent portion: find out how to embellish home windows with borders and bins utilizing Python’s curses module.

Adorning Home windows With Borders and Bins

Home windows could be embellished utilizing customized values, in addition to a default “field” adornment in Python. This may be achieved utilizing the window.field() and window.border(…) features. The Python code instance beneath creates a purple 5×5 window after which alternates displaying and clearing the border on every key press:

# demo-window-border.py

import curses
import math
import sys

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

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

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

  # Flip off blinking cursor
  curses.curs_set(False)

  # Allow shade if we will...
  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 = ""
  strive:
   # Create a 5x5 window within the middle of the terminal window, after which
   # alternate displaying a border and never on every key press.

   # We need not know the place the approximate middle of the terminal
   # is, however we do want to make use of the curses terminal measurement constants to
   # calculate the X, Y coordinates of the place we will place the window in
   # order for it to be roughly centered.
   topMostY = math.ground((curses.LINES - 5)/2)
   leftMostX = math.ground((curses.COLS - 5)/2)

   # Place a caption on the backside left of the terminal indicating 
   # motion keys.
   stdscr.addstr (curses.LINES-1, 0, "Press Q to give up, another key to alternate.")
   stdscr.refresh()
   
   # We're simply utilizing white on purple for the window right here:
   curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_RED)

   index = 0
   executed = False
   whereas False == executed:
    # If we're on the primary iteration, let's skip straight to creating the window.
    if 0 != index:
     # Grabs a price from the keyboard with out Enter having to be pressed. 
     ch = stdscr.getch()
     # Have to match on each upper-case or lower-case Q:
     if ch == ord('Q') or ch == ord('q'): 
      executed = True
    mainWindow = curses.newwin(5, 5, topMostY, leftMostX)
    mainWindow.bkgd(' ', curses.color_pair(1))
    if 0 == index % 2:
     mainWindow.field()
    else:
     # There isn't any strategy to "unbox," so clean out the border as a substitute.
     mainWindow.border(' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ')
    mainWindow.refresh()

    stdscr.addstr(0, 0, "Iteration [" + str(index) + "]")
    stdscr.refresh()
    index = 1 + index

  besides Exception as err:
   # Simply printing from right here won't work, as this system continues 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 ("Bought error(s) [" + caughtExceptions + "]")

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

This code was run over an SSH connection, so there may be an automated clearing of the display upon its completion. The border “crops” the inside of the window, and any textual content that’s positioned inside the window have to be adjusted accordingly. And, as the decision to the window.border(…) operate suggests, any character can be utilized for the border.

The code works by ready for a key to be pressed. If both Q or Shift+Q is pressed, the termination situation of the loop might be activated and this system will give up. Be aware that, urgent the arrow keys might return key presses and skip iterations.

Replace Content material in “Home windows” with Python curses

Simply as is the case with conventional graphical windowed packages, the textual content content material of a curses window could be modified. And, simply as is the case with graphical windowed packages, the previous content material of the window have to be “blanked out” earlier than any new content material could be positioned within the window.

The Python code instance beneath demonstrates a digital clock that’s centered on the display. It makes use of Python lists to retailer units of characters which when displayed, appear to be giant variations of digits.

A short notice: the code beneath will not be meant to be essentially the most environment friendly technique of displaying a clock, quite, it’s meant to be a extra moveable demonstration of how curses home windows are up to date.

# demo-clock.py

# These checklist assignments could be executed on single traces, nevertheless it's a lot simpler to see what
# these values characterize by doing it this manner.
area = [
"     ",
"     ",
"     ",
"     ",
"     ",
"     ",
"     ",
"     ",
"     ",
"     "]

colon = [
"     ",
"     ",
"  :::  ",
"  :::  ",
"     ",
"     ",
"  :::  ",
"  :::  ",
"     ",
"     "]

forwardSlash = [
"     ",
"    //",
"   // ",
"   // ",
"  //  ",
"  //  ",
" //   ",
" //   ",
"//    ",
"     "]

number0 = [
" 000000 ",
" 00  00 ",
" 00  00 ",
" 00  00 ",
" 00  00 ",
" 00  00 ",
" 00  00 ",
" 00  00 ",
" 00  00 ",
" 000000 "]

number1 = [
"  11  ",
"  111  ",
" 1111  ",
"  11  ",
"  11  ",
"  11  ",
"  11  ",
"  11  ",
"  11  ",
" 111111 "]

number2 = [
" 222222 ",
" 22  22 ",
" 22  22 ",
"   22  ",
"  22  ",
"  22   ",
" 22   ",
" 22    ",
" 22    ",
" 22222222 "]

number3 = [
" 333333 ",
" 33  33 ",
" 33  33 ",
"    33 ",
"  3333 ",
"    33 ",
"    33 ",
" 33  33 ",
" 33  33 ",
" 333333 "]

number4 = [
"   44  ",
"  444  ",
"  4444  ",
" 44 44  ",
" 44 44  ",
"444444444 ",
"   44  ",
"   44  ",
"   44  ",
"   44  "]

number5 = [
" 55555555 ",
" 55    ",
" 55    ",
" 55    ",
" 55555555 ",
"    55 ",
"    55 ",
"    55 ",
"    55 ",
" 55555555 "]

number6 = [
" 666666 ",
" 66  66 ",
" 66    ",
" 66    ",
" 6666666 ",
" 66  66 ",
" 66  66 ",
" 66  66 ",
" 66  66 ",
" 666666 "]

number7 = [
" 77777777 ",
"    77 ",
"   77 ",
"   77  ",
"  77  ",
"  77   ",
" 77   ",
" 77    ",
" 77    ",
" 77    "]

number8 = [
" 888888 ",
" 88  88 ",
" 88  88 ",
" 88  88 ",
" 888888 ",
" 88  88 ",
" 88  88 ",
" 88  88 ",
" 88  88 ",
" 888888 "]

number9 = [
" 999999 ",
" 99  99 ",
" 99  99 ",
" 99  99 ",
" 999999 ",
"    99 ",
"    99 ",
"    99 ",
" 99  99 ",
" 999999 "]

import curses
import math
import sys
import datetime

def putChar(windowObj, inChar, inAttr = 0):
 #windowObj.field()
 #windowObj.addstr(inChar)
 # The logic beneath maps the traditional character enter to an inventory which accommodates a "huge"
 # illustration of that character.
 charToPut = ""
 if '0' == inChar:
  charToPut = number0
 elif '1' == inChar:
  charToPut = number1
 elif '2' == inChar:
  charToPut = number2
 elif '3' == inChar:
  charToPut = number3
 elif '4' == inChar:
  charToPut = number4
 elif '5' == inChar:
  charToPut = number5
 elif '6' == inChar:
  charToPut = number6
 elif '7' == inChar:
  charToPut = number7
 elif '8' == inChar:
  charToPut = number8
 elif '9' == inChar:
  charToPut = number9
 elif ':' == inChar:
  charToPut = colon
 elif '/' == inChar:
  charToPut = forwardSlash
 elif ' ' == inChar:
  charToPut = area

 lineCount = 0
 # This loop will iterate every line within the window to show a "line" of the digit
 # to be displayed.
 for line in charToPut:
  # Attributes, or the bitwise combos of a number of attributes, are handed as-is
  # into addstr. Be aware that not all attributes, or combos of attributes, will 
  # work with each terminal.
  windowObj.addstr(lineCount, 0, charToPut[lineCount], inAttr)
  lineCount = 1 + lineCount
 windowObj.refresh()

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

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

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

  # Flip off blinking cursor
  curses.curs_set(False)

  # Allow shade if we will...
  if curses.has_colors():
    curses.start_color()

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

  caughtExceptions = ""
  strive:
    # First issues first, make certain we now have sufficient room!
    if curses.COLS <= 88 or curses.LINES <= 11:
     increase Exception ("This terminal window is just too small.rn")
    currentDT = datetime.datetime.now()
    hour = currentDT.strftime("%H")
    min = currentDT.strftime("%M")
    sec = currentDT.strftime("%S")

    # Relying on how the ground values are calculated, an additional character for every
    # window could also be wanted. This code crashed when the home windows had been set to precisely
    # 10x10

    topMostY = math.ground((curses.LINES - 11)/2)
    leftMostX = math.ground((curses.COLS - 88)/2)

    # Be aware 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 methodology and specify
    # the place the textual content will go. Then use the stdscr.refresh methodology to refresh the 
    # show.
    stdscr.addstr(curses.LINES-1, 0, "Press a key to give up.")
    stdscr.refresh()


    # Bins - Every field have to be 1 char larger than stuff put into it.
    hoursLeftWindow = curses.newwin(11, 11, topMostY,leftMostX)
    putChar(hoursLeftWindow, hour[0:1])
    hoursRightWindow = curses.newwin(11, 11, topMostY,leftMostX+11)
    putChar(hoursRightWindow, hour[-1])
    leftColonWindow = curses.newwin(11, 11, topMostY,leftMostX+22)
    putChar(leftColonWindow, ':', curses.A_BLINK | curses.A_BOLD)
    minutesLeftWindow = curses.newwin(11, 11, topMostY, leftMostX+33)
    putChar(minutesLeftWindow, min[0:1])
    minutesRightWindow = curses.newwin(11, 11, topMostY, leftMostX+44)
    putChar(minutesRightWindow, min[-1])
    rightColonWindow = curses.newwin(11, 11, topMostY, leftMostX+55)
    putChar(rightColonWindow, ':', curses.A_BLINK | curses.A_BOLD)
    leftSecondWindow = curses.newwin(11, 11, topMostY, leftMostX+66)
    putChar(leftSecondWindow, sec[0:1])
    rightSecondWindow = curses.newwin(11, 11, topMostY, leftMostX+77)
    putChar(rightSecondWindow, sec[-1])

    # One of many bins have to be non-blocking or we will by no means give up.
    hoursLeftWindow.nodelay(True)
    whereas True:
     c = hoursLeftWindow.getch()

     # In non-blocking mode, the getch methodology returns -1 besides when any secret is pressed.
     if -1 != c:
      break
     currentDT = datetime.datetime.now()
     currentDTUsec = currentDT.microsecond
     # Refreshing the clock "4ish" occasions a second could also be overkill, however doing
     # on each single loop iteration shoots energetic CPU utilization up considerably.
     # Sadly, if we solely refresh as soon as a second it's attainable to 
     # skip a second.

     # Nevertheless, the sort of restriction breaks performance in Home windows, so
     # for that surroundings, this has to run on Each. Single. Iteration.
     if 0 == currentDTUsec % 250000 or sys.platform.startswith("win"):

      hour = currentDT.strftime("%H")
      min = currentDT.strftime("%M")
      sec = currentDT.strftime("%S")

      putChar(hoursLeftWindow, hour[0:1], curses.A_BOLD)
      putChar(hoursRightWindow, hour[-1], curses.A_BOLD)
      putChar(minutesLeftWindow, min[0:1], curses.A_BOLD)
      putChar(minutesRightWindow, min[-1], curses.A_BOLD)
      putChar(leftSecondWindow, sec[0:1], curses.A_BOLD)
      putChar(rightSecondWindow, sec[-1], curses.A_BOLD)
    # After breaking out of the loop, we have to clear up the show earlier than quitting.
    # The code beneath blanks out the subwindows.
    putChar(hoursLeftWindow, ' ')
    putChar(hoursRightWindow, ' ')
    putChar(leftColonWindow, ' ')
    putChar(minutesLeftWindow, ' ')
    putChar(minutesRightWindow, ' ')
    putChar(rightColonWindow, ' ')
    putChar(leftSecondWindow, ' ')
    putChar(rightSecondWindow, ' ')    
    
    # De-initialize the window objects.
    hoursLeftWindow = None
    hoursRightWindow = None
    leftColonWindow = None
    minutesLeftWindow = None
    minutesRightWindow = None
    rightColonWindow = None
    leftSecondWindow = None
    rightSecondWindow = None

  besides Exception as err:
   # Simply printing from right here won't work, as this system continues to be set to
   # use ncurses.
   # print ("Some error [" + str(err) + "] occurred.")
   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 ("Bought error(s) [" + caughtExceptions + "]")

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


Checking Window Dimension

Be aware how the primary line inside the strive block within the essential operate checks the scale of the terminal window and raises an exception ought to it not be sufficiently giant sufficient to show the clock. It is a demonstration of “preemptive” error dealing with, as if the person window objects are written to a display which is just too small, a really uninformative exception might be raised.

Cleansing Up Home windows with curses

The instance above forces a cleanup of the display for all 3 working environments. That is executed utilizing the putChar(…) operate to print a clean area character to every window object upon breaking out of the whereas loop.. The objects are then set to None. Cleansing up window objects on this method could be a good follow when it isn’t attainable to know all of the totally different terminal configurations that the code may very well be operating on, and having a clean display on exit provides these sorts of purposes a cleaner look general.

CPU Utilization

Just like the earlier code instance, this too works as an “infinite” loop within the sense that it’s damaged by a situation that’s generated by urgent any key. Exhibiting two other ways to interrupt the loop is intentional, as some builders might lean in direction of one methodology or one other. Be aware that this code ends in extraordinarily excessive CPU utilization as a result of, when run inside a loop, Python will devour as a lot CPU time because it probably can. Usually, the sleep(…) operate is used to pause execution, however within the case of implementing a clock, this might not be one of the simplest ways to scale back general CPU utilization. Apparently sufficient although, the CPU utilization, as reported by the Home windows Process Supervisor for this course of is barely about 25%, in comparison with 100% in Linux.

One other attention-grabbing remark about CPU utilization in Linux: even when simulating important CPU utilization by means of the stress utility, as per the command beneath:

$ stress -t 30 -c 16

the demo-clock.py script was nonetheless capable of run with out dropping the correct time.

Going Additional with Python curses

This three-part introduction solely barely scratches the floor of the Python curses module, however with this basis, the duty of making sturdy consumer interfaces for text-based Python purposes turns into fairly doable, even for a novice developer. The one downsides are having to fret about how particular person terminal emulation implementations can influence the code, however that won’t be that important of an obstacle, and naturally, having to take care of the maths concerned in holding window objects correctly sized and positioned.

The Python curses module does present mechanisms for “shifting” home windows (albeit not very properly natively, however this may be mitigated), in addition to resizing home windows and even compensating for modifications within the terminal window measurement! Even complicated text-based video games could be (and have been) applied utilizing the Python curses module, or its underlying ncurses C/C++ libraries.

The whole documentation for the ncurses module could be discovered at curses — Terminal dealing with for character-cell shows — Python 3.10.5 documentation. Because the Python curses module makes use of syntax that’s “shut sufficient” to the underlying ncurses C/C++ libraries, the guide pages for these libraries, in addition to reference assets for these libraries may also be consulted for extra info.

Blissful “fake” Windowed Programming!

Learn extra Python programming tutorials and software program growth ideas.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments