• Got bored... wrote this...

    From flicker-youtube@anduin.net@21:1/5 to All on Sun Apr 26 12:39:08 2020
    It's the beginnings of a text mode windowed UI, written in rexx.
    Should be multiform.


    /*

    Test rexx app for UI
    version 0.1
    Tom Dyer 2020

    */

    call rxFuncAdd "SysLoadFuncs", "REXXUTIL", "SysLoadFuncs"
    call SysLoadFuncs


    wm = .windowmanager~new(.logger~new)
    wm~logger~setloglevel(2)
    win = .logwindow~new
    win~logger=wm~logger
    win= .window~new
    win~screen= wm~screen
    win~name = "The Logging Window"

    label1 = .label~new("this is a label",1,1)
    label2 = .label~new("a second label", 2,1)
    input1 = .inputbox~new("", 1, 20, 30)
    input2 = .inputbox~new("test", 2, 20, 30)
    input3 = .inputbox~new("test2", 3, 20, 30)

    fieldhelplabel = .label~new("",10,20)

    input1~validator= .validator~new()
    input2~validator= .numericvalidator~new()
    input1~validator~fieldhelp=fieldhelplabel
    input2~validator~fieldhelp=fieldhelplabel


    win~objects~insert(label1)
    win~objects~insert(label2)
    win~objects~insert(input1)
    win~objects~insert(input2)
    win~objects~insert(input3)
    win~objects~insert(fieldhelplabel)



    wm~window~insert(win)
    wm~run()

    wm~logger~log( "wm is run",1)

    do i = 1 to 3
    say "shutdown "
    call syssleep(i)
    end


    exit


    ::class windowmanager

    ::attribute running
    ::attribute logger
    ::attribute keyboard
    ::attribute screen
    ::attribute window
    ::attribute focusableitems
    ::attribute activeitem

    ::method init
    expose logger screen
    use arg logger
    self~window = .list~new
    self~focusableitems = .list~new
    screen= .screen~new()


    ::method run unguarded
    expose keyboard screen
    keyboard= .keyboard~new()
    keyboard~logger= self~logger
    keyboard~listener=self
    self~findfocusable

    self~drawwindows
    self~logger~log("start wm",1)
    self~keyboard~poll
    self~logger~log("back from pollkey",1 )

    return

    ::method findfocusable
    do i = 1 to self~window~at(0)~objects~items
    if (self~window~at(0)~objects~at(i))~isInstanceOf(.focusable) then do
    self~focusableitems~insert(self~window~at(0)~objects~at(i))
    end
    end
    self~focusableitems~at(0)~hasFocus = .true
    self~activeitem= self~focusableitems~at(0)



    ::method nextFocus
    self~logger~log(self~focusableitems~items,0)

    do i = 0 to self~focusableitems~items -1
    self~logger~log(i ||"might"|| self~focusableitems~at(i)~hasFocus,0)

    if self~focusableitems~at(i)~hasFocus = .true then do
    self~focusableitems~at(i)~lostFocus

    if i = self~focusableitems~items -1 then do
    self~focusableitems~at(0)~gotFocus
    self~activeitem=self~focusableitems~at(0)
    return
    end
    else do
    self~focusableitems~at(i+1)~gotFocus
    self~activeitem=self~focusableitems~at(i+1)
    return
    end
    end
    end




    ::method drawwindows
    do i = 1 to self~window~items
    self~window~at(i-1)~draw
    end


    ::method sendkey
    use arg key
    self~logger~log(key,0)

    select
    when key~alt= .true & upper(key~value) = "X" then do
    self~keyboard~running= .false
    self~screen~running = .false
    self~logger~log("shutdown called",0)
    end
    when key~alt= .true & upper(key~value) = "R" then do
    self~drawwindows
    self~screen~draw
    end
    when key~tab=.true then do
    self~nextFocus
    end
    otherwise
    self~activeitem~sendkey(key)

    end

    self~drawwindows


    ::class screen inherit AlarmNotification

    ::attribute logger
    ::attribute rows
    ::attribute columns
    ::attribute platform
    ::attribute running
    ::attribute data
    ::constant ansi '1b5b'x

    ::method ansiclear
    return self~ansi||'2J'

    ::constant isLinux linux
    ::constant isWindows windows

    ::method init
    use arg logger
    self~logger
    self~data = .mutablebuffer~new()

    if pos("Linux", SysVersion()) <> 0 then do
    self~platform=self~isLinux
    end
    if pos("Windows", SysVersion()) <> 0 then do
    self~platform =self~isWindows
    end

    self~getscreensize
    self~setalarm


    ::method setalarm
    if self~running = .false then exit
    alarm = .alarm~new(1, self)


    ::method getscreensize
    expose rows columns

    if self~platform = self~isLinux then do
    /* linux */
    do line over .SystemQueue~new("stty size ") ; nop ; end
    parse var line rows columns
    end

    if self~platform = self~isWindows then do
    /* not tested but should work eh */
    parse value SysTextScreenSize() with rows columns
    end


    ::method triggered
    self~draw

    ::method draw
    self~clear
    say self~data

    self~setalarm

    ::method clear
    say self~ansiclear
    /* call SysCls */

    ::class keyboard

    ::attribute logger
    ::attribute running
    ::attribute listener

    ::method poll unguarded
    expose k
    if self~running = .false then exit
    k = sysgetkey("noecho")
    key = .key~new
    key~value=k

    if c2x(k) = .key~tabcode then do
    key~tab=.true
    key~value=""
    end

    if c2x(k) = .key~bscode then do
    key~bs=.true
    key~value=" "
    end

    if c2x(k) = .key~spacecode then do
    key~space=.true
    key~value=" "
    end


    if c2x(k) = .key~altcode then do
    k = sysgetkey("noecho")
    key~alt=.true
    key~value= k
    end

    self~logger~log("key" key~get(), 1)
    self~listener~sendkey(key)
    self~poll


    ::class key
    ::constant altcode "1B"
    ::constant tabcode "09"
    ::constant spacecode "20"
    ::constant bscode "7F"
    ::attribute alt
    ::attribute tab
    ::attribute value
    ::attribute space
    ::attribute bs

    ::method get
    expose value
    return value


    ::class logger
    ::attribute logdata
    ::attribute running

    ::method init
    self~logdata = .list~new

    ::method log
    expose loglevel
    use arg message, level
    rc=lineout("logfile",message)

    if level > loglevel then do
    self~logdata~insert(message)
    rc=lineout("logfile",message)
    end

    ::method setloglevel
    expose loglevel
    parse arg loglevel



    ::class window
    ::attribute name
    ::attribute screen
    ::attribute objects

    ::method init
    self~objects = .list~new

    ::method draw
    self~makewindow
    self~screen~data = self~makewindow

    ::method maketitlebar
    titlebar = center( self~name, self~screen~columns, "*")
    return titlebar

    ::method makewindow
    d = self~maketitlebar
    do i = 1 to self~screen~rows-3
    d = D||"*" || right("*",self~screen~columns-1," ")
    end

    time = time()
    d = d||center(time, self~screen~columns, "*")

    do i = 1 to self~objects~items
    d=d||self~objects~at(i-1)~draw
    end

    return d

    ::class logwindow subclass window
    ::attribute logger

    ::method draw
    super~draw
    do i = 1 to logger~logdata~allItems
    self~screen~data(logger~logdata[i])
    end



    ::class widget
    ::attribute fieldhelp
    ::attribute row
    ::attribute column
    ::attribute data
    ::attribute disdata
    ::attribute validator

    ::method predraw
    self~disdata = self~data

    ::method draw
    self~predraw
    return x2c(1b)||x2c(5b)||self~row||";"self~column||"H "||self~disdata

    ::method init
    expose data row column
    use arg data, row , column
    row = row + 2
    column = column + 2

    ::class label subclass widget


    ::class inputbox subclass widget inherit focusable
    ::attribute length

    ::method init
    expose data row column length
    use arg data, row, column, length
    self~validator = .nil
    self~init:super(data, row, column)

    ::method predraw
    self~disdata = left(self~data,self~length,"_")
    if self~hasFocus = .true then do
    self~disdata = self~disdata ||" * "
    end

    ::method sendkey
    use arg key
    if key~bs = .true & length(self~data ) > 0 then do
    self~data = left(self~data, length(self~data)-1 )
    end
    else do
    if self~validator <> .nil then do
    if self~validator~isValid(key) = .false then key~value = ""
    end

    self~data = self~data||key~value
    end

    ::class focusable mixinclass object
    ::attribute hasFocus

    ::method gotFocus
    self~hasfocus = .true


    ::method lostFocus
    self~hasfocus = .false


    ::class validator
    /*
    normal validator letters and numbers
    */
    ::attribute validkeys
    ::attribute fieldhelpstring
    ::attribute fieldhelp

    ::method init
    self~validkeys = .string~graph || " £"
    self~fieldhelpstring = "This field is alphanumeric"


    ::method isValid
    use arg key
    if self~fieldhelp <> .nil then self~fieldhelp~data = self~fieldhelpstring
    if pos(key~value, self~validkeys) > 0 then return .true
    else return .false

    ::class numericvalidator subclass validator
    /*
    numeric validator 4
    */
    ::method init
    self~validkeys = .string~digit
    self~fieldhelpstring = "This field is numeric"


    ::class SystemQueue subclass RexxQueue
    ::method init
    use strict arg command
    command "| rxqueue"
    self~init:super

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Chris Date@21:1/5 to flicker...@anduin.net on Mon Apr 27 04:10:03 2020
    On Sunday, 26 April 2020 20:39:09 UTC+1, flicker...@anduin.net wrote:
    It's the beginnings of a text mode windowed UI, written in rexx.
    Should be multiform.


    /*

    Test rexx app for UI
    version 0.1
    Tom Dyer 2020

    */

    call rxFuncAdd "SysLoadFuncs", "REXXUTIL", "SysLoadFuncs"
    call SysLoadFuncs


    wm = .windowmanager~new(.logger~new)
    wm~logger~setloglevel(2)
    win = .logwindow~new
    win~logger=wm~logger
    win= .window~new
    win~screen= wm~screen
    win~name = "The Logging Window"

    label1 = .label~new("this is a label",1,1)
    label2 = .label~new("a second label", 2,1)
    input1 = .inputbox~new("", 1, 20, 30)
    input2 = .inputbox~new("test", 2, 20, 30)
    input3 = .inputbox~new("test2", 3, 20, 30)

    fieldhelplabel = .label~new("",10,20)

    input1~validator= .validator~new()
    input2~validator= .numericvalidator~new()
    input1~validator~fieldhelp=fieldhelplabel
    input2~validator~fieldhelp=fieldhelplabel


    win~objects~insert(label1)
    win~objects~insert(label2)
    win~objects~insert(input1)
    win~objects~insert(input2)
    win~objects~insert(input3)
    win~objects~insert(fieldhelplabel)



    wm~window~insert(win)
    wm~run()

    wm~logger~log( "wm is run",1)

    do i = 1 to 3
    say "shutdown "
    call syssleep(i)
    end


    exit


    ::class windowmanager

    ::attribute running
    ::attribute logger
    ::attribute keyboard
    ::attribute screen
    ::attribute window
    ::attribute focusableitems
    ::attribute activeitem

    ::method init
    expose logger screen
    use arg logger
    self~window = .list~new
    self~focusableitems = .list~new
    screen= .screen~new()


    ::method run unguarded
    expose keyboard screen
    keyboard= .keyboard~new()
    keyboard~logger= self~logger
    keyboard~listener=self
    self~findfocusable

    self~drawwindows
    self~logger~log("start wm",1)
    self~keyboard~poll
    self~logger~log("back from pollkey",1 )

    return

    ::method findfocusable
    do i = 1 to self~window~at(0)~objects~items
    if (self~window~at(0)~objects~at(i))~isInstanceOf(.focusable) then do
    self~focusableitems~insert(self~window~at(0)~objects~at(i))
    end
    end
    self~focusableitems~at(0)~hasFocus = .true
    self~activeitem= self~focusableitems~at(0)



    ::method nextFocus
    self~logger~log(self~focusableitems~items,0)

    do i = 0 to self~focusableitems~items -1
    self~logger~log(i ||"might"|| self~focusableitems~at(i)~hasFocus,0)

    if self~focusableitems~at(i)~hasFocus = .true then do
    self~focusableitems~at(i)~lostFocus

    if i = self~focusableitems~items -1 then do
    self~focusableitems~at(0)~gotFocus
    self~activeitem=self~focusableitems~at(0)
    return
    end
    else do
    self~focusableitems~at(i+1)~gotFocus
    self~activeitem=self~focusableitems~at(i+1)
    return
    end
    end
    end




    ::method drawwindows
    do i = 1 to self~window~items
    self~window~at(i-1)~draw
    end


    ::method sendkey
    use arg key
    self~logger~log(key,0)

    select
    when key~alt= .true & upper(key~value) = "X" then do
    self~keyboard~running= .false
    self~screen~running = .false
    self~logger~log("shutdown called",0)
    end
    when key~alt= .true & upper(key~value) = "R" then do
    self~drawwindows
    self~screen~draw
    end
    when key~tab=.true then do
    self~nextFocus
    end
    otherwise
    self~activeitem~sendkey(key)

    end

    self~drawwindows


    ::class screen inherit AlarmNotification

    ::attribute logger
    ::attribute rows
    ::attribute columns
    ::attribute platform
    ::attribute running
    ::attribute data
    ::constant ansi '1b5b'x

    ::method ansiclear
    return self~ansi||'2J'

    ::constant isLinux linux
    ::constant isWindows windows

    ::method init
    use arg logger
    self~logger
    self~data = .mutablebuffer~new()

    if pos("Linux", SysVersion()) <> 0 then do
    self~platform=self~isLinux
    end
    if pos("Windows", SysVersion()) <> 0 then do
    self~platform =self~isWindows
    end

    self~getscreensize
    self~setalarm


    ::method setalarm
    if self~running = .false then exit
    alarm = .alarm~new(1, self)


    ::method getscreensize
    expose rows columns

    if self~platform = self~isLinux then do
    /* linux */
    do line over .SystemQueue~new("stty size ") ; nop ; end
    parse var line rows columns
    end

    if self~platform = self~isWindows then do
    /* not tested but should work eh */
    parse value SysTextScreenSize() with rows columns
    end


    ::method triggered
    self~draw

    ::method draw
    self~clear
    say self~data

    self~setalarm

    ::method clear
    say self~ansiclear
    /* call SysCls */

    ::class keyboard

    ::attribute logger
    ::attribute running
    ::attribute listener

    ::method poll unguarded
    expose k
    if self~running = .false then exit
    k = sysgetkey("noecho")
    key = .key~new
    key~value=k

    if c2x(k) = .key~tabcode then do
    key~tab=.true
    key~value=""
    end

    if c2x(k) = .key~bscode then do
    key~bs=.true
    key~value=" "
    end

    if c2x(k) = .key~spacecode then do
    key~space=.true
    key~value=" "
    end


    if c2x(k) = .key~altcode then do
    k = sysgetkey("noecho")
    key~alt=.true
    key~value= k
    end

    self~logger~log("key" key~get(), 1)
    self~listener~sendkey(key)
    self~poll


    ::class key
    ::constant altcode "1B"
    ::constant tabcode "09"
    ::constant spacecode "20"
    ::constant bscode "7F"
    ::attribute alt
    ::attribute tab
    ::attribute value
    ::attribute space
    ::attribute bs

    ::method get
    expose value
    return value


    ::class logger
    ::attribute logdata
    ::attribute running

    ::method init
    self~logdata = .list~new

    ::method log
    expose loglevel
    use arg message, level
    rc=lineout("logfile",message)

    if level > loglevel then do
    self~logdata~insert(message)
    rc=lineout("logfile",message)
    end

    ::method setloglevel
    expose loglevel
    parse arg loglevel



    ::class window
    ::attribute name
    ::attribute screen
    ::attribute objects

    ::method init
    self~objects = .list~new

    ::method draw
    self~makewindow
    self~screen~data = self~makewindow

    ::method maketitlebar
    titlebar = center( self~name, self~screen~columns, "*")
    return titlebar

    ::method makewindow
    d = self~maketitlebar
    do i = 1 to self~screen~rows-3
    d = D||"*" || right("*",self~screen~columns-1," ")
    end

    time = time()
    d = d||center(time, self~screen~columns, "*")

    do i = 1 to self~objects~items
    d=d||self~objects~at(i-1)~draw
    end

    return d

    ::class logwindow subclass window
    ::attribute logger

    ::method draw
    super~draw
    do i = 1 to logger~logdata~allItems
    self~screen~data(logger~logdata[i])
    end



    ::class widget
    ::attribute fieldhelp
    ::attribute row
    ::attribute column
    ::attribute data
    ::attribute disdata
    ::attribute validator

    ::method predraw
    self~disdata = self~data

    ::method draw
    self~predraw
    return x2c(1b)||x2c(5b)||self~row||";"self~column||"H "||self~disdata

    ::method init
    expose data row column
    use arg data, row , column
    row = row + 2
    column = column + 2

    ::class label subclass widget


    ::class inputbox subclass widget inherit focusable
    ::attribute length

    ::method init
    expose data row column length
    use arg data, row, column, length
    self~validator = .nil
    self~init:super(data, row, column)

    ::method predraw
    self~disdata = left(self~data,self~length,"_")
    if self~hasFocus = .true then do
    self~disdata = self~disdata ||" * "
    end

    ::method sendkey
    use arg key
    if key~bs = .true & length(self~data ) > 0 then do
    self~data = left(self~data, length(self~data)-1 )
    end
    else do
    if self~validator <> .nil then do
    if self~validator~isValid(key) = .false then key~value = ""
    end

    self~data = self~data||key~value
    end

    ::class focusable mixinclass object
    ::attribute hasFocus

    ::method gotFocus
    self~hasfocus = .true


    ::method lostFocus
    self~hasfocus = .false


    ::class validator
    /*
    normal validator letters and numbers
    */
    ::attribute validkeys
    ::attribute fieldhelpstring
    ::attribute fieldhelp

    ::method init
    self~validkeys = .string~graph || " £"
    self~fieldhelpstring = "This field is alphanumeric"


    ::method isValid
    use arg key
    if self~fieldhelp <> .nil then self~fieldhelp~data = self~fieldhelpstring
    if pos(key~value, self~validkeys) > 0 then return .true
    else return .false

    ::class numericvalidator subclass validator
    /*
    numeric validator 4
    */
    ::method init
    self~validkeys = .string~digit
    self~fieldhelpstring = "This field is numeric"


    ::class SystemQueue subclass RexxQueue
    ::method init
    use strict arg command
    command "| rxqueue"
    self~init:super

    I would certainly like to use this TUI. Another good reason for me to switch from Regina to OORexx! Just need to decide whether to try version 4.2.0 or dive right into 5.0.0beta. Any thoughts? I use Debian Linux.

    Chris

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Bob Bobskin@21:1/5 to All on Mon Apr 27 06:59:10 2020

    I would certainly like to use this TUI. Another good reason for me to switch from Regina to OORexx! Just need to decide whether to try version 4.2.0 or dive right into 5.0.0beta. Any thoughts? I use Debian Linux.

    Chris

    It's a bit of fun. I wrote it on oorexx 5 beta (I can't remember if I used any of the new functionality, I might have).

    From my perspective, on various linux platforms, I am finding v5b working well.

    I (hope) it displays some good OOP and OOD.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Bob Bobskin@21:1/5 to Bob Bobskin on Mon Apr 27 07:22:29 2020
    On Monday, 27 April 2020 14:59:12 UTC+1, Bob Bobskin wrote:

    I would certainly like to use this TUI. Another good reason for me to switch from Regina to OORexx! Just need to decide whether to try version 4.2.0 or dive right into 5.0.0beta. Any thoughts? I use Debian Linux.

    Chris

    It's a bit of fun. I wrote it on oorexx 5 beta (I can't remember if I used any of the new functionality, I might have).

    From my perspective, on various linux platforms, I am finding v5b working well.

    I (hope) it displays some good OOP and OOD.

    When one can add a password field with this amount of code, it's nice :)
    (my partner disagrees, she looks bemused that anyone can be excited by a box you can type in, in a black screen, which does nothing).

    ::class passwordbox subclass inputbox
    ::method predraw
    self~disdata = copies("*",self~length)
    if self~hasFocus = .true then do
    self~disdata = self~disdata ||" P "
    end

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rony@21:1/5 to Bob Bobskin on Wed Apr 29 14:09:43 2020
    Hi Chris,

    *very* impressive!

    Had to test it on Windows where in principle it works (having ansicon installed, cf.
    <https://github.com/adoxa/ansicon/releases> for the time being).

    One thing that is interesting on Windows: in my case the screen buffer size is 135x3000 (width x
    height) the window size is 135 x 45. It seems that the screen buffer size gets used on Windows. Also
    the cursor positioning does not quite work (it is placed on the first column almost at the bottom),
    though entering text will appear in the first field.

    Hope you remain bored :), please keep up your interesting work!

    ---rony



    On 27.04.2020 16:22, Bob Bobskin wrote:
    On Monday, 27 April 2020 14:59:12 UTC+1, Bob Bobskin wrote:

    I would certainly like to use this TUI. Another good reason for me to switch from Regina to OORexx! Just need to decide whether to try version 4.2.0 or dive right into 5.0.0beta. Any thoughts? I use Debian Linux.

    Chris

    It's a bit of fun. I wrote it on oorexx 5 beta (I can't remember if I used any of the new functionality, I might have).

    From my perspective, on various linux platforms, I am finding v5b working well.

    I (hope) it displays some good OOP and OOD.

    When one can add a password field with this amount of code, it's nice :)
    (my partner disagrees, she looks bemused that anyone can be excited by a box you can type in, in a black screen, which does nothing).

    ::class passwordbox subclass inputbox
    ::method predraw
    self~disdata = copies("*",self~length)
    if self~hasFocus = .true then do
    self~disdata = self~disdata ||" P "
    end


    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rony@21:1/5 to Rony on Wed Apr 29 15:02:18 2020
    Hi Chris,

    one hint ad Windows: SysTextScreenSize() got updated in ooRexx 5.0 and can be used to find out more
    about the current command line window (and also to set the respective data).

    Your current usage will return the "screen buffer size", which in my case is 135 x 3000.

    The following Rexx program will demonstrate the new features and what they give in my case:

    --- testscreen.rex - begin ---
    -- screen buffer
    parse value SysTextScreenSize() with rows columns

    -- visible window size, positions in buffer (0-based)
    parse value SysTextScreenSize("windowrect") with top left bottom right
    say "current console buffer is" rows "rows by" columns "columns"
    say "current window rectangle is ("top"," left") ("bottom"," right")" -
    "(0-based, position in buffer)"

    say "---"
    say "sysTextScreenSize():" sysTextScreenSize()
    say
    do arg over ("buffersize", "windowrect", "maxwindowsize")
    say "SysTextScreenSize("arg"):" sysTextScreenSize(arg)
    end
    --- testscreen.rex - end ---

    Here the output:

    --- output - begin ---
    current console buffer is 3000 rows by 135 columns
    current window rectangle is (0, 0) (44, 134) (0-based, position in buffer)
    ---
    sysTextScreenSize(): 3000 135

    SysTextScreenSize(buffersize): 3000 135
    SysTextScreenSize(windowrect): 0 0 44 134
    SysTextScreenSize(maxwindowsize): 56 135
    --- output - end ---
  • From Gil Barmwater@21:1/5 to Rony on Wed Apr 29 09:13:53 2020
    Rony,

    I think the OP is Bob, not Chris. As for the issues you noted on
    Windows, you should be aware of an open bug against the ANSI escape
    sequence support. There is an easy workaround however; just issue a
    "dummy" command to cmd.exe - like 'rem' - before you (or Bob's code)
    issues any ANSI sequences. This will get the support enabled which it automatically is for cmd.exe but not (yet) for ooRexx. HTH,

    Gil B.
    On 4/29/2020 8:09 AM, Rony wrote:
    Hi Chris,

    *very* impressive!

    Had to test it on Windows where in principle it works (having ansicon installed, cf.
    <https://github.com/adoxa/ansicon/releases> for the time being).

    One thing that is interesting on Windows: in my case the screen buffer size is 135x3000 (width x
    height) the window size is 135 x 45. It seems that the screen buffer size gets used on Windows. Also
    the cursor positioning does not quite work (it is placed on the first column almost at the bottom),
    though entering text will appear in the first field.

    Hope you remain bored :), please keep up your interesting work!

    ---rony

    --
    Gil Barmwater

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Gil Barmwater@21:1/5 to Gil Barmwater on Wed Apr 29 09:27:54 2020
    For anyone else who might be interested in using this on Windows, you
    should be aware that Windows 10 since 2016 supports ANSI sequences to
    the console. If you try it from ooRexx, however, it would appear to not
    work. This is the bug I referenced below. But after ooRexx invokes CMD
    the first time, the support becomes enabled so then ooRexx can use the sequences.

    On 4/29/2020 9:13 AM, Gil Barmwater wrote:
    Rony,

    I think the OP is Bob, not Chris. As for the issues you noted on
    Windows, you should be aware of an open bug against the ANSI escape
    sequence support. There is an easy workaround however; just issue a
    "dummy" command to cmd.exe - like 'rem' - before you (or Bob's code)
    issues any ANSI sequences. This will get the support enabled which it automatically is for cmd.exe but not (yet) for ooRexx. HTH,

    Gil B.

    --
    Gil Barmwater

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rony@21:1/5 to All on Wed Apr 29 15:16:31 2020
    Bob, sorry for addressing you as "Chris", somehow the names got mixed-up in my head, apologies...

    ---rony

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Bob Bobskin@21:1/5 to Rony on Wed Apr 29 11:21:16 2020
    I don't have a windows machine to try it with, and whilst I did put in some (best guess) code to try to make it work on Windows, I wasn't really able to check.

    Given that there is some interest, I think I'll aim to get bored again, and upload it to github or something as a project, so that people can "improve" it.

    Best






    On Wednesday, 29 April 2020 14:02:20 UTC+1, Rony wrote:
    Hi Chris,

    one hint ad Windows: SysTextScreenSize() got updated in ooRexx 5.0 and can be used to find out more
    about the current command line window (and also to set the respective data).

    Your current usage will return the "screen buffer size", which in my case is 135 x 3000.

    The following Rexx program will demonstrate the new features and what they give in my case:

    --- testscreen.rex - begin ---
    -- screen buffer
    parse value SysTextScreenSize() with rows columns

    -- visible window size, positions in buffer (0-based)
    parse value SysTextScreenSize("windowrect") with top left bottom right
    say "current console buffer is" rows "rows by" columns "columns"
    say "current window rectangle is ("top"," left") ("bottom"," right")" -
    "(0-based, position in buffer)"

    say "---"
    say "sysTextScreenSize():" sysTextScreenSize()
    say
    do arg over ("buffersize", "windowrect", "maxwindowsize")
    say "SysTextScreenSize("arg"):" sysTextScreenSize(arg)
    end
    --- testscreen.rex - end ---

    Here the output:

    --- output - begin ---
    current console buffer is 3000 rows by 135 columns
    current window rectangle is (0, 0) (44, 134) (0-based, position in buffer) ---
    sysTextScreenSize(): 3000 135

    SysTextScreenSize(buffersize): 3000 135
    SysTextScreenSize(windowrect): 0 0 44 134
    SysTextScreenSize(maxwindowsize): 56 135
    --- output - end ---

    After having used the command line screen for output from different commands, the buffer got filled
    already and the rectangle that gets shown off the buffer is given e.g. as:

    --- output - begin ---
    current console buffer is 3000 rows by 135 columns
    current window rectangle is (46, 0) (90, 134) (0-based, position in buffer) ---
    sysTextScreenSize(): 3000 135

    SysTextScreenSize(buffersize): 3000 135
    SysTextScreenSize(windowrect): 52 0 96 134
    SysTextScreenSize(maxwindowsize): 56 135
    --- output - end ---

    If you look up rexxref.pdf, 8.66. *CHG* SysTextScreenSize (Windows only), you will find more
    information, also about setting/changing these values.

    ---

    Another hint: as you are using ooRexx 5.0 (it is of release quality, faster, comes with very
    interesting new features) you can also use ANSI/Regina style commands with the ability of
    redirecting stdin, stdout and stderr from/to stems, but also from/to e.g. Rexx arrays.
    rexxpg.ref,6.5 ADDRESS Instruction, gives an example of this new ooRexx feature, rexxref.pdf
    documents it in full. You can therefore from now on forgo the " | rxqueue" pipe, if you want.


    ---rony






    On 29.04.2020 14:09, Rony wrote:
    Hi Chris,

    *very* impressive!

    Had to test it on Windows where in principle it works (having ansicon installed, cf.
    <https://github.com/adoxa/ansicon/releases> for the time being).

    One thing that is interesting on Windows: in my case the screen buffer size is 135x3000 (width x
    height) the window size is 135 x 45. It seems that the screen buffer size gets used on Windows. Also
    the cursor positioning does not quite work (it is placed on the first column almost at the bottom),
    though entering text will appear in the first field.

    Hope you remain bored :), please keep up your interesting work!

    ---rony



    On 27.04.2020 16:22, Bob Bobskin wrote:
    On Monday, 27 April 2020 14:59:12 UTC+1, Bob Bobskin wrote:

    I would certainly like to use this TUI. Another good reason for me to switch from Regina to
    OORexx! Just need to decide whether to try version 4.2.0 or dive right into 5.0.0beta. Any
    thoughts? I use Debian Linux.

    Chris

    It's a bit of fun. I wrote it on oorexx 5 beta (I can't remember if I used any of the new
    functionality, I might have).

    From my perspective, on various linux platforms, I am finding v5b working well.

    I (hope) it displays some good OOP and OOD.

    When one can add a password field with this amount of code, it's nice :) >> (my partner disagrees, she looks bemused that anyone can be excited by a box you can type in, in
    a black screen, which does nothing).

    ::class passwordbox subclass inputbox
    ::method predraw
    self~disdata = copies("*",self~length)
    if self~hasFocus = .true then do
    self~disdata = self~disdata ||" P "
    end



    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Bob Bobskin@21:1/5 to Gil Barmwater on Wed Apr 29 11:18:08 2020
    Gil,

    Thanks. I think I've got "just the location" to insert this "fix" in the new code base, in a bit of per platform code.

    On Wednesday, 29 April 2020 14:13:50 UTC+1, Gil Barmwater wrote:
    Rony,

    I think the OP is Bob, not Chris. As for the issues you noted on
    Windows, you should be aware of an open bug against the ANSI escape
    sequence support. There is an easy workaround however; just issue a
    "dummy" command to cmd.exe - like 'rem' - before you (or Bob's code)
    issues any ANSI sequences. This will get the support enabled which it automatically is for cmd.exe but not (yet) for ooRexx. HTH,

    Gil B.
    On 4/29/2020 8:09 AM, Rony wrote:
    Hi Chris,

    *very* impressive!

    Had to test it on Windows where in principle it works (having ansicon installed, cf.
    <https://github.com/adoxa/ansicon/releases> for the time being).

    One thing that is interesting on Windows: in my case the screen buffer size is 135x3000 (width x
    height) the window size is 135 x 45. It seems that the screen buffer size gets used on Windows. Also
    the cursor positioning does not quite work (it is placed on the first column almost at the bottom),
    though entering text will appear in the first field.

    Hope you remain bored :), please keep up your interesting work!

    ---rony

    --
    Gil Barmwater

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Bob Bobskin@21:1/5 to Gil Barmwater on Wed Apr 29 11:15:30 2020
    Well, I have to admit, much to my partner's extreme annoyance, I had another day of boredom. I get bored easily... I've only got half a dozen businesses to run (including my IT / Legal compliance consultancy, an advertising agency which I run with my
    partner, an Immigration consultancy which I am trying to close which I ran with my ex-partner and a new online tabloid-esq newspaper which I've just launched... because I was bored and got even more bored of various editors and sub-editors taking my
    perfectly well argued freelance articles, and making a dogs dinner of them because they don't like content which is too aggressive).

    For anyone looking for a read, or to write something knowing that our editors will correct your grammar and spelling, but will not try to censor your political views, check out my week old baby ... www.news-cyprus.com

    Now back to the topic at hand.. (I know... I need a beer).

    I am really quite surprised that this little thing has gone down so well. Last night, I sat down for a couple of hours, and made a series of improvements, which really helped with usability and the practicalness of the concept.

    So here we go, v 0.12 of rexx tui, which now works in a cls file.


    -- class file --
    /*
    tui.cls
    Text Mode UI
    v0.12
    Tom Dyer flicker@anduin.net
    2020

    Free to use
    */

    call rxFuncAdd "SysLoadFuncs", "REXXUTIL", "SysLoadFuncs"
    call SysLoadFuncs


    ::class WindowManager public inherit SanityCheckSupported


    ::attribute running
    ::attribute logger
    ::attribute keyboard
    ::attribute screen
    ::attribute window
    ::attribute focusableitems
    ::attribute activeitem
    ::attribute activewindow
    ::attribute keybindabletasks
    ::attribute wmdms /* data management service for window manager, provides some validation etc */

    ::method init
    expose logger screen
    use arg logger
    self~window = .list~new
    self~focusableitems = .list~new
    self~keybindabletasks= .list~new
    screen= .screen~new()


    ::method run unguarded
    expose keyboard screen
    keyboard= .keyboard~new()
    keyboard~logger= self~logger
    keyboard~wm=self
    self~findfocusable

    self~drawwindows
    self~logger~log("Within the WM code, about to poll keyboard",1)
    self~keyboard~poll
    self~logger~log("We have returned from polling the keyboard",1 )

    return

    /*
    shortcode
    */
    ::method add
    use arg obj
    if .SanityChecker~new~check(obj ,self) = .false then return

    if obj~isInstanceOf(.window) then do
    self~addWindow(obj)
    end

    if obj~isInstanceOf(.keybindable) then do
    self~addKeyBindableTask(obj)
    end

    if obj~isInstanceOf(.WMDataManagementService) then do
    self~logger~log("added wdms",1)
    self~wmdms = obj
    end

    if obj~isInstanceOf(.screen) then do
    self~logger~log("added screen",1)
    self~screen = obj
    end

    ::method addKeyBindableTask
    use arg keyBindableTask

    illegaltobindkey1 = .key~new("[")
    illegaltobindkey1~alt=.true
    illegaltobindkey2 = .key~new("O")
    illegaltobindkey2~alt=.true

    if keyBindableTask~key~detail = illegaltobindkey1~detail | keyBindableTask~key~detail = illegaltobindkey2~detail
    then do
    self~logger~log("tried to add an illegal key binding, refused for" keyBindableTask~key~detail,1)
    end
    else do

    self~logger~log("added keybindanble task to" keyBindableTask~key~detail,1)
    self~keybindabletasks~insert(keyBindableTask)

    end

    ::method addWindow
    use arg window
    if .SanityChecker~new~check(window,self) = .false then return
    window~screen = self~screen
    window~wm=self
    self~window~insert(window)
    if self~window~items=1 then do
    self~activewindow = 0
    end

    ::method gotoWindow
    use arg windowNumber
    self~activewindow= windowNumber
    self~findfocusable


    ::method findfocusable
    self~focusableitems = .list~new
    do i = 0 to self~window~at(self~activewindow)~objects~items -1
    if (self~window~at(self~activewindow)~objects~at(i))~isInstanceOf(.focusable) then do
    self~focusableitems~insert(self~window~at(self~activewindow)~objects~at(i))
    end
    end
    self~focusableitems~at(0)~hasFocus = .true
    self~activeitem= self~focusableitems~at(0)



    ::method nextFocus
    self~logger~log("Changing to next focus" self~focusableitems~items,0)

    do i = 0 to self~focusableitems~items -1

    if self~focusableitems~at(i)~hasFocus = .true then do
    self~focusableitems~at(i)~lostFocus

    if i = self~focusableitems~items -1 then do
    self~focusableitems~at(0)~gotFocus
    self~activeitem=self~focusableitems~at(0)
    return
    end
    else do
    self~focusableitems~at(i+1)~gotFocus
    self~activeitem=self~focusableitems~at(i+1)
    return
    end
    end
    end



    ::method drawwindows
    do i = 0 to self~window~items
    self~window~at(self~activewindow)~draw
    end


    ::method sendkey
    use arg key
    self~logger~log("WM - key pressed was" key~detail,0)

    select
    when key~alt= .true & upper(key~value) = "X" then do
    self~keyboard~running= .false
    self~screen~running = .false
    self~logger~log("shutdown called",0)
    end
    when key~alt= .true & upper(key~value) = "R" then do
    self~drawwindows
    self~screen~draw
    end
    when key~tab=.true then do
    self~nextFocus
    end
    when key~alt= .true then do
    /*
    send it to anything with global keybindability on alt
    */
    do i = 0 to self~keybindabletasks~items -1
    self~logger~log("WM sending keystroke to keybindabletask",2)
    self~keybindabletasks~at(i)~sendkey(key)
    end
    end

    otherwise
    /*
    then send the key to the active item
    */
    self~activeitem~sendkey(key)

    end

    self~drawwindows


    ::class screen public inherit AlarmNotification SanityCheckSupported

    ::attribute logger
    ::attribute rows
    ::attribute columns
    ::attribute platform
    ::attribute running
    ::attribute data
    ::constant ansi '1b5b'x

    ::constant black 0
    ::constant red 1
    ::constant green 2
    ::constant yellow 3
    ::constant blue 4
    ::constant magenta 5
    ::constant cyan 6
    ::constant white 7
    ::constant grey 8
    ::constant brightred 9
    ::constant brightgreen 10
    ::constant brightyellow 11
    ::constant brightblue 12
    ::constant brightmagenta 13
    ::constant brightcyan 14
    ::constant brightwhite 15

    ::attribute recheckScreensize
    ::attribute forceClear


    ::method ansiclear
    say self~ansi||'2J'

    ::constant isLinux linux
    ::constant isWindows windows

    ::method init
    use arg logger
    self~logger
    self~data = .mutablebuffer~new()
    self~recheckScreensize = 30 /* check every 30 seconds on repaint for screen size change */
    self~forceClear = .false

    if pos("Linux", SysVersion()) <> 0 then do
    self~platform=self~isLinux
    end
    if pos("Windows", SysVersion()) <> 0 then do
    self~platform =self~isWindows
    end

    self~getscreensize
    self~setalarm


    ::method setalarm
    if self~running = .false then exit
    alarm = .alarm~new(1, self)


    ::method setcursor
    use arg widget
    /* ansi movement code in screen */
    output=self~ansi||self~getPositionRow(widget)||";"self~getPositionColumn(widget)||"H "
    return output

    ::method setcolour
    use arg obj
    output = ""
    if obj~isInstanceOf(.coloured) then do
    output = self~getColourForObject(obj)
    end
    return output


    /*
    relies on ansi colours working
    */
    ::method getColourForObject
    use arg obj

    if obj~fgcolour= "FGCOLOUR" then do
    /* default */
    output = self~ansi||"39m"
    end
    else do
    output = self~ansi||"38;5;"||obj~fgcolour||"m"
    end


    /*
    bgcolour doesn't work reliably
    */

    if obj~bgcolour= "BGCOLOUR" then do
    output = output /* don't deal with no bg colour */
    end
    else do
    output = output||self~ansi||"48;5;"||obj~bgcolour||"m"
    end




    return output

    ::method getscreensize
    expose rows columns

    if self~platform = self~isLinux then do
    /* linux */
    do line over .SystemQueue~new("stty size ") ; nop ; end
    parse var line rows columns
    end

    if self~platform = self~isWindows then do
    /* not tested but should work eh */
    parse value SysTextScreenSize() with rows columns
    end


    ::method triggered

    t = time(Seconds)
    if t // self~recheckScreensize = 1 then do
    self~getscreensize
    end

    self~draw

    ::method draw
    if self~forceClear = .true then do
    self~clear
    end
    say self~data
    self~setalarm

    ::method clear
    self~ansiclear
    /* call SysCls */

    ::method getPositionRow
    use arg widget
    return widget~row

    ::method getPositionColumn
    use arg widget
    return widget~column

    /*
    Virtual Screen
    This class treats the screen as if the itens were positioned on a 80/25 display and then moves the items to fit the actual display
    */

    ::class VirtualScreen public subclass Screen

    ::constant vColumns 80
    ::constant vRows 25

    ::method init
    use arg logger
    self~init:super(logger) /* normal init */
    self~recheckScreensize = 5 /* set to check screen more frequently for size changes */
    self~PlatformCheck /* run platform specific checks - this isolates some platform specific code */


    ::method PlatformCheck
    if self~isLinux = .true then do
    /*
    Set the screen to run line by line, rather than char by char, used for high latency links
    and ideal for making a programme which paints the entire screen at a time
    function correctly.
    */

    "stty extproc"

    do line over .SystemQueue~new("echo $TERM") ; nop ; end /* $ */
    parse var line terminal
    if upper(terminal) = upper("LINUX") then do
    /* I think we have been opened using a full screen window on a hard tty */
    self~recheckScreensize = 60 /* so lets minimise the number of times we check the screen size to the minimum */

    end

    end



    ::method getPositionColumn
    use arg widget
    wcol = self~getPositionColumn:super(widget)
    factor = self~columns / self~vColumns
    rc=lineout("position", "factor c"||factor)

    posC = (factor * wcol ) %1
    rc=lineout("position", "C"||posC)
    return posC


    ::method getPositionRow
    use arg widget
    wrow = self~getPositionRow:super(widget)
    factor = self~rows / self~vRows
    rc=lineout("position", "factor r"||factor)
    posR = (factor * wrow)%1
    rc=lineout("position", "R"||posR)
    return posR

    ::class keyboard inherit SanityCheckSupported

    ::attribute logger
    ::attribute running
    ::attribute wm

    ::method poll unguarded

    if self~running = .false then exit

    /*
    get key from keyboard
    */

    k = sysgetkey("noecho")

    /* now create an object for the key */

    key = .key~new

    /* now work out the correct treatment */

    select
    /* tab pressed, probably used for moving between fields */
    when c2x(k) = .key~tabcode then do
    key~tab=.true
    key~value=""
    end
    /* backspace which will have special treatment */
    when c2x(k) = .key~bscode then do
    key~bs=.true
    key~value=""
    end
    /* space needs some care as well */
    when c2x(k) = .key~spacecode then do
    key~space=.true
    key~value=" "
    end
    /* if an alt button pressed, we need the next key to determine what was actually pressed */
    /* special keyboard control characters such as F1 -> F12, and arrow keys need multiple buttons */
    when c2x(k) = .key~altcode then do
    k2 = sysgetkey("noecho")

    select
    when k2 = "O" then do
    /* We've got a special control character such as fn buttons which use 3 parts */
    k3 = sysgetkey("noecho")
    /* note, you cannot bind to ALT-O alone */

    select
    when k3 = .key~fn1code then do
    key~fn1 = .true
    end
    when k3 = .key~fn2code then do
    key~fn2 = .true
    end
    when k3 = .key~fn3code then do
    key~fn3 = .true
    end
    when k3 = .key~fn4code then do
    key~fn4 = .true
    end
    when k3 = .key~fn5code then do
    key~fn5 = .true
    end
    when k3 = .key~fn6code then do
    key~fn6 = .true
    end
    when k3 = .key~fn7code then do
    key~fn7 = .true
    end
    when k3 = .key~fn8code then do
    key~fn8 = .true
    end
    when k3 = .key~fn9code then do
    key~fn9 = .true
    end
    when k3 = .key~fn10code then do
    key~fn10 = .true
    end
    when k3 = .key~fn11code then do
    key~fn11 = .true
    end
    when k3 = .key~fn12code then do
    key~fn12 = .true
    end
    otherwise do

    end
    end
    end
    when k2 = "[" then do
    /* We've got a special control character such as an arrow key which uses 3 parts */
    k3 = sysgetkey("noecho")
    select
    when k3 = .key~backtabcode then do
    key~backtab = .true
    end
    when k3 = .key~uparrowcode then do
    key~uparrow = .true
    end
    when k3 = .key~downarrowcode then do
    key~downarrow = .true
    end
    when k3 = .key~rightarrowcode then do
    key~rightarrow = .true
    end
    when k3 = .key~leftarrowcode then do
    key~leftarrow = .true
    end
    /* we are not handling these keys */
    otherwise do
    nop
    end
    end
    key~value=""
    end
    /* the key that was pressed was a standard ALT key such as ALT-P which uses 2 parts */
    otherwise do
    key~alt=.true
    key~value= k2
    end
    end
    end
    /* it seems that it was a normal keystroke, so let's just set the value */
    otherwise do
    key~value = k
    end
    end


    self~logger~log("keyboard class sending " key~detail(), 2)
    /*
    Now hand the job of working out what to do to the Window Manager
    */
    self~wm~sendkey(key)

    /*
    and kick off the poll again...
    */

    self~poll


    /*

    Class for keys used to handle input

    */

    ::class key public
    ::constant altcode "1B"
    ::constant tabcode "09"
    ::constant spacecode "20"
    ::constant bscode "7F"
    ::constant backtabcode "5A" /* 1B 5B 5A */
    ::constant uparrowcode "41" /* 1B 5B 41 */
    ::constant downarrowcode "42" /* 1B 5B 42 */
    ::constant rightarrowcode "43" /* 1B 5B 43 */
    ::constant leftarrowcode "44" /* 1B 5B 44 */
    ::constant fn1code
    ::constant fn2code
    ::constant fn3code
    ::constant fn4code
    ::constant fn5code
    ::constant fn6code
    ::constant fn7code
    ::constant fn8code
    ::constant fn9code
    ::constant fn10code
    ::constant fn11code
    ::constant fn12code

    ::attribute alt
    ::attribute tab
    ::attribute value
    ::attribute space
    ::attribute bs
    ::attribute uparrow
    ::attribute downarrow
    ::attribute rightarrow
    ::attribute leftarrow
    ::attribute backtab
    ::attribute fn1
    ::attribute fn2
    ::attribute fn3
    ::attribute fn4
    ::attribute fn5
    ::attribute fn6
    ::attribute fn7
    ::attribute fn8
    ::attribute fn9
    ::attribute fn10
    ::attribute fn11
    ::attribute fn12


    /*
    Comparison method for keys
    */
    ::method "="
    use arg obj

    /*
    By default they don't match
    */

    rc = .false

    select
    when obj~isInstanceOf(.Key) then do
    /* if both keys then compare detail */
    if obj~detail = self~detail then rc = .true
    end
    when obj~isInstanceOf(.String) then do
    /* if one is a string and the other is special, they can't match */
    if self~isSpecial = .true then rc = .false
    else do
    /* if it's not a special, then try comparing the letters themselves caseless*/
    if upper(obj)= upper(self~value) then rc = .true
    end
    end
    otherwise
    rc = .false
    end

    return rc



    ::method get
    expose value
    return value

    ::method isSpecial
    if self~alt = .true | self~tab = .true | self~space = .true | self~bs = .true | self~uparrow = .true | self~downarrow = .true | self~rightarrow = .true | self~leftarrow = .true | self~backtab = .true | self~fn1 = .true | self~fn2 = .true | self~fn3 = .
    true | self~fn4 = .true | self~fn5 = .true | self~fn6 = .true | self~fn7 = .true | self~fn8 = .true | self~fn9 = .true | self~fn10 = .true | self~fn11 = .true | self~fn12 = .true then return .true
    else
    return .false

    ::method init
    use arg kv=""
    self~value= kv

    /*
    detail of the key - in long form
    */
    ::method detail
    return self~value c2x(self~value) self~alt self~tab self~space self~bs self~uparrow self~downarrow self~rightarrow self~leftarrow self~backtab self~fn1 self~fn2 self~fn3 self~fn4 self~fn5 self~fn6 self~fn7 self~fn8 self~fn9 self~fn10 self~fn11 self~fn12



    /*
    This isn't anywhere near working but if you uncomment the lineouts.. you can get some output
    */

    ::class logger public
    ::attribute logdata
    ::attribute running

    ::method init
    expose stem
    use arg stem=.nil

    self~logdata = .list~new

    ::method log
    expose loglevel
    use arg message, level
    if level > loglevel then do
    self~logdata~insert(message)
    rc=lineout("logfile",message)
    end

    ::method setloglevel
    expose loglevel
    parse arg loglevel

    ::method unknown
    expose stem
    use arg msg, text
    stem[0] += 1
    index = stem[0]
    stem[index] = text~makeString
    self~log(msg,9)
    self~log(text,9)
    self~log(text~makeString,9)


    /*
    A window to the soul
    */

    ::class Window public inherit SanityCheckSupported coloured
    ::attribute name
    ::attribute screen
    ::attribute objects
    ::attribute wm
    ::attribute logger

    ::method init
    self~objects = .list~new

    ::method draw
    self~makewindow
    self~screen~data = self~makewindow

    ::method maketitlebar
    titlebar = center( self~name, self~screen~columns, "*")
    return titlebar

    ::method makewindow
    d = self~screen~setcolour(self)
    d = d||self~maketitlebar
    do i = 1 to self~screen~rows-2
    d = D||"*" || right("*",self~screen~columns-1," ")
    end

    time = time()
    d = d||center(time, self~screen~columns, "*")

    do i = 1 to self~objects~items
    d=d||self~screen~setcolour(self~objects~at(i-1))
    d=d||self~screen~setcursor(self~objects~at(i-1))
    d=d||self~objects~at(i-1)~draw
    end

    return d

    ::method add
    use arg obj
    if .SanityChecker~new~check(obj,self) = .false then return
    if obj~isInstanceOf(.widget) then do
    self~objects~insert(obj)
    end
    if obj~isInstanceOf(.keybindable) then do /* bind against the wm instead - fix for common error */
    self~wm~add(obj)
    end


    ::class logwindow subclass window
    ::attribute logger

    ::method draw
    super~draw
    do i = 1 to logger~logdata~allItems
    self~screen~data(logger~logdata[i])
    end








    /*
    The standard widget class

    */
    ::class widget inherit coloured
    ::attribute row
    ::attribute column
    ::attribute data
    ::attribute disdata
    ::attribute validator

    ::method predraw
    self~disdata = self~data

    ::method draw
    self~predraw
    return self~disdata

    ::method init
    expose data row column
    use arg data, row , column
    row = row + 2
    column = column + 2

    ::method add
    use arg obj

    if .SanityChecker~new~check(obj) = .false then return

    if obj~isInstanceOf(.validator) = .true then self~validator = obj
    /* assume that any label being added to a widget is actually the field help field */
    if obj~isInstanceOf(.label) = .true then self~validator~fieldhelp = obj
    /* assume that we are trying to set the field help */
    if obj~isInstanceOf(.string) = .true then self~validator~fieldhelpstring = obj




    /* labels are boring but necessary */

    ::class label public subclass widget

    /*everyone needs a button */

    ::class button public subclass widget inherit focusable focuscoloured ::attribute length
    ::attribute task

    ::method init
    expose length
    use arg data, row, column, length
    self~init:super(data,row, column)

    ::method predraw
    self~disdata = "["||center(self~data,self~length-2," ")||"]"

    if self~hasFocus = .true then do
    self~disdata = self~disdata ||" * "
    end

    ::method sendkey
    self~task~run


    /*

    Input box

    */


    ::class inputbox public subclass widget inherit focusable clearable focuscoloured
    ::attribute length
    ::attribute fixedlength
    ::attribute showlength

    ::method init
    expose data row column length
    use arg data, row, column, length
    self~validator = .validator~new() /* default validator used */
    self~fixedlength = .nil
    self~init:super(data, row, column)

    ::method predraw
    self~disdata = left(self~data,self~length,"_")
    if self~hasFocus = .true then do
    self~disdata = self~disdata ||" * "
    end

    if self~showlength= .true then do
    if self~fixedlength = .true then do
    self~disdata = self~disdata length(self~data)||"/"||self~length
    end
    else self~disdata = self~disdata length(self~data)
    end

    ::method sendkey
    use arg key
    if key~bs = .true & length(self~data ) > 0 then do
    self~data = left(self~data, length(self~data)-1 )
    end
    else do
    if self~validator~isNil = .false then do
    if self~validator~isValidKey(key) = .false then key~value = ""
    end
    /*
    can't type too much in a fixed length field
    */
    if self~fixedlength = .true then do
    if length(self~data)+1 > self~length then key~value = ""
    end
    self~data = self~data||key~value
    end

    /*

    */
    ::class searchbox public subclass inputbox
    ::attribute list
    ::attribute keyed
    ::attribute matching

    ::method init
    use arg data, row, column, length
    self~keyed=""
    self~matching=0
    self~init:super(data,row,column,length)

    ::method sendkey
    use arg key
    self~sendkey:super(key)
    self~matching = 0

    if key~bs = .true then do
    if length(self~keyed) >= 1 then do
    self~keyed = strip(left(self~keyed,length(self~keyed)-1))

    end
    end
    else do
    self~keyed = self~keyed||key~value
    end

    do i = 0 to self~list~items -1
    if upper(self~keyed) = upper(left(self~list~at(i),length(self~keyed))) then do
    self~matching = self~matching + 1
    self~data = self~list~at(i)
    end
    end

    if self~matching = 0 then self~data = ""


    ::method predraw
    self~predraw:super()
    self~disdata = self~disdata || "Matching " self~matching


    /*
    Password box
    */


    ::class passwordbox public subclass inputbox
    ::method predraw
    self~disdata = left(copies("*",self~data~length),self~length,"_")
    if self~hasFocus = .true then do
    self~disdata = self~disdata ||" P "
    end


    /*
    Radio box
    */


    ::class radiobox public subclass inputbox
    ::attribute options
    ::attribute selected

    ::method init
    use arg opts, row, column, length
    if .SanityChecker~new~check(opts) = .false then do
    self~lackingrequiredobject = .true
    end

    self~options= .list~new
    self~options= opts
    self~selected = 0
    self~init:super(self~options~at(0),row, column, length)

    ::method predraw
    self~disdata = ""
    do i = 0 to self~options~items -1
    if i = self~selected then do
    self~disdata = self~disdata || " X " || self~options~at(i)
    end
    else do
    self~disdata = self~disdata || " . " || self~options~at(i)
    end
    end
    if self~hasFocus = .true then do
    self~disdata = self~disdata ||" * "
    end

    ::method sendkey
    self~selected = self~selected + 1
    if self~selected = self~options~items then self~selected = 0
    self~data = self~options~at(self~selected)

    ::method clear
    self~selected = 0
    self~data=self~options~at(0)


    /*
    data entry validators
    */

    ::class validator public
    /*
    normal validator letters and numbers
    */
    ::attribute validkeys
    ::attribute fieldhelpstring
    ::attribute fieldhelp

    ::method init
    use arg fieldhelp=.nil
    self~validkeys = .string~graph || " £"
    self~fieldhelpstring = "This field is alphanumeric"
    self~fieldhelp = fieldhelp

    ::method isValidKey
    use arg key

    if self~fieldhelp~isNil = .false then do
    self~fieldhelp~data = self~fieldhelpstring
    end

    if pos(key~value, self~validkeys) > 0 then return .true
    else return .false

    ::method fieldContentValidate
    use arg widget
    if widget~isInstanceOf(.validatable) then do
    self~validatedata(widget)
    end



    ::class numericvalidator public subclass validator
    /*
    numeric validator
    */
    ::method init
    use arg fieldhelp=.nil
    self~init:super(fieldhelp)
    self~validkeys = .string~digit
    self~fieldhelpstring = "This field is numeric only"




    /*
    mixinclasses here

    */

    ::class focusable mixinclass object
    ::attribute hasFocus

    ::method gotFocus
    self~hasfocus = .true
    if self~isInstanceOf(.focuscoloured) then do
    self~changetoFocusColour()
    end


    ::method lostFocus
    self~hasfocus = .false
    if self~isInstanceOf(.focuscoloured) then do
    self~changetoDefaultColour()
    end


    /*

    */
    ::class validatable mixinclass object
    ::attribute valid



    /*
    clears fields to have blank by default. radio boxes need different treatment and to override this
    */

    ::class clearable mixinclass object
    ::method clear
    self~data = ""


    /*
    allows items to be coloured
    */
    ::class coloured mixinclass object
    ::attribute fgcolour
    ::attribute bgcolour

    /*
    focus coloured
    */

    ::class focuscoloured mixinclass object inherit coloured
    ::attribute focusfgcolour
    ::attribute defaultfgcolour

    ::method changetoFocusColour
    if self~focusfgcolour != "FOCUSFGCOLOUR" then do
    self~fgcolour = self~focusfgcolour
    end

    ::method changetoDefaultColour
    if self~defaultfgcolour != "DEFAULTFGCOLOUR" then do
    self~fgcolour = self~defaultfgcolour
    end



    /*
    used to make keybindable tasks
    */
    ::class keybindable mixinclass object
    ::attribute key

    ::method sendkey
    use arg keypressed
    if upper(self~key~value) = upper(keypressed~value) then do
    self~run()
    end

    ::class SanityCheckSupported mixinclass object

    ::method SanityCheckLog
    use arg msg
    self~logger~log(self " " message)


    /*
    because it's nice to have nice code
    */

    ::class SystemQueue subclass RexxQueue
    ::method init
    use strict arg command
    command "| rxqueue"
    self~init:super

    /*
    crashfile
    */

    ::class trapout public
    ::method init
    expose stem
    use arg stem


    ::method unknown
    expose stem
    use arg msg, text
    stem[0] += 1
    index = stem[0]
    stem[index] = text~makeString
    .error~destination
    call lineout("crashfile", msg " " text~makeString)



    /*
    Because it's not particularly easy if you insert a misformed object or widget, this SanityChecker is called to ensure that objects are correctly initialised
    prior to adding them to a window, windowmanager, task etc.
    */

    ::class SanityChecker
    ::attribute errorcheckcode

    ::method check
    use arg obj, callingobj=.nil
    if callingobj <> .nil then do
    if callingobj~isInstanceOf(.SanityCheckSupported) then do
    response = self~performcheck(obj)
    if response = .false then do
    callingobj~SanityCheckLog("Problem "self~errorcheckcode "with object" obj "called from" callingobj)
    end
    end
    end
    else do /* not able to notify back */
    response = self~performcheck(obj)
    end

    return response

    ::method performcheck
    expose checkerrorcode
    use arg obj

    if obj = .nil then do
    say "SanityChecker failed on" obj
    return .false
    end
    return .true




    /*
    override this to perform a job when action occurs
    */
    ::class Task public
    ::attribute window

    ::method init
    use arg window
    self~window= window

    ::method run
    /* override this method */



    /*
    example task to clear the fields
    */
    ::class clearEntryTask public subclass Task

    ::method run
    do i = 0 to (self~window~objects~items -1)
    if self~window~objects~at(i)~isInstanceOf(.clearable) = .true then do
    self~window~objects~at(i)~clear
    end
    end

    /*
    Task to move to a specific window
    */

    ::class goToWindowTask public subclass Task
    ::attribute windownumber

    ::method init
    use arg win, windownumber
    self~windownumber = windownumber
    self~init:super(win)

    ::method run
    self~window~wm~gotoWindow(self~windownumber)



    /*
    this isn't a button, this is registered against a keystroke. Works the same way though
    */
    ::class goToWindowTaskkey public subclass goToWindowTask inherit keybindable




    ::class printAllFieldsToFile public subclass Task

    ::method init
    use arg win
    self~init:super(win)

    ::method run
    do i over self~window~wm~wmdms~allIndexes
    call lineout "fields", i " " self~window~wm~wmdms~at(i) " " self~window~wm~wmdms~at(i)~data
    end



    /*
    WindowManager Data Management Service
    Provides information on whether fields and windows have been displayed, whether they validated, and allows the registration of certain data requirements
    to help for timely development
    at the moment it's just a directory of fields
    */


    ::class WMDataManagementService public subclass Directory

    ::method getValue
    use arg obj
    field = self~get(obj)
    if field~isNil = .false then return .nil
    else do
    return field~data
    end




    ===================== END CLASS FILE =====================

    ===================== Test Application ===================


    /*
    rexxTUI.rex
    Test rexx app for UI
    version 0.12

    */

    /*
    some code to get a crash file in case everything goes pearshaped
    */
    out.0 = 0 -- create the stem
    pdest = .error~destination(.trapout~new(out.))

    /*
    main example code
    */

    /*
    create the window manager and set logging level
    */

    wm = .windowmanager~new(.logger~new)
    wm~logger~setloglevel(-1)


    wmdms =.WMDataManagementService~new

    /*
    create a window
    */


    win = .window~new
    win~logger=wm~logger
    win= .window~new
    win~name = "Window number 1"


    win~bgcolour= .screen~blue
    win~fgcolour= .screen~white

    secondwin= .window~new
    secondwin~name = "Window number 2"
    secondwin~logger = wm~logger
    secondwin~bgcolour= .screen~blue
    secondwin~fgcolour= .screen~brightyellow

    /*
    create some elements for the windows
    */


    label1 = .label~new("this is a label",2,1)
    label2 = .label~new("a second label", 3,1)
    input1 = .inputbox~new("", 2, 20, 30)
    input1~fixedlength=.false
    input1~showlength=.true

    input1~focusfgcolour = .screen~brightgreen

    input2 = .inputbox~new("", 3, 20, 30)
    input2~showlength=.true
    input2~fixedlength=.true

    input2~fgcolour = .screen~green
    input2~focusfgcolour = .screen~brightyellow

    input3 = .passwordbox~new("", 4, 20, 30)
    input3~fgcolour = .screen~green
    input3~focusfgcolour = .screen~brightgreen

    radiooptions = .list~new
    radiooptions~insert("Free like beer")
    radiooptions~insert( "Free like speech")
    radiooptions~insert("Freedom to bare arms")
    input4 = .radiobox~new(radiooptions, 5,20,40)
    input4~fgcolour = .screen~yellow

    fieldhelplabel = .label~new("** I am sure I am not here **",15,20)


    input1s2 = .inputbox~new("",2, 20, 30)
    input2s2 = .searchbox~new("", 3, 20, 30)
    input2s2~list=.list~new()
    input2s2~list~insert("Dasha")
    input2s2~list~insert("Dusy")
    input2s2~list~insert("Thomas")
    input2s2~list~insert("Bob")
    input2s2~list~insert("Marina")
    input2s2~list~insert("Martin")
    input2s2~list~insert("Arthur")
    input2s2~list~insert("Tamsyn")

    input2s2~fgcolour=.screen~brightyellow




    /*
    set up a couple of field validators
    The
    */

    input1~add(.validator~new(fieldhelplabel))
    input2~add(.numericvalidator~new(fieldhelplabel))



    /*
    keyboard listening tasks which executes code ... the same tasks can also be used on buttons
    */

    task1= .goToWindowTaskKey~new(win,1)
    task1~key=.key~new("N")
    task1~key~alt=.true

    task2= .goToWindowTaskKey~new(win,0)

    [continued in next message]

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Bob Bobskin@21:1/5 to Bob Bobskin on Thu Apr 30 08:56:21 2020
    Thanks for all the appreciative comments...
    Uploaded it to github

    https://github.com/BobBobskin/oorexxTUI

    Now with a working slider, the F1 - F10 keybinding working in linux full screen and linux windowed (different keyboard codes eh)

    And a reasonable little demo.


    On Wednesday, 29 April 2020 19:21:17 UTC+1, Bob Bobskin wrote:
    I don't have a windows machine to try it with, and whilst I did put in some (best guess) code to try to make it work on Windows, I wasn't really able to check.

    Given that there is some interest, I think I'll aim to get bored again, and upload it to github or something as a project, so that people can "improve" it.

    Best






    On Wednesday, 29 April 2020 14:02:20 UTC+1, Rony wrote:
    Hi Chris,

    one hint ad Windows: SysTextScreenSize() got updated in ooRexx 5.0 and can be used to find out more
    about the current command line window (and also to set the respective data).

    Your current usage will return the "screen buffer size", which in my case is 135 x 3000.

    The following Rexx program will demonstrate the new features and what they give in my case:

    --- testscreen.rex - begin ---
    -- screen buffer
    parse value SysTextScreenSize() with rows columns

    -- visible window size, positions in buffer (0-based)
    parse value SysTextScreenSize("windowrect") with top left bottom right
    say "current console buffer is" rows "rows by" columns "columns"
    say "current window rectangle is ("top"," left") ("bottom"," right")" -
    "(0-based, position in buffer)"

    say "---"
    say "sysTextScreenSize():" sysTextScreenSize()
    say
    do arg over ("buffersize", "windowrect", "maxwindowsize")
    say "SysTextScreenSize("arg"):" sysTextScreenSize(arg)
    end
    --- testscreen.rex - end ---

    Here the output:

    --- output - begin ---
    current console buffer is 3000 rows by 135 columns
    current window rectangle is (0, 0) (44, 134) (0-based, position in buffer) ---
    sysTextScreenSize(): 3000 135

    SysTextScreenSize(buffersize): 3000 135
    SysTextScreenSize(windowrect): 0 0 44 134
    SysTextScreenSize(maxwindowsize): 56 135
    --- output - end ---

    After having used the command line screen for output from different commands, the buffer got filled
    already and the rectangle that gets shown off the buffer is given e.g. as:

    --- output - begin ---
    current console buffer is 3000 rows by 135 columns
    current window rectangle is (46, 0) (90, 134) (0-based, position in buffer) ---
    sysTextScreenSize(): 3000 135

    SysTextScreenSize(buffersize): 3000 135
    SysTextScreenSize(windowrect): 52 0 96 134 SysTextScreenSize(maxwindowsize): 56 135
    --- output - end ---

    If you look up rexxref.pdf, 8.66. *CHG* SysTextScreenSize (Windows only), you will find more
    information, also about setting/changing these values.

    ---

    Another hint: as you are using ooRexx 5.0 (it is of release quality, faster, comes with very
    interesting new features) you can also use ANSI/Regina style commands with the ability of
    redirecting stdin, stdout and stderr from/to stems, but also from/to e.g. Rexx arrays.
    rexxpg.ref,6.5 ADDRESS Instruction, gives an example of this new ooRexx feature, rexxref.pdf
    documents it in full. You can therefore from now on forgo the " | rxqueue" pipe, if you want.


    ---rony






    On 29.04.2020 14:09, Rony wrote:
    Hi Chris,

    *very* impressive!

    Had to test it on Windows where in principle it works (having ansicon installed, cf.
    <https://github.com/adoxa/ansicon/releases> for the time being).

    One thing that is interesting on Windows: in my case the screen buffer size is 135x3000 (width x
    height) the window size is 135 x 45. It seems that the screen buffer size gets used on Windows. Also
    the cursor positioning does not quite work (it is placed on the first column almost at the bottom),
    though entering text will appear in the first field.

    Hope you remain bored :), please keep up your interesting work!

    ---rony



    On 27.04.2020 16:22, Bob Bobskin wrote:
    On Monday, 27 April 2020 14:59:12 UTC+1, Bob Bobskin wrote:

    I would certainly like to use this TUI. Another good reason for me to switch from Regina to
    OORexx! Just need to decide whether to try version 4.2.0 or dive right into 5.0.0beta. Any
    thoughts? I use Debian Linux.

    Chris

    It's a bit of fun. I wrote it on oorexx 5 beta (I can't remember if I used any of the new
    functionality, I might have).

    From my perspective, on various linux platforms, I am finding v5b working well.

    I (hope) it displays some good OOP and OOD.

    When one can add a password field with this amount of code, it's nice :) >> (my partner disagrees, she looks bemused that anyone can be excited by a box you can type in, in
    a black screen, which does nothing).

    ::class passwordbox subclass inputbox
    ::method predraw
    self~disdata = copies("*",self~length)
    if self~hasFocus = .true then do
    self~disdata = self~disdata ||" P "
    end



    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Bob Bobskin@21:1/5 to All on Thu May 7 03:36:59 2020
    Substantial update made to improve behaviour.
    Would love to have some test feedback on it.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Bob Bobskin@21:1/5 to Bob Bobskin on Sun May 10 13:57:43 2020
    Hopefully, my recent updates will make it work a bit better, and be a bit more tolerant on errors.

    Updated a bit.

    I have a question, has anyone made a multi platform version of winsystm.cls?



    On Thursday, 7 May 2020 11:37:00 UTC+1, Bob Bobskin wrote:
    Substantial update made to improve behaviour.
    Would love to have some test feedback on it.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)