Class: Umbra::Form
- Inherits:
-
Object
- Object
- Umbra::Form
- Includes:
- EventHandler, KeyMappingHandler
- Defined in:
- lib/umbra/form.rb
Instance Attribute Summary collapse
-
#active_index ⇒ Object
index of active widget inside focusables array.
-
#focusables ⇒ Object
readonly
array of widgets, and those that can be traversed.
-
#name ⇒ Object
name given to form for debugging.
-
#widgets ⇒ Object
readonly
array of widgets, and those that can be traversed.
-
#window ⇒ Object
related window pointer used for printing or other FFI calls.
Instance Method Summary collapse
-
#add_widget(*widget) ⇒ Form
Add given widget to widget list and returns self A widget must be added to a Form for it to be painted and focussed.
-
#get_current_field ⇒ Widget?
(also: #current_widget)
NOTE 2018-05-17 - this is called by form in the very beginning when no field is actually focussed but active_index has been set to 0, so the on_enter has not been executed, but the handle_key is invoked.
-
#handle_key(ch) ⇒ Object
forms handle keys {{{ mainly traps tab and backtab to navigate between widgets. I know some widgets will want to use tab, e.g edit boxes for entering a tab or for completion. NOTE : please rescue exceptions when you use this in your main loop and alert() user.
-
#initialize(win, &block) ⇒ Form
constructor
A new instance of Form.
-
#map_keys ⇒ Object
NOTE: These mappings will only trigger if the current field does not use them in handle_key.
-
#pack ⇒ Object
Decide layout of objects.
-
#remove_widget(widget) ⇒ Object
remove a widget from form.
-
#repaint ⇒ Object
form repaint,calls repaint on each widget which will repaint it only if it has been modified since last call.
-
#repaint_all_widgets ⇒ Object
repaint all # {{{ this forces a repaint of all visible widgets and has been added for the case of overlapping windows, since a black rectangle is often left when a window is destroyed. This is internally triggered whenever a window is destroyed, and currently only for root window. NOTE: often the window itself or spaces between widgets also gets cleared, so basically the window itself may need recreating ? 2014-08-18 - 21:03.
-
#select_first_field ⇒ Object
take focus to first focusable field.
-
#select_last_field ⇒ Object
take focus to last field on form 2018-03-08 - WHY IS THIS REQUIRED NOT_SURE.
-
#select_next_field ⇒ Object
put focus on next field will cycle by default, unless navigation policy not :CYCLICAL in which case returns :NO_NEXT_FIELD.
-
#select_prev_field ⇒ nil, :NO_PREV_FIELD
put focus on previous field will cycle by default, unless navigation policy not :CYCLICAL in which case returns :NO_PREV_FIELD.
-
#select_widget(fld) ⇒ Object
(also: #select_field)
set focussed widget to given fld.
-
#to_s ⇒ Object
2010-02-07 14:50 to aid in debugging and comparing log files.
-
#update_focusables ⇒ Object
maintain a list of focusable objects so form can traverse between them easily.
Methods included from KeyMappingHandler
#_process_key, #bind_key, #bind_keys, #process_key, #unbind_key
Methods included from EventHandler
#bind_event, #event?, #fire_handler, #fire_property_change, #register_events
Constructor Details
#initialize(win, &block) ⇒ Form
Returns a new instance of Form.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/umbra/form.rb', line 38 def initialize win, &block @window = win @widgets = [] @active_index = nil @row = @col = 0 # 2018-03-07 - umbra @focusables = [] # focusable components register_events(:RESIZE) instance_eval &block if block_given? @name ||= "" # for debugging # for storing error message NOT_SURE #$error_message ||= Variable.new "" map_keys unless @keys_mapped $log ||= create_logger '/dev/null' ## FIXME temporarily adding here. If user has not ## specified a logger, create a quiet one. end |
Instance Attribute Details
#active_index ⇒ Object
index of active widget inside focusables array
31 32 33 |
# File 'lib/umbra/form.rb', line 31 def active_index @active_index end |
#focusables ⇒ Object (readonly)
array of widgets, and those that can be traversed
21 22 23 |
# File 'lib/umbra/form.rb', line 21 def focusables @focusables end |
#name ⇒ Object
name given to form for debugging
34 35 36 |
# File 'lib/umbra/form.rb', line 34 def name @name end |
#widgets ⇒ Object (readonly)
array of widgets, and those that can be traversed
21 22 23 |
# File 'lib/umbra/form.rb', line 21 def @widgets end |
#window ⇒ Object
related window pointer used for printing or other FFI calls
24 25 26 |
# File 'lib/umbra/form.rb', line 24 def window @window end |
Instance Method Details
#add_widget(*widget) ⇒ Form
Add given widget to widget list and returns self A widget must be added to a Form for it to be painted and focussed.
60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/umbra/form.rb', line 60 def * .each do |w| next if @widgets.include? w # NOTE: if form created with nil window (messagebox), then this would have to happen later w.graphic = @window if @window # 2018-03-19 - prevent widget from needing to call form back $log.warn "Window is nil in form.add_widget" unless @window w._form = self # 2018-04-20 - so that update_focusables can be called. @widgets << w end return self end |
#get_current_field ⇒ Widget? Also known as: current_widget
NOTE 2018-05-17 - this is called by form in the very beginning when no field is actually focussed
but active_index has been set to 0, so the on_enter has not been executed, but the handle_key
is invoked.
192 193 194 195 196 197 198 199 200 |
# File 'lib/umbra/form.rb', line 192 def get_current_field ## rewrite on 2018-05-18 - so that on_enter is called for first field if @_focussed_widget.nil? ## when form handle_key first called @focusables.first end return @_focussed_widget end |
#handle_key(ch) ⇒ Object
forms handle keys {{{ mainly traps tab and backtab to navigate between widgets. I know some widgets will want to use tab, e.g edit boxes for entering a tab
or for completion.
NOTE : please rescue exceptions when you use this in your main loop and alert() user
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 |
# File 'lib/umbra/form.rb', line 352 def handle_key(ch) handled = :UNHANDLED case ch when -1 #repaint # only for continuous updates, and will need to use wtimeout and not nodelay in getch return when 1000, 18 # what if someone has trapped this. # NOTE this works if widgets cover entire screen like text areas and lists but not in # dialogs where there is blank space. only widgets are painted. # testing out 12 is C-l, 18 is C-r $log.debug " form REFRESH_ALL repaint_all HK #{ch} #{self}, #{@name} " FFI::NCurses.endwin @window.wrefresh ## endwin must be followed by refresh @window.repaint @window.wrefresh return 0 when FFI::NCurses::KEY_RESIZE # SIGWINCH # UNTESTED XXX ## NOTE: this works but boxes are not resized since hardcoded height and width were given. ## 2018-05-13 - only if a layout is used, can a recalc happen. # note that in windows that have dialogs or text painted on window such as title or # box, the clear call will clear it out. these are not redrawn. # next line may be causing flicker, can we do without. FFI::NCurses.endwin @window.wrefresh @window.wclear lines = FFI::NCurses.LINES cols = FFI::NCurses.COLS #x = FFI::NCurses.stdscr.getmaxy x = @window.getmaxy #y = FFI::NCurses.stdscr.getmaxx y = @window.getmaxx @window.wresize(x,y) $log.debug " form RESIZE SIGWINCH HK #{ch} #{self}, #{@name}, #{ch}, x #{x} y #{y} lines #{lines} , cols: #{cols} " #alert "SIGWINCH WE NEED TO RECALC AND REPAINT resize #{lines}, #{cols}: #{x}, #{y} " #if @layout_manager #@layout_manager.do_layout ## we need to redo statusline and others that layout ignores #else #end ## added RESIZE on 2012-01-5 ## stuff that relies on last line such as statusline dock etc will need to be redrawn. fire_handler :RESIZE, self @window.wrefresh else field = get_current_field handled = :UNHANDLED handled = field.handle_key ch unless field.nil? # no field focussable ## next line "field" can print entire content of a list or table if to_s is large $log.debug "handled inside Form #{ch} from #{field} got #{handled} " # some widgets like textarea and list handle up and down if handled == :UNHANDLED or handled == -1 or field.nil? case ch when FFI::NCurses::KEY_TAB, ?\M-\C-i.getbyte(0) # tab and M-tab in case widget eats tab (such as Table) ret = select_next_field return ret if ret == :NO_NEXT_FIELD # alt-shift-tab or backtab (in case Table eats backtab) when FFI::NCurses::KEY_BTAB, 481 ## backtab added 2008-12-14 18:41 ret = select_prev_field return ret if ret == :NO_PREV_FIELD when FFI::NCurses::KEY_UP ret = select_prev_field return ret if ret == :NO_PREV_FIELD when FFI::NCurses::KEY_DOWN ret = select_next_field return ret if ret == :NO_NEXT_FIELD else #$log.debug " before calling process_key in form #{ch} " if $log.debug? ret = process_key ch, self # seems we need to flushinp in case composite has pushed key $log.debug "FORM process_key #{ch} got ret #{ret} in #{self}, flushing input " # 2014-06-01 - 17:01 added flush, maybe at some point we could do it only if unhandled # in case some method wishes to actually push some keys FFI::NCurses.flushinp return :UNHANDLED if ret == :UNHANDLED end elsif handled == :NO_NEXT_FIELD || handled == :NO_PREV_FIELD # 2011-10-4 return handled end end $log.debug " form before repaint #{self} , #{@name}, ret #{ret}" repaint ret || 0 # 2011-10-17 end |
#map_keys ⇒ Object
NOTE: These mappings will only trigger if the current field
does not use them in handle_key
315 316 317 318 319 320 |
# File 'lib/umbra/form.rb', line 315 def map_keys return if @keys_mapped #bind_key(FFI::NCurses::KEY_F1, 'help') { hm = help_manager(); hm.display_help } #bind_key(FFI::NCurses::KEY_F9, "Print keys", :print_key_bindings) # show bindings, tentative on F9 @keys_mapped = true end |
#pack ⇒ Object
Decide layout of objects. User has to call this after creating components More may come here.
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/umbra/form.rb', line 90 def pack update_focusables # set up hotkeys for buttons and labels with mnemonics and labels. @widgets.each do |w| #$log.debug " FOCUSABLES #{w.name} #{w.to_s} #{w.class}" if w.respond_to? :mnemonic if w.mnemonic ch = w.mnemonic.downcase()[0].ord # meta key mch = ?\M-a.getbyte(0) + (ch - ?a.getbyte(0)) if w.respond_to? :fire #$log.debug " setting hotkey #{mch} to button #{w} " self.bind_key(mch, "hotkey for button #{w} ") { w.fire } else # case of labels and labeled field #$log.debug " setting hotkey #{mch} to field #{w} " self.bind_key(mch, "hotkey for field #{w.} ") { #$log.debug " HOTKEY got key #{mch} : for #{w.related_widget} " self.select_field w. } end end end end @active_index = 0 if @focusables.size > 0 # 2018-04-14 - why the repaint here ? commenting off. Gave error in messagbox if no window yet. #repaint self end |
#remove_widget(widget) ⇒ Object
remove a widget from form. Will not be displayed or focussed.
75 76 77 78 |
# File 'lib/umbra/form.rb', line 75 def @widgets.delete @focusables.delete end |
#repaint ⇒ Object
form repaint,calls repaint on each widget which will repaint it only if it has been modified since last call. called after each keypress and on select_field.
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/umbra/form.rb', line 163 def repaint $log.debug " form repaint:#{self}, #{@name} , r #{@row} c #{@col} " if $log.debug? @widgets.each do |f| next if f.visible == false #f.repaint # changed on 2018-03-21 - so widgets don't need to do this. if f.repaint_required f.graphic = @window unless f.graphic # messageboxes may not have a window till very late f.repaint f.repaint_required = false f.instance_variable_set(:@_object_created, true) ## after this property_change handlers will fire end end # get curpos of active widget 2018-03-21 - form is taking control of this now. f = get_current_field if f @row, @col = f.rowcol _setpos end @window.wrefresh end |
#repaint_all_widgets ⇒ Object
repaint all # {{{ this forces a repaint of all visible widgets and has been added for the case of overlapping windows, since a black rectangle is often left when a window is destroyed. This is internally triggered whenever a window is destroyed, and currently only for root window. NOTE: often the window itself or spaces between widgets also gets cleared, so basically the window itself may need recreating ? 2014-08-18 - 21:03
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# File 'lib/umbra/form.rb', line 328 def $log.debug " REPAINT ALL in FORM called " #raise "it has come to repaint_all" @widgets.each do |w| next if w.visible == false #next if w.class.to_s == "Canis::MenuBar" $log.debug " ---- REPAINT ALL #{w.name} " w.repaint_required = true #w.repaint_all true w.repaint end $log.debug " REPAINT ALL in FORM complete " # place cursor on current_widget _setpos end |
#select_first_field ⇒ Object
take focus to first focusable field
205 206 207 |
# File 'lib/umbra/form.rb', line 205 def select_first_field select_field 0 end |
#select_last_field ⇒ Object
take focus to last field on form 2018-03-08 - WHY IS THIS REQUIRED NOT_SURE
211 212 213 214 215 216 |
# File 'lib/umbra/form.rb', line 211 def select_last_field raise return nil if @active_index.nil? # for forms that have no focusable field 2009-01-08 12:22 i = @focusables.length -1 select_field i end |
#select_next_field ⇒ Object
put focus on next field will cycle by default, unless navigation policy not :CYCLICAL in which case returns :NO_NEXT_FIELD. 2018-03-07 - UMBRA: let us force user to run validation when he does next field
225 226 227 228 229 230 231 232 233 234 |
# File 'lib/umbra/form.rb', line 225 def select_next_field return :UNHANDLED if @focusables.nil? || @focusables.empty? index = nil if @_focussed_widget index = @focusables.index @_focussed_widget end index = index ? index+1 : 0 index = 0 if index >= @focusables.length # CYCLICAL 2018-03-11 - @focusables[index] end |
#select_prev_field ⇒ nil, :NO_PREV_FIELD
put focus on previous field will cycle by default, unless navigation policy not :CYCLICAL in which case returns :NO_PREV_FIELD.
243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/umbra/form.rb', line 243 def select_prev_field return :UNHANDLED if @focusables.nil? or @focusables.empty? index = nil if @_focussed_widget index = @focusables.index @_focussed_widget else index = @focusables.length end index -= 1 index = @focusables.length-1 if index < 0 # CYCLICAL 2018-03-11 - @focusables[index] end |
#select_widget(fld) ⇒ Object Also known as: select_field
set focussed widget to given fld. This ensures that whenever a widget is given focus, the on_leave of the previous widget
is called, and the on_enter of this field is called.
2018-05-18 - rewrite of select_field which did not call on_leave
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/umbra/form.rb', line 127 def fld return nil unless fld ## no focusable if fld.is_a? Integer fld = @focusables[fld] end return unless fld.focusable ## leave existing widget if there was one fw = @_focussed_widget return if fw == fld if fw on_leave fw end ## enter given widget on_enter fld @_focussed_widget = fld ix = @focusables.index fld @active_index = ix @row, @col = fld.rowcol _setrowcol @row, @col repaint # 2018-03-21 - handle_key calls repaint, is this for cases not involving keypress ? @window.refresh end |
#to_s ⇒ Object
2010-02-07 14:50 to aid in debugging and comparing log files.
442 |
# File 'lib/umbra/form.rb', line 442 def to_s; @name || self; end |
#update_focusables ⇒ Object
maintain a list of focusable objects so form can traverse between them easily.
82 83 84 85 86 |
# File 'lib/umbra/form.rb', line 82 def update_focusables #$log.debug "1 inside update_focusables #{@focusables.count} " @focusables = @widgets.select { |w| w.focusable } #$log.debug "2 inside update_focusables #{@focusables.count} " end |