1

Code, using urwid library, below is supposed to display the contents of yml file (as given here) on a console screen (as per the Application Form layout here :

code is :

import sys
sys.path.append('./lib')
import os
from pprint import pprint
import random
import urwid
ui = urwid.raw_display.Screen()
pile = urwid.Pile([])
from collections import OrderedDict
import yaml_ordered_dict
import collections
import yaml

import itertools

import copy

class FormDisplay(object):

    def __init__(self):
        global ui
        #self.ui = urwid.raw_display.Screen()
        self.ui = ui
        self.palette = self.ui.register_palette([
            ('Field', 'dark green, bold', 'black'), # information fields, Search: etc.
            ('Info', 'dark green', 'black'), # information in fields
            ('Bg', 'black', 'black'), # screen background
            ('InfoFooterText', 'white', 'dark blue'), # footer text
            ('InfoFooterHotkey', 'dark cyan, bold', 'dark blue'), # hotkeys in footer text
            ('InfoFooter', 'black', 'dark blue'),  # footer background
            ('InfoHeaderText', 'white, bold', 'dark blue'), # header text
            ('InfoHeader', 'black', 'dark blue'), # header background
            ('BigText', RandomColor(), 'black'), # main menu banner text
            ('GeneralInfo', 'brown', 'black'), # main menu text
            ('LastModifiedField', 'dark cyan, bold', 'black'), # Last modified:
            ('LastModifiedDate', 'dark cyan', 'black'), # info in Last modified:
            ('PopupMessageText', 'black', 'dark cyan'), # popup message text
            ('PopupMessageBg', 'black', 'dark cyan'), # popup message background
            ('SearchBoxHeaderText', 'light gray, bold', 'dark cyan'), # field names in the search box
            ('SearchBoxHeaderBg', 'black', 'dark cyan'), # field name background in the search box
            ('OnFocusBg', 'white', 'dark magenta') # background when a widget is focused
           ])
    urwid.set_encoding('utf8')

    def main(self):
        global ui
        #self.view = ui.run_wrapper(formLayout)
        #urwid initialisation first or yml readin first. i chose yml reading
        yOrDict = yaml_ordered_dict.load('ApplyForm.yml')
        yOrDictLevel1 = yOrDict.get('ApplyForm')
        self.ui.start()
        self.view = formLayout(yOrDictLevel1)

        self.loop = urwid.MainLoop(self.view, self.palette)
        self.loop.widget = self.view
        self.loop.run()

def find_value(needle, container):
    # Already found the object. Return.
    if isinstance(container, basestring) and needle in container:
        return True

    values = None
    if isinstance(container, dict):
        values = container.values()
    elif hasattr(container, '__iter__'):
        values = container.__iter__()

    if values is None:
        return False

    # Check deeper in the container, if needed.
    for val in values:
        if find_value(needle, val):
            return True

    # No match found.
    return False

def getChildMap(tmpMap):
  tMap = tmpMap
  tmpLen = len(tMap)
  listOfKeys =  tMap.keys()
  topKey = listOfKeys[0]
  #print('topkey', topKey)
  mapObtained = tMap.get(topKey)

  #print('getChildMap',mapObtained)
  return mapObtained

def getMapForKeyIndex(tmpMap,index):
  listOfKeysForMap =  tmpMap.keys()
  i = index 
  mapObtained = collections.OrderedDict(itertools.islice(tmpMap.items(), i, i+1))
  #print(type(mapObtained))
  #print("getMapForKeyIndex: key and map",i,mapObtained)
  return mapObtained

def checkValOfKey(key,tmpMap):
    tMap = tmpMap

    for k, v in tMap.items():
        if k == key :
            tVal = tMap[key]

    #print('checkValOfKey:tVal',tVal)
    return tVal

def removeTopItem(tmpMap):
    tMap = tmpMap
    listOfKeys = tMap.keys()
    topKey = listOfKeys[0]
    del tMap[topKey]
    return tMap

def createBlock(mapToWorkWith) :
    global pile
    #pile = urwid.Pile([])


    tmapToWorkWith = copy.deepcopy(mapToWorkWith)
    tmapToWorkWith = getChildMap(tmapToWorkWith)
    #print('createBlock: tmapToWorkWith', tmapToWorkWith)
    reducedMapToCarry =  copy.deepcopy(tmapToWorkWith)

    listOfKeys =  tmapToWorkWith.keys()

    #if blockPresentAsValueInMap(tmapToWorkWith) == True :
    if checkValOfKey('$type',tmapToWorkWith) == 'Block' :
        #create a block nd pile
        print('XXXXXXXXcreate block and pile listXXXXXXXX')
        #pile = urwid.Pile([])
        keytoBeginPile = 2
        #if displayPresentAsKeyInMap(tmapToWorkWith) == True :
        #if checkContainmentOfKeyInMap('$display',(tmapToWorkWith)) :
        reducedMapToCarry = removeTopItem(reducedMapToCarry)
        reducedMapToCarry = removeTopItem(reducedMapToCarry)

        if (tmapToWorkWith.has_key('$display')) == True :
            print('display value ',tmapToWorkWith['$display'])
            displayText = tmapToWorkWith['$display']
            text = urwid.Text(('GeneralInfo', displayText),
                        align='center')
            pile.contents.append(text)


            keytoBeginPile +=1
            #addDisplayToPile
            reducedMapToCarry = removeTopItem(reducedMapToCarry)


        for k in range(keytoBeginPile,len(tmapToWorkWith)) :
            #//get the value of key at indexkeytoBeginPile in tmapToWorkWith
            tmpMap = tmapToWorkWith.get(listOfKeys[k])
            if  checkValOfKey('$type',tmpMap) != 'Block' :
                print('##add to Pile##')
                print('display value ',tmpMap['$display'])
                displayText = tmpMap['$display']
                text = urwid.Text(('GeneralInfo', displayText),
                        align='center')
                #pile.contents.append(text,options='pack')
                pile.append(text)
                keytoBeginPile +=1
                reducedMapToCarry = removeTopItem(reducedMapToCarry)
                #print('for Loop traversal: map', reducedMapToCarry)
                #mapToPass(pop)
                continue
            else :
                createBlock(reducedMapToCarry)

        #return  urwid.LineBox(urwid.AttrWrap(urwid.Overlay(urwid.LineBox(urwid.Padding(
         #       pile, align='center', left=3, right=3)), Banner, 'center', 150, 'middle', None),
          #      'GeneralInfo'))

def formLayout(topMap):
        global ui
        global pile

        f3 = open('OrderedMapInFinalApp.yaml',"w")
        newMap = topMap #check if needed at all
        yaml.dump(newMap, f3)


        content = urwid.SimpleListWalker([])
        listbox = urwid.ListBox(content)

        #orderedMap has come
        #read the key value and start for loop
        print('lenght of map',len(newMap))
        for i in range(len(newMap)):
            mapToWorkWith = getMapForKeyIndex(newMap,i)
            #print('for loop i nd mapToWorkWith Is',i,mapToWorkWith)

            if i == 0 :
                if (checkValOfKey('$type',mapToWorkWith) == 'Block') :
                    print('1st key read')
                    continue

            else :
                if (mapToWorkWith.has_key('TITLE') == True):
                    print('2nd key read')
                    continue

                else :
                    pile = ([])
                    createBlock(mapToWorkWith) 
                    #content.append(addBlockToOverallLayout())
                    content.append(pile)
                    continue
        fill = urwid.Filler(listbox)

        frame = urwid.Frame(fill,header=urwid.Pile([textT,textSH]),footer=textF)

        dim = ui.get_cols_rows()
        #ui is treated as global handle for all functions, either belonging
        #to any class or standalone functions such as formLayout
        #need to check if screen has been started
        if not ui._started:
            print("Screen has not been started, so no use of rendering.Thus return :-( ")
            return
        #ui.draw_screen(dim, listbox.render(dim, True))
        return frame
        #return listbox

def RandomColor():
    '''Pick a random color for the main menu text'''
    listOfColors = ['dark red', 'dark green', 'brown', 'dark blue',
                    'dark magenta', 'dark cyan', 'light gray',
                    'dark gray', 'light red', 'light green', 'yellow',
                    'light blue', 'light magenta', 'light cyan', 'default']
    color = listOfColors[random.randint(0, 14)]
    return color

def main():
    #global ui
    form = FormDisplay()
    form.main()

########################################
##### MAIN ENTRY POINT
########################################
if __name__ == '__main__':
    main()

#

The code use ui to represent the console screen. I have used a global listbox api that will contain the values, from yml file, whose key is $display. The code fails giving the error below :

Traceback (most recent call last):  
  File "./yamlUrwidUIPhase8FrameFinalAppC.py", line 308, in <module>  
    main()  
  File "./yamlUrwidUIPhase8FrameFinalAppC.py", line 302, in main  
    form.main()  
  File "./yamlUrwidUIPhase8FrameFinalAppC.py", line 61, in main  
    self.loop.run()  
  File "/home/gehna/urwidWorkInProgress/urwid/main_loop.py", line 272, in run  
    self.screen.run_wrapper(self._run)  
  File "/home/gehna/urwidWorkInProgress/urwid/raw_display.py", line 242, in        run_wrapper  
    return fn()  
  File "/home/gehna/urwidWorkInProgress/urwid/main_loop.py", line 312, in _run  
    self.draw_screen()  
  File "/home/gehna/urwidWorkInProgress/urwid/main_loop.py", line 563, in draw_screen  
    canvas = self._topmost_widget.render(self.screen_size, focus=True)  
  File "/home/gehna/urwidWorkInProgress/urwid/widget.py", line 141, in cached_render  
    canv = fn(self, size, focus=focus)  
  File "/home/gehna/urwidWorkInProgress/urwid/container.py", line 1058, in render  
    focus and self.focus_part == 'body')  
  File "/home/gehna/urwidWorkInProgress/urwid/widget.py", line 141, in cached_render  
    canv = fn(self, size, focus=focus)  
  File "/home/gehna/urwidWorkInProgress/urwid/decoration.py", line 811, in render  
    top, bottom = self.filler_values(size, focus)  
  File "/home/gehna/urwidWorkInProgress/urwid/decoration.py", line 796, in  filler_values  
    height = self._original_widget.rows((maxcol,),focus=focus)  
AttributeError: 'ListBox' object has no attribute 'rows'  

I checked the parsing section of the code with ddd and pydb debugger and the parsing yml section seems to be okay and the print statements give the values of $display correctly. Is there something wrong with the way I am using ui ? Should I use a draw_screen instead ?

Community
  • 1
  • 1

3 Answers3

2

I ran into the same error when trying to put the QuestionBox tutorial example in a ListBox. This answer solved it for me.

import urwid

def exit_on_q(key):
    if key in ('q', 'Q'):
        raise urwid.ExitMainLoop()

class QuestionBox(urwid.Filler):
    def keypress(self, size, key):
        if key != 'enter':
            return super(QuestionBox, self).keypress(size, key)
        self.original_widget = urwid.Text(
            u"Nice to meet you,\n%s.\n\nPress Q to exit." %
            edit.edit_text)

edit = urwid.Edit(u"What is your name?\n")
fill = urwid.BoxAdapter(QuestionBox(edit), height=2)
lb = urwid.ListBox([fill])
loop = urwid.MainLoop(lb, unhandled_input=exit_on_q)
loop.run()
Community
  • 1
  • 1
mgk
  • 313
  • 3
  • 10
1

Strange, your code should work as is. I had the same error when creating Frames and giving them the wrong order of arguments, so my best guess is to change this line

   frame = urwid.Frame(fill,header=urwid.Pile([textT,textSH]),footer=textF)

to

   frame = urwid.Frame(fill,urwid.Pile([textT,textSH]))

or

   frame = urwid.Frame(header=urwid.Pile([textT,textSH]),fill)

Discard the footer temporarily until you figure out the right order of parameters inside Frame and then put it back.

ychaouche
  • 4,922
  • 2
  • 44
  • 52
  • The third code-block here is an incorrect implementation. Positional argument always follows keyword arguments in Python. Even on a general note, this answer is incorrect. This has nothing to do with the order of arguments. OP has already defined header and footer as keyword args and changing any of the parameter's position shouldn't affect the code in any way whatsoever. – zean_7 May 24 '21 at 11:41
0

Have you solved this problem?may the problem is:

listbox is a box widget, its size decided by container, but the filler is a flow widget, its rows is decide by its content(children widget),

when filler calls listbox.render, it only give (cols,), don't have a rows int this tuple, but render of listbox wanted a rows. then error happen...

as documents of flow widget and filler described:

Flow widgets expect a single-element tuple (maxcol,) instead because they calculate their maxrow based on the maxcol value.

class urwid.Filler(body, valign='middle', height='pack', min_height=None, top=0, bottom=0)

'pack' if body is a flow widget given height integer number of rows for self.original_widget

so, you should given a height in your filler's constractor for listbox,

to tell filler the rows of listbox:

fill = urwid.Filler(body=listbox, height=20)

mark: haven't test this, just speculate.

  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Aug 04 '23 at 00:09