canis 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +45 -0
  3. data/CHANGES +52 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +24 -0
  7. data/Rakefile +2 -0
  8. data/canis.gemspec +25 -0
  9. data/examples/alpmenu.rb +46 -0
  10. data/examples/app.sample +19 -0
  11. data/examples/appemail.rb +191 -0
  12. data/examples/atree.rb +105 -0
  13. data/examples/bline.rb +181 -0
  14. data/examples/common/devel.rb +319 -0
  15. data/examples/common/file.rb +93 -0
  16. data/examples/data/README.markdown +9 -0
  17. data/examples/data/brew.txt +38 -0
  18. data/examples/data/color.2 +37 -0
  19. data/examples/data/gemlist.txt +59 -0
  20. data/examples/data/lotr.txt +12 -0
  21. data/examples/data/ports.txt +136 -0
  22. data/examples/data/table.txt +37 -0
  23. data/examples/data/tasks.csv +88 -0
  24. data/examples/data/tasks.txt +27 -0
  25. data/examples/data/todo.txt +16 -0
  26. data/examples/data/todocsv.csv +28 -0
  27. data/examples/data/unix1.txt +21 -0
  28. data/examples/data/unix2.txt +11 -0
  29. data/examples/dbdemo.rb +506 -0
  30. data/examples/dirtree.rb +177 -0
  31. data/examples/newtabbedwindow.rb +100 -0
  32. data/examples/newtesttabp.rb +92 -0
  33. data/examples/tabular.rb +212 -0
  34. data/examples/tasks.rb +179 -0
  35. data/examples/term2.rb +88 -0
  36. data/examples/testbuttons.rb +307 -0
  37. data/examples/testcombo.rb +102 -0
  38. data/examples/testdb.rb +182 -0
  39. data/examples/testfields.rb +208 -0
  40. data/examples/testflowlayout.rb +43 -0
  41. data/examples/testkeypress.rb +98 -0
  42. data/examples/testlistbox.rb +187 -0
  43. data/examples/testlistbox1.rb +199 -0
  44. data/examples/testmessagebox.rb +144 -0
  45. data/examples/testprogress.rb +116 -0
  46. data/examples/testree.rb +107 -0
  47. data/examples/testsplitlayout.rb +53 -0
  48. data/examples/testsplitlayout1.rb +49 -0
  49. data/examples/teststacklayout.rb +48 -0
  50. data/examples/testwsshortcuts.rb +68 -0
  51. data/examples/testwsshortcuts2.rb +129 -0
  52. data/lib/canis.rb +16 -0
  53. data/lib/canis/core/docs/index.txt +104 -0
  54. data/lib/canis/core/docs/list.txt +16 -0
  55. data/lib/canis/core/docs/style_help.yml +34 -0
  56. data/lib/canis/core/docs/tabbedpane.txt +15 -0
  57. data/lib/canis/core/docs/table.txt +31 -0
  58. data/lib/canis/core/docs/textpad.txt +48 -0
  59. data/lib/canis/core/docs/tree.txt +23 -0
  60. data/lib/canis/core/include/.DS_Store +0 -0
  61. data/lib/canis/core/include/action.rb +83 -0
  62. data/lib/canis/core/include/actionmanager.rb +49 -0
  63. data/lib/canis/core/include/appmethods.rb +179 -0
  64. data/lib/canis/core/include/bordertitle.rb +49 -0
  65. data/lib/canis/core/include/canisparser.rb +100 -0
  66. data/lib/canis/core/include/colorparser.rb +437 -0
  67. data/lib/canis/core/include/defaultfilerenderer.rb +64 -0
  68. data/lib/canis/core/include/io.rb +320 -0
  69. data/lib/canis/core/include/layouts/SplitLayout.rb +161 -0
  70. data/lib/canis/core/include/layouts/abstractlayout.rb +213 -0
  71. data/lib/canis/core/include/layouts/flowlayout.rb +104 -0
  72. data/lib/canis/core/include/layouts/stacklayout.rb +109 -0
  73. data/lib/canis/core/include/listbindings.rb +89 -0
  74. data/lib/canis/core/include/listeditable.rb +319 -0
  75. data/lib/canis/core/include/listoperations.rb +61 -0
  76. data/lib/canis/core/include/listselectionmodel.rb +388 -0
  77. data/lib/canis/core/include/multibuffer.rb +173 -0
  78. data/lib/canis/core/include/ractionevent.rb +73 -0
  79. data/lib/canis/core/include/rchangeevent.rb +27 -0
  80. data/lib/canis/core/include/rhistory.rb +95 -0
  81. data/lib/canis/core/include/rinputdataevent.rb +47 -0
  82. data/lib/canis/core/include/textdocument.rb +111 -0
  83. data/lib/canis/core/include/vieditable.rb +175 -0
  84. data/lib/canis/core/include/widgetmenu.rb +66 -0
  85. data/lib/canis/core/system/colormap.rb +165 -0
  86. data/lib/canis/core/system/keydefs.rb +32 -0
  87. data/lib/canis/core/system/ncurses.rb +237 -0
  88. data/lib/canis/core/system/panel.rb +129 -0
  89. data/lib/canis/core/system/window.rb +1081 -0
  90. data/lib/canis/core/util/ansiparser.rb +119 -0
  91. data/lib/canis/core/util/app.rb +696 -0
  92. data/lib/canis/core/util/basestack.rb +412 -0
  93. data/lib/canis/core/util/defaultcolorparser.rb +84 -0
  94. data/lib/canis/core/util/extras/README +5 -0
  95. data/lib/canis/core/util/extras/bottomline.rb +1815 -0
  96. data/lib/canis/core/util/extras/padreader.rb +192 -0
  97. data/lib/canis/core/util/focusmanager.rb +31 -0
  98. data/lib/canis/core/util/helpmanager.rb +160 -0
  99. data/lib/canis/core/util/oldwidgetshortcuts.rb +304 -0
  100. data/lib/canis/core/util/promptmenu.rb +235 -0
  101. data/lib/canis/core/util/rcommandwindow.rb +933 -0
  102. data/lib/canis/core/util/rdialogs.rb +520 -0
  103. data/lib/canis/core/util/textutils.rb +74 -0
  104. data/lib/canis/core/util/viewer.rb +238 -0
  105. data/lib/canis/core/util/widgetshortcuts.rb +508 -0
  106. data/lib/canis/core/widgets/applicationheader.rb +103 -0
  107. data/lib/canis/core/widgets/box.rb +58 -0
  108. data/lib/canis/core/widgets/divider.rb +310 -0
  109. data/lib/canis/core/widgets/extras/README.md +12 -0
  110. data/lib/canis/core/widgets/extras/rtextarea.rb +960 -0
  111. data/lib/canis/core/widgets/extras/stackflow.rb +474 -0
  112. data/lib/canis/core/widgets/keylabelprinter.rb +194 -0
  113. data/lib/canis/core/widgets/listbox.rb +326 -0
  114. data/lib/canis/core/widgets/listfooter.rb +86 -0
  115. data/lib/canis/core/widgets/rcombo.rb +210 -0
  116. data/lib/canis/core/widgets/rcontainer.rb +415 -0
  117. data/lib/canis/core/widgets/rlink.rb +30 -0
  118. data/lib/canis/core/widgets/rmenu.rb +970 -0
  119. data/lib/canis/core/widgets/rmenulink.rb +30 -0
  120. data/lib/canis/core/widgets/rmessagebox.rb +400 -0
  121. data/lib/canis/core/widgets/rprogress.rb +118 -0
  122. data/lib/canis/core/widgets/rtabbedpane.rb +631 -0
  123. data/lib/canis/core/widgets/rtabbedwindow.rb +70 -0
  124. data/lib/canis/core/widgets/rwidget.rb +3634 -0
  125. data/lib/canis/core/widgets/scrollbar.rb +147 -0
  126. data/lib/canis/core/widgets/statusline.rb +113 -0
  127. data/lib/canis/core/widgets/table.rb +1072 -0
  128. data/lib/canis/core/widgets/tabular.rb +264 -0
  129. data/lib/canis/core/widgets/textpad.rb +1674 -0
  130. data/lib/canis/core/widgets/tree.rb +690 -0
  131. data/lib/canis/core/widgets/tree/treecellrenderer.rb +150 -0
  132. data/lib/canis/core/widgets/tree/treemodel.rb +432 -0
  133. data/lib/canis/version.rb +3 -0
  134. 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=''
777
+ $kh[KEY_S_F1]="S-F1"
778
+ $kh['']="S-F2"
779
+ $kh['']="S-F3"
780
+ $kh['']="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