rbhex-layouts 1.1.0.alpha4

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