Class: Umbra::Multiline
Overview
Base class for widgets that have multiple lines and are scrollable. Preferably, use a concrete class such as Listbox, Table or Textbox. This is not editable by default. To delete or insert rows, set editable to true.
Instance Attribute Summary collapse
-
#current_index ⇒ Object
readonly
index of focussed row, starting 0, index into the list supplied.
-
#list ⇒ Object
array containing data (usually Strings).
-
#panned_cols ⇒ Object
readonly
How may columns has widget panned to right.
-
#search_offset ⇒ Object
adjustment for searching for a pattern in a line, if adding text to start of string.
Attributes inherited from Widget
#col, #col_offset, #curpos, #focusable, #graphic, #handler, #key_label, #modified, #name, #repaint_required, #row, #row_offset, #state
Instance Method Summary collapse
-
#_truncate_to_width(ff) ⇒ Object
truncate string to width, and handle panning {{{.
-
#ask_search ⇒ Object
Ask user for a pattern to look for in lines.
-
#color_of_row(index, state) ⇒ Object
(also: #_format_color)
Each row can be in one of the following states: 1.
-
#command(*args, &block) ⇒ Object
convenience method for calling most used event of a widget Called by user programs.
-
#current_row ⇒ String
returns current row as String 2018-04-11 - NOTE this may not be a String so we convert it to string before returning.
- #cursor_backward ⇒ Object
-
#cursor_down ⇒ Object
go to next row.
-
#cursor_end ⇒ Object
goto end of line.
-
#cursor_forward ⇒ Object
move cursor forward one character, called with KEY_RIGHT action.
-
#cursor_home ⇒ Object
position cursor at start of field.
- #cursor_up ⇒ Object
-
#delete_at(index) ⇒ Object
UNTESTED.
-
#ensure_visible(_row = @current_index) ⇒ Object
Ensure current row is visible, if not make it first row NOTE - need to check if its at end and then reduce scroll at rows, check_prow does that.
-
#find_more ⇒ Object
find more occurrences of match.
-
#fire_action_event ⇒ Object
event when user hits ENTER on a row, user would bind :PRESS callers may use w.current_index or w.current_row or w.curpos.
- #getvalue ⇒ Object
-
#goto_end ⇒ Object
go to end of file (last line).
-
#goto_line(line) ⇒ Object
NOTE what about firing handlers when moving rows, i suppose that these will be called from a binding.
-
#goto_start ⇒ Object
go to start of file (first line).
-
#handle_key(ch) ⇒ Object
Multiline key handling.
-
#initialize(config = {}, &block) ⇒ Multiline
constructor
{{{.
-
#insert(index, line) ⇒ Object
UNTESTED.
-
#is_visible?(_row) ⇒ Boolean
Is the given row visible UNTESTED.
-
#map_keys ⇒ Object
mapping of keys for multiline {{{.
-
#next_match(str, startline = nil, _curpos = nil, endline = nil) ⇒ Object
find the first or next occurrence of a pattern.
-
#on_enter ⇒ Object
on enter of this multiline.
-
#on_enter_row(index) ⇒ Object
called whenever a row entered.
-
#on_leave ⇒ Object
on leave of this multiline.
-
#on_leave_row(index) ⇒ Object
called when user leaves a row and when object is exited.
- #page_backward ⇒ Object
- #page_forward ⇒ Object
-
#paint_row(win, row, col, line, ctr) ⇒ Object
Paint given row.
-
#print_row(win, row, col, str, index, state) ⇒ Object
do the actual printing of the row, depending on index and state This method starts with underscore since it is only required to be overriden if an object has special printing needs.
-
#repaint ⇒ Object
repaints the entire multiline, called by
form
{{{. - #row_count ⇒ Object
- #scroll_down ⇒ Object
-
#scroll_left ⇒ Object
cursor_backward.
-
#scroll_right ⇒ Object
cursor_forward.
- #scroll_up ⇒ Object
- #state_of_row(ix) ⇒ Object
-
#to_searchable(index) ⇒ String
This needs to be overridden in case of lists that contain something other than ‘String` as their elements.
-
#value_of_row(line, ctr, state) ⇒ String
(also: #_format_value)
how to convert the line of the array to a simple String.
Methods inherited from Widget
#_form=, #color_pair, #getvalue_for_paint, #height, #highlight_attr, #init_vars, #modified?, #repaint_all, #rowcol, #set_form_col, #set_form_row, #text, #touch, #variable_set, #visible, #width
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(config = {}, &block) ⇒ Multiline
{{{
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/umbra/multiline.rb', line 35 def initialize config={}, &block # {{{ @focusable = false @editable = false @pstart = 0 # which row does printing start from @current_index = 0 # index of row on which cursor is @search_offset = 0 # search has no offset ## PRESS event relates to pressing RETURN/ENTER (10) register_events([:LEAVE_ROW, :ENTER_ROW, :PRESS]) super map_keys @row_offset = 0 @panned_cols = 0 ## how many columns the view has panned. ## The name means panned_cols @curpos = 0 ## Widget defines an accessor on this. @repaint_required = true end |
Instance Attribute Details
#current_index ⇒ Object (readonly)
index of focussed row, starting 0, index into the list supplied
33 34 35 |
# File 'lib/umbra/multiline.rb', line 33 def current_index @current_index end |
#list ⇒ Object
array containing data (usually Strings)
26 27 28 |
# File 'lib/umbra/multiline.rb', line 26 def list @list end |
#panned_cols ⇒ Object (readonly)
How may columns has widget panned to right
27 28 29 |
# File 'lib/umbra/multiline.rb', line 27 def panned_cols @panned_cols end |
#search_offset ⇒ Object
adjustment for searching for a pattern in a line, if adding text to start of string. Example, listbox adds one char to start. One may override and add two.
30 31 32 |
# File 'lib/umbra/multiline.rb', line 30 def search_offset @search_offset end |
Instance Method Details
#_truncate_to_width(ff) ⇒ Object
truncate string to width, and handle panning {{{
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/umbra/multiline.rb', line 241 def _truncate_to_width ff _width = self.width if ff if ff.size > _width # panned_cols can be greater than width then we get null if @panned_cols < ff.size ff = ff[@panned_cols..@panned_cols+_width-1] else ff = "" end else if @panned_cols < ff.size ff = ff[@panned_cols..-1] else ff = "" end end end ff = "" unless ff return ff end |
#ask_search ⇒ Object
Ask user for a pattern to look for in lines. Put cursor on first match.
531 532 533 534 535 536 537 538 539 540 541 542 543 |
# File 'lib/umbra/multiline.rb', line 531 def ask_search str = get_string("Search:") return unless str ix = next_match str if ix @current_index, @curpos = ix set_col_offset @curpos ## why do we need to do this also $log.debug " ask_search ci: #{@current_index} , #{@curpos} " @last_regex = str else alert "Pattern not found: #{str}" end end |
#color_of_row(index, state) ⇒ Object Also known as: _format_color
Each row can be in one of the following states:
1. HIGHLIGHTED: cursor is on the row, and the list is focussed (user is in it)
2. CURRENT : cursor was on this row, now user has exited the list
3. SELECTED : user has selected this row (this can also have above two states actually)
4. NORMAL : All other rows: not selected, not under cursor
returns color, attrib for given row
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/umbra/multiline.rb', line 193 def color_of_row index, state arr = case state #when :SELECTED #[@selected_color_pair, @selected_attr] when :HIGHLIGHTED [@highlight_color_pair || CP_WHITE, @highlight_attr || REVERSE] when :CURRENT [@color_pair, @attr] when :NORMAL _color = CP_CYAN _color = CP_WHITE if index % 2 == 0 #_color = @alt_color_pair if index % 2 == 0 [@color_pair || _color, @attr || NORMAL] end return arr end |
#command(*args, &block) ⇒ Object
convenience method for calling most used event of a widget Called by user programs.
475 476 477 |
# File 'lib/umbra/multiline.rb', line 475 def command *args, &block bind_event :ENTER_ROW, *args, &block end |
#current_row ⇒ String
returns current row as String 2018-04-11 - NOTE this may not be a String so we convert it to string before returning
359 360 361 362 |
# File 'lib/umbra/multiline.rb', line 359 def current_row s = @list[@current_index] value_of_row s, @current_index, :CURRENT end |
#cursor_backward ⇒ Object
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
# File 'lib/umbra/multiline.rb', line 374 def cursor_backward if @col_offset > 0 @curpos -= 1 add_col_offset -1 else # cur is on the first col, then scroll left if @panned_cols > 0 @panned_cols -= 1 @curpos -= 1 else # do nothing end end end |
#cursor_down ⇒ Object
go to next row
333 334 335 |
# File 'lib/umbra/multiline.rb', line 333 def cursor_down @current_index += 1 end |
#cursor_end ⇒ Object
goto end of line. This should be consistent with moving the cursor to the end of the row with right arrow
344 345 346 347 348 349 350 351 352 353 354 355 |
# File 'lib/umbra/multiline.rb', line 344 def cursor_end blen = current_row().length if blen < self.width set_col_offset blen # just after the last character @panned_cols = 0 else @panned_cols = blen-self.width #+2 # 2 is due to mark and space XXX could be a problem with textbox set_col_offset blen # just after the last character end @curpos = blen # this is position in array where editing or motion is to happen regardless of what you see # regardless of panned_cols (panning) end |
#cursor_forward ⇒ Object
move cursor forward one character, called with KEY_RIGHT action.
364 365 366 367 368 369 370 371 372 373 |
# File 'lib/umbra/multiline.rb', line 364 def cursor_forward blen = current_row().size # -1 if @curpos < blen if add_col_offset(1)==-1 # go forward if you can, else scroll #@panned_cols += 1 if @panned_cols < self.width @panned_cols += 1 if @panned_cols < blen end @curpos += 1 end end |
#cursor_home ⇒ Object
position cursor at start of field
337 338 339 340 341 |
# File 'lib/umbra/multiline.rb', line 337 def cursor_home @curpos = 0 @panned_cols = 0 set_col_offset 0 end |
#cursor_up ⇒ Object
329 330 331 |
# File 'lib/umbra/multiline.rb', line 329 def cursor_up @current_index -= 1 end |
#delete_at(index) ⇒ Object
UNTESTED
614 615 616 617 618 619 620 |
# File 'lib/umbra/multiline.rb', line 614 def delete_at index return unless @list return unless @editable #@repaint_all = true @repaint_required = true @list.delete_at index end |
#ensure_visible(_row = @current_index) ⇒ Object
Ensure current row is visible, if not make it first row NOTE - need to check if its at end and then reduce scroll at rows, check_prow does that
UNTESTED
509 510 511 512 513 |
# File 'lib/umbra/multiline.rb', line 509 def ensure_visible _row = @current_index unless is_visible? _row @pstart = _row end end |
#find_more ⇒ Object
find more occurrences of match. This is bound to ‘n’ key.
546 547 548 549 550 551 552 553 554 555 556 557 558 |
# File 'lib/umbra/multiline.rb', line 546 def find_more return unless @last_regex str = @last_regex $log.debug " FIND MORE last_regex is : #{@last_regex} " ix = next_match @last_regex #return unless ix if ix @current_index, @curpos = ix set_col_offset @curpos ## why do we need to do this also else alert "No more matches for: #{str}" end end |
#fire_action_event ⇒ Object
event when user hits ENTER on a row, user would bind :PRESS callers may use w.current_index or w.current_row or w.curpos.
obj.bind :PRESS { |w| w.current_row }
485 486 487 488 489 490 491 |
# File 'lib/umbra/multiline.rb', line 485 def fire_action_event return if @list.nil? || @list.size == 0 #require 'canis/core/include/ractionevent' #aev = text_action_event #fire_handler :PRESS, aev fire_handler :PRESS, self end |
#getvalue ⇒ Object
89 90 91 |
# File 'lib/umbra/multiline.rb', line 89 def getvalue @list end |
#goto_end ⇒ Object
go to end of file (last line)
430 431 432 433 |
# File 'lib/umbra/multiline.rb', line 430 def goto_end @current_index = @list.size-1 @panned_cols = @curpos = 0 end |
#goto_line(line) ⇒ Object
NOTE what about firing handlers when moving rows, i suppose that these will be called from a binding. UNTESTED
517 518 519 520 521 |
# File 'lib/umbra/multiline.rb', line 517 def goto_line line return if line < 0 or line >= self.row_count @current_index = line ensure_visible line end |
#goto_start ⇒ Object
go to start of file (first line)
424 425 426 427 428 |
# File 'lib/umbra/multiline.rb', line 424 def goto_start @current_index = 0 @panned_cols = @curpos = 0 set_col_offset 0 end |
#handle_key(ch) ⇒ Object
Multiline key handling. {{{ Called by form
from form’s handle_key
when this object is in focus.
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 |
# File 'lib/umbra/multiline.rb', line 452 def handle_key ch old_current_index = @current_index old_panned_cols = @panned_cols old_col_offset = @col_offset ret = super return ret ensure ## NOTE: it is possible that a block called above may have cleared the list. ## In that case, the on_enter_row will crash. I had put a check here, but it ## has vanished ??? @current_index = 0 if @current_index < 0 @current_index = @list.size-1 if @current_index >= @list.size if @current_index != old_current_index on_leave_row old_current_index on_enter_row @current_index @repaint_required = true end @repaint_required = true if old_panned_cols != @panned_cols or old_col_offset != @col_offset end |
#insert(index, line) ⇒ Object
UNTESTED
623 624 625 626 627 628 629 |
# File 'lib/umbra/multiline.rb', line 623 def insert index, line return unless @list return unless @editable #@repaint_all = true @repaint_required = true @list.insert index, line end |
#is_visible?(_row) ⇒ Boolean
Is the given row visible UNTESTED
498 499 500 501 |
# File 'lib/umbra/multiline.rb', line 498 def is_visible? _row j = _row - @pstart j >= 0 && j <= (self.height - 1) end |
#map_keys ⇒ Object
mapping of keys for multiline {{{
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/umbra/multiline.rb', line 265 def map_keys return if @keys_mapped bind_keys([?k,FFI::NCurses::KEY_UP], "Up") { cursor_up } bind_keys([?j,FFI::NCurses::KEY_DOWN], "Down") { cursor_down } bind_keys([?l,FFI::NCurses::KEY_RIGHT], "Right") { cursor_forward } bind_keys([?h,FFI::NCurses::KEY_LEFT], "Left") { cursor_backward } bind_key(?g, 'goto_start') { goto_start } bind_key(?G, 'goto_end') { goto_end } bind_key(FFI::NCurses::KEY_CTRL_A, 'cursor_home') { cursor_home } bind_key(FFI::NCurses::KEY_CTRL_E, 'cursor_end') { cursor_end } bind_key(FFI::NCurses::KEY_CTRL_F, 'page_forward') { page_forward } bind_key(32, 'page_forward') { page_forward } bind_key(FFI::NCurses::KEY_CTRL_B, 'page_backward'){ page_backward } bind_key(FFI::NCurses::KEY_CTRL_U, 'scroll_up') { scroll_up } bind_key(FFI::NCurses::KEY_CTRL_D, 'scroll_down') { scroll_down } ## C-h was not working, so trying C-j bind_key(FFI::NCurses::KEY_CTRL_J, 'scroll_left') { scroll_left } bind_key(FFI::NCurses::KEY_CTRL_L, 'scroll_right') { scroll_right } bind_key(?/, 'ask search') { ask_search } bind_key(?n, 'next match ') { find_more } @keys_mapped = true end |
#next_match(str, startline = nil, _curpos = nil, endline = nil) ⇒ Object
find the first or next occurrence of a pattern
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 |
# File 'lib/umbra/multiline.rb', line 565 def next_match str, startline = nil, _curpos = nil, endline = nil return unless str ## check current line for more occurrences. if !startline startline = @current_index _curpos ||= (@curpos + 1) # FIXME +1 should only happen if a search has already happened #_pos = @list[startline].index(str, _curpos) _pos = to_searchable(startline).index(str, _curpos) return [startline, _pos + search_offset] if _pos startline += 1 end ## Check rest of file ## loop through array, check after startline to eof @list.each_with_index do | line, ix| next if ix < startline break if endline && ix > endline #_found = line.index(str) _found = to_searchable(ix).index(str) #$log.debug " next_match: #{line}: #{_found} " if _found return [ix, _found + search_offset] if _found end if startline > 0 # this can get called again since startline was made 1 in above block. FIXME #return next_match str, 0, @current_index end return nil end |
#on_enter ⇒ Object
on enter of this multiline
289 290 291 292 293 294 295 |
# File 'lib/umbra/multiline.rb', line 289 def on_enter super on_enter_row @current_index # basically I need to only highlight the current index, not repaint all OPTIMIZE #touch ; repaint ## 2018 why was i calling repaint here ??? causing error in messagebox since window nil touch end |
#on_enter_row(index) ⇒ Object
called whenever a row entered. Call when object entered, also.
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
# File 'lib/umbra/multiline.rb', line 312 def on_enter_row index fire_handler(:ENTER_ROW, [@current_index]) # 2018-03-26 - improve this # if cursor ahead of blen then fix it blen = current_row().size-1 ## why -1 on above line. Empty lines will give -1 blen = 0 if blen < 0 if @curpos > blen @col_offset = blen - @panned_cols @curpos = blen if @panned_cols > blen @panned_cols = blen - self.width ## @int_width 2018-05-22 - @panned_cols = 0 if @panned_cols < 0 @col_offset = blen - @panned_cols end end @col_offset = 0 if @col_offset < 0 end |
#on_leave ⇒ Object
on leave of this multiline
298 299 300 301 302 303 304 |
# File 'lib/umbra/multiline.rb', line 298 def on_leave super on_leave_row @current_index # basically I need to only unhighlight the current index, not repaint all OPTIMIZE #touch ; repaint touch ##; repaint ## why repaint here ?? when was this necessary ? end |
#on_leave_row(index) ⇒ Object
called when user leaves a row and when object is exited.
307 308 309 |
# File 'lib/umbra/multiline.rb', line 307 def on_leave_row index fire_handler(:LEAVE_ROW, [index]) # 2018-03-26 - improve this end |
#page_backward ⇒ Object
440 441 442 |
# File 'lib/umbra/multiline.rb', line 440 def page_backward @current_index -= @page_lines end |
#page_forward ⇒ Object
443 444 445 |
# File 'lib/umbra/multiline.rb', line 443 def page_forward @current_index += @page_lines end |
#paint_row(win, row, col, line, ctr) ⇒ Object
Paint given row. {{{ This is not be be called by user, but may be overridden if caller wishes
to completely change the presentation of each row. In most cases, it should suffice
to override just +print_row+ or +value_of_row+ or +_format_color+.
165 166 167 168 169 170 171 172 173 174 |
# File 'lib/umbra/multiline.rb', line 165 def paint_row(win, row, col, line, ctr) state = state_of_row(ctr) ff = value_of_row(line, ctr, state) ff = _truncate_to_width( ff ) ## truncate and handle panning print_row(win, row, col, ff, ctr, state) end |
#print_row(win, row, col, str, index, state) ⇒ Object
do the actual printing of the row, depending on index and state This method starts with underscore since it is only required to be overriden if an object has special printing needs.
180 181 182 183 |
# File 'lib/umbra/multiline.rb', line 180 def print_row(win, row, col, str, index, state) arr = color_of_row index, state win.printstring(row, col, str, arr[0], arr[1]) end |
#repaint ⇒ Object
repaints the entire multiline, called by form
{{{
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 122 123 124 125 126 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 |
# File 'lib/umbra/multiline.rb', line 96 def repaint _calc_dimensions unless @_calc_dimensions return unless @repaint_required return unless @list win = @graphic raise "window nil in multiline" unless win r,c = self.row, self.col _attr = @attr || NORMAL _color = @color_pair || CP_WHITE curpos = 1 coffset = 0 rows = getvalue ht = self.height cur = @current_index st = pstart = @pstart # previous start pend = pstart + ht -1 # previous end if cur > pend st = (cur -ht) + 1 elsif cur < pstart st = cur end $log.debug "REPAINT #{self.class} : cur = #{cur} st = #{st} pstart = #{pstart} pend = #{pend} listsize = #{@list.size} " $log.debug "REPAINT ML : row = #{r} col = #{c} width = #{width}/#{@width}, height = #{ht}/#{@height} ( #{FFI::NCurses.COLS} #{FFI::NCurses.LINES} " y = 0 ctr = 0 filler = " "*(self.width) rows.each_with_index {|_f, y| next if y < st curpos = ctr if y == cur ## used for setting row_offset #_state = state_of_row(y) ## XXX should be move this into paint_row win.printstring(ctr + r, coffset+c, filler, _color ) ## print filler #paint_row( win, ctr+r, coffset+c, _f, y, _state) paint_row( win, ctr+r, coffset+c, _f, y) ctr += 1 @pstart = st break if ctr >= ht } ## if counter < ht then we need to clear the rest in case there was data earlier {{{ if ctr < ht while ctr < ht win.printstring(ctr + r, coffset+c, filler, _color ) ctr += 1 end end # }}} @row_offset = curpos ## used by +widget+ in +rowcol+ called by +Form+ #@col_offset = coffset ## NOTE listbox had this line, but it interferes with textbox @repaint_required = false end |
#row_count ⇒ Object
72 73 74 |
# File 'lib/umbra/multiline.rb', line 72 def row_count @list.size end |
#scroll_down ⇒ Object
434 435 436 |
# File 'lib/umbra/multiline.rb', line 434 def scroll_down @current_index += @scroll_lines end |
#scroll_left ⇒ Object
cursor_backward
420 421 422 |
# File 'lib/umbra/multiline.rb', line 420 def scroll_left ##cursor_backward @panned_cols -= 1 if @panned_cols > 0 end |
#scroll_right ⇒ Object
cursor_forward
416 417 418 419 |
# File 'lib/umbra/multiline.rb', line 416 def scroll_right ## cursor_forward blen = current_row().size-1 @panned_cols += 1 if @panned_cols < blen end |
#scroll_up ⇒ Object
437 438 439 |
# File 'lib/umbra/multiline.rb', line 437 def scroll_up @current_index -= @scroll_lines end |
#state_of_row(ix) ⇒ Object
223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/umbra/multiline.rb', line 223 def state_of_row ix _st = :NORMAL cur = @current_index if ix == cur # current row, row on which cursor is or was ## highlight only if object is focussed, otherwise just show mark if @state == :HIGHLIGHTED _st = :HIGHLIGHTED else ## cursor was on this row, but now user has tabbed out _st = :CURRENT end end return _st end |
#to_searchable(index) ⇒ String
This needs to be overridden in case of lists that contain something other than ‘String` as their elements. In such a case, `search_offset` may need to be adjusted also.
599 600 601 602 603 604 605 606 607 608 609 610 |
# File 'lib/umbra/multiline.rb', line 599 def to_searchable index s = @list[index] case s when String return s ## return the string as is. when Array ## may need to be overridden by caller return s.join(" ") else raise "Don't know how to handle this datatype, please override to_searchable" end end |
#value_of_row(line, ctr, state) ⇒ String Also known as: _format_value
how to convert the line of the array to a simple String. This is only required to be overridden if the list passed in is not an array of Strings.
218 219 220 |
# File 'lib/umbra/multiline.rb', line 218 def value_of_row line, ctr, state line end |