class Canis::Window

A Basic class of this library, all output is eventually done on a window. Root windows and dialogs use this class. A root window covers the entire screen, and is the basis of an application usually.

Examples

w = Canis::Window.root_window

w = Canis::Window.create_window( ht, wid, top, left)

w = Canis::Window.new( ht, wid, top, left)

layout = { :height => ht, :width => w, :top => t, :left => l }
w = Canis::Window.new( layout )

Commonly used methods

- destroy
- printstring
- wrefresh
- getchar
- print_border

Attributes

form[RW]

attr_accessor :modified # has it been modified and may need a refresh 2014-04-22 - 10:23 CLEANUP

for root windows we need to know the form so we can ask it to update when
  there are overlapping windows.
height[R]

dimensions of window

layout[RW]

hash containing dimensions

left[R]

dimensions of window

name[RW]
panel[R]

panel related to window, used to delete it in the end

top[R]

dimensions of window

width[R]

dimensions of window

Public Class Methods

create_window(h=0, w=0, t=0, l=0) click to toggle source

2009-10-13 12:24 not used as yet this is an alternative constructor created if you don't want to create a hash first

2011-09-21 V1.3.1 You can now send an array to Window constructor
# File lib/canis/core/system/window.rb, line 201
def self.create_window(h=0, w=0, t=0, l=0)
  layout = { :height => h, :width => w, :top => t, :left => l }
  @window = Window.new(layout)
  return @window
end
new(*args) click to toggle source

window init {{{ creation and layout related. @param [Array, Hash] window coordinates (ht, w, top, left)

or

@param [int, int, int, int] window coordinates (ht, w, top, left)

# File lib/canis/core/system/window.rb, line 74
def initialize(*args)

  case args.size
  when 1
    case args[0]
    when Array, Hash
     layout = args[0]
    else
      raise ArgumentError, "Window expects 4 ints, array of 4 ints, or Hash in constructor"
    end
  when 4
    layout = { :height => args[0], :width => args[1], :top => args[2], :left => args[3] }
  end

  @visible = true
  set_layout(layout)

  $log.debug "XXX:WINDOW got h #{@height}, w #{@width}, t #{@top}, l #{@left} "

  @height = FFI::NCurses.LINES if @height == 0   # 2011-11-14 added since tired of checking for zero
  @width = FFI::NCurses.COLS   if @width == 0

  @window = FFI::NCurses.newwin(@height, @width, @top, @left) # added FFI 2011-09-6
  # trying out refreshing underlying window.
  $global_windows ||= []
  # this causes issues padrefresh failing when display_list does a resize.
  $global_windows << self
  @panel = Ncurses::Panel.new(@window) # added FFI 2011-09-6
  #$error_message_row = $status_message_row = Ncurses.LINES-1
  $error_message_row ||= Ncurses.LINES-1
  $error_message_col ||= 1 # ask (bottomline) uses 0 as default so you can have mismatch. XXX
  $status_message ||= Canis::Variable.new # in case not an App

  # 2014-05-07 - 12:29 CANIS earlier this was called $key_map but that suggests a map.
  $key_map_type ||= :vim
  $esc_esc = true; # gove me double esc as 2727 so i can map it.
  init_vars

  unless @key_reader
    create_default_key_reader
  end


end
refresh_all(current_win=nil) click to toggle source

This refreshes the root window whenever overlapping windows are destroyed or moved. This works by asking the root window's form to repaint all its objects. This is now being called whenever a window is destroyed (and also resized). However, it must manually be called if you move a window. NOTE : if there are too many root windows, this could get expensive since we are updating all. We may need to have a way to specify which window to repaint.

If there are non-root windows above, we may have manually refresh only the previous one.

FIXME NOTE current our examples have lists and textpads that cover the root window so the

refresh all is fine, but if there is screen area vacant then that will still be left black
Similarly in the case of a dialog below, the window and box will not be painted.

We may need a key for refreshing the entire window, such as ^L in which the form and the window

is repainted.
# File lib/canis/core/system/window.rb, line 162
def self.refresh_all current_win=nil
  #Ncurses.touchwin(FFI::NCurses.stdscr)
  # above blanks out entire screen
  # in case of multiple root windows lets just do last otherwise too much refreshing.
  gw = $global_windows
  $log.debug " REFRESH_ALL windows count = #{gw.count} "
  if current_win
    gw = $global_windows.select {|e| e != current_win }
  end
  return unless gw.last
  wins = [ gw.last ]
  wins.each_with_index do |w,i|
    $log.debug " REFRESH_ALL on #{w.name} (#{i}) sending 1000"
    # NOTE 2014-05-01 - 20:25 although we have reached the root window from any level
    #  however, this is sending the hack to whoever is trapping the key, which in our current
    #  case happends to be Viewer, *not* the root form. We need to send to root form.
    f = w.form
    if f
      # send hack to root windows form if passed.
      f.handle_key 1000
    else
      $log.warn " REFRESH_ALL on #{w.name} (#{i}) NO FORM so could not send 1000 #{f}"
    end
    #w.ungetch(1000)
  # below blanks out entire screen too
    #FFI::NCurses.touchwin(w.get_window)
    #$log.debug "XXX:  refreshall diong window "
    #w.hide
    #w.show
    #Ncurses.refresh
    #w.wrefresh
  end
  #Ncurses::Panel.update_panels
end
root_window(layout = { :height => 0, :width => 0, :top => 0, :left => 0 }) click to toggle source

this is an alternative constructor, which by default creates a window that covers the entire screen

# File lib/canis/core/system/window.rb, line 135
def self.root_window(layout = { :height => 0, :width => 0, :top => 0, :left => 0 })

  @layout = layout
  @window = Window.new(@layout)
  @window.name = "Window::ROOTW:#{$global_windows.count}"
  @window.wrefresh
  Ncurses::Panel.update_panels
  # earlier we only put root window, now we may need to do all (bline - numbered menu - alert)
  $global_windows << @window unless $global_windows.include? @window
  return @window
end

Public Instance Methods

attroff(*args) click to toggle source
# File lib/canis/core/system/window.rb, line 261
def attroff *args
  FFI::NCurses.wattroff @window, *args
end
attron(*args) click to toggle source
# File lib/canis/core/system/window.rb, line 258
def attron *args
  FFI::NCurses.wattron @window, *args
end
close_command(*args, &block) click to toggle source

actions to perform when window closed.

Example

@window.close_command do
   if confirm("Save tasks?", :default_button => 0)
       take some actions
    end
end

@see command (alias)

# File lib/canis/core/system/window.rb, line 723
def close_command *args, &block
  @close_command ||= []
  @close_args ||= []
  @close_command << block
  @close_args << args
end
Also aliased as: command
color_parser(f) click to toggle source

supply with a color parser, if you supplied formatted text

# File lib/canis/core/system/window.rb, line 527
def color_parser f
  $log.debug "XXX:  color_parser setting in window to #{f} "
  require 'canis/core/include/colorparser'
  if f == :tmux
    @color_parser = get_default_color_parser()
  else
    @color_parser = f
  end
end
command(*args, &block)
Alias for: close_command
confirm_close_command(*args, &block) click to toggle source

set a single command to confirm whether window shoud close or not Block should return true or false for closing or not

Examples

@window.confirm_close_command do
   confirm "Sure you wanna quit?", :default_button => 1
end
# File lib/canis/core/system/window.rb, line 739
def confirm_close_command *args, &block
  @confirm_close_command = block
  @confirm_close_args    = args
end
convert_to_chunk(s, colorp=$datacolor, att=FFI::NCurses::A_NORMAL) click to toggle source

Takes a formatted string and converts the parsed parts to chunks.

@param [String] takes the entire line or string and breaks into an array of chunks @yield chunk if block @return [ChunkLine] # [Array] array of chunks

# File lib/canis/core/system/window.rb, line 543
def convert_to_chunk s, colorp=$datacolor, att=FFI::NCurses::A_NORMAL
  unless @color_parser
    require 'canis/core/include/colorparser'
    @color_parser = get_default_color_parser()
    @converter = Chunks::ColorParser.new @color_parser
    # we need to know set the parent in colorparser. 2014-05-26 - 14:49
    @converter.form = self.form
  end
  @converter.convert_to_chunk s, colorp, att
end
create_default_key_reader() click to toggle source

creates a key reader unless overridden by application which should be rare.

# File lib/canis/core/system/window.rb, line 764
def create_default_key_reader
  @key_reader = DefaultKeyReader.new self
end
delwin() click to toggle source

called by destroy()

# File lib/canis/core/system/window.rb, line 255
def delwin 
  Ncurses.delwin(@window)
end
destroy() click to toggle source

destroy window, panel and any pads that were requested

# File lib/canis/core/system/window.rb, line 429
def destroy
  # typically the ensure block should have this

  #$log.debug "win destroy start"

  $global_windows.delete self
  Ncurses::Panel.del_panel(@panel.pointer) if @panel
  delwin() if @window 
  Ncurses::Panel.update_panels # added so below window does not need to do this 2011-10-1

  # destroy any pads that were created by widgets using get_pad
  @pads.each { |pad|  
    FFI::NCurses.delwin(pad) if pad 
    pad = nil
  } if @pads
  # added here to hopefully take care of this issue once and for all.
  # Whenever any window is destroyed, the root window is repainted.
  #
  # 2014-08-18 - 20:35 trying out without refresh  all since lower dialog gets erased
  Window.refresh_all
  #$log.debug "win destroy end"
end
fire_close_handler() click to toggle source

Called when window close is requested by user. Executes confirm_close block and if it succeeds then executes close commands called by util/app.rb

# File lib/canis/core/system/window.rb, line 747
def fire_close_handler
  if @confirm_close_command
    comm = @confirm_close_command
    ret = comm.call(self, *@confirm_close_args) 
    return ret unless ret # only return if false returned
  end
  if @close_command
    @close_command.each_with_index do |comm, ix|
      comm.call(self, *@close_args[ix]) if comm
    end
  end
  @close_command = nil
  @close_args = nil
  return true
end
get_pad(content_rows, content_cols) click to toggle source

Widgets can get window to create a pad for them. This way when the window

is destroyed, it will delete all the pads. A widget wold not be able to do this.

The destroy method of the widget will be called.

# File lib/canis/core/system/window.rb, line 456
def get_pad content_rows, content_cols
  pad = FFI::NCurses.newpad(content_rows, content_cols)
  @pads ||= []
  @pads << pad
  ## added 2013-03-05 - 19:21 without next line how was pad being returned
  return pad
end
get_window() click to toggle source

This used to return an Ncurses window object, and you could call methods on it Now it returns a FFI::NCurses.window pointer which you cannot call methods on. You have to pass it to FFI::NCurses.<method>

# File lib/canis/core/system/window.rb, line 710
def get_window; @window; end
getch() click to toggle source

reads a character from keyboard and returns NOTE:

if a function key is pressed, multiple such ints will be returned one after the other
so the caller must decipher the same. See +getchar()+

@return int @return -1 if no char read ORIGINALLY After esc there was a timeout, but after others there was notimeout, so it would wait indefinitely for a key NOTE : caller may set a timeout prior to calling, but not change setting after since this method maintains the default state in ensure. e.g. widget.rb does a blocking get in _process_key Curses sets a timeout when ESCAPE is pressed, it is called ESCDELAY and is 1000 milliseconds. You may reduce it if you are not on some old slow telnet session. This returns faster from an esc although there are still some issues. ESC-ESC becomes an issue, but if i press ESC-ESC-1 then esc-esc comes together. otherwise there is a -1 between each esc.

# File lib/canis/core/system/window.rb, line 354
def getch
  #c = @window.getch
  #FFI::NCurses::nodelay(@window, true)
  #FFI::NCurses::wtimeout(@window, 0)
  #$log.debug " #{Time.now.to_f} inside MAIN before getch "
  c = FFI::NCurses.wgetch(@window)
  # the only reason i am doing this is so ESC can be returned if no key is pressed
  # after that, not sure how this effects everything. most likely I should just
  # go back to using a wtimeout, and not worry about resize requiring a keystroke
  if c == 27
    $escstart = Time.now.to_f
    # if ESC pressed don't wait too long for next key
    Ncurses::wtimeout(@window, $ncurses_timeout || 500) # will wait n millisecond on wgetch so that we can return if no
  else
    FFI::NCurses.set_escdelay(100)
    # this means keep waiting for a key.
    Ncurses::nowtimeout(@window, true)
  end
  c

rescue SystemExit, Interrupt 
  #FFI::NCurses.flushinp
  3 # is C-c
rescue StandardError
  -1 # is C-c
ensure
  # whatever the default is, is to be set here in case caller changed it.
  #FFI::NCurses::nodelay(@window, true)
end
getchar() click to toggle source

Earlier this was handled by window itself. Now we delegate to a reader @return int keycode, can be function key or meta or arrow key.

NOTE:

This is called by user programs in a loop. 
We are now moving from returning an int to returning a string similar to what
user would get on commandline using C-v
# File lib/canis/core/system/window.rb, line 392
def getchar
  @key_reader.getchar
end
hide() click to toggle source

hide the window

# File lib/canis/core/system/window.rb, line 404
def hide
  #return unless visible? # added 2011-10-14 these 2 are not behaving properly
  Ncurses::Panel.hide_panel @panel.pointer
  #Ncurses.refresh # wnoutrefresh
  Ncurses::Panel.update_panels # added so below window does not need to do this 2011-10-1
  @visible = false
end
init_vars() click to toggle source
# File lib/canis/core/system/window.rb, line 118
def init_vars
  Ncurses::keypad(@window, true)
  # Added this so we can get Esc, and also C-c pressed in succession does not crash system
  #  2011-12-20 half-delay crashes system as does cbreak
  #This causes us to be unable to process gg qq since getch won't wait.
  #FFI::NCurses::nodelay(@window, bf = true)
  # wtimeout was causing RESIZE sigwinch to only happen after pressing a key
  #Ncurses::wtimeout(@window, $ncurses_timeout || 500) # will wait a second on wgetch so we can get gg and qq
  #@stack = [] # since we have moved to handler 2014-04-20 - 11:15
  @name ||="#{self}"
  $log.debug "  WINDOW NAME is #{@name} "
  @modified = true
  $catch_alt_digits ||= false # is this where is should put globals ? 2010-03-14 14:00 XXX
end
method_missing(name, *args) click to toggle source
# File lib/canis/core/system/window.rb, line 294
def method_missing(name, *args)
  name = name.to_s
  if (name[0,2] == "mv")
    test_name = name.dup
    test_name[2,0] = "w" # insert "w" after"mv"
    if (FFI::NCurses.respond_to?(test_name))
      return FFI::NCurses.send(test_name, @window, *args)
    end
  end
  test_name = "w" + name
  if (FFI::NCurses.respond_to?(test_name))
    return FFI::NCurses.send(test_name, @window, *args)
  end
  FFI::NCurses.send(name, @window, *args)
end
move(y,x)
Alias for: wmove
print(string, width = self.width) click to toggle source
print_border(row, col, height, width, color, att=Ncurses::A_NORMAL) click to toggle source

prints a border around a widget, CLEARING the area.

If calling with a pad, you would typically use 0,0, h-1, w-1.
FIXME can this be moved to module Bordertitle ?
print_border_mb(row, col, height, width, color, attr) click to toggle source

prints the border for message boxes

NOTE : FOR MESSAGEBOXES ONLY !!!! Then why not move to messagebox FIXME

print_border_only(row, col, height, width, color, att=Ncurses::A_NORMAL) click to toggle source
print just the border, no cleanup

+ Earlier, we would clean up. Now in some cases, i'd like + to print border over what's been done.

XXX this reduces 1 from width but not height !!! FIXME 
 FIXME can this be moved to module Bordertitle ?
printstring(r,c,string, color, att = Ncurses::A_NORMAL) click to toggle source

prints a string at row, col, with given color and attribute added by rk 2008-11-29 19:01 I usually use this, not the others ones here @param r - row @param c - col @param string - text to print @param color - color pair @ param att - ncurses attribute: normal, bold, reverse, blink, underline

# File lib/canis/core/system/window.rb, line 565
def printstring(r,c,string, color, att = Ncurses::A_NORMAL)

  raise "printstring recvd nil row #{r} or col #{c} " if r.nil? || c.nil?
  #$log.debug " #{@name} inside window printstring r #{r} c #{c} #{string} "
  if att.nil? 
    att = Ncurses::A_NORMAL
  else
    att = get_attrib att
  end

  wattron(Ncurses.COLOR_PAIR(color) | att)
  mvwprintw(r, c, "%s", :string, string);
  wattroff(Ncurses.COLOR_PAIR(color) | att)
end
printstring_formatted(r,c,content, color, att = Ncurses::A_NORMAL) click to toggle source

prints a string formatted in our new experimental coloring format taken from tmux. Currently, since i have chunks workings, i convert to chunks and use the existing print function. This could change. An example of a formatted string is: s=“#testing chunks #[fg=yellow, bg=red, bold]yellow #[reverse] reverseme \

#[normal]normal#[bg = black]just yellow#[fg=blue],blue now #[underline] underlined text"

Ideally I should push and pop colors which the shell does not do with ansi terminal sequences. That way i can have a line in red,

with some word in yellow, and then the line continues in red.
# File lib/canis/core/system/window.rb, line 504
def printstring_formatted(r,c,content, color, att = Ncurses::A_NORMAL)
  att = get_attrib att unless att.is_a? Integer
  chunkline = convert_to_chunk(content, color, att)
  printstring_or_chunks r,c, chunkline, color, att
end
printstring_formatted_right(r,c,content, color, att = Ncurses::A_NORMAL) click to toggle source

print a formatted line right aligned c (col) is ignored and calculated based on width and unformatted string length

# File lib/canis/core/system/window.rb, line 513
def printstring_formatted_right(r,c,content, color, att = Ncurses::A_NORMAL)
  clean = content.gsub /#\[[^\]]*\]/,''  # clean out all markup
  #c = actual_width() - clean.length # actual width not working if resize
  c = getmaxx() - clean.length
  printstring_formatted(r,c,content, color, att )
end
printstring_or_chunks(r,c,content, color, att = Ncurses::A_NORMAL) click to toggle source

print and chunk related — {{{

Allows user to send data as normal string or chunks for printing An array is assumed to be a chunk containing color and attrib info

# File lib/canis/core/system/window.rb, line 469
def printstring_or_chunks(r,c,content, color, att = Ncurses::A_NORMAL)
  if content.is_a? String
    printstring(r,c,content, color, att)
  elsif content.is_a? AbstractChunkLine
    #$log.debug "XXX: using chunkline" # 2011-12-10 12:40:13
    wmove r, c
    a = get_attrib att
    # please add width to avoid overflow
    show_colored_chunks content, color, a
  elsif content.is_a? Array
    # several chunks in one row - NOTE Very experimental may change
    if content[0].is_a? Array
      $log.warn "XXX: WARNING outdated should send in a chunkline"
      wmove r, c
      a = get_attrib att
      # please add width to avoid overflow
      show_colored_chunks content, color, a
    else
      # a single row chunk - NOTE Very experimental may change
      text = content[1].dup
      printstring r, c, text, content[0] || color, content[2] || att
    end
  end
end
resize() click to toggle source

END FFI

# File lib/canis/core/system/window.rb, line 267
def resize
  resize_with(@layout)
end
resize_with(layout) click to toggle source
# File lib/canis/core/system/window.rb, line 207
def resize_with(layout)
  #$log.debug " DARN ! This awready duz a resize!! if h or w or even top or left changed!!! XXX"
  set_layout(layout)
  $log.debug "  resize after set_layout: #{@height} , #{@width} , #{@top} , #{@left},  "
  wresize(height, width)
  mvwin(top, left)
  Window.refresh_all self
end
respond_to?(name) click to toggle source
# File lib/canis/core/system/window.rb, line 310
def respond_to?(name)
  name = name.to_s
  if (name[0,2] == "mv" && FFI::NCurses.respond_to?("mvw" + name[2..-1]))
    return true
  end
  FFI::NCurses.respond_to?("w" + name) || FFI::NCurses.respond_to?(name)
end
set_layout(layout) click to toggle source

Creating variables case of array, we still create the hash @param array or hash containing h w t and l

# File lib/canis/core/system/window.rb, line 229
def set_layout(layout)
  case layout
  when Array
    $log.error  "NIL in window constructor" if layout.include? nil
    raise ArgumentError, "Nil in window constructor" if layout.include? nil
    # NOTE this is just setting, and not replacing zero with max values
    @height, @width, @top, @left = *layout
    raise ArgumentError, "Nil in window constructor" if @top.nil? || @left.nil?

    @layout = { :height => @height, :width => @width, :top => @top, :left => @left }
  when Hash
    @layout = layout

    [:height, :width, :top, :left].each do |name|
      instance_variable_set("@#{name}", @layout[name])
    end
  end
end
show() click to toggle source

show the window

# File lib/canis/core/system/window.rb, line 413
def show
  #return if visible? # added 2011-10-14 these 2 are not behaving properly
  Ncurses::Panel.show_panel @panel.pointer
  #Ncurses.refresh # wnoutrefresh
  Ncurses::Panel.update_panels # added so below window does not need to do this 2011-10-1
  @visible = true
end
show_colored_chunks(chunks, defcolor = nil, defattr = nil, wid = 999, pcol = 0) click to toggle source

Previously this printed a chunk as a full line, I've modified it to print on one line. This can be used for running text. NOTE 2013-03-08 - 17:02 added width so we don't overflow NOTE 2014-05-11 - textpad has its own version, so does not call this.

# File lib/canis/core/system/window.rb, line 663
def show_colored_chunks(chunks, defcolor = nil, defattr = nil, wid = 999, pcol = 0)
  return unless visible?
  ww = 0
  chunks.each_with_color do |text, color, attrib|

    ## 2013-03-08 - 19:11 take care of scrolling by means of pcol
    if pcol > 0
      if pcol > text.length 
        # ignore entire chunk and reduce pcol
        pcol -= text.length
        next
      else
        # print portion of chunk and zero pcol
        text = text[pcol..-1]
        pcol = 0
      end
    end
    oldw = ww
    ww += text.length
    if ww > wid
      # if we are exceeding the width then by howmuch
      rem = wid - oldw
      if rem > 0
        # take only as much as we are allowed
        text = text[0,rem]
      else
        break
      end
    end

    color ||= defcolor
    attrib ||= defattr

    cc, bg = ColorMap.get_colors_for_pair color
    #$log.debug "XXX: CHUNK window #{text}, cp #{color} ,  attrib #{attrib}. #{cc}, #{bg} "
    color_set(color,nil) if color
    wattron(attrib) if attrib
    #print(text)
    waddnstr(text.to_s, @width) # changed 2014-04-22 - 11:59  to reduce a function
    wattroff(attrib) if attrib
  end
end
to_s() click to toggle source

returns name of window or self (mostly for debugging)

# File lib/canis/core/system/window.rb, line 713
def to_s; @name || self; end
ungetch(ch) click to toggle source

return the character to the keyboard buffer to be read again.

# File lib/canis/core/system/window.rb, line 334
def ungetch(ch)
  Ncurses.ungetch(ch)
end
visible?() click to toggle source
# File lib/canis/core/system/window.rb, line 422
def visible?
  @visible
end
wmove(y,x) click to toggle source

move window to row and col

# File lib/canis/core/system/window.rb, line 288
def wmove y,x
  #Ncurses.wmove @window, y, x
  FFI::NCurses.wmove @window, y, x
end
Also aliased as: move
wrefresh() click to toggle source

ADDED DUE TO FFI

# File lib/canis/core/system/window.rb, line 250
def wrefresh
  Ncurses.wrefresh(@window)
end
x=(n) click to toggle source

Ncurses

# File lib/canis/core/system/window.rb, line 274
def x=(n) move(y, n) end
y=(n) click to toggle source
# File lib/canis/core/system/window.rb, line 275
def y=(n) move(n, x) end

Private Instance Methods

get_default_color_parser() click to toggle source
# File lib/canis/core/system/window.rb, line 521
def get_default_color_parser
  require 'canis/core/util/defaultcolorparser'
  @color_parser || DefaultColorParser.new
end