rbcurse 1.1.5 → 1.2.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. data/CHANGELOG +45 -0
  2. data/Makefile +1 -1
  3. data/Manifest.txt +91 -0
  4. data/NOTES +349 -2
  5. data/README.markdown +12 -0
  6. data/VERSION +1 -1
  7. data/examples/abasiclist.rb +25 -0
  8. data/examples/alpmenu.rb +42 -0
  9. data/examples/app.rb +883 -0
  10. data/examples/appcombo.rb +17 -0
  11. data/examples/appdirtree.rb +73 -0
  12. data/examples/appemail.rb +164 -0
  13. data/examples/appemaillb.rb +308 -0
  14. data/examples/appgcompose.rb +303 -0
  15. data/examples/appgmail.rb +951 -0
  16. data/examples/atree.rb +56 -0
  17. data/examples/dirtree.rb +78 -0
  18. data/examples/focusmanager.rb +31 -0
  19. data/examples/imap.rb +48 -0
  20. data/examples/menu1.rb +79 -0
  21. data/examples/multispl.rb +86 -0
  22. data/examples/rfe.rb +3 -4
  23. data/examples/rmail.rb +188 -0
  24. data/examples/s.rb +10 -0
  25. data/examples/scrollbar.rb +104 -0
  26. data/examples/splitp.rb +56 -0
  27. data/examples/table1.rb +30 -0
  28. data/examples/term.rb +48 -0
  29. data/examples/term2.rb +54 -0
  30. data/examples/test1.rb +4 -2
  31. data/examples/test2.rb +9 -9
  32. data/examples/testapp.rb +44 -0
  33. data/examples/testapp2.rb +51 -0
  34. data/examples/testcombo.rb +2 -2
  35. data/examples/testgmail.rb +46 -0
  36. data/examples/testlistbox.rb +0 -1
  37. data/examples/testmultispl.rb +199 -0
  38. data/examples/testree.rb +127 -0
  39. data/examples/testscroller.rb +0 -1
  40. data/examples/testscrolllb.rb +1 -1
  41. data/examples/testscrollp.rb +2 -1
  42. data/examples/testscrollta.rb +1 -1
  43. data/examples/testscrolltable.rb +1 -2
  44. data/examples/testsplit.rb +1 -1
  45. data/examples/testsplit2.rb +1 -1
  46. data/examples/testsplit3.rb +1 -1
  47. data/examples/testsplit3_1.rb +1 -1
  48. data/examples/testsplit3a.rb +1 -1
  49. data/examples/testsplit3b.rb +1 -1
  50. data/examples/testsplitta.rb +1 -1
  51. data/examples/testsplittv.rb +1 -1
  52. data/examples/testsplittvv.rb +1 -1
  53. data/examples/testtodo.rb +491 -488
  54. data/examples/testvimsplit.rb +111 -0
  55. data/examples/todo.db +0 -0
  56. data/examples/todocsv.csv +28 -0
  57. data/examples/viewtodo.rb +408 -403
  58. data/lib/rbcurse/action.rb +1 -0
  59. data/lib/rbcurse/app.rb +1294 -0
  60. data/lib/rbcurse/applicationheader.rb +7 -2
  61. data/lib/rbcurse/checkboxcellrenderer.rb +0 -12
  62. data/lib/rbcurse/colormap.rb +34 -8
  63. data/lib/rbcurse/comboboxcellrenderer.rb +0 -11
  64. data/lib/rbcurse/defaultlistselectionmodel.rb +23 -7
  65. data/lib/rbcurse/extras/bottomline.rb +1681 -0
  66. data/lib/rbcurse/extras/directorylist.rb +445 -0
  67. data/lib/rbcurse/extras/directorytree.rb +69 -0
  68. data/lib/rbcurse/extras/divider.rb +310 -0
  69. data/lib/rbcurse/extras/focusmanager.rb +31 -0
  70. data/lib/rbcurse/extras/listselectable.rb +222 -0
  71. data/lib/rbcurse/extras/masterdetail.rb +164 -0
  72. data/lib/rbcurse/extras/menutree.rb +63 -0
  73. data/lib/rbcurse/extras/rlink.rb +27 -0
  74. data/lib/rbcurse/extras/rmenulink.rb +21 -0
  75. data/lib/rbcurse/extras/scrollbar.rb +134 -0
  76. data/lib/rbcurse/extras/stdscrwindow.rb +247 -0
  77. data/lib/rbcurse/extras/tabular.rb +258 -0
  78. data/lib/rbcurse/extras/tabularwidget.rb +1070 -0
  79. data/lib/rbcurse/extras/viewer.rb +106 -0
  80. data/lib/rbcurse/io.rb +137 -80
  81. data/lib/rbcurse/keylabelprinter.rb +4 -0
  82. data/lib/rbcurse/listcellrenderer.rb +91 -59
  83. data/lib/rbcurse/listscrollable.rb +93 -95
  84. data/lib/rbcurse/listselectable.rb +60 -7
  85. data/lib/rbcurse/ractionevent.rb +67 -0
  86. data/lib/rbcurse/rbasiclistbox.rb +688 -0
  87. data/lib/rbcurse/rcombo.rb +5 -5
  88. data/lib/rbcurse/rcommandwindow.rb +555 -0
  89. data/lib/rbcurse/rinputdataevent.rb +12 -0
  90. data/lib/rbcurse/rlistbox.rb +305 -124
  91. data/lib/rbcurse/rmenu.rb +99 -46
  92. data/lib/rbcurse/rmessagebox.rb +13 -6
  93. data/lib/rbcurse/rmulticontainer.rb +54 -93
  94. data/lib/rbcurse/rmultisplit.rb +731 -0
  95. data/lib/rbcurse/rmultitextview.rb +3 -2
  96. data/lib/rbcurse/rpopupmenu.rb +0 -1
  97. data/lib/rbcurse/rprogress.rb +117 -0
  98. data/lib/rbcurse/rscrollpane.rb +2 -1
  99. data/lib/rbcurse/rsplitpane.rb +94 -20
  100. data/lib/rbcurse/rsplitpane2.rb +1009 -0
  101. data/lib/rbcurse/rtabbedpane.rb +3 -2
  102. data/lib/rbcurse/rtabbedwindow.rb +0 -1
  103. data/lib/rbcurse/rtable.rb +92 -64
  104. data/lib/rbcurse/rtextarea.rb +91 -57
  105. data/lib/rbcurse/rtextview.rb +223 -70
  106. data/lib/rbcurse/rtree.rb +723 -0
  107. data/lib/rbcurse/rviewport.rb +2 -1
  108. data/lib/rbcurse/rvimsplit.rb +768 -0
  109. data/lib/rbcurse/rwidget.rb +524 -325
  110. data/lib/rbcurse/table/tablecellrenderer.rb +1 -1
  111. data/lib/rbcurse/table/tabledatecellrenderer.rb +0 -1
  112. data/lib/rbcurse/tree/treecellrenderer.rb +137 -0
  113. data/lib/rbcurse/tree/treemodel.rb +428 -0
  114. data/lib/rbcurse/vieditable.rb +14 -13
  115. data/lib/ver/ncurses.rb +6 -0
  116. data/lib/ver/window.rb +67 -32
  117. metadata +99 -23
  118. data/bin/rbcurse +0 -0
  119. data/examples/rvimsplit.rb +0 -376
  120. data/examples/todo.rb +0 -1
  121. data/lib/rbcurse/rform.rb +0 -845
  122. data/lib/rbcurse/selectable.rb +0 -94
  123. data/rbcurse.gemspec +0 -188
data/README.markdown CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  * Version that works with ruby 1.9 (backward compatible with 1.8.7)
4
4
 
5
+ * 1.2.0: many additions (See CHANGELOG for details)
6
+ - App class that wraps the environment and makes application development very easy
7
+ - New controls such as:
8
+ - vimsplit: allows multiple vertical and horizontal splits in one component
9
+ - progress bar
10
+ - Tree control
11
+ - Divider (can grab and move, so as to visually resize components)
12
+ - Scrollbar class that can be easily attached to lists, tables etc
13
+ - Tabular data widgets and data converters
14
+
15
+ Please see CHANGELOG for changes.
16
+
5
17
  * rbcurse 1.1.4 works with 1.9.2 and also contains some files that got missed out in gemspec.
6
18
 
7
19
  * Corrections due to 1.9.2:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.5
1
+ 1.2.0.pre
@@ -0,0 +1,25 @@
1
+ require 'rbcurse/app'
2
+ require 'rbcurse/rbasiclistbox'
3
+
4
+ # just a simple test to ensure that rbasiclistbox is running inside a container.
5
+ App.new do
6
+ header = app_header "rbcurse 1.2.0", :text_center => "Basic List Demo", :text_right =>"New Improved!", :color => :black, :bgcolor => :white, :attr => :bold
7
+ message "Press F1 to escape from here"
8
+
9
+ list = %W{ bhikshu boddisattva avalokiteswara mu mun kwan paramita prajna samadhi sutra shakyamuni }
10
+ vimsplit :row => 1, :col => 0, :suppress_borders => false, :width => 60, :height => Ncurses.LINES-2, :weight => 0.4, :orientation => :VERTICAL do |s|
11
+ lb = RubyCurses::BasicListbox.new nil, :list => list, :suppress_borders => false
12
+ #lb = RubyCurses::BasicListbox.new nil, :list => list, :show_selector => true, :row_selected_symbol => "*", :suppress_borders => false
13
+ #lb = RubyCurses::BasicListbox.new nil, :list => list
14
+ #lb = list_box "A list", :list => list
15
+ lb.show_selector = false
16
+ #lb.row_selected_symbol = "*"
17
+ #lb = list_box "A list", :list => list
18
+
19
+ s.add lb, :FIRST
20
+ #lb2= RubyCurses::BasicListbox.new nil, :list => list.shuffle, :justify => :center
21
+ lb2 = basiclist :list => list.shuffle, :justify => :left, :suppress_borders => false
22
+ s.add lb2, :SECOND
23
+ end
24
+
25
+ end # app
@@ -0,0 +1,42 @@
1
+ require 'rbcurse/app'
2
+
3
+ App.new do
4
+ #title "Demo of Menu - rbcurse"
5
+ #subtitle "Hit F1 to quit, F2 for menubar toggle"
6
+ header = app_header "rbcurse 1.2.0", :text_center => "Alpine Menu Demo", :text_right =>""
7
+ message_row(27)
8
+
9
+ stack :margin_top => 10, :margin => 15 do
10
+ #w = "Messages".length + 1
11
+ w = 60
12
+ menulink "&View Todo", :width => w, :description => "View TODO in sqlite" do |s|
13
+ message "Pressed #{s.text} "
14
+ require './viewtodo'; todo = ViewTodo::TodoApp.new; todo.run
15
+ end
16
+ blank
17
+ menulink "&Edit Todo", :width => w, :description => "Edit TODO in CSV" do |s|
18
+ message "Pressed #{s.text} "
19
+ require './testtodo'; todo = TestTodo::TodoApp.new; todo.run
20
+ end
21
+ blank
22
+ menulink "&Messages", :width => w, :description => "View messages in current folder" do |s|
23
+ message "Pressed #{s.text} "
24
+ load './menu1.rb'
25
+ end
26
+ blank
27
+ menulink "&Compose", :width => w, :description => "Compose a mail" do |s|
28
+ message "Pressed #{s.getvalue} "
29
+ end
30
+ blank
31
+ menulink "Setup", :mnemonic => "?", :width => w, :description => "Configure Alpine options" do |s|
32
+ message "Pressed #{s.text} "
33
+ end
34
+ blank
35
+ menulink "&Quit", :width => w, :description => "Quit this application" do |s|
36
+ quit
37
+ end
38
+ @form.bind(:ENTER) do |w|
39
+ header.text_right = w.text
40
+ end
41
+ end # stack
42
+ end # app
data/examples/app.rb ADDED
@@ -0,0 +1,883 @@
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
+ * License:
9
+ Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
10
+
11
+ =end
12
+ #require 'rubygems'
13
+ require 'ncurses'
14
+ require 'logger'
15
+ require 'rbcurse'
16
+
17
+ include Ncurses
18
+ include RubyCurses
19
+ include RubyCurses::Utils
20
+ module RubyCurses
21
+ extend self
22
+
23
+ ##
24
+ #
25
+ # @since 1.1.6
26
+ # TODO -
27
+ # what of internal objects that don't want a form !
28
+ # - stack and flow should be objects in Form, put in widget when creating
29
+ # x method: quit
30
+ # - box / rect
31
+ # - animate(n) |i|
32
+ # x list_box :choose is the defauly
33
+ # x progress - like a label but fills itself, fraction
34
+ # - para looks like a label that is more than one line, and calculates rows itself based on text
35
+ # x other buttons: radio, check
36
+ # x edit_box = textarea
37
+ # - combo
38
+ # x menu
39
+ # - popup
40
+ # - multicontainer
41
+ # - multitextview, multisplit
42
+ # - tabbedpane
43
+ # / table - more work regarding vim keys, also editable
44
+ # - margin - is left offset
45
+ # http://lethain.com/entry/2007/oct/15/getting-started-shoes-os-x/
46
+ # - promptmenu
47
+ #
48
+
49
+ class Widget
50
+ def changed *args, &block
51
+ bind :CHANGED, *args, &block
52
+ end
53
+ def leave *args, &block
54
+ bind :LEAVE, *args, &block
55
+ end
56
+ def enter *args, &block
57
+ bind :ENTER, *args, &block
58
+ end
59
+ # actually we already have command() for buttons
60
+ def click *args, &block
61
+ bind :PRESS, *args, &block
62
+ end
63
+ end
64
+ #class Listbox
65
+ #def text
66
+ #return get_content()[@current_index]
67
+ #end
68
+ #end
69
+ class CheckBox
70
+ # a little dicey XXX
71
+ def text(*val)
72
+ if val.empty?
73
+ @value ? @onvalue : @offvalue
74
+ else
75
+ super
76
+ end
77
+ end
78
+ end
79
+ class App
80
+ attr_reader :config
81
+ attr_reader :form
82
+ attr_reader :window
83
+ attr_writer :quit_key
84
+
85
+
86
+ # i should be able to pass window coords here in config
87
+ # :title
88
+ def initialize config={}, &block
89
+ #$log.debug " inside constructor of APP #{config} "
90
+ @config = config
91
+ @app_row = @app_col = 0
92
+ @stack = [] # stack's coordinates
93
+ @flowstack = []
94
+ @variables = {}
95
+ # if we are creating child objects then we will not use outer form. this object will manage
96
+ @current_object = []
97
+ init_vars
98
+ run &block
99
+ end
100
+ def init_vars
101
+ @quit_key ||= KEY_F1
102
+ # actually this should be maintained inside ncurses pack, so not loaded 2 times.
103
+ # this way if we call an app from existing program, App won't start ncurses.
104
+ unless $ncurses_started
105
+ init_ncurses
106
+ end
107
+ unless $log
108
+ $log = Logger.new((File.join(ENV["LOGDIR"] || "./" ,"view.log")))
109
+ $log.level = Logger::DEBUG
110
+ colors = Ncurses.COLORS
111
+ $log.debug "START #{colors} colors --------- #{$0}"
112
+ end
113
+ end
114
+ def logger; return $log; end
115
+ def close
116
+ $log.debug " INSIDE CLOSE, #{@stop_ncurses_on_close} "
117
+ @window.destroy if !@window.nil?
118
+ $log.debug " INSIDE CLOSE, #{@stop_ncurses_on_close} "
119
+ if @stop_ncurses_on_close
120
+ VER::stop_ncurses
121
+ $log.debug " CLOSING NCURSES"
122
+ end
123
+ #p $error_message.value unless $error_message.value.nil?
124
+ $log.debug " CLOSING APP"
125
+ #end
126
+ end
127
+ # not sure, but user shuld be able to trap keystrokes if he wants
128
+ # but do i still call handle_key if he does, or give him total control.
129
+ # But loop is already called by framework
130
+ def loop &block
131
+ @form.repaint
132
+ @window.wrefresh
133
+ Ncurses::Panel.update_panels
134
+ while((ch = @window.getchar()) != @quit_key )
135
+ str = keycode_tos ch
136
+ @keyblock.call(str.gsub(/-/, "_").to_sym) if @keyblock
137
+ $log.debug "#{ch} got (#{str})"
138
+ yield ch if block # <<<----
139
+ @form.handle_key ch
140
+ @form.repaint
141
+ @window.wrefresh
142
+ end
143
+ end
144
+ # returns a symbol of the key pressed
145
+ # e.g. :C_c for Ctrl-C
146
+ # :Space, :bs, :M_d etc
147
+ def keypress &block
148
+ @keyblock = block
149
+ end
150
+ # updates a global var with text. Calling app has to set up a Variable with that name and attach to
151
+ # a label so it can be printed.
152
+ def message text
153
+ @message.value = text
154
+ end
155
+ def message_row row
156
+ @message_label.row = row
157
+ end
158
+ #
159
+ # @group methods to create widgets easily
160
+ #
161
+ # process arguments based on datatype, perhaps making configuration
162
+ # of some components easier for caller avoiding too much boiler plate code
163
+ #
164
+ # create a field
165
+ def field *args, &block
166
+ config = {}
167
+ events = [ :CHANGED, :LEAVE, :ENTER, :CHANGE ]
168
+ block_event = :CHANGED # LEAVE, ENTER, CHANGE
169
+
170
+ args.each do |arg|
171
+ case arg
172
+ when Array
173
+ #puts "row, col #{arg[0]} #{arg[1]} "
174
+ # we can use r,c, w, h
175
+ row, col, display_length = arg
176
+ config[:row] = row
177
+ config[:col] = col
178
+ config[:display_length] = display_length if display_length
179
+ when Hash
180
+ config.merge!(arg)
181
+ block_event = config.delete(:block_event){ block_event }
182
+ raise "Invalid event. Use #{events}" unless events.include? block_event
183
+ #puts "hash #{config}"
184
+ when String
185
+ title = arg
186
+ config[:name] = title
187
+ end
188
+ end
189
+ if @instack
190
+ # most likely you won't have row and col. should we check or just go ahead
191
+ col = @stack.last.margin
192
+ config[:row] = @app_row
193
+ config[:col] = col
194
+ @app_row += 1
195
+ end
196
+ field = Field.new @form, config
197
+ # shooz uses CHANGED, which is equivalent to our CHANGE. Our CHANGED means modified and exited
198
+ if block
199
+ field.bind(block_event, &block)
200
+ end
201
+ return field
202
+ end
203
+ #instance_eval &block if block_given?
204
+ # or
205
+ #@blk = block # for later execution using @blk.call()
206
+ #colorlabel = Label.new @form, {'text' => "Select a color:", "row" => row, "col" => col, "color"=>"cyan", "mnemonic" => 'S'}
207
+ #var = RubyCurses::Label.new @form, {'text_variable' => $results, "row" => r, "col" => fc}
208
+
209
+ def label *args
210
+ block_event = nil
211
+ config = {}
212
+
213
+ args.each do |arg|
214
+ case arg
215
+ when Array
216
+ row, col, display_length, height = arg
217
+ config[:row] = row
218
+ config[:col] = col
219
+ config[:display_length] = display_length if display_length
220
+ config[:height] = height || 1
221
+ when Hash
222
+ config.merge!(arg)
223
+ when String
224
+ config[:text] = arg
225
+ end
226
+ end
227
+ config[:height] ||= 1
228
+ if @instack
229
+ # most likely you won't have row and col. should we check or just go ahead
230
+ col = @stack.last.margin
231
+ config[:row] = @app_row
232
+ config[:col] = col
233
+ @app_row += config[:height]
234
+ end
235
+ label = Label.new @form, config
236
+ # shooz uses CHANGED, which is equivalent to our CHANGE. Our CHANGED means modified and exited
237
+ return label
238
+ end
239
+ alias :text :label
240
+ def button *args, &block
241
+ config = {}
242
+ events = [ :PRESS, :LEAVE, :ENTER ]
243
+ block_event = :PRESS
244
+
245
+ #process_args args, config, block_event, events
246
+ args.each do |arg|
247
+ case arg
248
+ when Array
249
+ #puts "row, col #{arg[0]} #{arg[1]} "
250
+ # we can use r,c, w, h
251
+ row, col, display_length, height = arg
252
+ config[:row] = row
253
+ config[:col] = col
254
+ config[:display_length] = display_length if display_length
255
+ config[:height] = height if height
256
+ when Hash
257
+ config.merge!(arg)
258
+ block_event = config.delete(:block_event){ block_event }
259
+ raise "Invalid event. Use #{events}" unless events.include? block_event
260
+ #puts "hash #{config}"
261
+ when String
262
+ config[:text] = arg
263
+ config[:name] = arg
264
+ end
265
+ end
266
+ # flow gets precedence over stack
267
+ _position(config)
268
+ button = Button.new @form, config
269
+ # shooz uses CHANGED, which is equivalent to our CHANGE. Our CHANGED means modified and exited
270
+ if block
271
+ button.bind(block_event, &block)
272
+ end
273
+ return button
274
+ end
275
+ #
276
+ # create a list
277
+ # Since we are mouseless, one can traverse without selection. So we have a different
278
+ # way of selecting row/s and traversal. XXX this aspect of LB's has always troubled me hugely.
279
+ def list_box *args, &block
280
+ config = {}
281
+ # TODO confirm events
282
+ # listdataevent has interval added and interval removed, due to multiple
283
+ # selection, we have to make that simple for user here.
284
+ events = [ :LEAVE, :ENTER, :ENTER_ROW, :LEAVE_ROW, :LIST_DATA_EVENT ]
285
+ # TODO how to do this so he gets selected row easily
286
+ block_event = :ENTER_ROW
287
+
288
+ # TODO abstract this into a new method so no copying
289
+ args.each do |arg|
290
+ case arg
291
+ when Array
292
+ #puts "row, col #{arg[0]} #{arg[1]} "
293
+ # we can use r,c, w, h
294
+ row, col, display_length, height = arg
295
+ config[:row] = row
296
+ config[:col] = col
297
+ config[:display_length] = display_length if display_length
298
+ config[:height] = height if height
299
+ when Hash
300
+ config.merge!(arg)
301
+ block_event = config.delete(:block_event){ block_event }
302
+ raise "Invalid event. Use #{events}" unless events.include? block_event
303
+ #puts "hash #{config}"
304
+ when String
305
+ config[:name] = arg
306
+ config[:title] = arg
307
+ end
308
+ end
309
+ # naive defaults, since list could be large or have very long items
310
+ # usually user will provide
311
+ config[:height] ||= config[:list].length + 2
312
+ if @current_object.empty?
313
+ $log.debug "1 APP LB w: #{config[:width]} ,#{config[:name]} "
314
+ config[:width] ||= @stack.last.width if @stack.last
315
+ $log.debug "2 APP LB w: #{config[:width]} "
316
+ config[:width] ||= longest_in_list(config[:list])+2
317
+ $log.debug "3 APP LB w: #{config[:width]} "
318
+ end
319
+ # if no width given, expand to flows width XXX SHOULD BE NOT EXPAND ?
320
+ #config[:width] ||= @stack.last.width if @stack.last
321
+ #if config.has_key? :choose
322
+ config[:default_values] = config.delete :choose
323
+ # we make the default single unless specified
324
+ config[:selection_mode] = :single unless config.has_key? :selection_mode
325
+ if @current_object.empty?
326
+ if @instack
327
+ # most likely you won't have row and col. should we check or just go ahead
328
+ col = @stack.last.margin
329
+ config[:row] = @app_row
330
+ config[:col] = col
331
+ @app_row += config[:height] # this needs to take into account height of prev object
332
+ end
333
+ end
334
+ useform = nil
335
+ useform = @form if @current_object.empty?
336
+ field = Listbox.new useform, config
337
+ # shooz uses CHANGED, which is equivalent to our CHANGE. Our CHANGED means modified and exited
338
+ if block
339
+ # this way you can't pass params to the block
340
+ field.bind(block_event, &block)
341
+ end
342
+ return field
343
+ end
344
+
345
+ # toggle button
346
+ def toggle *args, &block
347
+ config = {}
348
+ # TODO confirm events
349
+ events = [ :PRESS, :LEAVE, :ENTER ]
350
+ block_event = :PRESS
351
+ _process_args args, config, block_event, events
352
+ config[:text] ||= longest_in_list2( [config[:onvalue], config[:offvalue]])
353
+ #config[:onvalue] # needed for flow, we need a better way FIXME
354
+ _position(config)
355
+ toggle = ToggleButton.new @form, config
356
+ if block
357
+ toggle.bind(block_event, &block)
358
+ end
359
+ return toggle
360
+ end
361
+ # check button
362
+ def check *args, &block
363
+ config = {}
364
+ # TODO confirm events
365
+ events = [ :PRESS, :LEAVE, :ENTER ]
366
+ block_event = :PRESS
367
+ _process_args args, config, block_event, events
368
+ _position(config)
369
+ toggle = CheckBox.new @form, config
370
+ if block
371
+ toggle.bind(block_event, &block)
372
+ end
373
+ return toggle
374
+ end
375
+ # radio button
376
+ def radio *args, &block
377
+ config = {}
378
+ # TODO confirm events
379
+ events = [ :PRESS, :LEAVE, :ENTER ]
380
+ block_event = :PRESS
381
+ _process_args args, config, block_event, events
382
+ a = config[:group]
383
+ # FIXME we should check if user has set a varialbe in :variable.
384
+ # we should create a variable, so he can use it if he wants.
385
+ if @variables.has_key? a
386
+ v = @variables[a]
387
+ else
388
+ v = Variable.new
389
+ @variables[a] = v
390
+ end
391
+ config[:variable] = v
392
+ config.delete(:group)
393
+ _position(config)
394
+ radio = RadioButton.new @form, config
395
+ if block
396
+ radio.bind(block_event, &block)
397
+ end
398
+ return radio
399
+ end
400
+ # editable text area
401
+ def textarea *args, &block
402
+ require 'rbcurse/rtextarea'
403
+ config = {}
404
+ # TODO confirm events many more
405
+ events = [ :CHANGE, :LEAVE, :ENTER ]
406
+ block_event = events[0]
407
+ _process_args args, config, block_event, events
408
+ config[:width] = config[:display_length] unless config.has_key? :width
409
+ _position(config)
410
+ # if no width given, expand to flows width
411
+ config[:width] ||= @stack.last.width if @stack.last
412
+ useform = nil
413
+ useform = @form if @current_object.empty?
414
+ w = TextArea.new useform, config
415
+ if block
416
+ w.bind(block_event, &block)
417
+ end
418
+ return w
419
+ end
420
+ # progress bar
421
+ def progress *args, &block
422
+ require 'rbcurse/rprogress'
423
+ config = {}
424
+ # TODO confirm events many more
425
+ events = [ :CHANGE, :LEAVE, :ENTER ]
426
+ block_event = nil
427
+ _process_args args, config, block_event, events
428
+ config[:width] = config[:display_length] || 10 unless config.has_key? :width
429
+ _position(config)
430
+ w = Progress.new @form, config
431
+ #if block
432
+ #w.bind(block_event, &block)
433
+ #end
434
+ return w
435
+ end
436
+
437
+ # table widget
438
+ # @example
439
+ # data = [["Roger",16,"SWI"], ["Phillip",1, "DEU"]]
440
+ # colnames = ["Name", "Wins", "Place"]
441
+ # t = table :width => 40, :height => 10, :columns => colnames, :data => data, :estimate_widths => true
442
+ # other options are :column_widths => [12,4,12]
443
+ # :size_to_fit => true
444
+ def table *args, &block
445
+ require 'rbcurse/rtable'
446
+ config = {}
447
+ # TODO confirm events many more
448
+ events = [ :ENTER_ROW, :LEAVE, :ENTER ]
449
+ block_event = events[0]
450
+ _process_args args, config, block_event, events
451
+ # if user is leaving out width, then we don't want it in config
452
+ # else Widget will put a value of 10 as default, overriding what we've calculated
453
+ if config.has_key? :display_length
454
+ config[:width] = config[:display_length] unless config.has_key? :width
455
+ end
456
+ ext = config.delete :extended_keys
457
+
458
+ model = nil
459
+ _position(config)
460
+ # if no width given, expand to flows width
461
+ config[:width] ||= @stack.last.width if @stack.last
462
+ w = Table.new @form, config
463
+ if ext
464
+ require 'rbcurse/extras/tableextended'
465
+ # so we can increase and decrease column width using keys
466
+ w.extend TableExtended
467
+ w.bind_key(?w){ w.next_column }
468
+ w.bind_key(?b){ w.previous_column }
469
+ w.bind_key(?+) { w.increase_column }
470
+ w.bind_key(?-) { w.decrease_column }
471
+ w.bind_key([?d, ?d]) { w.table_model.delete_at w.current_index }
472
+ w.bind_key(?u) { w.table_model.undo w.current_index}
473
+ end
474
+ if block
475
+ w.bind(block_event, &block)
476
+ end
477
+ return w
478
+ end
479
+ # print a title on first row
480
+ def title string, config={}
481
+ ## TODO center it
482
+ @window.printstring 1, 30, string, $normalcolor, 'reverse'
483
+ end
484
+ # print a sutitle on second row
485
+ def subtitle string, config={}
486
+ @window.printstring 2, 30, string, $datacolor, 'normal'
487
+ end
488
+ # menu bar
489
+ def menubar &block
490
+ require 'rbcurse/rmenu'
491
+ RubyCurses::MenuBar.new &block
492
+ end
493
+
494
+ # creates a blank row
495
+ def blank rows=1, config={}
496
+ @app_row += rows
497
+ end
498
+ # displays a horizontal line
499
+ # takes col (column to start from) from current stack
500
+ # take row from app_row
501
+ #
502
+ # requires width to be passed in config, else defaults to 20
503
+ # @example
504
+ # hline :width => 55
505
+ def hline config={}
506
+ row = @app_row
507
+ width = config[:width] || 20
508
+ _position config
509
+ col = config[:col] || 1
510
+ @window.mvwhline( row, col, ACS_HLINE, width)
511
+ @app_row += 1
512
+ end
513
+ def app_header title, config={}, &block
514
+ require 'rbcurse/applicationheader'
515
+ header = ApplicationHeader.new @form, title, config, &block
516
+ end
517
+ def dock labels, config={}, &block
518
+ require 'rbcurse/keylabelprinter'
519
+ klp = RubyCurses::KeyLabelPrinter.new @form, labels, config, &block
520
+ end
521
+ def link *args, &block
522
+ require 'rbcurse/extras/rlink'
523
+ config = {}
524
+ events = [ :PRESS, :LEAVE, :ENTER ]
525
+ block_event = :PRESS
526
+ _process_args args, config, block_event, events
527
+ _position(config)
528
+ config[:text] ||= config.delete :title
529
+ config[:highlight_foreground] = "yellow"
530
+ config[:highlight_background] = "red"
531
+ toggle = Link.new @form, config
532
+ if block
533
+ toggle.bind(block_event, toggle, &block)
534
+ end
535
+ return toggle
536
+ end
537
+ def menulink *args, &block
538
+ require 'rbcurse/extras/rmenulink'
539
+ config = {}
540
+ events = [ :PRESS, :LEAVE, :ENTER ]
541
+ block_event = :PRESS
542
+ _process_args args, config, block_event, events
543
+ _position(config)
544
+ config[:text] ||= config.delete :title
545
+ config[:highlight_foreground] = "yellow"
546
+ config[:highlight_background] = "red"
547
+ toggle = MenuLink.new @form, config
548
+ if block
549
+ toggle.bind(block_event, toggle, &block)
550
+ end
551
+ return toggle
552
+ end
553
+ def splitpane *args, &block
554
+ require 'rbcurse/rsplitpane2'
555
+ config = {}
556
+ events = [ :PROPERTY_CHANGE, :LEAVE, :ENTER ]
557
+ block_event = events[0]
558
+ _process_args args, config, block_event, events
559
+ _position(config)
560
+ # if no width given, expand to flows width
561
+ config[:width] ||= @stack.last.width if @stack.last
562
+ config.delete :title
563
+ useform = nil
564
+ useform = @form if @current_object.empty?
565
+
566
+ w = SplitPane.new useform, config
567
+ #if block
568
+ #w.bind(block_event, w, &block)
569
+ #end
570
+ if block_given?
571
+ @current_object << w
572
+ #instance_eval &block if block_given?
573
+ yield w
574
+ @current_object.pop
575
+ end
576
+ return w
577
+ end
578
+ def multisplit *args, &block
579
+ require 'rbcurse/rmultisplit'
580
+ config = {}
581
+ events = [ :PROPERTY_CHANGE, :LEAVE, :ENTER ]
582
+ block_event = events[0]
583
+ _process_args args, config, block_event, events
584
+ _position(config)
585
+ # if no width given, expand to flows width
586
+ config[:width] ||= @stack.last.width if @stack.last
587
+ config.delete :title
588
+ useform = nil
589
+ useform = @form if @current_object.empty?
590
+
591
+ w = MultiSplit.new useform, config
592
+ #if block
593
+ #w.bind(block_event, w, &block)
594
+ #end
595
+ if block_given?
596
+ @current_object << w
597
+ #instance_eval &block if block_given?
598
+ yield w
599
+ @current_object.pop
600
+ end
601
+ return w
602
+ end
603
+
604
+ # ADD new widget above this
605
+
606
+ # @endgroup
607
+
608
+ # @group positioning of components
609
+
610
+ # line up vertically whatever comes in, ignoring r and c
611
+ # margin_top to add to margin of existing stack (if embedded) such as extra spacing
612
+ # margin to add to margin of existing stack, or window (0)
613
+ Stack = Struct.new(:margin_top, :margin, :width)
614
+ def stack config={}, &block
615
+ @instack = true
616
+ mt = config[:margin_top] || 1
617
+ mr = config[:margin] || 0
618
+ # must take into account margin
619
+ defw = Ncurses.COLS - mr
620
+ w = config[:width] || [50, defw].min
621
+ s = Stack.new(mt, mr, w)
622
+ @app_row += mt
623
+ mr += @stack.last.margin if @stack.last
624
+ #@stack << mr
625
+ @stack << s
626
+ instance_eval &block if block_given?
627
+ @stack.pop
628
+ @instack = false if @stack.empty?
629
+ @app_row = 0 if @stack.empty?
630
+ end
631
+ # keep adding to right of previous and when no more space
632
+ # move down and continue fitting in.
633
+ # Useful for button positioning. Currently, we can use a second flow
634
+ # to get another row.
635
+ # TODO: move down when row filled
636
+ # TODO: align right, center
637
+ def flow config={}, &block
638
+ @inflow = true
639
+ mt = config[:margin_top] || 0
640
+ @app_row += mt
641
+ col = @flowstack.last || @stack.last.margin || @app_col
642
+ col += config[:margin] || 0
643
+ @flowstack << col
644
+ @flowcol = col
645
+ instance_eval &block if block_given?
646
+ @flowstack.pop
647
+ @inflow = false if @flowstack.empty?
648
+ end
649
+
650
+ private
651
+ def quit
652
+ throw(:close)
653
+ end
654
+ # Initialize curses
655
+ def init_ncurses
656
+ VER::start_ncurses # this is initializing colors via ColorMap.setup
657
+ #$ncurses_started = true
658
+ @stop_ncurses_on_close = true
659
+ end
660
+
661
+ # returns length of longest
662
+ def longest_in_list list
663
+ longest = list.inject(0) do |memo,word|
664
+ memo >= word.length ? memo : word.length
665
+ end
666
+ longest
667
+ end
668
+ # returns longest item
669
+ def longest_in_list2 list
670
+ longest = list.inject(list[0]) do |memo,word|
671
+ memo.length >= word.length ? memo : word
672
+ end
673
+ longest
674
+ end
675
+ def run &block
676
+ begin
677
+
678
+ # check if user has passed window coord in config, else root window
679
+ @window = VER::Window.root_window
680
+ catch(:close) do
681
+ @form = Form.new @window
682
+ @message = Variable.new
683
+ @message.value = "Message Comes Here"
684
+ @message_label = RubyCurses::Label.new @form, {:text_variable => @message, :name=>"message_label",:row => 24, :col => 1, :display_length => 60, :height => 1, :color => 'cyan'}
685
+ $error_message.update_command { @message.set_value($error_message.value) }
686
+ if block
687
+ begin
688
+ #yield(self, @window, @form)
689
+ instance_eval &block if block_given?
690
+ loop
691
+ rescue => ex
692
+ $log.debug( "APP.rb rescue reached ")
693
+ $log.debug( ex) if ex
694
+ $log.debug(ex.backtrace.join("\n")) if ex
695
+ ensure
696
+ close
697
+ # putting it here allows it to be printed on screen, otherwise it was not showing at all.
698
+ if ex
699
+ puts "========== EXCEPTION =========="
700
+ p ex
701
+ puts "=========="
702
+ puts(ex.backtrace.join("\n"))
703
+ end
704
+ end
705
+ nil
706
+ else
707
+ #@close_on_terminate = true
708
+ self
709
+ end #if block
710
+ end
711
+ end
712
+ end
713
+ # TODO
714
+ # process args, all widgets should call this
715
+ def _process_args args, config, block_event, events
716
+ args.each do |arg|
717
+ case arg
718
+ when Array
719
+ # we can use r,c, w, h
720
+ row, col, display_length, height = arg
721
+ config[:row] = row
722
+ config[:col] = col
723
+ config[:display_length] = display_length if display_length
724
+ config[:width] = display_length if display_length
725
+ # width for most XXX ?
726
+ config[:height] = height if height
727
+ when Hash
728
+ config.merge!(arg)
729
+ if block_event
730
+ block_event = config.delete(:block_event){ block_event }
731
+ raise "Invalid event. Use #{events}" unless events.include? block_event
732
+ end
733
+ when String
734
+ config[:name] = arg
735
+ config[:title] = arg # some may not have title
736
+ #config[:text] = arg # some may not have title
737
+ end
738
+ end
739
+ end # _process
740
+ # position object based on whether in a flow or stack.
741
+ # @app_row is prepared for next object based on this objects ht
742
+ def _position config
743
+ unless @current_object.empty?
744
+ $log.debug " WWWW returning from position #{@current_object.last} "
745
+ return
746
+ end
747
+ if @inflow
748
+ #col = @flowstack.last
749
+ config[:row] = @app_row
750
+ config[:col] = @flowcol
751
+ $log.debug " YYYY config #{config} "
752
+ @flowcol += config[:text].length + 5 # 5 came from buttons
753
+ elsif @instack
754
+ # most likely you won't have row and col. should we check or just go ahead
755
+ col = @stack.last.margin
756
+ config[:row] = @app_row
757
+ config[:col] = col
758
+ @app_row += config[:height] || 1
759
+ # TODO need to allow stack to have its spacing, but we don't have an object as yet.
760
+ end
761
+ end
762
+ end # class
763
+ end # module
764
+ if $0 == __FILE__
765
+ include RubyCurses
766
+ #app = App.new
767
+ #window = app.window
768
+ #window.printstring 2, 30, "Demo of Listbox - rbcurse", $normalcolor, 'reverse'
769
+ #app.logger.info "beforegetch"
770
+ #window.getch
771
+ #app.close
772
+ # this was the yield example, but now we've moved to instance eval
773
+ App.new do
774
+ @window.printstring 0, 30, "Demo of Listbox - rbcurse", $normalcolor, 'reverse'
775
+ @window.printstring 1, 30, "Hit F1 to quit", $datacolor, 'normal'
776
+ form = @form
777
+ fname = "Search"
778
+ r, c = 7, 30
779
+ c += fname.length + 1
780
+ #field1 = field( [r,c, 30], fname, :bgcolor => "cyan", :block_event => :CHANGE) do |fld|
781
+ stack :margin_top => 2, :margin => 10 do
782
+ lbl = label({:text => fname, :color=>'white',:bgcolor=>'red', :mnemonic=> 's'})
783
+ field1 = field( [r,c, 30], fname, :bgcolor => "cyan",:block_event => :CHANGE) do |fld|
784
+ message("You entered #{fld.getvalue}. To quit enter quit and tab out")
785
+ if fld.getvalue == "quit"
786
+ logger.info "you typed quit!"
787
+ throw :close
788
+ end
789
+ end
790
+ #field1.set_label Label.new @form, {:text => fname, :color=>'white',:bgcolor=>'red', :mnemonic=> 's'}
791
+ field1.set_label( lbl )
792
+ field1.enter do
793
+ message "you entered this field"
794
+ end
795
+
796
+ stack :margin_top => 2, :margin => 0 do
797
+ #label( [8, 30, 60],{:text => "A label", :color=>'white',:bgcolor=>'blue'} )
798
+ end
799
+
800
+ @bluelabel = label( [8, 30, 60],{:text => "B label", :color=>'white',:bgcolor=>'blue'} )
801
+
802
+ stack :margin_top => 2, :margin => 0 do
803
+ toggle :onvalue => " Toggle Down ", :offvalue => " Untoggle ", :mnemonic => 'T', :value => true
804
+
805
+ toggle :onvalue => " On ", :offvalue => " Off ", :value => true do |e|
806
+ alert "You pressed me #{e.state}"
807
+ end
808
+ check :text => "Check me!", :onvalue => "Checked", :offvalue => "Unchecked", :value => true do |e|
809
+ # this works but long and complicated
810
+ #@bluelabel.text = e.item.getvalue ? e.item.onvalue : e.item.offvalue
811
+ @bluelabel.text = e.item.text
812
+ end
813
+ radio :text => "red", :value => "RED", :color => "red", :group => :colors
814
+ radio :text => "green", :value => "GREEN", :color => "green", :group => :colors
815
+ flow do
816
+ button_row = 17
817
+ ok_button = button( [button_row,30], "OK", {:mnemonic => 'O'}) do
818
+ alert("About to dump data into log file!")
819
+ message "Dumped data to log file"
820
+ end
821
+
822
+ # using ampersand to set mnemonic
823
+ cancel_button = button( [button_row, 40], "&Cancel" ) do
824
+ if confirm("Do your really want to quit?")== :YES
825
+ #throw(:close);
826
+ quit
827
+ else
828
+ message "Quit aborted"
829
+ end
830
+ end # cancel
831
+ button "Don't know"
832
+ end
833
+ flow :margin_top => 2 do
834
+ button "Another"
835
+ button "Line"
836
+ end
837
+ stack :margin_top => 2, :margin => 0 do
838
+ @pbar = progress :width => 20, :bgcolor => 'white', :color => 'red'
839
+ @pbar1 = progress :width => 20, :style => :old
840
+ end
841
+ end
842
+ end # stack
843
+ # lets make another column
844
+ stack :margin_top => 2, :margin => 70 do
845
+ l = label "Column 2"
846
+ f1 = field "afield", :bgcolor => 'white', :color => 'black'
847
+ list_box "A list", :list => ["Square", "Oval", "Rectangle", "Somethinglarge"], :choose => ["Square"]
848
+ lb = list_box "Another", :list => ["Square", "Oval", "Rectangle", "Somethinglarge"] do |list|
849
+ #f1.set_buffer list.text
850
+ #f1.text list.text
851
+ f1.text = list.text
852
+ l.text = list.text.upcase
853
+ end
854
+ t = textarea :height => 10 do |e|
855
+ #@bluelabel.text = e.to_s.tr("\n",' ')
856
+ @bluelabel.text = e.text.gsub("\n"," ")
857
+ len = e.source.get_text.length
858
+ len = len % 20 if len > 20
859
+ $log.debug " PBAR len of text is #{len}: #{len/20.0} "
860
+ @pbar.fraction(len/20.0)
861
+ @pbar1.fraction(len/20.0)
862
+ i = ((len/20.0)*100).to_i
863
+ @pbar.text = "completed:#{i}"
864
+ end
865
+ t.leave do |c|
866
+ @bluelabel.text = c.get_text.gsub("\n"," ")
867
+ end
868
+
869
+ end
870
+
871
+ # Allow user to get the keys
872
+ keypress do |key|
873
+ if key == :C_c
874
+ message "You tried to cancel"
875
+ #throw :close
876
+ quit
877
+ else
878
+ #app.message "You pressed #{key}, #{char} "
879
+ message "You pressed #{key}"
880
+ end
881
+ end
882
+ end
883
+ end