rbcurse-core 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. data/README.md +69 -0
  2. data/VERSION +1 -0
  3. data/examples/abasiclist.rb +151 -0
  4. data/examples/alpmenu.rb +46 -0
  5. data/examples/app.sample +17 -0
  6. data/examples/atree.rb +100 -0
  7. data/examples/common/file.rb +45 -0
  8. data/examples/data/README.markdown +9 -0
  9. data/examples/data/brew.txt +38 -0
  10. data/examples/data/color.2 +37 -0
  11. data/examples/data/gemlist.txt +60 -0
  12. data/examples/data/lotr.txt +12 -0
  13. data/examples/data/ports.txt +136 -0
  14. data/examples/data/table.txt +37 -0
  15. data/examples/data/tasks.csv +88 -0
  16. data/examples/data/tasks.txt +27 -0
  17. data/examples/data/todo.txt +10 -0
  18. data/examples/data/todocsv.csv +28 -0
  19. data/examples/data/unix1.txt +21 -0
  20. data/examples/data/unix2.txt +11 -0
  21. data/examples/dbdemo.rb +487 -0
  22. data/examples/dirtree.rb +90 -0
  23. data/examples/newtabbedwindow.rb +100 -0
  24. data/examples/newtesttabp.rb +92 -0
  25. data/examples/tabular.rb +132 -0
  26. data/examples/tasks.rb +167 -0
  27. data/examples/term2.rb +83 -0
  28. data/examples/testkeypress.rb +72 -0
  29. data/examples/testlistbox.rb +158 -0
  30. data/examples/testmessagebox.rb +140 -0
  31. data/examples/testree.rb +106 -0
  32. data/examples/testwsshortcuts.rb +66 -0
  33. data/examples/testwsshortcuts2.rb +127 -0
  34. data/lib/rbcurse.rb +8 -0
  35. data/lib/rbcurse/core/docs/index.txt +73 -0
  36. data/lib/rbcurse/core/include/action.rb +40 -0
  37. data/lib/rbcurse/core/include/appmethods.rb +112 -0
  38. data/lib/rbcurse/core/include/bordertitle.rb +41 -0
  39. data/lib/rbcurse/core/include/chunk.rb +182 -0
  40. data/lib/rbcurse/core/include/io.rb +953 -0
  41. data/lib/rbcurse/core/include/listcellrenderer.rb +140 -0
  42. data/lib/rbcurse/core/include/listeditable.rb +317 -0
  43. data/lib/rbcurse/core/include/listscrollable.rb +590 -0
  44. data/lib/rbcurse/core/include/listselectable.rb +264 -0
  45. data/lib/rbcurse/core/include/multibuffer.rb +83 -0
  46. data/lib/rbcurse/core/include/orderedhash.rb +77 -0
  47. data/lib/rbcurse/core/include/ractionevent.rb +67 -0
  48. data/lib/rbcurse/core/include/rchangeevent.rb +27 -0
  49. data/lib/rbcurse/core/include/rhistory.rb +62 -0
  50. data/lib/rbcurse/core/include/rinputdataevent.rb +47 -0
  51. data/lib/rbcurse/core/include/vieditable.rb +170 -0
  52. data/lib/rbcurse/core/system/colormap.rb +163 -0
  53. data/lib/rbcurse/core/system/keyboard.rb +150 -0
  54. data/lib/rbcurse/core/system/keydefs.rb +30 -0
  55. data/lib/rbcurse/core/system/ncurses.rb +218 -0
  56. data/lib/rbcurse/core/system/panel.rb +162 -0
  57. data/lib/rbcurse/core/system/window.rb +901 -0
  58. data/lib/rbcurse/core/util/ansiparser.rb +117 -0
  59. data/lib/rbcurse/core/util/app.rb +1235 -0
  60. data/lib/rbcurse/core/util/basestack.rb +407 -0
  61. data/lib/rbcurse/core/util/bottomline.rb +1850 -0
  62. data/lib/rbcurse/core/util/colorparser.rb +71 -0
  63. data/lib/rbcurse/core/util/focusmanager.rb +31 -0
  64. data/lib/rbcurse/core/util/padreader.rb +189 -0
  65. data/lib/rbcurse/core/util/rcommandwindow.rb +587 -0
  66. data/lib/rbcurse/core/util/rdialogs.rb +619 -0
  67. data/lib/rbcurse/core/util/viewer.rb +149 -0
  68. data/lib/rbcurse/core/util/widgetshortcuts.rb +505 -0
  69. data/lib/rbcurse/core/widgets/applicationheader.rb +102 -0
  70. data/lib/rbcurse/core/widgets/box.rb +58 -0
  71. data/lib/rbcurse/core/widgets/divider.rb +310 -0
  72. data/lib/rbcurse/core/widgets/keylabelprinter.rb +178 -0
  73. data/lib/rbcurse/core/widgets/rcombo.rb +238 -0
  74. data/lib/rbcurse/core/widgets/rcontainer.rb +415 -0
  75. data/lib/rbcurse/core/widgets/rlink.rb +30 -0
  76. data/lib/rbcurse/core/widgets/rlist.rb +723 -0
  77. data/lib/rbcurse/core/widgets/rmenu.rb +939 -0
  78. data/lib/rbcurse/core/widgets/rmenulink.rb +22 -0
  79. data/lib/rbcurse/core/widgets/rmessagebox.rb +373 -0
  80. data/lib/rbcurse/core/widgets/rprogress.rb +118 -0
  81. data/lib/rbcurse/core/widgets/rtabbedpane.rb +615 -0
  82. data/lib/rbcurse/core/widgets/rtabbedwindow.rb +68 -0
  83. data/lib/rbcurse/core/widgets/rtextarea.rb +920 -0
  84. data/lib/rbcurse/core/widgets/rtextview.rb +780 -0
  85. data/lib/rbcurse/core/widgets/rtree.rb +787 -0
  86. data/lib/rbcurse/core/widgets/rwidget.rb +3040 -0
  87. data/lib/rbcurse/core/widgets/scrollbar.rb +143 -0
  88. data/lib/rbcurse/core/widgets/statusline.rb +94 -0
  89. data/lib/rbcurse/core/widgets/tabular.rb +264 -0
  90. data/lib/rbcurse/core/widgets/tabularwidget.rb +1211 -0
  91. data/lib/rbcurse/core/widgets/textpad.rb +516 -0
  92. data/lib/rbcurse/core/widgets/tree/treecellrenderer.rb +150 -0
  93. data/lib/rbcurse/core/widgets/tree/treemodel.rb +428 -0
  94. metadata +156 -0
@@ -0,0 +1,117 @@
1
+ # ----------------------------------------------------------------------------- #
2
+ # File: colorparser.rb
3
+ # Description: Default parse for our tmux format
4
+ # The aim is to be able to specify parsers so different kinds
5
+ # of formatting or documents can be used, such as ANSI formatted
6
+ # manpages.
7
+ # Author: rkumar http://github.com/rkumar/rbcurse/
8
+ # Date: 07.11.11 - 13:17
9
+ # License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
10
+ # Last update: use ,,L
11
+ # ----------------------------------------------------------------------------- #
12
+ # == TODO
13
+ # - perhaps we can compile the regexp once and reuse
14
+ #
15
+
16
+ class AnsiParser
17
+
18
+ # NOTE: Experimental and minimal
19
+ # parses the formatted string and yields either an array of color, bgcolor and attrib
20
+ # or the text. This will be called by convert_to_chunk.
21
+ #
22
+ # Currently, assumes colors and attributes are correct. No error checking or fancy stuff.
23
+ # s="#[fg=green]hello there#[fg=yellow, bg=black, dim]"
24
+ # @since 1.4.1 2011-11-3 experimental, can change
25
+ # @return [nil] knows nothign about output format.
26
+
27
+ def parse_format s # yields attribs or text
28
+ ## set default colors
29
+ color = :white
30
+ bgcolor = :black
31
+ attrib = FFI::NCurses::A_NORMAL
32
+ text = ""
33
+
34
+ ## split #[...]
35
+ #a = s.split /(#\[[^\]]*\])/
36
+ a = s.split /(\x1b\[\d*(?:;\d+)*?[a-zA-Z])/
37
+ a.each { |e|
38
+ ## process color or attrib portion
39
+ #[ "", "\e[1m", "", "\e[34m", "", "\e[47m", "Showing all items...", "\e[0m", "", "\e[0m", "\n"]
40
+ if e[0] == "\x1b" && e[-1] == "m"
41
+
42
+ #e.each { |f| x=/^.\[(.*).$/.match(f)
43
+ $log.debug "XXX: ANSI e #{e} "
44
+ x=/^.\[(.*).$/.match(e)
45
+ color, bgcolor, attrib = nil, nil, nil
46
+ $log.debug "XXX: ANSI #{x} ..... #{x[1]} "
47
+ args = x[1].split ';'
48
+ ## first split on commas to separate fg, bg and attr
49
+ # http://ascii-table.com/ansi-escape-sequences.php
50
+ args.each { |att|
51
+ $log.debug "XXX: ANSI att: #{att} "
52
+ case att.to_i
53
+ when 0
54
+ color, bgcolor, attrib = nil, nil, nil
55
+ yield :reset # actually this resets all so we need an endall or clearall reset
56
+
57
+ when 1
58
+ attrib = 'bold'
59
+ when 2
60
+ attrib = 'dim'
61
+ when 4
62
+ attrib = 'underline'
63
+ when 5
64
+ attrib = 'blink'
65
+ when 7
66
+ attrib = 'reverse'
67
+ when 8
68
+ attrib = 'hidden' # XXX
69
+ when 30
70
+ color = 'black'
71
+ when 31
72
+ color = 'red'
73
+ when 32
74
+ color = 'green'
75
+ when 33
76
+ color = 'yellow'
77
+ when 34
78
+ color = 'blue'
79
+ when 35
80
+ color = 'magenta'
81
+ when 36
82
+ color = 'cyan'
83
+ when 37
84
+ color = 'white'
85
+
86
+ #Background colors
87
+ when 40
88
+ bgcolor = 'black'
89
+ when 41
90
+ bgcolor = 'red'
91
+ when 42
92
+ bgcolor = 'green'
93
+ when 43
94
+ bgcolor = 'yellow'
95
+ when 44
96
+ bgcolor = 'blue'
97
+ when 45
98
+ bgcolor = 'magenta'
99
+ when 46
100
+ bgcolor = 'cyan'
101
+ when 47
102
+ bgcolor = 'white'
103
+ else
104
+ $log.warn "XXX: WARN ANSI not used #{att} "
105
+ end
106
+ } # args.ea
107
+ #} # e.each
108
+ $log.debug "XXX: ANSI YIELDING #{color} , #{bgcolor} , #{attrib} "
109
+ yield [color,bgcolor,attrib] if block_given?
110
+ else
111
+ text = e
112
+ yield text if block_given?
113
+ end
114
+ } # a.each
115
+ end
116
+
117
+ end
@@ -0,0 +1,1235 @@
1
+ =begin
2
+ * Name: App
3
+ * Description: Experimental Application class
4
+ * Author: rkumar (arunachalesha)
5
+ * file created 2010-09-04 22:10
6
+ Todo:
7
+
8
+ * 1.5.0 : redo the constructors of these widgets
9
+ as per stack flow improved, and make the constructor
10
+ simpler, no need for jugglery of row col etc. let user
11
+ specify in config.
12
+ --------
13
+ * License:
14
+ Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
15
+
16
+ =end
17
+ require 'logger'
18
+ require 'rbcurse'
19
+ require 'rbcurse/core/util/widgetshortcuts'
20
+
21
+ require 'rbcurse/core/util/bottomline'
22
+ $tt ||= RubyCurses::Bottomline.new
23
+ $tt.name = "$tt"
24
+ require 'forwardable'
25
+ module Kernel
26
+ extend Forwardable
27
+ def_delegators :$tt, :ask, :say, :agree, :choose, :numbered_menu, :display_text, :display_text_interactive, :display_list, :say_with_pause, :hide_bottomline, :say_with_wait
28
+ end
29
+ include RubyCurses
30
+ include RubyCurses::Utils
31
+ include Io
32
+ module RubyCurses
33
+ extend self
34
+ ##
35
+ #
36
+ # @since 1.2.0
37
+ # TODO -
38
+ # / combo
39
+ # - popup
40
+ # - promptmenu
41
+ # - stack and flow should be objects in Form/App?, put in widget when creating
42
+ # - box / rect
43
+ # - para looks like a label that is more than one line, and calculates rows itself based on text
44
+ # - multicontainer
45
+ # - multitextview, multisplit
46
+ # - tabbedpane
47
+ # / table - more work regarding vim keys, also editable
48
+ # - margin - is left offset
49
+ # http://lethain.com/entry/2007/oct/15/getting-started-shoes-os-x/
50
+ #
51
+
52
+ class Widget
53
+ def changed *args, &block
54
+ bind :CHANGED, *args, &block
55
+ end
56
+ def leave *args, &block
57
+ bind :LEAVE, *args, &block
58
+ end
59
+ def enter *args, &block
60
+ bind :ENTER, *args, &block
61
+ end
62
+ # actually we already have command() for buttons
63
+ def click *args, &block
64
+ bind :PRESS, *args, &block
65
+ end
66
+ end
67
+ class CheckBox
68
+ # a little dicey XXX
69
+ def text(*val)
70
+ if val.empty?
71
+ @value ? @onvalue : @offvalue
72
+ else
73
+ super
74
+ end
75
+ end
76
+ end
77
+ # This is the Application class which does the job of setting up the
78
+ # environment, and closing it at the end.
79
+ class App
80
+ include RubyCurses::WidgetShortcuts
81
+ attr_reader :config
82
+ attr_reader :form
83
+ attr_reader :window
84
+ attr_writer :quit_key
85
+ # the row on which to prompt user for any inputs
86
+ #attr_accessor :prompt_row # 2011-10-17 14:06:22
87
+
88
+ extend Forwardable
89
+ def_delegators :$tt, :ask, :say, :agree, :choose, :numbered_menu, :display_text, :display_text_interactive, :display_list
90
+
91
+ # TODO: i should be able to pass window coords here in config
92
+ # :title
93
+ def initialize config={}, &block
94
+ #$log.debug " inside constructor of APP #{config} "
95
+ @config = config
96
+
97
+
98
+ widget_shortcuts_init
99
+ #@app_row = @app_col = 0
100
+ #@stack = [] # stack's coordinates
101
+ #@flowstack = []
102
+ @variables = {}
103
+ # if we are creating child objects then we will not use outer form. this object will manage
104
+ @current_object = []
105
+ @_system_commands = %w{ bind_global bind_component field_help_text }
106
+
107
+ init_vars
108
+ $log.debug "XXX APP CONFIG: #{@config} " if $log.debug?
109
+ run &block
110
+ end
111
+ def init_vars
112
+ @quit_key ||= FFI::NCurses::KEY_F10
113
+ # actually this should be maintained inside ncurses pack, so not loaded 2 times.
114
+ # this way if we call an app from existing program, App won't start ncurses.
115
+ unless $ncurses_started
116
+ init_ncurses
117
+ end
118
+ $lastline = Ncurses.LINES - 1
119
+ #@message_row = Ncurses.LINES-1
120
+ #@prompt_row = @message_row # hope to use for ask etc # 2011-10-17 14:06:27
121
+ unless $log
122
+ path = File.join(ENV["LOGDIR"] || "./" ,"rbc13.log")
123
+ file = File.open(path, File::WRONLY|File::TRUNC|File::CREAT)
124
+ $log = Logger.new(path)
125
+ $log.level = Logger::DEBUG # change to warn when you've tested your app.
126
+ colors = Ncurses.COLORS
127
+ $log.debug "START #{colors} colors --------- #{$0} win: #{@window} "
128
+ end
129
+ end
130
+ def logger; return $log; end
131
+ def close
132
+ raw_message_destroy
133
+ $log.debug " INSIDE CLOSE, #{@stop_ncurses_on_close} "
134
+ @window.destroy if !@window.nil?
135
+ $log.debug " INSIDE CLOSE, #{@stop_ncurses_on_close} "
136
+ if @stop_ncurses_on_close
137
+ $tt.destroy # added on 2011-10-9 since we created a window, but only hid it after use
138
+ VER::stop_ncurses
139
+ $log.debug " CLOSING NCURSES"
140
+ end
141
+ #p $error_message.value unless $error_message.value.nil?
142
+ $log.debug " CLOSING APP"
143
+ #end
144
+ end
145
+ # not sure, but user shuld be able to trap keystrokes if he wants
146
+ # but do i still call handle_key if he does, or give him total control.
147
+ # But loop is already called by framework
148
+ def loop &block
149
+ @form.repaint
150
+ @window.wrefresh
151
+ Ncurses::Panel.update_panels
152
+ @break_key = ?\C-q.getbyte(0)
153
+ # added this extra loop since from some places we exit using throw :close
154
+ # amd that was in a much higher place, and was getting us right out, with
155
+ # no chance of user canceling quit. This extra loop allows us to remain
156
+ # added on 2011-11-24
157
+ while true
158
+ catch :close do
159
+ while((ch = @window.getchar()) != 999 )
160
+ #break if ch == @break_key
161
+ if ch == @break_key || ch == @quit_key
162
+ #stopping = @window.fire_close_handler
163
+ #break if stopping.nil? || stopping
164
+ break
165
+ end
166
+
167
+ if @keyblock
168
+ str = keycode_tos ch
169
+ @keyblock.call(str.gsub(/-/, "_").to_sym) # not used ever
170
+ end
171
+
172
+ yield ch if block # <<<----
173
+ # this is what the user should have control ove. earlier we would put this in
174
+ # a try catch block so user could do what he wanted with the error. Now we
175
+ # need to get it to him somehow, perhaps through a block or on_error event
176
+ begin
177
+ @form.handle_key ch
178
+ rescue => err
179
+ alert err.to_s
180
+ $log.debug( "handle_key rescue reached ")
181
+ $log.debug( err)
182
+ $log.debug(err.backtrace.join("\n"))
183
+ end
184
+ #@form.repaint # was this duplicate ?? handle calls repaint not needed
185
+ @window.wrefresh
186
+ end
187
+ end # catch
188
+ stopping = @window.fire_close_handler
189
+ @window.wrefresh
190
+ break if stopping.nil? || stopping
191
+ end # while
192
+ end
193
+ # if calling loop separately better to call this, since it will shut off ncurses
194
+ # and print error on screen.
195
+ def safe_loop &block
196
+ begin
197
+ loop &block
198
+ rescue => ex
199
+ $log.debug( "APP.rb rescue reached ")
200
+ $log.debug( ex) if ex
201
+ $log.debug(ex.backtrace.join("\n")) if ex
202
+ ensure
203
+ close
204
+ # putting it here allows it to be printed on screen, otherwise it was not showing at all.
205
+ if ex
206
+ puts "========== EXCEPTION =========="
207
+ p ex
208
+ puts "==============================="
209
+ puts(ex.backtrace.join("\n"))
210
+ end
211
+ end
212
+ end
213
+ # returns a symbol of the key pressed
214
+ # e.g. :C_c for Ctrl-C
215
+ # :Space, :bs, :M_d etc
216
+ def keypress &block
217
+ @keyblock = block
218
+ end
219
+ # updates a global var with text. Calling app has to set up a Variable with that name and attach to
220
+ # a label so it can be printed.
221
+ def message text
222
+ $status_message.value = text # trying out 2011-10-9
223
+ #@message.value = text # 2011-10-17 14:07:01
224
+ end
225
+ # @deprecated please use {#status_line} instead of a message label
226
+ def message_row row
227
+ raise "Please use create_message_label first as message_label is no longer default behaviour" unless @message_label
228
+ @message_label.row = row
229
+ end
230
+ # during a process, when you wish to update status, since ordinarily the thread is busy
231
+ # and form does not get control back, so the window won't refresh.
232
+ # This will only update on keystroke since it uses statusline
233
+ # @deprecated please use {#status_line} instead of a message label
234
+ def message_immediate text
235
+ $log.warn "DEPRECATED, use message(), or say_with_pause, or say"
236
+ $status_message.value = text # trying out 2011-10-9 user needs to use in statusline command
237
+ # 2011-10-17 knocking off label, should be printed on status_line
238
+ end
239
+ # Usage: application is inside a long processing loop and wishes to print ongoing status
240
+ # NOTE: if you use this, you must use raw_message_destroy at some stage, after processing
241
+ # or on_leave of object.
242
+ # @deprecated Use say_with_pause or use rdialogs status_window, see test2.rb
243
+ def raw_message text, config={}, &blk
244
+ $raw_window ||= one_line_window last_line(), config, &blk
245
+ width = $raw_window.width == 0 ? FFI::NCurses.COLS : $raw_window.width
246
+ text = "%-*s" % [width, text]
247
+
248
+ $raw_window.attron(Ncurses.COLOR_PAIR($normalcolor) )
249
+ $raw_window.printstring 0,0,text, $normalcolor #, 'normal' if @title
250
+ $raw_window.wrefresh
251
+
252
+ end
253
+ def raw_message_destroy
254
+ if $raw_window
255
+ $raw_window.destroy
256
+ $raw_window = nil
257
+ end
258
+ end
259
+ # shows a simple progress bar on last row, using stdscr
260
+ # @param [Float, Array<Fixnum,Fixnum>] percentage, or part/total
261
+ # If Array of two numbers is given then also print part/total on left of bar
262
+ # @deprecated - don't use stdscr at all, use rdialogs status_window (see test2.rb)
263
+ def raw_progress arg
264
+ $log.warning "WARNING: don't use this method as it uses stdscr"
265
+ row = @message_label ? @message_label.row : Ncurses.LINES-1
266
+ s = nil
267
+ case arg
268
+ when Array
269
+ #calculate percentage
270
+ pc = (arg[0]*1.0)/arg[1]
271
+ # print items/total also
272
+ s = "%-10s" % "(#{arg[0]}/#{arg[1]})"
273
+ when
274
+ Float
275
+ pc = arg
276
+ end
277
+ scr = Ncurses.stdscr
278
+ endcol = Ncurses.COLS-1
279
+ startcol = endcol - 12
280
+ stext = ("=" * (pc*10).to_i)
281
+ text = "[" + "%-10s" % stext + "]"
282
+ Ncurses.mvprintw( row ,startcol-10, s) if s
283
+ Ncurses.mvprintw row ,startcol, text
284
+ #scr.refresh() # XXX FFI NW
285
+
286
+ end
287
+ # used only by LiveConsole, if enables in an app, usually only during testing.
288
+ def get_binding
289
+ return binding()
290
+ end
291
+ #
292
+ # suspends curses so you can play around on the shell
293
+ # or in cooked mode like Vim does. Expects a block to be passed.
294
+ # Purpose: you can print some stuff without creating a window, or
295
+ # just run shell commands without coming out.
296
+ # NOTE: if you pass clear as true, then the screen will be cleared
297
+ # and you can use puts or print to print. You may have to flush.
298
+ # However, with clear as false, the screen will not be cleared. You
299
+ # will have to print using printw, and if you expect user input
300
+ # you must do a "system /bin/stty sane"
301
+ # If you print stuff, you will have to put a getch() or system("read")
302
+ # to pause the screen.
303
+ def suspend clear=true
304
+ return unless block_given?
305
+ Ncurses.def_prog_mode
306
+ if clear
307
+ Ncurses.endwin
308
+ # NOTE: avoid false since screen remains half off
309
+ # too many issues
310
+ else
311
+ system "/bin/stty sane"
312
+ end
313
+ yield if block_given?
314
+ Ncurses.reset_prog_mode
315
+ if !clear
316
+ # Hope we don't screw your terminal up with this constantly.
317
+ VER::stop_ncurses
318
+ VER::start_ncurses
319
+ #@form.reset_all # not required
320
+ end
321
+ @form.repaint
322
+ @window.wrefresh
323
+ Ncurses::Panel.update_panels
324
+ end
325
+ def get_all_commands
326
+ opts = @_system_commands.dup
327
+ if respond_to? :get_commands
328
+ opts.push(*get_commands())
329
+ end
330
+ opts
331
+ end
332
+ # bind a key to a method at global (form) level
333
+ # Note that individual component may be overriding this.
334
+ # FIXME: why are we using rawmessage and then getchar when ask would suffice
335
+ def bind_global
336
+ opts = get_all_commands
337
+ cmd = ask("Select a command (TAB for choices) : ", opts)
338
+ if cmd.nil? || cmd == ""
339
+ say_with_pause "Aborted."
340
+ return
341
+ end
342
+ key = []
343
+ str = ""
344
+ raw_message "Enter one or 2 keys. Finish with ENTER. Enter first key:"
345
+ #raw_message "Enter first key:"
346
+ ch = @window.getchar()
347
+ raw_message_destroy
348
+ if [KEY_ENTER, 10, 13, ?\C-g.getbyte(0)].include? ch
349
+ say_with_pause "Aborted."
350
+ return
351
+ end
352
+ key << ch
353
+ str << keycode_tos(ch)
354
+ raw_message "Enter second key or hit return:"
355
+ ch = @window.getchar()
356
+ raw_message_destroy
357
+ if ch == 3 || ch == ?\C-g.getbyte(0)
358
+ say_with_pause "Aborted."
359
+ return
360
+ end
361
+ if ch == 10 || ch == KEY_ENTER || ch == 13
362
+ else
363
+ key << ch
364
+ str << keycode_tos(ch)
365
+ end
366
+ if !key.empty?
367
+ say_with_pause "Binding #{cmd} to #{str} "
368
+ key = key[0] if key.size == 1
369
+ #@form.bind_key(key, cmd.to_sym) # not finding it, getting called by that comp
370
+ @form.bind_key(key){ send(cmd.to_sym) }
371
+ end
372
+ #message "Bound #{str} to #{cmd} "
373
+ raw_message_destroy
374
+ end
375
+ def bind_component
376
+ say_with_pause "Todo. <press>"
377
+ # the idea here is to get the current component
378
+ # and bind some keys to some methods.
379
+ # however, how do we divine the methods we can map to
380
+ # and also in some cases the components itself has multiple components
381
+ end
382
+ # displays help_text associated with field. 2011-10-15
383
+ def field_help_text
384
+ f = @form.get_current_field
385
+ if f.respond_to?('help_text')
386
+ h = f.help_text
387
+ textdialog "#{h}"
388
+ else
389
+ alert "Could not get field #{f} or does not respond to helptext"
390
+ end
391
+ end
392
+ # prompts user for a command. we need to get this back to the calling app
393
+ # or have some block stuff TODO
394
+ # Actually, this is naive, you would want to pass some values in like current data value
395
+ # or lines ??
396
+ # Also may want command completion, or help so all commands can be displayed
397
+ def get_command_from_user choices=["quit","help"]
398
+ #code, str = rbgetstr(@window, $lastline, 0, "", 80, :default => ":")
399
+ #return unless code == 0
400
+ @_command_history ||= Array.new
401
+ str = ask("Cmd: ", choices) { |q| q.default = @_previous_command; q.history = @_command_history }
402
+ @_command_history << str unless @_command_history.include? str
403
+ # shell the command
404
+ if str =~ /^!/
405
+ str = str[1..-1]
406
+ suspend(false) {
407
+ #system(str);
408
+ $log.debug "XXX STR #{str} " if $log.debug?
409
+
410
+ output=`#{str}`
411
+ system("echo ' ' ");
412
+ $log.debug "XXX output #{output} " if $log.debug?
413
+ system("echo '#{output}' ");
414
+ system("echo Press Enter to continue.");
415
+ system("read");
416
+ }
417
+ return nil # i think
418
+ else
419
+ # TODO
420
+ # here's where we can take internal commands
421
+ #alert "[#{str}] string did not match :!"
422
+ str = str.to_s #= str[1..-1]
423
+ cmdline = str.split
424
+ cmd = cmdline.shift #.to_sym
425
+ return unless cmd # added 2011-09-11 FFI
426
+ f = @form.get_current_field
427
+ if respond_to?(cmd, true)
428
+ if cmd == "close"
429
+ throw :close # other seg faults in del_panel window.destroy executes 2x
430
+ else
431
+ res = send cmd, *cmdline
432
+ end
433
+ elsif f.respond_to?(cmd, true)
434
+ res = f.send(cmd, *cmdline)
435
+ else
436
+ alert "App: #{self.class} does not respond to #{cmd} "
437
+ ret = false
438
+ # what is this execute_this: some kind of general routine for all apps ?
439
+ ret = execute_this(cmd, *cmdline) if respond_to?(:execute_this, true)
440
+ say_with_pause("#{self.class} does not respond to #{cmd} ", :color_pair => $promptcolor) unless ret
441
+ # should be able to say in red as error
442
+ end
443
+ end
444
+ end
445
+ #
446
+ # @group methods to create widgets easily
447
+ #
448
+ # process arguments based on datatype, perhaps making configuration
449
+ # of some components easier for caller avoiding too much boiler plate code
450
+ #
451
+ # create a field
452
+ def OLDfield *args, &block
453
+ config = {}
454
+ events = [ :CHANGED, :LEAVE, :ENTER, :CHANGE ]
455
+ block_event = :CHANGED # LEAVE, ENTER, CHANGE
456
+
457
+ _process_args args, config, block_event, events
458
+ config.delete(:title)
459
+ _position config
460
+ # hope next line doesn't bonk anything
461
+ config[:display_length] ||= @stack.last.width if @stack.last # added here not sure 2010-11-17 18:43
462
+ field = Field.new @form, config
463
+ # shooz uses CHANGED, which is equivalent to our CHANGE. Our CHANGED means modified and exited
464
+ if block
465
+ field.bind(block_event, &block)
466
+ end
467
+ return field
468
+ end
469
+ #instance_eval &block if block_given?
470
+ # or
471
+ #@blk = block # for later execution using @blk.call()
472
+ #colorlabel = Label.new @form, {'text' => "Select a color:", "row" => row, "col" => col, "color"=>"cyan", "mnemonic" => 'S'}
473
+ #var = RubyCurses::Label.new @form, {'text_variable' => $results, "row" => r, "col" => fc}
474
+
475
+ def OLDlabel *args
476
+ events = block_event = nil
477
+ config = {}
478
+ _process_args args, config, block_event, events
479
+ config[:text] ||= config[:name]
480
+ config[:height] ||= 1
481
+ config.delete(:title)
482
+ _position(config)
483
+ label = Label.new @form, config
484
+ # shooz uses CHANGED, which is equivalent to our CHANGE. Our CHANGED means modified and exited
485
+ return label
486
+ end
487
+ alias :text :label
488
+ def OLDbutton *args, &block
489
+ config = {}
490
+ events = [ :PRESS, :LEAVE, :ENTER ]
491
+ block_event = :PRESS
492
+
493
+ _process_args args, config, block_event, events
494
+ config[:text] ||= config[:name]
495
+ config.delete(:title)
496
+ # flow gets precedence over stack
497
+ _position(config)
498
+ button = Button.new @form, config
499
+ # shooz uses CHANGED, which is equivalent to our CHANGE. Our CHANGED means modified and exited
500
+ if block
501
+ button.bind(block_event, &block)
502
+ end
503
+ return button
504
+ end
505
+ #
506
+ # create a list
507
+ # Since we are mouseless, one can traverse without selection. So we have a different
508
+ # way of selecting row/s and traversal. XXX this aspect of LB's has always troubled me hugely.
509
+ def OLDedit_list *args, &block # earlier list_box
510
+ config = {}
511
+ # TODO confirm events
512
+ # listdataevent has interval added and interval removed, due to multiple
513
+ # selection, we have to make that simple for user here.
514
+ events = [ :LEAVE, :ENTER, :ENTER_ROW, :LEAVE_ROW, :LIST_DATA_EVENT ]
515
+ # TODO how to do this so he gets selected row easily
516
+ block_event = :ENTER_ROW
517
+
518
+ _process_args args, config, block_event, events
519
+ # naive defaults, since list could be large or have very long items
520
+ # usually user will provide
521
+ if !config.has_key? :height
522
+ ll = 0
523
+ ll = config[:list].length + 2 if config.has_key? :list
524
+ config[:height] ||= ll
525
+ config[:height] = 15 if config[:height] > 20
526
+ end
527
+ if @current_object.empty?
528
+ $log.debug "1 APP LB w: #{config[:width]} ,#{config[:name]} "
529
+ config[:width] ||= @stack.last.width if @stack.last
530
+ $log.debug "2 APP LB w: #{config[:width]} "
531
+ config[:width] ||= longest_in_list(config[:list])+2
532
+ $log.debug "3 APP LB w: #{config[:width]} "
533
+ end
534
+ # if no width given, expand to flows width XXX SHOULD BE NOT EXPAND ?
535
+ #config[:width] ||= @stack.last.width if @stack.last
536
+ #if config.has_key? :choose
537
+ config[:default_values] = config.delete :choose
538
+ # we make the default single unless specified
539
+ config[:selection_mode] = :single unless config.has_key? :selection_mode
540
+ if @current_object.empty?
541
+ if @instack
542
+ # most likely you won't have row and col. should we check or just go ahead
543
+ col = @stack.last.margin
544
+ config[:row] = @app_row
545
+ config[:col] = col
546
+ @app_row += config[:height] # this needs to take into account height of prev object
547
+ end
548
+ end
549
+ useform = nil
550
+ useform = @form if @current_object.empty?
551
+ field = EditList.new useform, config # earlier ListBox
552
+ # shooz uses CHANGED, which is equivalent to our CHANGE. Our CHANGED means modified and exited
553
+ if block
554
+ # this way you can't pass params to the block
555
+ field.bind(block_event, &block)
556
+ end
557
+ return field
558
+ end
559
+
560
+ # toggle button
561
+ def OLDtoggle *args, &block
562
+ config = {}
563
+ # TODO confirm events
564
+ events = [ :PRESS, :LEAVE, :ENTER ]
565
+ block_event = :PRESS
566
+ _process_args args, config, block_event, events
567
+ config[:text] ||= longest_in_list2( [config[:onvalue], config[:offvalue]])
568
+ #config[:onvalue] # needed for flow, we need a better way FIXME
569
+ _position(config)
570
+ toggle = ToggleButton.new @form, config
571
+ if block
572
+ toggle.bind(block_event, &block)
573
+ end
574
+ return toggle
575
+ end
576
+ # check button
577
+ def OLDcheck *args, &block
578
+ config = {}
579
+ # TODO confirm events
580
+ events = [ :PRESS, :LEAVE, :ENTER ]
581
+ block_event = :PRESS
582
+ _process_args args, config, block_event, events
583
+ _position(config)
584
+ toggle = CheckBox.new @form, config
585
+ if block
586
+ toggle.bind(block_event, &block)
587
+ end
588
+ return toggle
589
+ end
590
+ # radio button
591
+ def OLDradio *args, &block
592
+ config = {}
593
+ # TODO confirm events
594
+ events = [ :PRESS, :LEAVE, :ENTER ]
595
+ block_event = :PRESS
596
+ _process_args args, config, block_event, events
597
+ a = config[:group]
598
+ # FIXME we should check if user has set a varialbe in :variable.
599
+ # we should create a variable, so he can use it if he wants.
600
+ if @variables.has_key? a
601
+ v = @variables[a]
602
+ else
603
+ v = Variable.new
604
+ @variables[a] = v
605
+ end
606
+ config[:variable] = v
607
+ config.delete(:group)
608
+ _position(config)
609
+ radio = RadioButton.new @form, config
610
+ if block
611
+ radio.bind(block_event, &block)
612
+ end
613
+ return radio
614
+ end
615
+ # editable text area
616
+ def OLDtextarea *args, &block
617
+ require 'rbcurse/core/widgets/rtextarea'
618
+ config = {}
619
+ # TODO confirm events many more
620
+ events = [ :CHANGE, :LEAVE, :ENTER ]
621
+ block_event = events[0]
622
+ _process_args args, config, block_event, events
623
+ config[:width] = config[:display_length] unless config.has_key? :width
624
+ _position(config)
625
+ # if no width given, expand to flows width
626
+ config[:width] ||= @stack.last.width if @stack.last
627
+ useform = nil
628
+ useform = @form if @current_object.empty?
629
+ w = TextArea.new useform, config
630
+ if block
631
+ w.bind(block_event, &block)
632
+ end
633
+ return w
634
+ end
635
+ # similar definitions for textview and resultsettextview
636
+ # NOTE This is not allowing me to send blocks,
637
+ # so do not use for containers
638
+ {
639
+ 'rbcurse/core/widgets/rtextview' => 'TextView',
640
+ 'rbcurse/experimental/resultsettextview' => 'ResultsetTextView',
641
+ 'rbcurse/core/widgets/rcontainer' => 'Container',
642
+ 'rbcurse/extras/rcontainer2' => 'Container2',
643
+ }.each_pair {|k,p|
644
+ eval(
645
+ "def OLD#{p.downcase} *args, &block
646
+ require \"#{k}\"
647
+ config = {}
648
+ # TODO confirm events many more
649
+ events = [ :PRESS, :LEAVE, :ENTER ]
650
+ block_event = events[0]
651
+ _process_args args, config, block_event, events
652
+ config[:width] = config[:display_length] unless config.has_key? :width
653
+ _position(config)
654
+ # if no width given, expand to flows width
655
+ config[:width] ||= @stack.last.width if @stack.last
656
+ raise \"height needed for #{p.downcase}\" if !config.has_key? :height
657
+ useform = nil
658
+ useform = @form if @current_object.empty?
659
+ w = #{p}.new useform, config
660
+ if block
661
+ w.bind(block_event, &block)
662
+ end
663
+ return w
664
+ end"
665
+ )
666
+ }
667
+
668
+ # table widget
669
+ # @example
670
+ # data = [["Roger",16,"SWI"], ["Phillip",1, "DEU"]]
671
+ # colnames = ["Name", "Wins", "Place"]
672
+ # t = table :width => 40, :height => 10, :columns => colnames, :data => data, :estimate_widths => true
673
+ # other options are :column_widths => [12,4,12]
674
+ # :size_to_fit => true
675
+ def edit_table *args, &block # earlier table
676
+ require 'rbcurse/extras/widgets/rtable'
677
+ config = {}
678
+ # TODO confirm events many more
679
+ events = [ :ENTER_ROW, :LEAVE, :ENTER ]
680
+ block_event = events[0]
681
+ _process_args args, config, block_event, events
682
+ # if user is leaving out width, then we don't want it in config
683
+ # else Widget will put a value of 10 as default, overriding what we've calculated
684
+ if config.has_key? :display_length
685
+ config[:width] = config[:display_length] unless config.has_key? :width
686
+ end
687
+ ext = config.delete :extended_keys
688
+
689
+ model = nil
690
+ _position(config)
691
+ # if no width given, expand to flows width
692
+ config[:width] ||= @stack.last.width if @stack.last
693
+ w = Table.new @form, config
694
+ if ext
695
+ require 'rbcurse/extras/include/tableextended'
696
+ # so we can increase and decrease column width using keys
697
+ w.extend TableExtended
698
+ w.bind_key(?w){ w.next_column }
699
+ w.bind_key(?b){ w.previous_column }
700
+ w.bind_key(?+) { w.increase_column }
701
+ w.bind_key(?-) { w.decrease_column }
702
+ w.bind_key([?d, ?d]) { w.table_model.delete_at w.current_index }
703
+ w.bind_key(?u) { w.table_model.undo w.current_index}
704
+ end
705
+ if block
706
+ w.bind(block_event, &block)
707
+ end
708
+ return w
709
+ end
710
+ # print a title on first row
711
+ def title string, config={}
712
+ ## TODO center it
713
+ @window.printstring 1, 30, string, $normalcolor, 'reverse'
714
+ end
715
+ # print a sutitle on second row
716
+ def subtitle string, config={}
717
+ @window.printstring 2, 30, string, $datacolor, 'normal'
718
+ end
719
+ # menu bar
720
+
721
+ # creates a blank row
722
+ def OLDblank rows=1, config={}
723
+ @app_row += rows
724
+ end
725
+ # displays a horizontal line
726
+ # takes col (column to start from) from current stack
727
+ # take row from app_row
728
+ #
729
+ # requires width to be passed in config, else defaults to 20
730
+ # @example
731
+ # hline :width => 55
732
+ def hline config={}
733
+ row = config[:row] || @app_row
734
+ width = config[:width] || 20
735
+ _position config
736
+ col = config[:col] || 1
737
+ @color_pair = config[:color_pair] || $datacolor
738
+ @attrib = config[:attrib] || Ncurses::A_NORMAL
739
+ @window.attron(Ncurses.COLOR_PAIR(@color_pair) | @attrib)
740
+ @window.mvwhline( row, col, FFI::NCurses::ACS_HLINE, width)
741
+ @window.attron(Ncurses.COLOR_PAIR(@color_pair) | @attrib)
742
+ @app_row += 1
743
+ end
744
+
745
+ def TODOmultisplit *args, &block
746
+ require 'rbcurse/extras/widgets/rmultisplit'
747
+ config = {}
748
+ events = [ :PROPERTY_CHANGE, :LEAVE, :ENTER ]
749
+ block_event = events[0]
750
+ _process_args args, config, block_event, events
751
+ _position(config)
752
+ # if no width given, expand to flows width
753
+ config[:width] ||= @stack.last.width if @stack.last
754
+ config.delete :title
755
+ useform = nil
756
+ useform = @form if @current_object.empty?
757
+
758
+ w = MultiSplit.new useform, config
759
+ #if block
760
+ #w.bind(block_event, w, &block)
761
+ #end
762
+ if block_given?
763
+ @current_object << w
764
+ #instance_eval &block if block_given?
765
+ yield w
766
+ @current_object.pop
767
+ end
768
+ return w
769
+ end
770
+ # create a readonly list
771
+ # I don't want to rename this to list, as that could lead to
772
+ # confusion, maybe rlist
773
+ def OLDlistbox *args, &block # earlier basic_list
774
+ require 'rbcurse/core/widgets/rlist'
775
+ config = {}
776
+ #TODO check these
777
+ events = [ :LEAVE, :ENTER, :ENTER_ROW, :LEAVE_ROW, :LIST_DATA_EVENT ]
778
+ # TODO how to do this so he gets selected row easily
779
+ block_event = :ENTER_ROW
780
+ _process_args args, config, block_event, events
781
+ # some guesses at a sensible height for listbox
782
+ if !config.has_key? :height
783
+ ll = 0
784
+ ll = config[:list].length + 2 if config.has_key? :list
785
+ config[:height] ||= ll
786
+ config[:height] = 15 if config[:height] > 20
787
+ end
788
+ _position(config)
789
+ # if no width given, expand to flows width
790
+ config[:width] ||= @stack.last.width if @stack.last
791
+ config[:width] ||= longest_in_list(config[:list])+2
792
+ #config.delete :title
793
+ #config[:default_values] = config.delete :choose
794
+ config[:selection_mode] = :single unless config.has_key? :selection_mode
795
+ useform = nil
796
+ useform = @form if @current_object.empty?
797
+
798
+ w = List.new useform, config # NO BLOCK GIVEN
799
+ if block_given?
800
+ field.bind(block_event, &block)
801
+ end
802
+ return w
803
+ end
804
+ alias :basiclist :listbox # this alias will be removed
805
+ def TODOmaster_detail *args, &block
806
+ require 'rbcurse/experimental/widgets/masterdetail'
807
+ config = {}
808
+ events = [:PROPERTY_CHANGE, :LEAVE, :ENTER ]
809
+ block_event = nil
810
+ _process_args args, config, block_event, events
811
+ #config[:height] ||= 10
812
+ _position(config)
813
+ # if no width given, expand to flows width
814
+ config[:width] ||= @stack.last.width if @stack.last
815
+ #config.delete :title
816
+ useform = nil
817
+ useform = @form if @current_object.empty?
818
+
819
+ w = MasterDetail.new useform, config # NO BLOCK GIVEN
820
+ if block_given?
821
+ @current_object << w
822
+ yield_or_eval &block
823
+ @current_object.pop
824
+ end
825
+ return w
826
+ end
827
+ # scrollbar attached to the right of a parent object
828
+ def OLDscrollbar *args, &block
829
+ require 'rbcurse/core/widgets/scrollbar'
830
+ config = {}
831
+ events = [:PROPERTY_CHANGE, :LEAVE, :ENTER ] # # none really at present
832
+ block_event = nil
833
+ _process_args args, config, block_event, events
834
+ raise "parent needed for scrollbar" if !config.has_key? :parent
835
+ useform = nil
836
+ useform = @form if @current_object.empty?
837
+ sb = Scrollbar.new useform, config
838
+ end
839
+ # divider used to resize neighbouring components TOTEST XXX
840
+ def OLDdivider *args, &block
841
+ require 'rbcurse/core/widgets/divider'
842
+ config = {}
843
+ events = [:PROPERTY_CHANGE, :LEAVE, :ENTER, :DRAG_EVENT ] # # none really at present
844
+ block_event = nil
845
+ _process_args args, config, block_event, events
846
+ useform = nil
847
+ useform = @form if @current_object.empty?
848
+ sb = Divider.new useform, config
849
+ end
850
+ # creates a simple readonly table, that allows users to click on rows
851
+ # and also on the header. Header clicking is for column-sorting.
852
+ def OLDcombo *args, &block
853
+ require 'rbcurse/core/widgets/rcombo'
854
+ config = {}
855
+ events = [:PROPERTY_CHANGE, :LEAVE, :ENTER, :CHANGE, :ENTER_ROW, :PRESS ] # XXX
856
+ block_event = nil
857
+ _process_args args, config, block_event, events
858
+ _position(config)
859
+ # if no width given, expand to flows width
860
+ config[:width] ||= @stack.last.width if @stack.last
861
+ #config.delete :title
862
+ useform = nil
863
+ useform = @form if @current_object.empty?
864
+
865
+ w = ComboBox.new useform, config # NO BLOCK GIVEN
866
+ if block_given?
867
+ @current_object << w
868
+ yield_or_eval &block
869
+ @current_object.pop
870
+ end
871
+ return w
872
+ end
873
+
874
+ # ADD new widget above this
875
+
876
+ # @endgroup
877
+
878
+ # @group positioning of components
879
+
880
+ # line up vertically whatever comes in, ignoring r and c
881
+ # margin_top to add to margin of existing stack (if embedded) such as extra spacing
882
+ # margin to add to margin of existing stack, or window (0)
883
+ # NOTE: since these coordins are calculated at start
884
+ # therefore if window resized i can't recalculate.
885
+ #Stack = Struct.new(:margin_top, :margin, :width)
886
+ def OLDstack config={}, &block
887
+ @instack = true
888
+ mt = config[:margin_top] || 1
889
+ mr = config[:margin] || 0
890
+ # must take into account margin
891
+ defw = Ncurses.COLS - mr
892
+ config[:width] = defw if config[:width] == :EXPAND
893
+ w = config[:width] || [50, defw].min
894
+ s = Stack.new(mt, mr, w)
895
+ @app_row += mt
896
+ mr += @stack.last.margin if @stack.last
897
+ @stack << s
898
+ yield_or_eval &block if block_given?
899
+ @stack.pop
900
+ @instack = false if @stack.empty?
901
+ @app_row = 0 if @stack.empty?
902
+ end
903
+ # keep adding to right of previous and when no more space
904
+ # move down and continue fitting in.
905
+ # Useful for button positioning. Currently, we can use a second flow
906
+ # to get another row.
907
+ # TODO: move down when row filled
908
+ # TODO: align right, center
909
+ def OLDflow config={}, &block
910
+ @inflow = true
911
+ mt = config[:margin_top] || 0
912
+ @app_row += mt
913
+ col = @flowstack.last || @stack.last.margin || @app_col
914
+ col += config[:margin] || 0
915
+ @flowstack << col
916
+ @flowcol = col
917
+ yield_or_eval &block if block_given?
918
+ @flowstack.pop
919
+ @inflow = false if @flowstack.empty?
920
+ end
921
+
922
+ private
923
+ def quit
924
+ throw(:close)
925
+ end
926
+ def help; display_app_help; end
927
+ # Initialize curses
928
+ def init_ncurses
929
+ VER::start_ncurses # this is initializing colors via ColorMap.setup
930
+ #$ncurses_started = true
931
+ @stop_ncurses_on_close = true
932
+ end
933
+
934
+ # returns length of longest
935
+ def longest_in_list list #:nodoc:
936
+ longest = list.inject(0) do |memo,word|
937
+ memo >= word.length ? memo : word.length
938
+ end
939
+ longest
940
+ end
941
+ # returns longest item
942
+ # rows = list.max_by(&:length)
943
+ #
944
+ def longest_in_list2 list #:nodoc:
945
+ longest = list.inject(list[0]) do |memo,word|
946
+ memo.length >= word.length ? memo : word
947
+ end
948
+ longest
949
+ end
950
+
951
+ # if partial command entered then returns matches
952
+ def _resolve_command opts, cmd
953
+ return cmd if opts.include? cmd
954
+ matches = opts.grep Regexp.new("^#{cmd}")
955
+ end
956
+ # Now i am not creating this unless user wants it. Pls avoid it.
957
+ # Use either say_with_pause, or put $status_message in command of statusline
958
+ # @deprecated please use {#status_line} instead of a message label
959
+ def create_message_label row=Ncurses.LINES-1
960
+ @message_label = RubyCurses::Label.new @form, {:text_variable => @message, :name=>"message_label",:row => row, :col => 0, :display_length => Ncurses.COLS, :height => 1, :color => :white}
961
+ end
962
+
963
+ def run &block
964
+ begin
965
+
966
+ # check if user has passed window coord in config, else root window
967
+ @window = VER::Window.root_window
968
+ awin = @window
969
+ catch(:close) do
970
+ @form = Form.new @window
971
+ @form.bind_key([?\C-x, ?c], 'suspend') { suspend(false) do
972
+ system("tput cup 26 0")
973
+ system("tput ed")
974
+ system("echo Enter C-d to return to application")
975
+ system (ENV['PS1']='\s-\v\$ ')
976
+ system(ENV['SHELL']);
977
+ end
978
+ }
979
+ # this is a very rudimentary default command executer, it does not
980
+ # allow tab completion. App should use M-x with names of commands
981
+ # as in appgmail
982
+ @form.bind_key(?:, 'prompt') {
983
+ str = get_command_from_user
984
+ }
985
+
986
+ @form.bind_key(?\M-x, 'M-x commands'){
987
+ # TODO previous command to be default
988
+ opts = get_all_commands()
989
+ @_command_history ||= Array.new
990
+ # previous command should be in opts, otherwise it is not in this context
991
+ cmd = ask("Command: ", opts){ |q| q.default = @_previous_command; q.history = @_command_history }
992
+ if cmd.nil? || cmd == ""
993
+ else
994
+ @_command_history << cmd unless @_command_history.include? cmd
995
+ cmdline = cmd.split
996
+ cmd = cmdline.shift
997
+ # check if command is a substring of a larger command
998
+ if !opts.include?(cmd)
999
+ rcmd = _resolve_command(opts, cmd) if !opts.include?(cmd)
1000
+ if rcmd.size == 1
1001
+ cmd = rcmd.first
1002
+ elsif !rcmd.empty?
1003
+ say_with_pause "Cannot resolve #{cmd}. Matches are: #{rcmd} "
1004
+ end
1005
+ end
1006
+ if respond_to?(cmd, true)
1007
+ @_previous_command = cmd
1008
+ #raw_message "calling #{cmd} "
1009
+ begin
1010
+ send cmd, *cmdline
1011
+ rescue => exc
1012
+ $log.error "ERR EXC: send throwing an exception now. Duh. IMAP keeps crashing haha !! #{exc} " if $log.debug?
1013
+ if exc
1014
+ $log.debug( exc)
1015
+ $log.debug(exc.backtrace.join("\n"))
1016
+ say_with_pause exc.to_s
1017
+ end
1018
+ end
1019
+ else
1020
+ say_with_pause("Command [#{cmd}] not supported by #{self.class} ", :color_pair => $promptcolor)
1021
+ end
1022
+ end
1023
+ }
1024
+ @form.bind_key(KEY_F1, 'help'){ display_app_help }
1025
+ @form.bind_key([?q,?q], 'quit' ){ throw :close } if $log.debug?
1026
+
1027
+ #@message = Variable.new
1028
+ #@message.value = ""
1029
+ $status_message ||= Variable.new # remember there are multiple levels of apps
1030
+ $status_message.value = ""
1031
+ #$error_message.update_command { @message.set_value($error_message.value) }
1032
+ if block
1033
+ begin
1034
+ yield_or_eval &block if block_given? # modified 2010-11-17 20:36
1035
+ # how the hell does a user trap exception if the loop is hidden from him ? FIXME
1036
+ loop
1037
+ rescue => ex
1038
+ $log.debug( "APP.rb rescue reached ")
1039
+ $log.debug( ex) if ex
1040
+ $log.debug(ex.backtrace.join("\n")) if ex
1041
+ ensure
1042
+ close
1043
+ # putting it here allows it to be printed on screen, otherwise it was not showing at all.
1044
+ if ex
1045
+ puts "========== EXCEPTION =========="
1046
+ p ex
1047
+ puts "==============================="
1048
+ puts(ex.backtrace.join("\n"))
1049
+ end
1050
+ end
1051
+ nil
1052
+ else
1053
+ #@close_on_terminate = true
1054
+ self
1055
+ end #if block
1056
+ end # :close
1057
+ end
1058
+ end
1059
+ # TODO
1060
+ # process args, all widgets should call this
1061
+ def _process_args args, config, block_event, events #:nodoc:
1062
+ args.each do |arg|
1063
+ case arg
1064
+ when Array
1065
+ # please don't use this, keep it simple and use hash NOTE
1066
+ # we can use r,c, w, h
1067
+ row, col, display_length, height = arg
1068
+ config[:row] = row
1069
+ config[:col] = col
1070
+ config[:display_length] = display_length if display_length
1071
+ config[:width] = display_length if display_length
1072
+ # width for most XXX ?
1073
+ config[:height] = height if height
1074
+ when Hash
1075
+ config.merge!(arg)
1076
+ if block_event
1077
+ block_event = config.delete(:block_event){ block_event }
1078
+ raise "Invalid event. Use #{events}" unless events.include? block_event
1079
+ end
1080
+ when String
1081
+ config[:name] = arg
1082
+ config[:title] = arg # some may not have title
1083
+ #config[:text] = arg # some may not have title
1084
+ end
1085
+ end
1086
+ end # _process
1087
+ # position object based on whether in a flow or stack.
1088
+ # @app_row is prepared for next object based on this objects ht
1089
+ def OLD_position config #:nodoc:
1090
+ unless @current_object.empty?
1091
+ $log.debug " WWWW returning from position #{@current_object.last} "
1092
+ return
1093
+ end
1094
+ if @inflow
1095
+ #col = @flowstack.last
1096
+ config[:row] = @app_row
1097
+ config[:col] = @flowcol
1098
+ $log.debug " YYYY config #{config} "
1099
+ if config[:text]
1100
+ @flowcol += config[:text].length + 5 # 5 came from buttons
1101
+ else
1102
+ @flowcol += (config[:length] || 10) + 5 # trying out for combo
1103
+ end
1104
+ elsif @instack
1105
+ # most likely you won't have row and col. should we check or just go ahead
1106
+ # what if he has put it 2011-10-19 as in a container
1107
+ col = @stack.last.margin
1108
+ config[:row] ||= @app_row
1109
+ config[:col] ||= col
1110
+ @app_row += config[:height] || 1 #unless config[:no_advance]
1111
+ # TODO need to allow stack to have its spacing, but we don't have an object as yet.
1112
+ end
1113
+ end
1114
+ end # class
1115
+ end # module
1116
+ if $0 == __FILE__
1117
+ include RubyCurses
1118
+ #app = App.new
1119
+ #window = app.window
1120
+ #window.printstring 2, 30, "Demo of Listbox - rbcurse", $normalcolor, 'reverse'
1121
+ #app.logger.info "beforegetch"
1122
+ #window.getch
1123
+ #app.close
1124
+ # this was the yield example, but now we've moved to instance eval
1125
+ App.new do
1126
+ @window.printstring 0, 30, "Demo of Listbox - rbcurse", $normalcolor, 'reverse'
1127
+ @window.printstring 1, 30, "Hit F1 to quit", $datacolor, 'normal'
1128
+ form = @form
1129
+ fname = "Search"
1130
+ r, c = 7, 30
1131
+ c += fname.length + 1
1132
+ #field1 = field( [r,c, 30], fname, :bgcolor => "cyan", :block_event => :CHANGE) do |fld|
1133
+ stack :margin_top => 2, :margin => 10 do
1134
+ lbl = label({:text => fname, :color=>'white',:bgcolor=>'red', :mnemonic=> 's'})
1135
+ field1 = field( [r,c, 30], fname, :bgcolor => "cyan",:block_event => :CHANGE) do |fld|
1136
+ message("You entered #{fld.getvalue}. To quit enter quit and tab out")
1137
+ if fld.getvalue == "quit"
1138
+ logger.info "you typed quit!"
1139
+ throw :close
1140
+ end
1141
+ end
1142
+ #field1.set_label Label.new @form, {:text => fname, :color=>'white',:bgcolor=>'red', :mnemonic=> 's'}
1143
+ field1.set_label( lbl )
1144
+ field1.enter do
1145
+ message "you entered this field"
1146
+ end
1147
+
1148
+ stack :margin_top => 2, :margin => 0 do
1149
+ #label( [8, 30, 60],{:text => "A label", :color=>'white',:bgcolor=>'blue'} )
1150
+ end
1151
+
1152
+ @bluelabel = label( [8, 30, 60],{:text => "B label", :color=>'white',:bgcolor=>'blue'} )
1153
+
1154
+ stack :margin_top => 2, :margin => 0 do
1155
+ toggle :onvalue => " Toggle Down ", :offvalue => " Untoggle ", :mnemonic => 'T', :value => true
1156
+
1157
+ toggle :onvalue => " On ", :offvalue => " Off ", :value => true do |e|
1158
+ alert "You pressed me #{e.state}"
1159
+ end
1160
+ check :text => "Check me!", :onvalue => "Checked", :offvalue => "Unchecked", :value => true do |e|
1161
+ # this works but long and complicated
1162
+ #@bluelabel.text = e.item.getvalue ? e.item.onvalue : e.item.offvalue
1163
+ @bluelabel.text = e.item.text
1164
+ end
1165
+ radio :text => "red", :value => "RED", :color => "red", :group => :colors
1166
+ radio :text => "green", :value => "GREEN", :color => "green", :group => :colors
1167
+ flow do
1168
+ button_row = 17
1169
+ ok_button = button( [button_row,30], "OK", {:mnemonic => 'O'}) do
1170
+ alert("About to dump data into log file!")
1171
+ message "Dumped data to log file"
1172
+ end
1173
+
1174
+ # using ampersand to set mnemonic
1175
+ cancel_button = button( [button_row, 40], "&Cancel" ) do
1176
+ if confirm("Do your really want to quit?")== :YES
1177
+ #throw(:close);
1178
+ quit
1179
+ else
1180
+ message "Quit aborted"
1181
+ end
1182
+ end # cancel
1183
+ button "Don't know"
1184
+ end
1185
+ flow :margin_top => 2 do
1186
+ button "Another"
1187
+ button "Line"
1188
+ end
1189
+ stack :margin_top => 2, :margin => 0 do
1190
+ @pbar = progress :width => 20, :bgcolor => 'white', :color => 'red'
1191
+ @pbar1 = progress :width => 20, :style => :old
1192
+ end
1193
+ end
1194
+ end # stack
1195
+ # lets make another column
1196
+ stack :margin_top => 2, :margin => 70 do
1197
+ l = label "Column 2"
1198
+ f1 = field "afield", :bgcolor => 'white', :color => 'black'
1199
+ listbox "A list", :list => ["Square", "Oval", "Rectangle", "Somethinglarge"], :choose => ["Square"]
1200
+ lb = listbox "Another", :list => ["Square", "Oval", "Rectangle", "Somethinglarge"] do |list|
1201
+ #f1.set_buffer list.text
1202
+ #f1.text list.text
1203
+ f1.text = list.text
1204
+ l.text = list.text.upcase
1205
+ end
1206
+ t = textarea :height => 10 do |e|
1207
+ #@bluelabel.text = e.to_s.tr("\n",' ')
1208
+ @bluelabel.text = e.text.gsub("\n"," ")
1209
+ len = e.source.get_text.length
1210
+ len = len % 20 if len > 20
1211
+ $log.debug " PBAR len of text is #{len}: #{len/20.0} "
1212
+ @pbar.fraction(len/20.0)
1213
+ @pbar1.fraction(len/20.0)
1214
+ i = ((len/20.0)*100).to_i
1215
+ @pbar.text = "completed:#{i}"
1216
+ end
1217
+ t.leave do |c|
1218
+ @bluelabel.text = c.get_text.gsub("\n"," ")
1219
+ end
1220
+
1221
+ end
1222
+
1223
+ # Allow user to get the keys
1224
+ keypress do |key|
1225
+ if key == :C_c
1226
+ message "You tried to cancel"
1227
+ #throw :close
1228
+ quit
1229
+ else
1230
+ #app.message "You pressed #{key}, #{char} "
1231
+ message "You pressed #{key}"
1232
+ end
1233
+ end
1234
+ end
1235
+ end