Module: RubyCurses::Utils

Included in:
CommandWindow, Form, ListCellRenderer, MessageBox, TreeCellRenderer, Widget
Defined in:
lib/rbcurse/core/widgets/rwidget.rb,
lib/rbcurse/core/include/appmethods.rb

Overview

Since:

  • 1.2.0

Instance Method Summary collapse

Instance Method Details

#_process_key(keycode, object, window) ⇒ Object

e.g. process_key ch, self returns UNHANDLED if no block for it after form handles basic keys, it gives unhandled key to current field, if current field returns unhandled, then it checks this map. added 2009-01-06 19:13 since widgets need to handle keys properly added 2009-01-18 12:58 returns ret val of blk.call so that if block does not handle, the key can still be handled e.g. table last row, last col does not handle, so it will auto go to next field

2010-02-24 13:45 handles 2 key combinations, copied from Form, must be identical in logic
except maybe for window pointer. TODO not tested

Since:

  • 1.2.0



548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
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
593
594
595
# File 'lib/rbcurse/core/widgets/rwidget.rb', line 548

def _process_key keycode, object, window
  return :UNHANDLED if @key_handler.nil?
  blk = @key_handler[keycode]
  $log.debug "XXX:  _process key keycode #{keycode} #{blk.class}, #{self.class} "
  return :UNHANDLED if blk.nil?
  if blk.is_a? OrderedHash 
    #Ncurses::nodelay(window.get_window, bf = false)
    # if you set nodelay in ncurses.rb then this will not
    # wait for second key press, so you then must either make it blocking
    # here, or set a wtimeout here.
    #
    # This is since i have removed timeout globally since resize was happeing
    # after a keypress. maybe we can revert to timeout and not worry about resize so much
    Ncurses::wtimeout(window.get_window, $ncurses_timeout || 500) # will wait a second on wgetch so we can get gg and qq
    ch = window.getch
    Ncurses::nowtimeout(window.get_window, true)
    #Ncurses::nodelay(window.get_window, bf = true)

    $log.debug " process_key: got #{keycode} , #{ch} "
    # next line ignores function keys etc. C-x F1, thus commented 255 2012-01-11 
    if ch < 0 #|| ch > 255
      return nil
    end
    #yn = ch.chr
    blk1 = blk[ch]
    # FIXME we are only returning the second key, what if form
    # has mapped first and second combo. We should unget keycode and ch. 2011-12-23 
    # check this out first.
    window.ungetch(ch) if blk1.nil? # trying  2011-09-27 
    return :UNHANDLED if blk1.nil? # changed nil to unhandled 2011-09-27 
    $log.debug " process_key: found block for #{keycode} , #{ch} "
    blk = blk1
  end
  if blk.is_a? Symbol
    if respond_to? blk
      return send(blk, *@key_args[keycode])
    else
      ## 2013-03-05 - 19:50 why the hell is there an alert here, nowhere else
      alert "This ( #{self.class} ) does not respond to #{blk.to_s} "
      # added 2013-03-05 - 19:50 so called can know
      return :UNHANDLED 
    end
  else
    $log.debug "rwidget BLOCK called _process_key " if $log.debug? 
    return blk.call object,  *@key_args[keycode]
  end
  #0
end

#bind_key(keycode, *args, &blk) ⇒ Object

bind an action to a key, required if you create a button which has a hotkey or a field to be focussed on a key, or any other user defined action based on key e.g. bind_key ?C-x, object, block added 2009-01-06 19:13 since widgets need to handle keys properly

2010-02-24 12:43 trying to take in multiple key bindings, TODO unbind
TODO add symbol so easy to map from config file or mapping file

Since:

  • 1.2.0



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
# File 'lib/rbcurse/core/widgets/rwidget.rb', line 367

def bind_key keycode, *args, &blk
  #$log.debug " #{@name} bind_key received #{keycode} "
  @key_handler ||= {}
  #
  # added on 2011-12-4 so we can pass a description for a key and print it
  # The first argument may be a string, it will not be removed
  # so existing programs will remain as is.
  @key_label ||= {}
  if args[0].is_a?(String) || args[0].is_a?(Symbol)
    @key_label[keycode] = args[0] 
  else
    @key_label[keycode] = :unknown
  end

  if !block_given?
    blk = args.pop
    raise "If block not passed, last arg should be a method symbol" if !blk.is_a? Symbol
    #$log.debug " #{@name} bind_key received a symbol #{blk} "
  end
  case keycode
  when String
    # single assignment
    keycode = keycode.getbyte(0) #if keycode.class==String ##    1.9 2009-10-05 19:40 
    #$log.debug " #{name} Widg String called bind_key BIND #{keycode}, #{keycode_tos(keycode)}  "
    #$log.debug " assigning #{keycode}  " if $log.debug? 
    @key_handler[keycode] = blk
  when Array
    # double assignment
    # for starters lets try with 2 keys only
    raise "A one key array will not work. Pass without array" if keycode.size == 1
    a0 = keycode[0]
    a0 = keycode[0].getbyte(0) if keycode[0].class == String
    a1 = keycode[1]
    a1 = keycode[1].getbyte(0) if keycode[1].class == String
    @key_handler[a0] ||= OrderedHash.new
    #$log.debug " assigning #{keycode} , A0 #{a0} , A1 #{a1} " if $log.debug? 
    @key_handler[a0][a1] = blk
    #$log.debug " XX assigning #{keycode} to  key_handler " if $log.debug? 
  else
    #$log.debug " assigning #{keycode} to  key_handler " if $log.debug? 
    @key_handler[keycode] = blk
  end
  @key_args ||= {}
  @key_args[keycode] = args

end

#bind_keys(keycodes, *args, &blk) ⇒ Object

Since:

  • 1.2.0



535
536
537
# File 'lib/rbcurse/core/widgets/rwidget.rb', line 535

def bind_keys keycodes, *args, &blk
  keycodes.each { |k| bind_key k, *args, &blk }
end

#clean_string!(content) ⇒ Object

Since:

  • 1.2.0



197
198
199
200
201
202
# File 'lib/rbcurse/core/widgets/rwidget.rb', line 197

def clean_string! content
  content.chomp! # don't display newline
  content.gsub!(/[\t\n]/, '  ') # don't display tab
  content.gsub!(/[^[:print:]]/, '')  # don't display non print characters
  content
end

#define_key(_symbol, _keycode, *args, &blk) ⇒ Object

Raises:

  • (ArgumentError)

Since:

  • 1.2.0



457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
# File 'lib/rbcurse/core/widgets/rwidget.rb', line 457

def define_key _symbol, _keycode, *args, &blk
  #_symbol = @symbol
  h = $rb_prefix_map[_symbol]
  raise ArgumentError, "No such keymap #{_symbol} defined. Use define_prefix_command." unless h
  _keycode = _keycode[0].getbyte(0) if _keycode[0].class == String
  arg = args.shift
  if arg.is_a? String
    desc = arg
    arg = args.shift
  elsif arg.is_a? Symbol
    # its a symbol
    desc = arg.to_s
  elsif arg.nil?
    desc = "unknown"
  else
    raise ArgumentError, "Don't know how to handle #{arg.class} in PrefixManager"
  end
  # 2013-03-20 - 18:45 187compat gave error in 187 cannot convert string to int
  #@descriptions ||= []
  @descriptions ||= {}
  @descriptions[_keycode] = desc

  if !block_given?
    blk = arg
  end
  h[_keycode] = blk
end

#define_prefix_command(_name, config = {}) ⇒ Object

_mapvar=nil, _prompt=nil

Since:

  • 1.2.0



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
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/rbcurse/core/widgets/rwidget.rb', line 414

def define_prefix_command _name, config={} #_mapvar=nil, _prompt=nil
  $rb_prefix_map ||= {}
  _name = _name.to_sym unless _name.is_a? Symbol
  $rb_prefix_map[_name] ||= {}
  scope = config[:scope] || self
  $rb_prefix_map[_name][:scope] = scope


  # create a variable by name _name
  # create a method by same name to use
  # Don;t let this happen more than once
instance_eval %{
  def #{_name.to_s} *args
     #$log.debug "XXX:  came inside #{_name} "
     h = $rb_prefix_map["#{_name}".to_sym]
     raise "No prefix_map named #{_name}, #{$rb_prefix_map.keys} " unless h
     ch = @window.getchar
     if ch
      if ch == KEY_F1
        text =  ["Options are: "]
        h.keys.each { |e| c = keycode_tos(e); text << c + " " + @descriptions[e]  }
        textdialog text, :title => "#{_name} key bindings"
        return
      end
        res =  h[ch]
        if res.is_a? Proc
          res.call
        elsif res.is_a? Symbol
           scope = h[:scope]
           scope.send(res)
        elsif res.nil?
          Ncurses.beep
           return :UNHANDLED
        end
     else
           #@window.ungetch(ch)
           :UNHANDLED
     end
  end
}
  return _name
end

#display_app_help(help_array = nil) ⇒ Object

Displays application help using either array provided or checking for :help_text method

Parameters:

  • help (Array)

    text

Since:

  • 1.2.0



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/rbcurse/core/include/appmethods.rb', line 45

def display_app_help help_array= nil
  if help_array
    arr = help_array
  elsif respond_to? :help_text
    arr = help_text
  else
    arr = []
    arr << "    NO HELP SPECIFIED FOR APP #{self}  "
    arr << "    "
    arr << "     --- General help ---          "
    arr << "    F10         -  exit application "
    arr << "    Alt-x       -  select commands  "
    arr << "    :           -  select commands  "
    arr << "    "
  end
  case arr
  when String
    arr = arr.split("\n")
  when Array
  end
  w = arr.max_by(&:length).length

  require 'rbcurse/core/util/viewer'
  RubyCurses::Viewer.view(arr, :layout => [2, 10, [4+arr.size, 24].min, w+2],:close_key => KEY_ENTER, :title => "<Enter> to close", :print_footer => true) do |t|
  # you may configure textview further here.
  #t.suppress_borders true
  #t.color = :black
  #t.bgcolor = :white
  # or
  t.attr = :reverse
  end
end

#get_attrib(str) ⇒ Object

convert a string to integer attribute FIXME: what if user wishes to OR two attribs, this will give error

Parameters:

  • e.g. (String)

    reverse bold normal underline if a Fixnum is passed, it is returned as is assuming to be an attrib

Since:

  • 1.2.0



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/rbcurse/core/widgets/rwidget.rb', line 289

def get_attrib str
  return FFI::NCurses::A_NORMAL unless str
  # next line allows us to do a one time conversion and keep the value
  #  in the same variable
  if str.is_a? Fixnum
    if [
      FFI::NCurses::A_BOLD,
      FFI::NCurses::A_REVERSE,    
      FFI::NCurses::A_NORMAL,
      FFI::NCurses::A_UNDERLINE,
      FFI::NCurses::A_STANDOUT,    
      FFI::NCurses::A_DIM,    
      FFI::NCurses::A_BOLD | FFI::NCurses::A_REVERSE,    
      FFI::NCurses::A_BOLD | FFI::NCurses::A_UNDERLINE,    
      FFI::NCurses::A_REVERSE | FFI::NCurses::A_UNDERLINE,    
      FFI::NCurses::A_BLINK
    ].include? str
    return str
    else
      raise ArgumentError, "get_attrib got a wrong value: #{str} "
    end
  end


  att = nil
  str = str.downcase.to_sym if str.is_a? String
  case str #.to_s.downcase
  when :bold
    att = FFI::NCurses::A_BOLD
  when :reverse
    att = FFI::NCurses::A_REVERSE    
  when :normal
    att = FFI::NCurses::A_NORMAL
  when :underline
    att = FFI::NCurses::A_UNDERLINE
  when :standout
    att = FFI::NCurses::A_STANDOUT
  when :bold_reverse
    att = FFI::NCurses::A_BOLD | FFI::NCurses::A_REVERSE
  when :bold_underline
    att = FFI::NCurses::A_BOLD | FFI::NCurses::A_UNDERLINE
  when :dim
    att = FFI::NCurses::A_DIM    
  when :blink
    att = FFI::NCurses::A_BLINK    # unlikely to work
  else
    att = FFI::NCurses::A_NORMAL
  end
  return att
end

#get_color(default = $datacolor, color = @color, bgcolor = @bgcolor) ⇒ Object

if passed a string in second or third param, will create a color and return, else it will return default color Use this in order to create a color pair with the colors provided, however, if user has not provided, use supplied default.

Examples:

get_color $promptcolor, :white, :cyan

Parameters:

  • color_pair (Fixnum)

    created by ncurses

  • color (Symbol) (defaults to: @color)

    name such as white black cyan magenta red green yellow

  • bgcolor (Symbol) (defaults to: @bgcolor)

    name such as white black cyan magenta red green yellow

Raises:

  • (ArgumentError)

Since:

  • 1.2.0



276
277
278
279
280
281
282
# File 'lib/rbcurse/core/widgets/rwidget.rb', line 276

def get_color default=$datacolor, color=@color, bgcolor=@bgcolor
  return default if color.nil? || bgcolor.nil?
  raise ArgumentError, "Color not valid: #{color}: #{ColorMap.colors} " if !ColorMap.is_color? color
  raise ArgumentError, "Bgolor not valid: #{bgcolor} : #{ColorMap.colors} " if !ColorMap.is_color? bgcolor
  acolor = ColorMap.get_color(color, bgcolor)
  return acolor
end

#keycode_tos(keycode) ⇒ Object

needs to move to a keystroke class please use these only for printing or debugging, not comparing I could soon return symbols instead 2010-09-07 14:14

Since:

  • 1.2.0



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/rbcurse/core/widgets/rwidget.rb', line 206

def keycode_tos keycode
  case keycode
  when 33..126
    return keycode.chr
  when ?\C-a.getbyte(0) .. ?\C-z.getbyte(0)
    return "C-" + (keycode + ?a.getbyte(0) -1).chr 
  when ?\M-A.getbyte(0)..?\M-z.getbyte(0)
    return "M-"+ (keycode - 128).chr
  when ?\M-\C-A.getbyte(0)..?\M-\C-Z.getbyte(0)
    return "M-C-"+ (keycode - 32).chr
  when ?\M-0.getbyte(0)..?\M-9.getbyte(0)
    return "M-"+ (keycode-?\M-0.getbyte(0)).to_s
  when 32
    return "space" # changed to lowercase so consistent
  when 27
    return "esc" # changed to lowercase so consistent
  when ?\C-].getbyte(0)
    return "C-]"
  when 258
    return "down"
  when 259
    return "up"
  when 260
    return "left"
  when 261
    return "right"
  when FFI::NCurses::KEY_F1..FFI::NCurses::KEY_F12
    return "F"+ (keycode-264).to_s
  when 330
    return "delete"
  when 127
    return "bs"
  when 353
    return "btab"
  when 481
    return "M-S-tab"
  when 393..402
    return "M-F"+ (keycode-392).to_s
  when 0
    return "C-space" 
  when 160
    return "M-space" # at least on OSX Leopard now (don't remember this working on PPC)
  when C_LEFT
    return "C-left"
  when C_RIGHT
    return "C-right"
  when S_F9
    return "S_F9"
  else
    others=[?\M--,?\M-+,?\M-=,?\M-',?\M-",?\M-;,?\M-:,?\M-\,, ?\M-.,?\M-<,?\M->,?\M-?,?\M-/,?\M-!]
    others.collect! {|x| x.getbyte(0)  }  ## added 2009-10-04 14:25 for 1.9
    s_others=%w[M-- M-+ M-= M-' M-"   M-;   M-:   M-, M-. M-< M-> M-? M-/ M-!]
    if others.include? keycode
      index = others.index keycode
      return s_others[index]
    end
    # all else failed
    return keycode.to_s
  end
end

#last_lineObject

returns last line of full screen, should it be current window ?

Since:

  • 1.2.0



341
# File 'lib/rbcurse/core/widgets/rwidget.rb', line 341

def last_line; FFI::NCurses.LINES-1; end

#OLDdefine_key(_symbol, _keycode, _method = nil, &blk) ⇒ Object

define a key within a prefix key map such as C-x

Raises:

  • (ArgumentError)

Since:

  • 1.2.0



485
486
487
488
489
490
491
492
493
# File 'lib/rbcurse/core/widgets/rwidget.rb', line 485

def OLDdefine_key _symbol, _keycode, _method=nil, &blk
  h = $rb_prefix_map[_symbol]
  raise ArgumentError, "No such keymap #{_symbol} defined. Use define_prefix_command." unless h
  _keycode = _keycode[0].getbyte(0) if _keycode[0].class == String
  if !block_given?
    blk = _method
  end
  h[_keycode] = blk
end

#one_line_window(at = last_line(), config = {}, &blk) ⇒ Object

Create a one line window typically at the bottom should we really put this here, too much clutter ?

Since:

  • 1.2.0



345
346
347
348
349
# File 'lib/rbcurse/core/widgets/rwidget.rb', line 345

def one_line_window at=last_line(), config={}, &blk
  at ||= last_line()
  at = FFI::NCurses.LINES-at if at < 0
  VER::Window.new(1,0,at,0)
end

#parse_formatted_text(color_parser, formatted_text) ⇒ Object

This has been put here since the API is not yet stable, and i don’t want to have to change in many places. 2011-11-10

Converts formatted text into chunkline objects.

To print chunklines you may for each row:

window.wmove row+height, col
a = get_attrib @attrib
window.show_colored_chunks content, color, a

Parameters:

  • object (color_parser)

    or symbol :tmux, :ansi the color_parser implements parse_format, the symbols relate to default parsers provided.

  • string (String)

    containing formatted text

Since:

  • 1.2.0



181
182
183
184
185
186
187
# File 'lib/rbcurse/core/widgets/rwidget.rb', line 181

def parse_formatted_text(color_parser, formatted_text)
  require 'rbcurse/core/include/chunk'
  cp = Chunks::ColorParser.new color_parser
  l = []
  formatted_text.each { |e| l << cp.convert_to_chunk(e) }
  return l
end

Display key bindings for current widget and form in dialog

Since:

  • 1.2.0



495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
# File 'lib/rbcurse/core/widgets/rwidget.rb', line 495

def print_key_bindings *args
  f  = get_current_field
  #labels = [@key_label, f.key_label]
  #labels = [@key_label]
  #labels << f.key_label if f.key_label
  labels = []
  labels << (f.key_label || {}) #if f.key_label
  labels << @key_label
  arr = []
  if get_current_field.help_text 
    arr << get_current_field.help_text 
  end
  labels.each_with_index { |h, i|  
    case i
    when 0
      arr << "  ===  Current widget bindings ==="
    when 1
      arr << "  === Form bindings ==="
    end

    h.each_pair { |name, val| 
      if name.is_a? Fixnum
        name = keycode_tos name
      elsif name.is_a? String
        name = keycode_tos(name.getbyte(0))
      elsif name.is_a? Array
        s = []
        name.each { |e|
          s << keycode_tos(e.getbyte(0))
        }
        name = s
      else
        #$log.debug "XXX: KEY #{name} #{name.class} "
      end
      arr << " %-30s %s" % [name ,val]
      $log.debug "KEY: #{name} : #{val} "
    }
  }
  textdialog arr, :title => "Key Bindings"
end

#repeatmObject

repeats the given action based on how value of universal numerica argument + set using the C-u key. Or in vim-mode using numeric keys

Since:

  • 1.2.0



352
353
354
355
356
357
358
# File 'lib/rbcurse/core/widgets/rwidget.rb', line 352

def repeatm
  $inside_multiplier_action = true
  _multiplier = ( ($multiplier.nil? || $multiplier == 0) ? 1 : $multiplier )
  _multiplier.times { yield }
  $multiplier = 0
  $inside_multiplier_action = false
end

#run_command(cmd) ⇒ Object

executes given command and displays in viewer

Parameters:

  • unix (String)

    command, e.g., git -st

Since:

  • 1.2.0



96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/rbcurse/core/include/appmethods.rb', line 96

def run_command cmd
  # http://whynotwiki.com/Ruby_/_Process_management#What_happens_to_standard_error_.28stderr.29.3F
  require 'rbcurse/core/util/viewer'
  begin
    res = `#{cmd} 2>&1`
  rescue => ex
    res = ex.to_s
    res << ex.backtrace.join("\n") 
  end
  res.gsub!("\t","   ")
  RubyCurses::Viewer.view(res.split("\n"), :close_key => KEY_ENTER, :title => "<Enter> to close, M-l M-h to scroll")
end

#shell_out(command) ⇒ Object

Since:

  • 1.2.0



108
109
110
111
112
113
114
115
116
117
# File 'lib/rbcurse/core/include/appmethods.rb', line 108

def shell_out command
  w = @window || @form.window
  w.hide
  Ncurses.endwin
  ret = system command
  Ncurses.refresh
  #Ncurses.curs_set 0  # why ?
  w.show
  return ret
end

#shell_outputObject

prompts user for unix command and displays output in viewer

Since:

  • 1.2.0



80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/rbcurse/core/include/appmethods.rb', line 80

def shell_output
  $shell_history ||= []
  cmd = get_string("Enter shell command:", :maxlen => 50) do |f|
    require 'rbcurse/core/include/rhistory'
    f.extend(FieldHistory)
    f.history($shell_history)
  end
  if cmd && !cmd.empty?
    run_command cmd
    $shell_history.push(cmd) unless $shell_history.include? cmd
  end
end

#suspendObject

Since:

  • 1.2.0



31
32
33
34
35
36
37
38
39
# File 'lib/rbcurse/core/include/appmethods.rb', line 31

def suspend
  _suspend(false) do
    system("tput cup 26 0")
    system("tput ed")
    system("echo Enter C-d to return to application")
    system (ENV['PS1']='\s-\v\$ ') if ENV['SHELL']== '/bin/bash'
    system(ENV['SHELL']);
  end
end

#view(what, config = {}, &block) ⇒ Object

view a file or array of strings

Since:

  • 1.2.0



597
598
599
600
# File 'lib/rbcurse/core/widgets/rwidget.rb', line 597

def view what, config={}, &block # :yields: textview for further configuration
  require 'rbcurse/core/util/viewer'
  RubyCurses::Viewer.view what, config, &block
end

#wrap_text(txt, max) ⇒ Object

wraps text given max length, puts newlines in it. it does not take into account existing newlines Some classes have @maxlen or display_length which may be passed as the second parameter

Since:

  • 1.2.0



193
194
195
196
# File 'lib/rbcurse/core/widgets/rwidget.rb', line 193

def wrap_text(txt, max )
  txt.gsub(/(.{1,#{max}})( +|$\n?)|(.{1,#{max}})/,
           "\\1\\3\n") 
end