canis 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +45 -0
- data/CHANGES +52 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +24 -0
- data/Rakefile +2 -0
- data/canis.gemspec +25 -0
- data/examples/alpmenu.rb +46 -0
- data/examples/app.sample +19 -0
- data/examples/appemail.rb +191 -0
- data/examples/atree.rb +105 -0
- data/examples/bline.rb +181 -0
- data/examples/common/devel.rb +319 -0
- data/examples/common/file.rb +93 -0
- data/examples/data/README.markdown +9 -0
- data/examples/data/brew.txt +38 -0
- data/examples/data/color.2 +37 -0
- data/examples/data/gemlist.txt +59 -0
- data/examples/data/lotr.txt +12 -0
- data/examples/data/ports.txt +136 -0
- data/examples/data/table.txt +37 -0
- data/examples/data/tasks.csv +88 -0
- data/examples/data/tasks.txt +27 -0
- data/examples/data/todo.txt +16 -0
- data/examples/data/todocsv.csv +28 -0
- data/examples/data/unix1.txt +21 -0
- data/examples/data/unix2.txt +11 -0
- data/examples/dbdemo.rb +506 -0
- data/examples/dirtree.rb +177 -0
- data/examples/newtabbedwindow.rb +100 -0
- data/examples/newtesttabp.rb +92 -0
- data/examples/tabular.rb +212 -0
- data/examples/tasks.rb +179 -0
- data/examples/term2.rb +88 -0
- data/examples/testbuttons.rb +307 -0
- data/examples/testcombo.rb +102 -0
- data/examples/testdb.rb +182 -0
- data/examples/testfields.rb +208 -0
- data/examples/testflowlayout.rb +43 -0
- data/examples/testkeypress.rb +98 -0
- data/examples/testlistbox.rb +187 -0
- data/examples/testlistbox1.rb +199 -0
- data/examples/testmessagebox.rb +144 -0
- data/examples/testprogress.rb +116 -0
- data/examples/testree.rb +107 -0
- data/examples/testsplitlayout.rb +53 -0
- data/examples/testsplitlayout1.rb +49 -0
- data/examples/teststacklayout.rb +48 -0
- data/examples/testwsshortcuts.rb +68 -0
- data/examples/testwsshortcuts2.rb +129 -0
- data/lib/canis.rb +16 -0
- data/lib/canis/core/docs/index.txt +104 -0
- data/lib/canis/core/docs/list.txt +16 -0
- data/lib/canis/core/docs/style_help.yml +34 -0
- data/lib/canis/core/docs/tabbedpane.txt +15 -0
- data/lib/canis/core/docs/table.txt +31 -0
- data/lib/canis/core/docs/textpad.txt +48 -0
- data/lib/canis/core/docs/tree.txt +23 -0
- data/lib/canis/core/include/.DS_Store +0 -0
- data/lib/canis/core/include/action.rb +83 -0
- data/lib/canis/core/include/actionmanager.rb +49 -0
- data/lib/canis/core/include/appmethods.rb +179 -0
- data/lib/canis/core/include/bordertitle.rb +49 -0
- data/lib/canis/core/include/canisparser.rb +100 -0
- data/lib/canis/core/include/colorparser.rb +437 -0
- data/lib/canis/core/include/defaultfilerenderer.rb +64 -0
- data/lib/canis/core/include/io.rb +320 -0
- data/lib/canis/core/include/layouts/SplitLayout.rb +161 -0
- data/lib/canis/core/include/layouts/abstractlayout.rb +213 -0
- data/lib/canis/core/include/layouts/flowlayout.rb +104 -0
- data/lib/canis/core/include/layouts/stacklayout.rb +109 -0
- data/lib/canis/core/include/listbindings.rb +89 -0
- data/lib/canis/core/include/listeditable.rb +319 -0
- data/lib/canis/core/include/listoperations.rb +61 -0
- data/lib/canis/core/include/listselectionmodel.rb +388 -0
- data/lib/canis/core/include/multibuffer.rb +173 -0
- data/lib/canis/core/include/ractionevent.rb +73 -0
- data/lib/canis/core/include/rchangeevent.rb +27 -0
- data/lib/canis/core/include/rhistory.rb +95 -0
- data/lib/canis/core/include/rinputdataevent.rb +47 -0
- data/lib/canis/core/include/textdocument.rb +111 -0
- data/lib/canis/core/include/vieditable.rb +175 -0
- data/lib/canis/core/include/widgetmenu.rb +66 -0
- data/lib/canis/core/system/colormap.rb +165 -0
- data/lib/canis/core/system/keydefs.rb +32 -0
- data/lib/canis/core/system/ncurses.rb +237 -0
- data/lib/canis/core/system/panel.rb +129 -0
- data/lib/canis/core/system/window.rb +1081 -0
- data/lib/canis/core/util/ansiparser.rb +119 -0
- data/lib/canis/core/util/app.rb +696 -0
- data/lib/canis/core/util/basestack.rb +412 -0
- data/lib/canis/core/util/defaultcolorparser.rb +84 -0
- data/lib/canis/core/util/extras/README +5 -0
- data/lib/canis/core/util/extras/bottomline.rb +1815 -0
- data/lib/canis/core/util/extras/padreader.rb +192 -0
- data/lib/canis/core/util/focusmanager.rb +31 -0
- data/lib/canis/core/util/helpmanager.rb +160 -0
- data/lib/canis/core/util/oldwidgetshortcuts.rb +304 -0
- data/lib/canis/core/util/promptmenu.rb +235 -0
- data/lib/canis/core/util/rcommandwindow.rb +933 -0
- data/lib/canis/core/util/rdialogs.rb +520 -0
- data/lib/canis/core/util/textutils.rb +74 -0
- data/lib/canis/core/util/viewer.rb +238 -0
- data/lib/canis/core/util/widgetshortcuts.rb +508 -0
- data/lib/canis/core/widgets/applicationheader.rb +103 -0
- data/lib/canis/core/widgets/box.rb +58 -0
- data/lib/canis/core/widgets/divider.rb +310 -0
- data/lib/canis/core/widgets/extras/README.md +12 -0
- data/lib/canis/core/widgets/extras/rtextarea.rb +960 -0
- data/lib/canis/core/widgets/extras/stackflow.rb +474 -0
- data/lib/canis/core/widgets/keylabelprinter.rb +194 -0
- data/lib/canis/core/widgets/listbox.rb +326 -0
- data/lib/canis/core/widgets/listfooter.rb +86 -0
- data/lib/canis/core/widgets/rcombo.rb +210 -0
- data/lib/canis/core/widgets/rcontainer.rb +415 -0
- data/lib/canis/core/widgets/rlink.rb +30 -0
- data/lib/canis/core/widgets/rmenu.rb +970 -0
- data/lib/canis/core/widgets/rmenulink.rb +30 -0
- data/lib/canis/core/widgets/rmessagebox.rb +400 -0
- data/lib/canis/core/widgets/rprogress.rb +118 -0
- data/lib/canis/core/widgets/rtabbedpane.rb +631 -0
- data/lib/canis/core/widgets/rtabbedwindow.rb +70 -0
- data/lib/canis/core/widgets/rwidget.rb +3634 -0
- data/lib/canis/core/widgets/scrollbar.rb +147 -0
- data/lib/canis/core/widgets/statusline.rb +113 -0
- data/lib/canis/core/widgets/table.rb +1072 -0
- data/lib/canis/core/widgets/tabular.rb +264 -0
- data/lib/canis/core/widgets/textpad.rb +1674 -0
- data/lib/canis/core/widgets/tree.rb +690 -0
- data/lib/canis/core/widgets/tree/treecellrenderer.rb +150 -0
- data/lib/canis/core/widgets/tree/treemodel.rb +432 -0
- data/lib/canis/version.rb +3 -0
- metadata +229 -0
@@ -0,0 +1,129 @@
|
|
1
|
+
require "ffi-ncurses"
|
2
|
+
module Ncurses # changed on 2011-09-8
|
3
|
+
# making minimal changes as per ffi-ncurses 0.4.0 which implements panels
|
4
|
+
#module Canis # too many places call Ncurses::Panel
|
5
|
+
class Panel #< Struct.new(:pointer)
|
6
|
+
|
7
|
+
def initialize(window)
|
8
|
+
if window.respond_to?(:pointer)
|
9
|
+
@pointer = FFI::NCurses.new_panel(window.pointer)
|
10
|
+
else
|
11
|
+
@pointer = FFI::NCurses.new_panel(window)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
def pointer
|
15
|
+
@pointer
|
16
|
+
end
|
17
|
+
|
18
|
+
# Puts panel below all other panels.
|
19
|
+
def bottom_panel
|
20
|
+
FFI::NCurses.bottom_panel(@pointer)
|
21
|
+
end
|
22
|
+
alias bottom bottom_panel
|
23
|
+
|
24
|
+
# Put the visible panel on top of all other panels in the stack.
|
25
|
+
#
|
26
|
+
# To ensure compatibility across platforms, use this method instead of
|
27
|
+
# {show_panel} when the panel is shown.
|
28
|
+
def top_panel
|
29
|
+
FFI::NCurses.top_panel(@pointer)
|
30
|
+
end
|
31
|
+
alias top top_panel
|
32
|
+
|
33
|
+
# Makes hidden panel visible by placing it on the top of the stack.
|
34
|
+
#
|
35
|
+
# To ensure compatibility across platforms, use this method instead of
|
36
|
+
# {top_panel} when the panel is hidden.
|
37
|
+
def show_panel
|
38
|
+
FFI::NCurses.show_panel(@pointer)
|
39
|
+
end
|
40
|
+
alias show show_panel
|
41
|
+
|
42
|
+
# Removes the given panel from the panel stack and thus hides it from
|
43
|
+
# view.
|
44
|
+
# The PANEL structure is not lost, merely removed from the stack.
|
45
|
+
def hide_panel
|
46
|
+
FFI::NCurses.hide_panel(@pointer)
|
47
|
+
end
|
48
|
+
alias hide hide_panel
|
49
|
+
|
50
|
+
# Returns a pointer to the window of the given panel.
|
51
|
+
def panel_window
|
52
|
+
FFI::NCurses.panel_window(@pointer)
|
53
|
+
end
|
54
|
+
alias window panel_window
|
55
|
+
|
56
|
+
# Replace the window of the panel with the given window.
|
57
|
+
# Useful, for example, if you want to resize a panel.
|
58
|
+
# You can call {replace_panel} on the output of {wresize}.
|
59
|
+
# It does not change the position of the panel in the stack.
|
60
|
+
def replace_panel(window)
|
61
|
+
FFI::NCurses.replace_panel(@pointer, window)
|
62
|
+
end
|
63
|
+
alias replace replace_panel
|
64
|
+
|
65
|
+
# Move the panel window so that its upper-left corner is at
|
66
|
+
# (+starty+,+startx+).
|
67
|
+
# It does not change the position of the panel in the stack.
|
68
|
+
# Be sure to use this method instead of {mvwin}, to move a panel window.
|
69
|
+
def move_panel(starty = 0, startx = 0)
|
70
|
+
FFI::NCurses.move_panel(@pointer, starty, startx)
|
71
|
+
end
|
72
|
+
alias move move_panel
|
73
|
+
|
74
|
+
# Returns true if the panel is in the panel stack, false if not.
|
75
|
+
# Returns ERR if the panel pointer is a null pointer.
|
76
|
+
def panel_hidden
|
77
|
+
FFI::NCurses.panel_hidden(@pointer) == 0
|
78
|
+
end
|
79
|
+
alias hidden? panel_hidden
|
80
|
+
|
81
|
+
# Returns pointer to the panel above.
|
82
|
+
def panel_above
|
83
|
+
FFI::NCurses.panel_above(@pointer)
|
84
|
+
end
|
85
|
+
alias above panel_above
|
86
|
+
|
87
|
+
# Return a pointer to the panel just below panel.
|
88
|
+
# If the panel argument is a pointer to 0, it returns a pointer to the
|
89
|
+
# top panel in the stack.
|
90
|
+
def panel_below
|
91
|
+
FFI::NCurses.panel_below(@pointer)
|
92
|
+
end
|
93
|
+
alias below panel_below
|
94
|
+
|
95
|
+
# Returns the user pointer for a given panel.
|
96
|
+
def panel_userptr
|
97
|
+
FFI::NCurses.panel_userptr(@pointer)
|
98
|
+
end
|
99
|
+
alias userptr panel_userptr
|
100
|
+
|
101
|
+
# sets the panel's user pointer.
|
102
|
+
def set_panel_userptr(user_pointer)
|
103
|
+
FFI::NCurses.set_panel_userptr(@pointer, user_pointer)
|
104
|
+
end
|
105
|
+
alias userptr= set_panel_userptr
|
106
|
+
|
107
|
+
# Remove the panel from the stack and deallocate the PANEL structure.
|
108
|
+
# Doesn't remove the associated window.
|
109
|
+
def del_panel
|
110
|
+
FFI::NCurses.del_panel(@pointer)
|
111
|
+
end
|
112
|
+
alias del del_panel
|
113
|
+
alias delete del_panel
|
114
|
+
|
115
|
+
class << self
|
116
|
+
# these will be used when you say Ncurses::Panel.del_panel(@panel.pointer)
|
117
|
+
# You could directly say FFI:NCurses or even @panel.del_panel.
|
118
|
+
def update_panels
|
119
|
+
FFI::NCurses.update_panels
|
120
|
+
end
|
121
|
+
def method_missing(name, *args)
|
122
|
+
if (FFI::NCurses.respond_to?(name))
|
123
|
+
return FFI::NCurses.send(name, *args)
|
124
|
+
end
|
125
|
+
raise "Panel did not respond_to #{name} "
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,1081 @@
|
|
1
|
+
# ----------------------------------------------------------------------------- #
|
2
|
+
# File: window.rb
|
3
|
+
# Description: A wrapper over window
|
4
|
+
# Author: jkepler http://github.com/mare-imbrium/canis/
|
5
|
+
# Date: Around for a long time
|
6
|
+
# License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
7
|
+
# Last update: 2014-07-10 00:13
|
8
|
+
#
|
9
|
+
# == CHANGED
|
10
|
+
# removed dead or redudant code - 2014-04-22 - 12:53
|
11
|
+
# - replaced getchar with new simpler one - 2014-05-04
|
12
|
+
# - introduced key_tos to replace keycode_tos, moved to Util in rwidget.rb
|
13
|
+
# - reintroduced nedelay and reduced escdelay
|
14
|
+
#
|
15
|
+
# == TODO
|
16
|
+
# strip and remove cruft. Several methods marked as deprecated.
|
17
|
+
# ----------------------------------------------------------------------------- #
|
18
|
+
#
|
19
|
+
require 'canis/core/system/ncurses'
|
20
|
+
require 'canis/core/system/panel'
|
21
|
+
# this is since often windows are declared with 0 height or width and this causes
|
22
|
+
# crashes in the most unlikely places. This prevceents me from having to write ternary
|
23
|
+
# e.g.
|
24
|
+
# @layout[:width].ifzero(FFI::NCurses::LINES-2)
|
25
|
+
class Fixnum
|
26
|
+
def ifzero v
|
27
|
+
return self if self != 0
|
28
|
+
return v
|
29
|
+
end
|
30
|
+
end
|
31
|
+
# This class is to be extended so that it can be called by anyone wanting to implement
|
32
|
+
# chunks ot text with color and attributes. Chunkline consists of multiple chunks of colored text
|
33
|
+
# and should implement a +each_with_color+.
|
34
|
+
# The purpose of adding this is so that +chunk.rb+ does not need to be required if colored text
|
35
|
+
# is not being used by an application.
|
36
|
+
class AbstractChunkLine; end
|
37
|
+
|
38
|
+
module Canis
|
39
|
+
class Window
|
40
|
+
attr_reader :width, :height, :top, :left
|
41
|
+
attr_accessor :layout # hash containing hwtl
|
42
|
+
attr_reader :panel # reader requires so he can del it in end
|
43
|
+
attr_accessor :name # more for debugging log files. 2010-02-02 19:58
|
44
|
+
#attr_accessor :modified # has it been modified and may need a refresh 2014-04-22 - 10:23 CLEANUP
|
45
|
+
# for root windows we need to know the form so we can ask it to update when
|
46
|
+
# there are overlapping windows.
|
47
|
+
attr_accessor :form
|
48
|
+
|
49
|
+
# creation and layout related {{{
|
50
|
+
# @param [Array, Hash] window coordinates (ht, w, top, left)
|
51
|
+
# or
|
52
|
+
# @param [int, int, int, int] window coordinates (ht, w, top, left)
|
53
|
+
# 2011-09-21 allowing array, or 4 ints, in addition to hash @since 1.3.1
|
54
|
+
def initialize(*args)
|
55
|
+
|
56
|
+
case args.size
|
57
|
+
when 1
|
58
|
+
case args[0]
|
59
|
+
when Array, Hash
|
60
|
+
layout = args[0]
|
61
|
+
else
|
62
|
+
raise ArgumentError, "Window expects 4 ints, array of 4 ints, or Hash in constructor"
|
63
|
+
end
|
64
|
+
when 4
|
65
|
+
layout = { :height => args[0], :width => args[1], :top => args[2], :left => args[3] }
|
66
|
+
end
|
67
|
+
|
68
|
+
@visible = true
|
69
|
+
set_layout(layout)
|
70
|
+
|
71
|
+
#$log.debug "XXX:WINDOW got h #{@height}, w #{@width}, t #{@top}, l #{@left} "
|
72
|
+
|
73
|
+
@height = FFI::NCurses.LINES if @height == 0 # 2011-11-14 added since tired of checking for zero
|
74
|
+
@width = FFI::NCurses.COLS if @width == 0
|
75
|
+
|
76
|
+
@window = FFI::NCurses.newwin(@height, @width, @top, @left) # added FFI 2011-09-6
|
77
|
+
# trying out refreshing underlying window.
|
78
|
+
$global_windows ||= []
|
79
|
+
# this causes issues padrefresh failing when display_list does a resize.
|
80
|
+
#$global_windows << self
|
81
|
+
@panel = Ncurses::Panel.new(@window) # added FFI 2011-09-6
|
82
|
+
#$error_message_row = $status_message_row = Ncurses.LINES-1
|
83
|
+
$error_message_row ||= Ncurses.LINES-1
|
84
|
+
$error_message_col ||= 1 # ask (bottomline) uses 0 as default so you can have mismatch. XXX
|
85
|
+
$status_message ||= Canis::Variable.new # in case not an App
|
86
|
+
|
87
|
+
# 2014-05-07 - 12:29 CANIS earlier this was called $key_map but that suggests a map.
|
88
|
+
$key_map_type ||= :vim
|
89
|
+
$esc_esc = true; # gove me double esc as 2727 so i can map it.
|
90
|
+
init_vars
|
91
|
+
|
92
|
+
unless @key_reader
|
93
|
+
create_default_key_reader
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
end
|
98
|
+
def init_vars
|
99
|
+
Ncurses::keypad(@window, true)
|
100
|
+
# Added this so we can get Esc, and also C-c pressed in succession does not crash system
|
101
|
+
# 2011-12-20 half-delay crashes system as does cbreak
|
102
|
+
#This causes us to be unable to process gg qq since getch won't wait.
|
103
|
+
#FFI::NCurses::nodelay(@window, bf = true)
|
104
|
+
# wtimeout was causing RESIZE sigwinch to only happen after pressing a key
|
105
|
+
#Ncurses::wtimeout(@window, $ncurses_timeout || 500) # will wait a second on wgetch so we can get gg and qq
|
106
|
+
#@stack = [] # since we have moved to handler 2014-04-20 - 11:15
|
107
|
+
@name ||="#{self}"
|
108
|
+
@modified = true
|
109
|
+
$catch_alt_digits ||= false # is this where is should put globals ? 2010-03-14 14:00 XXX
|
110
|
+
end
|
111
|
+
##
|
112
|
+
# this is an alternative constructor
|
113
|
+
def self.root_window(layout = { :height => 0, :width => 0, :top => 0, :left => 0 })
|
114
|
+
|
115
|
+
@layout = layout
|
116
|
+
@window = Window.new(@layout)
|
117
|
+
@window.name = "Window::ROOTW:#{$global_windows.count}"
|
118
|
+
@window.wrefresh
|
119
|
+
Ncurses::Panel.update_panels
|
120
|
+
# earlier we only put root window, now we may need to do all (bline - numbered menu - alert)
|
121
|
+
$global_windows << @window unless $global_windows.include? @window
|
122
|
+
return @window
|
123
|
+
end
|
124
|
+
|
125
|
+
# This refreshes the root window whenever overlapping windows are
|
126
|
+
# destroyed or moved.
|
127
|
+
# This works by asking the root window's form to repaint all its objects.
|
128
|
+
# This is now being called whenever a window is destroyed (and also resized).
|
129
|
+
# However, it must
|
130
|
+
# manually be called if you move a window.
|
131
|
+
# NOTE : if there are too many root windows, this could get expensive since we are updating all.
|
132
|
+
# We may need to have a way to specify which window to repaint.
|
133
|
+
# If there are non-root windows above, we may have manually refresh only the previous one.
|
134
|
+
#
|
135
|
+
def self.refresh_all current_win=nil
|
136
|
+
#Ncurses.touchwin(FFI::NCurses.stdscr)
|
137
|
+
# above blanks out entire screen
|
138
|
+
# in case of multiple root windows lets just do last otherwise too much refreshing.
|
139
|
+
gw = $global_windows
|
140
|
+
if current_win
|
141
|
+
gw = $global_windows.select {|e| e != current_win }
|
142
|
+
end
|
143
|
+
return unless gw.last
|
144
|
+
wins = [ gw.last ]
|
145
|
+
wins.each_with_index do |w,i|
|
146
|
+
$log.debug " REFRESH_ALL on #{w.name} (#{i}) sending 1000"
|
147
|
+
# NOTE 2014-05-01 - 20:25 although we have reached the root window from any level
|
148
|
+
# however, this is sending the hack to whoever is trapping the key, which in our current
|
149
|
+
# case happends to be Viewer, *not* the root form. We need to send to root form.
|
150
|
+
f = w.form
|
151
|
+
if f
|
152
|
+
# send hack to root windows form if passed.
|
153
|
+
f.handle_key 1000
|
154
|
+
end
|
155
|
+
#w.ungetch(1000)
|
156
|
+
# below blanks out entire screen too
|
157
|
+
#FFI::NCurses.touchwin(w.get_window)
|
158
|
+
#$log.debug "XXX: refreshall diong window "
|
159
|
+
#w.hide
|
160
|
+
#w.show
|
161
|
+
#Ncurses.refresh
|
162
|
+
#w.wrefresh
|
163
|
+
end
|
164
|
+
#Ncurses::Panel.update_panels
|
165
|
+
end
|
166
|
+
# 2009-10-13 12:24
|
167
|
+
# not used as yet
|
168
|
+
# this is an alternative constructor
|
169
|
+
# created if you don't want to create a hash first
|
170
|
+
# 2011-09-21 V1.3.1 You can now send an array to Window constructor
|
171
|
+
def self.create_window(h=0, w=0, t=0, l=0)
|
172
|
+
layout = { :height => h, :width => w, :top => t, :left => l }
|
173
|
+
@window = Window.new(layout)
|
174
|
+
return @window
|
175
|
+
end
|
176
|
+
|
177
|
+
def resize_with(layout)
|
178
|
+
#$log.debug " DARN ! This awready duz a resize!! if h or w or even top or left changed!!! XXX"
|
179
|
+
set_layout(layout)
|
180
|
+
$log.debug " resize after set_layout: #{@height} , #{@width} , #{@top} , #{@left}, "
|
181
|
+
wresize(height, width)
|
182
|
+
mvwin(top, left)
|
183
|
+
Window.refresh_all self
|
184
|
+
end
|
185
|
+
|
186
|
+
%w[width height top left].each do |side|
|
187
|
+
eval(
|
188
|
+
"def #{side}=(n)
|
189
|
+
return if n == #{side}
|
190
|
+
@layout[:#{side}] = n
|
191
|
+
resize_with @layout
|
192
|
+
end"
|
193
|
+
)
|
194
|
+
end
|
195
|
+
|
196
|
+
##
|
197
|
+
# Creating variables case of array, we still create the hash
|
198
|
+
# @param array or hash containing h w t and l
|
199
|
+
def set_layout(layout)
|
200
|
+
case layout
|
201
|
+
when Array
|
202
|
+
$log.error "NIL in window constructor" if layout.include? nil
|
203
|
+
raise ArgumentError, "Nil in window constructor" if layout.include? nil
|
204
|
+
# NOTE this is just setting, and not replacing zero with max values
|
205
|
+
@height, @width, @top, @left = *layout
|
206
|
+
raise ArgumentError, "Nil in window constructor" if @top.nil? || @left.nil?
|
207
|
+
|
208
|
+
@layout = { :height => @height, :width => @width, :top => @top, :left => @left }
|
209
|
+
when Hash
|
210
|
+
@layout = layout
|
211
|
+
|
212
|
+
[:height, :width, :top, :left].each do |name|
|
213
|
+
instance_variable_set("@#{name}", @layout[name])
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
# --- layout and creation related }}}
|
218
|
+
|
219
|
+
# ADDED DUE TO FFI
|
220
|
+
def wrefresh
|
221
|
+
Ncurses.wrefresh(@window)
|
222
|
+
end
|
223
|
+
def delwin # 2011-09-7
|
224
|
+
Ncurses.delwin(@window)
|
225
|
+
end
|
226
|
+
def attron *args
|
227
|
+
FFI::NCurses.wattron @window, *args
|
228
|
+
end
|
229
|
+
def attroff *args
|
230
|
+
FFI::NCurses.wattroff @window, *args
|
231
|
+
end
|
232
|
+
#
|
233
|
+
# ## END FFI
|
234
|
+
|
235
|
+
def resize
|
236
|
+
resize_with(@layout)
|
237
|
+
end
|
238
|
+
|
239
|
+
# Ncurses
|
240
|
+
|
241
|
+
|
242
|
+
def x=(n) move(y, n) end
|
243
|
+
def y=(n) move(n, x) end
|
244
|
+
|
245
|
+
#def move(y, x)
|
246
|
+
#return unless @visible
|
247
|
+
## Log.debug([y, x] => caller[0,4])
|
248
|
+
##@window.wmove(y, x) # bombing since ffi-ncurses 0.4.0 (maybe it was never called
|
249
|
+
##earlier. was crashing in appemail.rb testchoose.
|
250
|
+
#wmove y,x # can alias it
|
251
|
+
#end
|
252
|
+
# since include FFI is taking over, i need to force it here. not going into
|
253
|
+
# method_missing
|
254
|
+
def wmove y,x
|
255
|
+
#Ncurses.wmove @window, y, x
|
256
|
+
FFI::NCurses.wmove @window, y, x
|
257
|
+
end
|
258
|
+
alias :move :wmove
|
259
|
+
|
260
|
+
def method_missing(name, *args)
|
261
|
+
name = name.to_s
|
262
|
+
if (name[0,2] == "mv")
|
263
|
+
test_name = name.dup
|
264
|
+
test_name[2,0] = "w" # insert "w" after"mv"
|
265
|
+
if (FFI::NCurses.respond_to?(test_name))
|
266
|
+
return FFI::NCurses.send(test_name, @window, *args)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
test_name = "w" + name
|
270
|
+
if (FFI::NCurses.respond_to?(test_name))
|
271
|
+
return FFI::NCurses.send(test_name, @window, *args)
|
272
|
+
end
|
273
|
+
FFI::NCurses.send(name, @window, *args)
|
274
|
+
end
|
275
|
+
|
276
|
+
def respond_to?(name)
|
277
|
+
name = name.to_s
|
278
|
+
if (name[0,2] == "mv" && FFI::NCurses.respond_to?("mvw" + name[2..-1]))
|
279
|
+
return true
|
280
|
+
end
|
281
|
+
FFI::NCurses.respond_to?("w" + name) || FFI::NCurses.respond_to?(name)
|
282
|
+
end
|
283
|
+
|
284
|
+
#--
|
285
|
+
# removing some methods that not used or used once
|
286
|
+
# leaving here so we not what to do to print in these cases
|
287
|
+
def print(string, width = width)
|
288
|
+
w = width == 0? Ncurses.COLS : width
|
289
|
+
waddnstr(string.to_s, w) # changed 2011 dts
|
290
|
+
end
|
291
|
+
|
292
|
+
#def print_yx(string, y = 0, x = 0)
|
293
|
+
#w = width == 0? Ncurses.COLS : width
|
294
|
+
#mvwaddnstr(y, x, string, w) # changed 2011 dts
|
295
|
+
#end
|
296
|
+
#++
|
297
|
+
|
298
|
+
|
299
|
+
# return the character to the keyboard buffer to be read again.
|
300
|
+
def ungetch(ch)
|
301
|
+
Ncurses.ungetch(ch)
|
302
|
+
end
|
303
|
+
|
304
|
+
# reads a character from keyboard and returns
|
305
|
+
# NOTE:
|
306
|
+
# if a function key is pressed, multiple such ints will be returned one after the other
|
307
|
+
# so the caller must decipher the same. See +getchar()+
|
308
|
+
#
|
309
|
+
# @return int
|
310
|
+
# @return -1 if no char read
|
311
|
+
# ORIGINALLY After esc there was a timeout, but after others there was notimeout, so it would wait
|
312
|
+
# indefinitely for a key
|
313
|
+
# NOTE : caller may set a timeout prior to calling, but not change setting after since this method
|
314
|
+
# maintains the default state in +ensure+. e.g. +widget.rb+ does a blocking get in +_process_key+
|
315
|
+
# Curses sets a timeout when ESCAPE is pressed, it is called ESCDELAY and is 1000 milliseconds.
|
316
|
+
# You may reduce it if you are not on some old slow telnet session. This returns faster from an esc
|
317
|
+
# although there are still some issues. ESC-ESC becomes an issue, but if i press ESC-ESC-1 then esc-esc comes
|
318
|
+
# together. otherwise there is a -1 between each esc.
|
319
|
+
#
|
320
|
+
def getch
|
321
|
+
#c = @window.getch
|
322
|
+
#FFI::NCurses::nodelay(@window, true)
|
323
|
+
#FFI::NCurses::wtimeout(@window, 0)
|
324
|
+
#$log.debug " #{Time.now.to_f} inside MAIN before getch "
|
325
|
+
c = FFI::NCurses.wgetch(@window)
|
326
|
+
# the only reason i am doing this is so ESC can be returned if no key is pressed
|
327
|
+
# after that, not sure how this effects everything. most likely I should just
|
328
|
+
# go back to using a wtimeout, and not worry about resize requiring a keystroke
|
329
|
+
if c == 27
|
330
|
+
$escstart = Time.now.to_f
|
331
|
+
# if ESC pressed don't wait too long for next key
|
332
|
+
Ncurses::wtimeout(@window, $ncurses_timeout || 500) # will wait n millisecond on wgetch so that we can return if no
|
333
|
+
else
|
334
|
+
FFI::NCurses.set_escdelay(100)
|
335
|
+
# this means keep waiting for a key.
|
336
|
+
Ncurses::nowtimeout(@window, true)
|
337
|
+
end
|
338
|
+
c
|
339
|
+
|
340
|
+
rescue SystemExit, Interrupt
|
341
|
+
#FFI::NCurses.flushinp
|
342
|
+
3 # is C-c
|
343
|
+
rescue StandardError
|
344
|
+
-1 # is C-c
|
345
|
+
ensure
|
346
|
+
# whatever the default is, is to be set here in case caller changed it.
|
347
|
+
#FFI::NCurses::nodelay(@window, true)
|
348
|
+
end
|
349
|
+
|
350
|
+
# Earlier this was handled by window itself. Now we delegate to a reader
|
351
|
+
# @return int keycode, can be function key or meta or arrow key.
|
352
|
+
#
|
353
|
+
# NOTE:
|
354
|
+
# This is called by user programs in a loop.
|
355
|
+
# We are now moving from returning an int to returning a string similar to what
|
356
|
+
# user would get on commandline using C-v
|
357
|
+
#
|
358
|
+
def getchar
|
359
|
+
@key_reader.getchar
|
360
|
+
end
|
361
|
+
|
362
|
+
|
363
|
+
# setup and reset
|
364
|
+
|
365
|
+
|
366
|
+
|
367
|
+
# Ncurses panel
|
368
|
+
|
369
|
+
def hide
|
370
|
+
#return unless visible? # added 2011-10-14 these 2 are not behaving properly
|
371
|
+
Ncurses::Panel.hide_panel @panel.pointer
|
372
|
+
#Ncurses.refresh # wnoutrefresh
|
373
|
+
Ncurses::Panel.update_panels # added so below window does not need to do this 2011-10-1
|
374
|
+
@visible = false
|
375
|
+
end
|
376
|
+
|
377
|
+
def show
|
378
|
+
#return if visible? # added 2011-10-14 these 2 are not behaving properly
|
379
|
+
Ncurses::Panel.show_panel @panel.pointer
|
380
|
+
#Ncurses.refresh # wnoutrefresh
|
381
|
+
Ncurses::Panel.update_panels # added so below window does not need to do this 2011-10-1
|
382
|
+
@visible = true
|
383
|
+
end
|
384
|
+
|
385
|
+
|
386
|
+
def visible?
|
387
|
+
@visible
|
388
|
+
end
|
389
|
+
|
390
|
+
##
|
391
|
+
# destroy window, panel and any pads that were requested
|
392
|
+
#
|
393
|
+
def destroy
|
394
|
+
# typically the ensure block should have this
|
395
|
+
|
396
|
+
#$log.debug "win destroy start"
|
397
|
+
|
398
|
+
$global_windows.delete self
|
399
|
+
Ncurses::Panel.del_panel(@panel.pointer) if @panel
|
400
|
+
delwin() if @window
|
401
|
+
Ncurses::Panel.update_panels # added so below window does not need to do this 2011-10-1
|
402
|
+
|
403
|
+
# destroy any pads that were created by widgets using get_pad
|
404
|
+
@pads.each { |pad|
|
405
|
+
FFI::NCurses.delwin(pad) if pad
|
406
|
+
pad = nil
|
407
|
+
} if @pads
|
408
|
+
# added here to hopefully take care of this issue once and for all.
|
409
|
+
# Whenever any window is destroyed, the root window is repainted.
|
410
|
+
#
|
411
|
+
Window.refresh_all
|
412
|
+
#$log.debug "win destroy end"
|
413
|
+
end
|
414
|
+
|
415
|
+
#
|
416
|
+
# 2011-11-13 since 1.4.1
|
417
|
+
# Widgets can get window to create a pad for them. This way when the window
|
418
|
+
# is destroyed, it will delete all the pads. A widget wold not be able to do this.
|
419
|
+
# The destroy method of the widget will be called.
|
420
|
+
def get_pad content_rows, content_cols
|
421
|
+
pad = FFI::NCurses.newpad(content_rows, content_cols)
|
422
|
+
@pads ||= []
|
423
|
+
@pads << pad
|
424
|
+
## added 2013-03-05 - 19:21 without next line how was pad being returned
|
425
|
+
return pad
|
426
|
+
end
|
427
|
+
|
428
|
+
# print and chunk related --- {{{
|
429
|
+
#
|
430
|
+
# Allows user to send data as normal string or chunks for printing
|
431
|
+
# An array is assumed to be a chunk containing color and attrib info
|
432
|
+
#
|
433
|
+
def printstring_or_chunks(r,c,content, color, att = Ncurses::A_NORMAL)
|
434
|
+
if content.is_a? String
|
435
|
+
printstring(r,c,content, color, att)
|
436
|
+
elsif content.is_a? AbstractChunkLine
|
437
|
+
#$log.debug "XXX: using chunkline" # 2011-12-10 12:40:13
|
438
|
+
wmove r, c
|
439
|
+
a = get_attrib att
|
440
|
+
# please add width to avoid overflow
|
441
|
+
show_colored_chunks content, color, a
|
442
|
+
elsif content.is_a? Array
|
443
|
+
# several chunks in one row - NOTE Very experimental may change
|
444
|
+
if content[0].is_a? Array
|
445
|
+
$log.warn "XXX: WARNING outdated should send in a chunkline"
|
446
|
+
wmove r, c
|
447
|
+
a = get_attrib att
|
448
|
+
# please add width to avoid overflow
|
449
|
+
show_colored_chunks content, color, a
|
450
|
+
else
|
451
|
+
# a single row chunk - NOTE Very experimental may change
|
452
|
+
text = content[1].dup
|
453
|
+
printstring r, c, text, content[0] || color, content[2] || att
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
457
|
+
#
|
458
|
+
# prints a string formatted in our new experimental coloring format
|
459
|
+
# taken from tmux. Currently, since i have chunks workings, i convert
|
460
|
+
# to chunks and use the existing print function. This could change.
|
461
|
+
# An example of a formatted string is:
|
462
|
+
# s="#[fg=green]testing chunks #[fg=yellow, bg=red, bold]yellow #[reverse] reverseme \
|
463
|
+
# #[normal]normal#[bg = black]just yellow#[fg=blue],blue now #[underline] underlined text"
|
464
|
+
# Ideally I should push and pop colors which the shell does not do with ansi terminal sequences.
|
465
|
+
# That way i can have a line in red,
|
466
|
+
# with some word in yellow, and then the line continues in red.
|
467
|
+
#
|
468
|
+
def printstring_formatted(r,c,content, color, att = Ncurses::A_NORMAL)
|
469
|
+
att = get_attrib att unless att.is_a? Fixnum
|
470
|
+
chunkline = convert_to_chunk(content, color, att)
|
471
|
+
printstring_or_chunks r,c, chunkline, color, att
|
472
|
+
end # print
|
473
|
+
#
|
474
|
+
# print a formatted line right aligned
|
475
|
+
# c (col) is ignored and calculated based on width and unformatted string length
|
476
|
+
#
|
477
|
+
def printstring_formatted_right(r,c,content, color, att = Ncurses::A_NORMAL)
|
478
|
+
clean = content.gsub /#\[[^\]]*\]/,'' # clean out all markup
|
479
|
+
#c = actual_width() - clean.length # actual width not working if resize
|
480
|
+
c = getmaxx() - clean.length
|
481
|
+
printstring_formatted(r,c,content, color, att )
|
482
|
+
end
|
483
|
+
|
484
|
+
private
|
485
|
+
def get_default_color_parser
|
486
|
+
require 'canis/core/util/defaultcolorparser'
|
487
|
+
@color_parser || DefaultColorParser.new
|
488
|
+
end
|
489
|
+
# supply with a color parser, if you supplied formatted text
|
490
|
+
public
|
491
|
+
def color_parser f
|
492
|
+
$log.debug "XXX: color_parser setting in window to #{f} "
|
493
|
+
require 'canis/core/include/colorparser'
|
494
|
+
if f == :tmux
|
495
|
+
@color_parser = get_default_color_parser()
|
496
|
+
else
|
497
|
+
@color_parser = f
|
498
|
+
end
|
499
|
+
end
|
500
|
+
#
|
501
|
+
# Takes a formatted string and converts the parsed parts to chunks.
|
502
|
+
#
|
503
|
+
# @param [String] takes the entire line or string and breaks into an array of chunks
|
504
|
+
# @yield chunk if block
|
505
|
+
# @return [ChunkLine] # [Array] array of chunks
|
506
|
+
public
|
507
|
+
def convert_to_chunk s, colorp=$datacolor, att=FFI::NCurses::A_NORMAL
|
508
|
+
unless @color_parser
|
509
|
+
require 'canis/core/include/colorparser'
|
510
|
+
@color_parser = get_default_color_parser()
|
511
|
+
@converter = Chunks::ColorParser.new @color_parser
|
512
|
+
# we need to know set the parent in colorparser. 2014-05-26 - 14:49
|
513
|
+
@converter.form = self.form
|
514
|
+
end
|
515
|
+
@converter.convert_to_chunk s, colorp, att
|
516
|
+
end
|
517
|
+
|
518
|
+
##
|
519
|
+
# prints a string at row, col, with given color and attribute
|
520
|
+
# added by rk 2008-11-29 19:01
|
521
|
+
# I usually use this, not the others ones here
|
522
|
+
# @param r - row
|
523
|
+
# @param c - col
|
524
|
+
# @param string - text to print
|
525
|
+
# @param color - color pair
|
526
|
+
# @ param att - ncurses attribute: normal, bold, reverse, blink,
|
527
|
+
# underline
|
528
|
+
public
|
529
|
+
def printstring(r,c,string, color, att = Ncurses::A_NORMAL)
|
530
|
+
|
531
|
+
raise "printstring recvd nil row #{r} or col #{c} " if r.nil? || c.nil?
|
532
|
+
#$log.debug " #{@name} inside window printstring r #{r} c #{c} #{string} "
|
533
|
+
if att.nil?
|
534
|
+
att = Ncurses::A_NORMAL
|
535
|
+
else
|
536
|
+
att = get_attrib att
|
537
|
+
end
|
538
|
+
|
539
|
+
wattron(Ncurses.COLOR_PAIR(color) | att)
|
540
|
+
mvwprintw(r, c, "%s", :string, string);
|
541
|
+
wattroff(Ncurses.COLOR_PAIR(color) | att)
|
542
|
+
end
|
543
|
+
##
|
544
|
+
# prints the border for message boxes
|
545
|
+
#
|
546
|
+
# NOTE : FOR MESSAGEBOXES ONLY !!!! Then why not move to messagebox FIXME
|
547
|
+
def print_border_mb row, col, height, width, color, attr
|
548
|
+
# the next is for xterm-256
|
549
|
+
att = get_attrib attr
|
550
|
+
len = width
|
551
|
+
len = Ncurses.COLS-0 if len == 0
|
552
|
+
# print a bar across the screen
|
553
|
+
#attron(Ncurses.COLOR_PAIR(color) | att)
|
554
|
+
# this works for newmessagebox but not for old one.
|
555
|
+
# Even now in some cases some black shows through, if the widget is printing spaces
|
556
|
+
# such as field or textview on a messagebox.
|
557
|
+
(row-1).upto(row+height-1) do |r|
|
558
|
+
mvwhline(r, col, 1, len)
|
559
|
+
end
|
560
|
+
#attroff(Ncurses.COLOR_PAIR(color) | att)
|
561
|
+
|
562
|
+
mvwaddch row, col, Ncurses::ACS_ULCORNER
|
563
|
+
mvwhline( row, col+1, Ncurses::ACS_HLINE, width-6)
|
564
|
+
mvwaddch row, col+width-5, Ncurses::ACS_URCORNER
|
565
|
+
mvwvline( row+1, col, Ncurses::ACS_VLINE, height-4)
|
566
|
+
|
567
|
+
mvwaddch row+height-3, col, Ncurses::ACS_LLCORNER
|
568
|
+
mvwhline(row+height-3, col+1, Ncurses::ACS_HLINE, width-6)
|
569
|
+
mvwaddch row+height-3, col+width-5, Ncurses::ACS_LRCORNER
|
570
|
+
mvwvline( row+1, col+width-5, Ncurses::ACS_VLINE, height-4)
|
571
|
+
end
|
572
|
+
|
573
|
+
##
|
574
|
+
# prints a border around a widget, CLEARING the area.
|
575
|
+
# If calling with a pad, you would typically use 0,0, h-1, w-1.
|
576
|
+
# FIXME can this be moved to module Bordertitle ?
|
577
|
+
def print_border row, col, height, width, color, att=Ncurses::A_NORMAL
|
578
|
+
raise "height needs to be supplied." if height.nil?
|
579
|
+
raise "width needs to be supplied." if width.nil?
|
580
|
+
att ||= Ncurses::A_NORMAL
|
581
|
+
|
582
|
+
#$log.debug " inside window print_border r #{row} c #{col} h #{height} w #{width} "
|
583
|
+
|
584
|
+
# 2009-11-02 00:45 made att nil for blanking out
|
585
|
+
# FIXME - in tabbedpanes this clears one previous line ??? XXX when using a textarea/view
|
586
|
+
# when using a pad this calls pads printstring which again reduces top and left !!! 2010-01-26 23:53
|
587
|
+
ww=width-2
|
588
|
+
clr = " "*ww
|
589
|
+
(row+1).upto(row+height-1) do |r|
|
590
|
+
printstring( r, col+1, clr, color, att)
|
591
|
+
end
|
592
|
+
print_border_only row, col, height, width, color, att
|
593
|
+
end
|
594
|
+
|
595
|
+
|
596
|
+
## print just the border, no cleanup
|
597
|
+
#+ Earlier, we would clean up. Now in some cases, i'd like
|
598
|
+
#+ to print border over what's been done.
|
599
|
+
# XXX this reduces 1 from width but not height !!! FIXME
|
600
|
+
# FIXME can this be moved to module Bordertitle ?
|
601
|
+
def print_border_only row, col, height, width, color, att=Ncurses::A_NORMAL
|
602
|
+
if att.nil?
|
603
|
+
att = Ncurses::A_NORMAL
|
604
|
+
else
|
605
|
+
att = get_attrib att
|
606
|
+
end
|
607
|
+
wattron(Ncurses.COLOR_PAIR(color) | att)
|
608
|
+
mvwaddch row, col, Ncurses::ACS_ULCORNER
|
609
|
+
mvwhline( row, col+1, Ncurses::ACS_HLINE, width-2)
|
610
|
+
mvwaddch row, col+width-1, Ncurses::ACS_URCORNER
|
611
|
+
mvwvline( row+1, col, Ncurses::ACS_VLINE, height-1)
|
612
|
+
|
613
|
+
mvwaddch row+height-0, col, Ncurses::ACS_LLCORNER
|
614
|
+
mvwhline(row+height-0, col+1, Ncurses::ACS_HLINE, width-2)
|
615
|
+
mvwaddch row+height-0, col+width-1, Ncurses::ACS_LRCORNER
|
616
|
+
mvwvline( row+1, col+width-1, Ncurses::ACS_VLINE, height-1)
|
617
|
+
wattroff(Ncurses.COLOR_PAIR(color) | att)
|
618
|
+
end
|
619
|
+
|
620
|
+
# Previously this printed a chunk as a full line, I've modified it to print on
|
621
|
+
# one line. This can be used for running text.
|
622
|
+
# NOTE 2013-03-08 - 17:02 added width so we don't overflow
|
623
|
+
# NOTE 2014-05-11 - textpad has its own version, so does not call this.
|
624
|
+
def show_colored_chunks(chunks, defcolor = nil, defattr = nil, wid = 999, pcol = 0)
|
625
|
+
return unless visible?
|
626
|
+
ww = 0
|
627
|
+
chunks.each_with_color do |text, color, attrib|
|
628
|
+
|
629
|
+
## 2013-03-08 - 19:11 take care of scrolling by means of pcol
|
630
|
+
if pcol > 0
|
631
|
+
if pcol > text.length
|
632
|
+
# ignore entire chunk and reduce pcol
|
633
|
+
pcol -= text.length
|
634
|
+
next
|
635
|
+
else
|
636
|
+
# print portion of chunk and zero pcol
|
637
|
+
text = text[pcol..-1]
|
638
|
+
pcol = 0
|
639
|
+
end
|
640
|
+
end
|
641
|
+
oldw = ww
|
642
|
+
ww += text.length
|
643
|
+
if ww > wid
|
644
|
+
# if we are exceeding the width then by howmuch
|
645
|
+
rem = wid - oldw
|
646
|
+
if rem > 0
|
647
|
+
# take only as much as we are allowed
|
648
|
+
text = text[0,rem]
|
649
|
+
else
|
650
|
+
break
|
651
|
+
end
|
652
|
+
end
|
653
|
+
|
654
|
+
color ||= defcolor
|
655
|
+
attrib ||= defattr
|
656
|
+
|
657
|
+
cc, bg = ColorMap.get_colors_for_pair color
|
658
|
+
#$log.debug "XXX: CHUNK window #{text}, cp #{color} , attrib #{attrib}. #{cc}, #{bg} "
|
659
|
+
color_set(color,nil) if color
|
660
|
+
wattron(attrib) if attrib
|
661
|
+
#print(text)
|
662
|
+
waddnstr(text.to_s, @width) # changed 2014-04-22 - 11:59 to reduce a function
|
663
|
+
wattroff(attrib) if attrib
|
664
|
+
end
|
665
|
+
end
|
666
|
+
# ----- }}}
|
667
|
+
|
668
|
+
# This used to return an Ncurses window object, and you could call methods on it
|
669
|
+
# Now it returns a FFI::NCurses.window pointer which you cannot call methods on.
|
670
|
+
# You have to pass it to FFI::NCurses.<method>
|
671
|
+
def get_window; @window; end
|
672
|
+
|
673
|
+
# returns name of window or self (mostly for debugging)
|
674
|
+
def to_s; @name || self; end
|
675
|
+
|
676
|
+
# actions to perform when window closed.
|
677
|
+
# == Example
|
678
|
+
# @window.close_command do
|
679
|
+
# if confirm("Save tasks?", :default_button => 0)
|
680
|
+
# take some actions
|
681
|
+
# end
|
682
|
+
# end
|
683
|
+
def close_command *args, &block
|
684
|
+
@close_command ||= []
|
685
|
+
@close_args ||= []
|
686
|
+
@close_command << block
|
687
|
+
@close_args << args
|
688
|
+
end
|
689
|
+
alias :command :close_command
|
690
|
+
|
691
|
+
# set a single command to confirm whether window shoud close or not
|
692
|
+
# Block should return true or false for closing or not
|
693
|
+
# == Examples
|
694
|
+
#
|
695
|
+
# @window.confirm_close_command do
|
696
|
+
# confirm "Sure you wanna quit?", :default_button => 1
|
697
|
+
# end
|
698
|
+
#
|
699
|
+
def confirm_close_command *args, &block
|
700
|
+
@confirm_close_command = block
|
701
|
+
@confirm_close_args = args
|
702
|
+
end
|
703
|
+
|
704
|
+
# Called when window close is requested by user.
|
705
|
+
# Executes confirm_close block and if it succeeds then executes close commands
|
706
|
+
# called by util/app.rb
|
707
|
+
def fire_close_handler
|
708
|
+
if @confirm_close_command
|
709
|
+
comm = @confirm_close_command
|
710
|
+
ret = comm.call(self, *@confirm_close_args)
|
711
|
+
return ret unless ret # only return if false returned
|
712
|
+
end
|
713
|
+
if @close_command
|
714
|
+
@close_command.each_with_index do |comm, ix|
|
715
|
+
comm.call(self, *@close_args[ix]) if comm
|
716
|
+
end
|
717
|
+
end
|
718
|
+
@close_command = nil
|
719
|
+
@close_args = nil
|
720
|
+
return true
|
721
|
+
end
|
722
|
+
|
723
|
+
# creates a key reader unless overridden by application which should be rare.
|
724
|
+
def create_default_key_reader
|
725
|
+
@key_reader = DefaultKeyReader.new self
|
726
|
+
end
|
727
|
+
|
728
|
+
|
729
|
+
end # window
|
730
|
+
|
731
|
+
# created on 2014-04-20 - 00:19 so that user can install own handler
|
732
|
+
#
|
733
|
+
#
|
734
|
+
# A class that reads keys and handles function, shifted function, control, alt, and other
|
735
|
+
# extended keys.
|
736
|
+
# THis essentially consists of a method getchar which will be called by the application
|
737
|
+
# to get keys in a loop. Application may also call getchar to get one key in some situations.
|
738
|
+
#
|
739
|
+
# Originally, rbcurse returned an int, but we are movign to a string, so that user can use the exact
|
740
|
+
# control codes he gets on the terminal using C-v and map them here.
|
741
|
+
#
|
742
|
+
#
|
743
|
+
class DefaultKeyReader # --- {{{
|
744
|
+
def initialize win
|
745
|
+
@window = win
|
746
|
+
@stack = []
|
747
|
+
end
|
748
|
+
|
749
|
+
# return an int for the key read. this is just a single int, and is not interpreted
|
750
|
+
# for control or function keys. it also will return -1 when no action.
|
751
|
+
# You may re-implenent it or call the original one.
|
752
|
+
#
|
753
|
+
def getch
|
754
|
+
@window.getch
|
755
|
+
end
|
756
|
+
|
757
|
+
|
758
|
+
# A map of int keycodes associated with a string name which is defined in $kh
|
759
|
+
$kh_int ||= Hash.new {|hash, key| hash[key] = key.hash }
|
760
|
+
# these 4 for xterm-color which does not send 265 on F1
|
761
|
+
$kh_int["F1"] = 265
|
762
|
+
$kh_int["F2"] = 266
|
763
|
+
$kh_int["F3"] = 267
|
764
|
+
$kh_int["F4"] = 268
|
765
|
+
# testing out shift+Function. these are the codes my kb generates
|
766
|
+
if File.exists? File.expand_path("~/ncurses-keys.yml")
|
767
|
+
# a sample of this file should be available with this
|
768
|
+
# the file is a hash or mapping, but should not contrain control characters.
|
769
|
+
# Usually delete the control character and insert a "\e" in its place.
|
770
|
+
# "\e[1;3C": C-RIGHT
|
771
|
+
require 'yaml'
|
772
|
+
$kh = YAML::load( File.open( File.expand_path("~/ncurses-keys.yml" ) ))
|
773
|
+
else
|
774
|
+
# if we could not find any mappings then use some dummy ones that work on my laptop.
|
775
|
+
$kh=Hash.new
|
776
|
+
KEY_S_F1='[1;2P'
|
777
|
+
$kh[KEY_S_F1]="S-F1"
|
778
|
+
$kh['[1;2Q']="S-F2"
|
779
|
+
$kh['[1;2R']="S-F3"
|
780
|
+
$kh['[1;2S']="S-F4"
|
781
|
+
$kh['[15;2~']="S-F5"
|
782
|
+
|
783
|
+
end
|
784
|
+
# this is for xterm-color which does not send 265 on F1
|
785
|
+
$kh['OP']="F1"
|
786
|
+
$kh['OQ']="F2"
|
787
|
+
$kh['OR']="F3"
|
788
|
+
$kh['OS']="F4"
|
789
|
+
|
790
|
+
# NOTE: This is a reworked and much simpler version of the original getchar which was taken from manveru's
|
791
|
+
# codebase. This also currently returns the keycode as int while placing the char version in a
|
792
|
+
# global $key_chr. Until we are ready to return a char, we use this.
|
793
|
+
#
|
794
|
+
# FIXME : I have tried very hard to revert to nodelay but it does not seem to have an effect when ESC is pressed.
|
795
|
+
# Somewhere, there is a delay when ESC is pressed. I not longer wish to provide the feature of pressing ESC
|
796
|
+
# and then a key to be evaluated as Meta-key. This slows down when a user just presses ESC.
|
797
|
+
#
|
798
|
+
# Read a char from the window (from user) and returns int code.
|
799
|
+
# In some cases, such as codes entered in the $kh hash, we do not yet have a keycode defined
|
800
|
+
# so we return 9999 and the user can access $key_chr.
|
801
|
+
#
|
802
|
+
# NOTE: Do not convert to string, that is doing two things. Allow user to convert if required using
|
803
|
+
# `key_tos`
|
804
|
+
def getchar
|
805
|
+
$key_chr = nil
|
806
|
+
c = nil
|
807
|
+
while true
|
808
|
+
c = self.getch
|
809
|
+
break if c != -1
|
810
|
+
end
|
811
|
+
|
812
|
+
cn = c
|
813
|
+
$key_int = c
|
814
|
+
# handle control codes 0 to 127 but not escape
|
815
|
+
if cn >= 0 && cn < 128 && cn != 27
|
816
|
+
#$key_chr = key_tos(c)
|
817
|
+
return c
|
818
|
+
end
|
819
|
+
|
820
|
+
# if escape then get into a loop and keep checking till -1 or another escape
|
821
|
+
#
|
822
|
+
if c == 27
|
823
|
+
buff=c.chr
|
824
|
+
# if there is another escape coming through then 2 keys were pressed so
|
825
|
+
# evaluate upon hitting an escape
|
826
|
+
# NOTE : i think only if ESc is followed by [ should be keep collectig
|
827
|
+
# otherwise the next char should evaluate. cases like F1 are already being sent in as high integer codes
|
828
|
+
while true
|
829
|
+
#$log.debug " #{Time.now.to_f} inside LOOP before getch "
|
830
|
+
# This getch seems to take enough time not to return a -1 for almost a second
|
831
|
+
# even if nodelay is true ??? XXX
|
832
|
+
FFI::NCurses.set_escdelay(5)
|
833
|
+
k = self.getch
|
834
|
+
#$log.debug "elapsed #{elapsed} millis inside LOOP AFTER getch #{k} (#{elapsed1})"
|
835
|
+
$log.debug "inside LOOP AFTER getch #{k} "
|
836
|
+
|
837
|
+
if k == 27
|
838
|
+
# seems like two Meta keys pressed in quick succession without chance for -1 to kick in
|
839
|
+
# but this still does not catch meta char followed by single char. M-za , it does.
|
840
|
+
if $esc_esc
|
841
|
+
if buff == 27.chr
|
842
|
+
$key_chr = "<ESC-ESC>"
|
843
|
+
return 2727
|
844
|
+
else
|
845
|
+
alert "buff is #{buff}"
|
846
|
+
end
|
847
|
+
end
|
848
|
+
$log.debug " 1251 before evaluate "
|
849
|
+
x = _evaluate_buff buff
|
850
|
+
# return ESC so it can be interpreted again.
|
851
|
+
@window.ungetch k
|
852
|
+
$key_chr = x if x
|
853
|
+
return $key_int if x
|
854
|
+
$log.warn "getchar: window.rb 1200 Found no mapping for #{buff} "
|
855
|
+
$key_chr = buff
|
856
|
+
return $key_int
|
857
|
+
#return buff # otherwise caught in loop ???
|
858
|
+
elsif k > -1
|
859
|
+
# FIXME next lne crashes if M-C-h pressed which gives 263
|
860
|
+
if k > 255
|
861
|
+
$log.warn "getchar: window.rb 1247 Found no mapping for #{buff} #{k} "
|
862
|
+
$key_int = k + 128
|
863
|
+
return $key_int
|
864
|
+
# this contains ESc followed by a high number
|
865
|
+
=begin
|
866
|
+
ka = key_tos(k)
|
867
|
+
if ka
|
868
|
+
$key_chr = "<M-" + ka[1..-1]
|
869
|
+
$key_int = k + 128
|
870
|
+
return $key_int
|
871
|
+
else
|
872
|
+
$key_chr = "UNKNOWN: Meta + #{k}"
|
873
|
+
return 9999
|
874
|
+
end
|
875
|
+
=end
|
876
|
+
end
|
877
|
+
|
878
|
+
buff += k.chr
|
879
|
+
# this is an alt/meta code. All other complex codes seem to have a [ after the escape
|
880
|
+
# so we will keep accumulating them.
|
881
|
+
# NOTE this still means that user can press Alt-[ and some letter in quick succession
|
882
|
+
# and it will accumulate rather than be interpreted as M-[.
|
883
|
+
#
|
884
|
+
if buff.length == 2 and k == 79
|
885
|
+
# this is Alt-O and can be a F key in some terms like xterm-color
|
886
|
+
elsif buff.length == 2 and k.chr != '['
|
887
|
+
x = _evaluate_buff buff
|
888
|
+
|
889
|
+
$key_chr = x
|
890
|
+
return $key_int if x
|
891
|
+
end
|
892
|
+
#$log.debug "XXX: getchar adding #{k}, #{k.chr} to buff #{buff} "
|
893
|
+
else
|
894
|
+
#$log.debug " GOT -1 in escape "
|
895
|
+
# it is -1 so evaluate
|
896
|
+
x = _evaluate_buff buff
|
897
|
+
$key_chr = x if x
|
898
|
+
return $key_int if x
|
899
|
+
$log.warn "getchar: window.rb 1256 Found no mapping for #{buff} "
|
900
|
+
$key_chr = buff
|
901
|
+
return $key_int
|
902
|
+
end
|
903
|
+
end
|
904
|
+
end
|
905
|
+
|
906
|
+
# what if keyname does not return anything
|
907
|
+
if c > 127
|
908
|
+
#$log.info "xxxgetchar: window.rb sending #{c} "
|
909
|
+
=begin
|
910
|
+
ch = FFI::NCurses::keyname(c)
|
911
|
+
# remove those ugly brackets around function keys
|
912
|
+
if ch && ch[-1]==')'
|
913
|
+
ch = ch.gsub(/[()]/,'')
|
914
|
+
end
|
915
|
+
if ch && ch.index("KEY_")
|
916
|
+
ch = ch.gsub(/KEY_/,'')
|
917
|
+
end
|
918
|
+
ch = "<#{ch}>" if ch
|
919
|
+
#return ch if ch
|
920
|
+
$key_chr = ch if ch
|
921
|
+
$key_chr = "UNKNOWN:#{c}" unless ch
|
922
|
+
$log.warn "getchar: window.rb 1234 Found no mapping for #{c} " unless ch
|
923
|
+
=end
|
924
|
+
#$key_chr = key_tos(ch)
|
925
|
+
return c
|
926
|
+
end
|
927
|
+
if c
|
928
|
+
#$key_chr = c.chr
|
929
|
+
return c
|
930
|
+
end
|
931
|
+
end
|
932
|
+
|
933
|
+
|
934
|
+
def getchar_as_char
|
935
|
+
$key_int = getchar
|
936
|
+
$key_chr = key_tos( $key_int )
|
937
|
+
return $key_chr
|
938
|
+
end
|
939
|
+
|
940
|
+
|
941
|
+
=begin
|
942
|
+
# NOTE I cannot use this since we are not ready to take a string, that is a big decision that
|
943
|
+
# requries a lot of work, and some decisions. We may bind using "<CR>" or "<C-d>" so
|
944
|
+
# maybe that's how we may need to send back
|
945
|
+
## get a character from user and return as a string
|
946
|
+
# Adapted from:
|
947
|
+
#http://stackoverflow.com/questions/174933/how-to-get-a-single-character-without-pressing-enter/8274275#8274275
|
948
|
+
# Need to take complex keys and matc against a hash.
|
949
|
+
# We cannot use the cetus example as is since here $stdin.ready? does not work and more importantly
|
950
|
+
# we have keyboard set to true so function keys and arrow keys are not returned as multiple values but as
|
951
|
+
# one int in the 255 and above range. so that must be interpreted separately.
|
952
|
+
#
|
953
|
+
# If we wait for -1 then quick M-a can get concatenated. we need to take care
|
954
|
+
# a ESC means the previous one should be evaluated and not contactenated
|
955
|
+
# FIXME = ESCESC 2727 - can't do this as will clash with Esc, M-(n).
|
956
|
+
# this is a rework of the above but returns an int so that the existing programs can keep working.
|
957
|
+
# We will store the char codes/ in a global string so user can get esp if unknown.
|
958
|
+
# UNUSED since we are still using int codes.
|
959
|
+
def getchar_as_char # -- {{{
|
960
|
+
c = nil
|
961
|
+
while true
|
962
|
+
c = self.getch
|
963
|
+
break if c != -1
|
964
|
+
end
|
965
|
+
|
966
|
+
cn = c
|
967
|
+
#return FFI::NCurses::keyname(c) if [10,13,127,0,32,8].include? c
|
968
|
+
$key_int = c
|
969
|
+
if cn >= 0 && cn < 128 && cn != 27
|
970
|
+
$key_chr = key_tos(c)
|
971
|
+
return $key_chr
|
972
|
+
end
|
973
|
+
|
974
|
+
# if escape then get into a loop and keep checking till -1 or another escape
|
975
|
+
#
|
976
|
+
if c == 27
|
977
|
+
buff=c.chr
|
978
|
+
# if there is another escape coming through then 2 keys were pressed so
|
979
|
+
# evaluate upon hitting an escape
|
980
|
+
# NOTE : i think only if ESc is followed by [ should be keep collectig
|
981
|
+
# otherwise the next char should evaluate. cases like F1 are already being sent in as high integer codes
|
982
|
+
while true
|
983
|
+
|
984
|
+
k = self.getch
|
985
|
+
|
986
|
+
if k == 27
|
987
|
+
# seems like two Meta keys pressed in quick succession without chance for -1 to kick in
|
988
|
+
# but this still does not catch meta char followed by single char. M-za
|
989
|
+
x = _evaluate_buff buff
|
990
|
+
# return ESC so it can be interpreted again.
|
991
|
+
@window.ungetch k
|
992
|
+
return x if x
|
993
|
+
$log.warn "getchar: window.rb 1200 Found no mapping for #{buff} "
|
994
|
+
return buff # otherwise caught in loop ???
|
995
|
+
elsif k > -1
|
996
|
+
buff += k.chr
|
997
|
+
# this is an alt/meta code. All other complex codes seem to have a [ after the escape
|
998
|
+
# so we will keep accumulating them.
|
999
|
+
# NOTE this still means that user can press Alt-[ and some letter in quick succession
|
1000
|
+
# and it will accumulate rather than be interpreted as M-[.
|
1001
|
+
#
|
1002
|
+
if buff.length == 2 and k.chr != '['
|
1003
|
+
x = _evaluate_buff buff
|
1004
|
+
return x if x
|
1005
|
+
end
|
1006
|
+
#$log.debug "XXX: getchar adding #{k}, #{k.chr} to buff #{buff} "
|
1007
|
+
else
|
1008
|
+
# it is -1 so evaluate
|
1009
|
+
x = _evaluate_buff buff
|
1010
|
+
return x if x
|
1011
|
+
return buff
|
1012
|
+
end
|
1013
|
+
end
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
# what if keyname does not return anything
|
1017
|
+
if c > 127
|
1018
|
+
#$log.info "xxxgetchar: window.rb sending #{c} "
|
1019
|
+
ch = FFI::NCurses::keyname(c)
|
1020
|
+
# remove those ugly brackets around function keys
|
1021
|
+
if ch && ch[-1]==')'
|
1022
|
+
ch = ch.gsub(/[()]/,'')
|
1023
|
+
end
|
1024
|
+
return ch if ch
|
1025
|
+
$log.warn "getchar: window.rb 1234 Found no mapping for #{c} "
|
1026
|
+
return c
|
1027
|
+
end
|
1028
|
+
return c.chr if c
|
1029
|
+
end # -- }}}
|
1030
|
+
=end
|
1031
|
+
|
1032
|
+
# Generate and return an int for a newkey which user has specified in yml file.
|
1033
|
+
# We use hash, which won't allow me to derive key string
|
1034
|
+
# in case loop user can do:
|
1035
|
+
# when KEY_ENTER
|
1036
|
+
# when 32
|
1037
|
+
# when $kh_int["S-F2"]
|
1038
|
+
def _get_int_for_newkey x
|
1039
|
+
# FIXME put the declaration somewhere else maybe in window cons ???
|
1040
|
+
y = $kh_int[x]
|
1041
|
+
# when i give user the hash, he can get the string back ???
|
1042
|
+
$kh_int[y] = x unless $kh_int.key? y
|
1043
|
+
return y
|
1044
|
+
end
|
1045
|
+
# check buffer if some key mapped in global kh for this
|
1046
|
+
# Otherwise if it is 2 keys then it is a Meta key
|
1047
|
+
# Can return nil if no mapping
|
1048
|
+
# @return [String] string code for key (since it is mostly from $kh. Also sets, $key_int
|
1049
|
+
private
|
1050
|
+
def _evaluate_buff buff
|
1051
|
+
if buff == 27.chr
|
1052
|
+
$key_int = 27
|
1053
|
+
#$escend = Time.now.to_f
|
1054
|
+
#elapsed = ($escend - $escstart)*1000
|
1055
|
+
#$log.debug " #{elapsed} evaluated to ESC"
|
1056
|
+
$key_chr = "<ESC>"
|
1057
|
+
return $key_chr
|
1058
|
+
end
|
1059
|
+
x=$kh[buff]
|
1060
|
+
if x
|
1061
|
+
$key_int = 9999
|
1062
|
+
$key_int = _get_int_for_newkey(x)
|
1063
|
+
$key_cache[$key_int] = x unless $key_cache.key? $key_int
|
1064
|
+
# FIXME currently 9999 signifies unknown key, but since this is derived from a user list
|
1065
|
+
# we could have some dummy number being passed or set by user too.
|
1066
|
+
return "<#{x}>"
|
1067
|
+
end
|
1068
|
+
#$log.debug "XXX: getchar returning with #{buff}"
|
1069
|
+
if buff.size == 2
|
1070
|
+
## possibly a meta/alt char
|
1071
|
+
k = buff[-1]
|
1072
|
+
$key_int = 128 + k.ord
|
1073
|
+
return key_tos( $key_int )
|
1074
|
+
end
|
1075
|
+
$key_int = 99999
|
1076
|
+
nil
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
end # class DefaultKeyReader -- }}}
|
1080
|
+
|
1081
|
+
end
|