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
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.
dimensions of window
hash containing dimensions
dimensions of window
panel related to window, used to delete it in the end
dimensions of window
dimensions of window
Public Class Methods
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
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
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
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
# File lib/canis/core/system/window.rb, line 261 def attroff *args FFI::NCurses.wattroff @window, *args end
# File lib/canis/core/system/window.rb, line 258 def attron *args FFI::NCurses.wattron @window, *args end
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
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
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
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
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
called by destroy()
# File lib/canis/core/system/window.rb, line 255 def delwin Ncurses.delwin(@window) end
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
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
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
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
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
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 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
# 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
# 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
# File lib/canis/core/system/window.rb, line 321 def print(string, width = self.width) w = width == 0? Ncurses.COLS : width waddnstr(string.to_s, w) # changed 2011 dts end
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 ?
# File lib/canis/core/system/window.rb, line 616 def print_border row, col, height, width, color, att=Ncurses::A_NORMAL raise "height needs to be supplied." if height.nil? raise "width needs to be supplied." if width.nil? att ||= Ncurses::A_NORMAL #$log.debug " inside window print_border r #{row} c #{col} h #{height} w #{width} " # 2009-11-02 00:45 made att nil for blanking out # FIXME - in tabbedpanes this clears one previous line ??? XXX when using a textarea/view # when using a pad this calls pads printstring which again reduces top and left !!! 2010-01-26 23:53 ww=width-2 clr = " "*ww (row+1).upto(row+height-1) do |r| printstring( r, col+1, clr, color, att) end print_border_only row, col, height, width, color, att end
prints the border for message boxes
NOTE : FOR MESSAGEBOXES ONLY !!!! Then why not move to messagebox FIXME
# File lib/canis/core/system/window.rb, line 583 def print_border_mb row, col, height, width, color, attr # the next is for xterm-256 att = get_attrib attr len = width len = Ncurses.COLS-0 if len == 0 # print a bar across the screen #attron(Ncurses.COLOR_PAIR(color) | att) # this works for newmessagebox but not for old one. # Even now in some cases some black shows through, if the widget is printing spaces # such as field or textview on a messagebox. # 2016-01-14 - replacing 1 with space since junk is showing up in some cases. space_char = " ".codepoints.first (row-1).upto(row+height-1) do |r| # this loop clears the screen, printing spaces does not work since ncurses does not do anything mvwhline(r, col, space_char, len) end #attroff(Ncurses.COLOR_PAIR(color) | att) mvwaddch row, col, Ncurses::ACS_ULCORNER mvwhline( row, col+1, Ncurses::ACS_HLINE, width-6) mvwaddch row, col+width-5, Ncurses::ACS_URCORNER mvwvline( row+1, col, Ncurses::ACS_VLINE, height-4) mvwaddch row+height-3, col, Ncurses::ACS_LLCORNER mvwhline(row+height-3, col+1, Ncurses::ACS_HLINE, width-6) mvwaddch row+height-3, col+width-5, Ncurses::ACS_LRCORNER mvwvline( row+1, col+width-5, Ncurses::ACS_VLINE, height-4) end
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 ?
# File lib/canis/core/system/window.rb, line 640 def print_border_only row, col, height, width, color, att=Ncurses::A_NORMAL if att.nil? att = Ncurses::A_NORMAL else att = get_attrib att end wattron(Ncurses.COLOR_PAIR(color) | att) mvwaddch row, col, Ncurses::ACS_ULCORNER mvwhline( row, col+1, Ncurses::ACS_HLINE, width-2) mvwaddch row, col+width-1, Ncurses::ACS_URCORNER mvwvline( row+1, col, Ncurses::ACS_VLINE, height-1) mvwaddch row+height-0, col, Ncurses::ACS_LLCORNER mvwhline(row+height-0, col+1, Ncurses::ACS_HLINE, width-2) mvwaddch row+height-0, col+width-1, Ncurses::ACS_LRCORNER mvwvline( row+1, col+width-1, Ncurses::ACS_VLINE, height-1) wattroff(Ncurses.COLOR_PAIR(color) | att) end
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
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
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
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
END FFI
# File lib/canis/core/system/window.rb, line 267 def resize resize_with(@layout) end
# 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
# 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
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 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
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
returns name of window or self (mostly for debugging)
# File lib/canis/core/system/window.rb, line 713 def to_s; @name || self; end
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
# File lib/canis/core/system/window.rb, line 422 def visible? @visible end
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
ADDED DUE TO FFI
# File lib/canis/core/system/window.rb, line 250 def wrefresh Ncurses.wrefresh(@window) end
# File lib/canis/core/system/window.rb, line 274 def x=(n) move(y, n) end
# File lib/canis/core/system/window.rb, line 275 def y=(n) move(n, x) end
Private Instance Methods
# File lib/canis/core/system/window.rb, line 521 def get_default_color_parser require 'canis/core/util/defaultcolorparser' @color_parser || DefaultColorParser.new end