rbcurse-core 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +69 -0
- data/VERSION +1 -0
- data/examples/abasiclist.rb +151 -0
- data/examples/alpmenu.rb +46 -0
- data/examples/app.sample +17 -0
- data/examples/atree.rb +100 -0
- data/examples/common/file.rb +45 -0
- data/examples/data/README.markdown +9 -0
- data/examples/data/brew.txt +38 -0
- data/examples/data/color.2 +37 -0
- data/examples/data/gemlist.txt +60 -0
- data/examples/data/lotr.txt +12 -0
- data/examples/data/ports.txt +136 -0
- data/examples/data/table.txt +37 -0
- data/examples/data/tasks.csv +88 -0
- data/examples/data/tasks.txt +27 -0
- data/examples/data/todo.txt +10 -0
- data/examples/data/todocsv.csv +28 -0
- data/examples/data/unix1.txt +21 -0
- data/examples/data/unix2.txt +11 -0
- data/examples/dbdemo.rb +487 -0
- data/examples/dirtree.rb +90 -0
- data/examples/newtabbedwindow.rb +100 -0
- data/examples/newtesttabp.rb +92 -0
- data/examples/tabular.rb +132 -0
- data/examples/tasks.rb +167 -0
- data/examples/term2.rb +83 -0
- data/examples/testkeypress.rb +72 -0
- data/examples/testlistbox.rb +158 -0
- data/examples/testmessagebox.rb +140 -0
- data/examples/testree.rb +106 -0
- data/examples/testwsshortcuts.rb +66 -0
- data/examples/testwsshortcuts2.rb +127 -0
- data/lib/rbcurse.rb +8 -0
- data/lib/rbcurse/core/docs/index.txt +73 -0
- data/lib/rbcurse/core/include/action.rb +40 -0
- data/lib/rbcurse/core/include/appmethods.rb +112 -0
- data/lib/rbcurse/core/include/bordertitle.rb +41 -0
- data/lib/rbcurse/core/include/chunk.rb +182 -0
- data/lib/rbcurse/core/include/io.rb +953 -0
- data/lib/rbcurse/core/include/listcellrenderer.rb +140 -0
- data/lib/rbcurse/core/include/listeditable.rb +317 -0
- data/lib/rbcurse/core/include/listscrollable.rb +590 -0
- data/lib/rbcurse/core/include/listselectable.rb +264 -0
- data/lib/rbcurse/core/include/multibuffer.rb +83 -0
- data/lib/rbcurse/core/include/orderedhash.rb +77 -0
- data/lib/rbcurse/core/include/ractionevent.rb +67 -0
- data/lib/rbcurse/core/include/rchangeevent.rb +27 -0
- data/lib/rbcurse/core/include/rhistory.rb +62 -0
- data/lib/rbcurse/core/include/rinputdataevent.rb +47 -0
- data/lib/rbcurse/core/include/vieditable.rb +170 -0
- data/lib/rbcurse/core/system/colormap.rb +163 -0
- data/lib/rbcurse/core/system/keyboard.rb +150 -0
- data/lib/rbcurse/core/system/keydefs.rb +30 -0
- data/lib/rbcurse/core/system/ncurses.rb +218 -0
- data/lib/rbcurse/core/system/panel.rb +162 -0
- data/lib/rbcurse/core/system/window.rb +901 -0
- data/lib/rbcurse/core/util/ansiparser.rb +117 -0
- data/lib/rbcurse/core/util/app.rb +1235 -0
- data/lib/rbcurse/core/util/basestack.rb +407 -0
- data/lib/rbcurse/core/util/bottomline.rb +1850 -0
- data/lib/rbcurse/core/util/colorparser.rb +71 -0
- data/lib/rbcurse/core/util/focusmanager.rb +31 -0
- data/lib/rbcurse/core/util/padreader.rb +189 -0
- data/lib/rbcurse/core/util/rcommandwindow.rb +587 -0
- data/lib/rbcurse/core/util/rdialogs.rb +619 -0
- data/lib/rbcurse/core/util/viewer.rb +149 -0
- data/lib/rbcurse/core/util/widgetshortcuts.rb +505 -0
- data/lib/rbcurse/core/widgets/applicationheader.rb +102 -0
- data/lib/rbcurse/core/widgets/box.rb +58 -0
- data/lib/rbcurse/core/widgets/divider.rb +310 -0
- data/lib/rbcurse/core/widgets/keylabelprinter.rb +178 -0
- data/lib/rbcurse/core/widgets/rcombo.rb +238 -0
- data/lib/rbcurse/core/widgets/rcontainer.rb +415 -0
- data/lib/rbcurse/core/widgets/rlink.rb +30 -0
- data/lib/rbcurse/core/widgets/rlist.rb +723 -0
- data/lib/rbcurse/core/widgets/rmenu.rb +939 -0
- data/lib/rbcurse/core/widgets/rmenulink.rb +22 -0
- data/lib/rbcurse/core/widgets/rmessagebox.rb +373 -0
- data/lib/rbcurse/core/widgets/rprogress.rb +118 -0
- data/lib/rbcurse/core/widgets/rtabbedpane.rb +615 -0
- data/lib/rbcurse/core/widgets/rtabbedwindow.rb +68 -0
- data/lib/rbcurse/core/widgets/rtextarea.rb +920 -0
- data/lib/rbcurse/core/widgets/rtextview.rb +780 -0
- data/lib/rbcurse/core/widgets/rtree.rb +787 -0
- data/lib/rbcurse/core/widgets/rwidget.rb +3040 -0
- data/lib/rbcurse/core/widgets/scrollbar.rb +143 -0
- data/lib/rbcurse/core/widgets/statusline.rb +94 -0
- data/lib/rbcurse/core/widgets/tabular.rb +264 -0
- data/lib/rbcurse/core/widgets/tabularwidget.rb +1211 -0
- data/lib/rbcurse/core/widgets/textpad.rb +516 -0
- data/lib/rbcurse/core/widgets/tree/treecellrenderer.rb +150 -0
- data/lib/rbcurse/core/widgets/tree/treemodel.rb +428 -0
- metadata +156 -0
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'rbcurse/core/widgets/rwidget'
|
2
|
+
#include Ncurses # FFI 2011-09-8
|
3
|
+
include RubyCurses
|
4
|
+
module RubyCurses
|
5
|
+
#
|
6
|
+
# This paints labels for various keys at the bottom of the screen, in 2 rows.
|
7
|
+
# This is based on alpines last 2 rows. Modes are supported so that the
|
8
|
+
# labels change as you enter a widget.
|
9
|
+
# For an example, see dbdemo.rb or rfe.rb
|
10
|
+
# NOTE: applications using 'App' use a shortcut "dock" to create this.
|
11
|
+
#
|
12
|
+
# The most minimal keylabel to print one label in first row, and none in second is:
|
13
|
+
# [["F1", "Help"], nil]
|
14
|
+
# To print 2 labels, one over the other:
|
15
|
+
# [["F1", "Help"], ["F10", "Quit"]]
|
16
|
+
#
|
17
|
+
class KeyLabelPrinter < Widget
|
18
|
+
attr_reader :key_labels
|
19
|
+
# the current mode (labels are based on mode, changing the mode, changes the labels
|
20
|
+
# displayed)
|
21
|
+
dsl_property :mode
|
22
|
+
# set the color of the labels, overriding the defaults
|
23
|
+
dsl_accessor :footer_color_pair
|
24
|
+
# set the color of the mnemonic, overriding the defaults
|
25
|
+
dsl_accessor :footer_mnemonic_color_pair
|
26
|
+
|
27
|
+
def initialize form, key_labels, config={}, &block
|
28
|
+
|
29
|
+
case key_labels
|
30
|
+
when Hash
|
31
|
+
raise "KeyLabelPrinter: KeyLabels cannot be a hash, Array of key labels required. Perhaps you did not pass labels"
|
32
|
+
when Array
|
33
|
+
else
|
34
|
+
raise "KeyLabelPrinter: Array of key labels required. Perhaps you did not pass labels"
|
35
|
+
end
|
36
|
+
super form, config, &block
|
37
|
+
@mode ||= :normal
|
38
|
+
#@key_labels = key_labels
|
39
|
+
@key_hash = {}
|
40
|
+
@key_hash[@mode] = key_labels
|
41
|
+
@editable = false
|
42
|
+
@focusable = false
|
43
|
+
@cols ||= Ncurses.COLS-1
|
44
|
+
@row ||= Ncurses.LINES-2
|
45
|
+
@col ||= 0
|
46
|
+
@repaint_required = true
|
47
|
+
@footer_color_pair ||= $bottomcolor
|
48
|
+
@footer_mnemonic_color_pair ||= $reversecolor #2
|
49
|
+
end
|
50
|
+
def key_labels mode=@mode
|
51
|
+
@key_hash[mode]
|
52
|
+
end
|
53
|
+
# returns the keys as printed. these may or may not help
|
54
|
+
# in validation depedign on what you passed as zeroth index
|
55
|
+
def get_current_keys
|
56
|
+
a = []
|
57
|
+
@key_hash[@mode].each do |arr|
|
58
|
+
a << arr[0] unless arr.nil?
|
59
|
+
end
|
60
|
+
return a
|
61
|
+
end
|
62
|
+
def getvalue
|
63
|
+
@key_hash
|
64
|
+
end
|
65
|
+
def set_key_labels _key_labels, mode=:normal
|
66
|
+
@key_hash[mode] = _key_labels
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# XXX need to move wrapping etc up and done once.
|
71
|
+
def repaint
|
72
|
+
return unless @repaint_required
|
73
|
+
r,c = rowcol
|
74
|
+
arr = key_labels()
|
75
|
+
print_key_labels(arr, mode=@mode)
|
76
|
+
@repaint_required = false
|
77
|
+
end
|
78
|
+
# ?? does not use mode, i think key_labels is unused. a hash is now used 2011-10-11 XXX FIXME
|
79
|
+
# WARNING, i have not tested this after changing it.
|
80
|
+
def append_key_label key, label, mode=@mode
|
81
|
+
#@key_labels << [key, label] if !@key_labels.include? [key, label]
|
82
|
+
@key_hash[mode] << [key, label] if !@key_hash[mode].include? [key, label]
|
83
|
+
@repaint_required = true
|
84
|
+
end
|
85
|
+
def print_key_labels(arr = key_labels(), mode=@mode)
|
86
|
+
#return if !@show_key_labels # XXX
|
87
|
+
@win ||= @form.window
|
88
|
+
$log.debug "XXX: PKL #{arr.length}, #{arr}"
|
89
|
+
@padding = @cols / (arr.length/2)
|
90
|
+
posx = 0
|
91
|
+
even = []
|
92
|
+
odd = []
|
93
|
+
arr.each_index { |i|
|
94
|
+
if i % 2 == 0
|
95
|
+
#arr[i+1] = ['',''] if arr[i+1].nil?
|
96
|
+
nextarr = arr[i+1] || ['', '']
|
97
|
+
keyw = [arr[i][0].length, nextarr[0].length].max
|
98
|
+
labelw = [arr[i][1].length, nextarr[1].length].max
|
99
|
+
|
100
|
+
even << [ sprintf("%*s", keyw, arr[i][0]), sprintf("%-*s", labelw, arr[i][1]) ]
|
101
|
+
odd << [ sprintf("%*s", keyw, nextarr[0]), sprintf("%-*s", labelw, nextarr[1]) ]
|
102
|
+
#$log.debug("loop even: #{even.inspect}")
|
103
|
+
else
|
104
|
+
end
|
105
|
+
}
|
106
|
+
#$log.debug("even: #{even.inspect}")
|
107
|
+
#$log.debug("odd : #{odd.inspect}")
|
108
|
+
#posy = @barrow-1
|
109
|
+
posy = @row
|
110
|
+
print_key_labels_row(posy, posx, even)
|
111
|
+
posy = @row+1
|
112
|
+
print_key_labels_row(posy, posx, odd)
|
113
|
+
# uncommented next line after ffi-ncurses else not showing till key press FFI 2011-09-17
|
114
|
+
@win.wrefresh # needed else secod row not shown after askchoice XXX
|
115
|
+
end
|
116
|
+
def print_key_labels_row(posy, posx, arr)
|
117
|
+
# FIXME: this logic of padding needs to take into account
|
118
|
+
# width of window
|
119
|
+
padding = 8
|
120
|
+
padding = 4 if arr.length > 5
|
121
|
+
padding = 2 if arr.length > 7
|
122
|
+
padding = 0 if arr.length > 9
|
123
|
+
#padding = @padding # XXX 2008-11-13 23:01
|
124
|
+
my_form_win = @win
|
125
|
+
@win.printstring(posy,0, "%-*s" % [@cols," "], @footer_color_pair, @attr)
|
126
|
+
arr.each do |kl|
|
127
|
+
key = kl[0]
|
128
|
+
lab = kl[1]
|
129
|
+
if key !="" # don't print that white blank space for fillers
|
130
|
+
color_pair= @footer_mnemonic_color_pair # $reversecolor #2
|
131
|
+
x = posx + (key.length - key.strip.length)
|
132
|
+
my_form_win.attron(Ncurses.COLOR_PAIR(color_pair))
|
133
|
+
my_form_win.mvprintw(posy, x, "%s" % kl[0].strip );
|
134
|
+
my_form_win.attroff(Ncurses.COLOR_PAIR(color_pair))
|
135
|
+
end
|
136
|
+
color_pair=@footer_color_pair
|
137
|
+
posx = posx + kl[0].length
|
138
|
+
my_form_win.attron(Ncurses.COLOR_PAIR(color_pair))
|
139
|
+
|
140
|
+
#lab = sprintf(" %s %*s" , kl[1], padding, " ");
|
141
|
+
lab = sprintf(" %s %s" , kl[1], " "*padding);
|
142
|
+
my_form_win.mvprintw(posy, posx, lab)
|
143
|
+
my_form_win.attroff(Ncurses.COLOR_PAIR(color_pair))
|
144
|
+
posx = posx + lab.length
|
145
|
+
end
|
146
|
+
end
|
147
|
+
##
|
148
|
+
# updates existing label with a new one.
|
149
|
+
# @return true if updated, else false
|
150
|
+
# @example update "C-x", "C-x", "Disable"
|
151
|
+
def update_application_key_label(display_code, new_display_code, text)
|
152
|
+
@repaint_required = true
|
153
|
+
labels = key_labels()
|
154
|
+
raise "labels are nil !!!" unless labels
|
155
|
+
labels.each_index do |ix|
|
156
|
+
lab = labels[ix]
|
157
|
+
next if lab.nil?
|
158
|
+
if lab[0] == display_code
|
159
|
+
labels[ix] = [new_display_code , text]
|
160
|
+
$log.debug("updated #{labels[ix]}")
|
161
|
+
return true
|
162
|
+
end
|
163
|
+
end
|
164
|
+
return false
|
165
|
+
end
|
166
|
+
alias :update :update_application_key_label
|
167
|
+
##
|
168
|
+
# inserts an application label at given index
|
169
|
+
# to add the key, use create_datakeys to add bindings
|
170
|
+
# remember to call restore_application_key_labels after updating/inserting
|
171
|
+
def insert_application_key_label(index, display_code, text)
|
172
|
+
@repaint_required = true
|
173
|
+
labels = key_labels()
|
174
|
+
labels.insert(index, [display_code , text] )
|
175
|
+
end
|
176
|
+
# ADD HERE KEYLABEL
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
# ----------------------------------------------------------------------------- #
|
2
|
+
# File: rcombo.rb
|
3
|
+
# Description: Non-editable combo box.
|
4
|
+
# Make it dead-simple to use.
|
5
|
+
# This is a simpler version of the original ComboBox which allowed
|
6
|
+
# editing and used rlistbox. This simpler class is meant for the rbcurse
|
7
|
+
# core package and will only depend on a core class if at all.
|
8
|
+
# Author: rkumar http://github.com/rkumar/rbcurse/
|
9
|
+
# Date: 2011-11-11 - 21:42
|
10
|
+
# License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
11
|
+
# Last update: use ,,L
|
12
|
+
# ----------------------------------------------------------------------------- #
|
13
|
+
#
|
14
|
+
require 'rbcurse'
|
15
|
+
|
16
|
+
include RubyCurses
|
17
|
+
module RubyCurses
|
18
|
+
extend self
|
19
|
+
|
20
|
+
# the quick approach would be to use field, and just add a popup.
|
21
|
+
# Or since we are not editing, we could use a Label and a popup
|
22
|
+
# Or just display a label and a popup without using anything else.
|
23
|
+
#
|
24
|
+
|
25
|
+
class ComboBox < Field
|
26
|
+
include RubyCurses::EventHandler
|
27
|
+
dsl_accessor :list_config
|
28
|
+
|
29
|
+
attr_accessor :current_index
|
30
|
+
# the symbol you want to use for combos
|
31
|
+
attr_accessor :COMBO_SYMBOL
|
32
|
+
attr_accessor :show_symbol # show that funny symbol after a combo to signify its a combo
|
33
|
+
dsl_accessor :arrow_key_policy # :IGNORE :NEXT_ROW :POPUP
|
34
|
+
|
35
|
+
def initialize form, config={}, &block
|
36
|
+
@arrow_key_policy = :ignore
|
37
|
+
@editable = false
|
38
|
+
@COMBO_SYMBOL = "v".ord # trying this out
|
39
|
+
@current_index = 0
|
40
|
+
super
|
41
|
+
# added if check since it was overriding set_buffer in creation. 2009-01-18 00:03
|
42
|
+
set_buffer @list[@current_index].dup if @buffer.nil? or @buffer.empty?
|
43
|
+
init_vars
|
44
|
+
@_events.push(*[:CHANGE, :ENTER_ROW, :LEAVE_ROW])
|
45
|
+
end
|
46
|
+
def init_vars
|
47
|
+
super
|
48
|
+
@show_symbol = true if @show_symbol.nil? # if set to false don't touch
|
49
|
+
#@show_symbol = false if @label # 2011-11-13
|
50
|
+
@COMBO_SYMBOL ||= FFI::NCurses::ACS_DARROW #GEQUAL
|
51
|
+
bind_key(KEY_UP) { previous_row }
|
52
|
+
bind_key(KEY_DOWN) { next_row }
|
53
|
+
end
|
54
|
+
def selected_item
|
55
|
+
@list[@current_index]
|
56
|
+
end
|
57
|
+
def selected_index
|
58
|
+
@current_index
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# convert given list to datamodel
|
63
|
+
def list alist=nil
|
64
|
+
return @list if alist.nil?
|
65
|
+
#@list = RubyCurses::ListDataModel.new(alist)
|
66
|
+
@list = alist
|
67
|
+
end
|
68
|
+
##
|
69
|
+
# combo edit box key handling
|
70
|
+
# removed UP and DOWN and bound it, so it can be unbound
|
71
|
+
def handle_key(ch)
|
72
|
+
@current_index ||= 0
|
73
|
+
# added 2009-01-18 22:44 no point moving horiz or passing up to Field if not edit
|
74
|
+
if !@editable
|
75
|
+
if ch == KEY_LEFT or ch == KEY_RIGHT
|
76
|
+
return :UNHANDLED
|
77
|
+
end
|
78
|
+
end
|
79
|
+
case @arrow_key_policy
|
80
|
+
when :ignore
|
81
|
+
if ch == KEY_DOWN or ch == KEY_UP
|
82
|
+
return :UNHANDLED
|
83
|
+
end
|
84
|
+
when :popup
|
85
|
+
if ch == KEY_DOWN or ch == KEY_UP
|
86
|
+
popup
|
87
|
+
end
|
88
|
+
end
|
89
|
+
case ch
|
90
|
+
#when KEY_UP # show previous value
|
91
|
+
# previous_row
|
92
|
+
#when KEY_DOWN # show previous value
|
93
|
+
# next_row
|
94
|
+
# adding spacebar to popup combo, as in microemacs 2010-10-01 13:21
|
95
|
+
when 32, KEY_DOWN+ META_KEY # alt down
|
96
|
+
popup # pop up the popup
|
97
|
+
else
|
98
|
+
super
|
99
|
+
end
|
100
|
+
end
|
101
|
+
def DEPprevious_row
|
102
|
+
@current_index -= 1 if @current_index > 0
|
103
|
+
set_buffer @list[@current_index].dup
|
104
|
+
set_modified(true) ## ??? not required
|
105
|
+
fire_handler :ENTER_ROW, self
|
106
|
+
@list.on_enter_row self
|
107
|
+
end
|
108
|
+
def DEPnext_row
|
109
|
+
@current_index += 1 if @current_index < @list.length()-1
|
110
|
+
set_buffer @list[@current_index].dup
|
111
|
+
set_modified(true) ## ??? not required
|
112
|
+
fire_handler :ENTER_ROW, self
|
113
|
+
@list.on_enter_row self
|
114
|
+
end
|
115
|
+
##
|
116
|
+
# calls a popup list
|
117
|
+
# TODO: should not be positioned so that it goes off edge
|
118
|
+
# user's customizations of list should be passed in
|
119
|
+
# The dup of listconfig is due to a tricky feature/bug.
|
120
|
+
# I try to keep the config hash and instance variables in synch. So
|
121
|
+
# this config hash is sent to popuplist which updates its row col and
|
122
|
+
# next time we pop up the popup row and col are zero.
|
123
|
+
#
|
124
|
+
#
|
125
|
+
# added dup in PRESS since editing edit field mods this
|
126
|
+
# on pressing ENTER, value set back and current_index updated
|
127
|
+
def popup
|
128
|
+
@list_config ||= {}
|
129
|
+
@list_config[:row] ||= @row
|
130
|
+
@list_config[:col] ||= @col
|
131
|
+
@list_config[:relative_to] ||= self
|
132
|
+
# this does not allow us to bind to events in the list
|
133
|
+
index = popuplist @list, @list_config
|
134
|
+
if index
|
135
|
+
set_buffer @list[index].dup
|
136
|
+
set_modified(true) if @current_index != index
|
137
|
+
@current_index = index
|
138
|
+
end
|
139
|
+
end
|
140
|
+
def OLDpopup
|
141
|
+
listconfig = (@list_config && @list_config.dup) || {}
|
142
|
+
dm = @list
|
143
|
+
# current item in edit box will be focussed when list pops up
|
144
|
+
#$log.debug "XXX POPUP: #{dm.selected_index} = #{@current_index}, value #{@buffer}"
|
145
|
+
# we are having some problms when using this in a list. it retains earlier value
|
146
|
+
_index = dm.index @buffer
|
147
|
+
dm.selected_index = _index # @current_index
|
148
|
+
poprow = @row+0 # one row below the edit box
|
149
|
+
popcol = @col
|
150
|
+
dlength = @display_length
|
151
|
+
f = self
|
152
|
+
@popup = RubyCurses::PopupList.new do
|
153
|
+
row poprow
|
154
|
+
col popcol
|
155
|
+
width dlength
|
156
|
+
list_data_model dm
|
157
|
+
list_selection_mode 'single'
|
158
|
+
relative_to f
|
159
|
+
list_config listconfig
|
160
|
+
bind(:PRESS) do |index|
|
161
|
+
f.set_buffer dm[index].dup
|
162
|
+
f.set_modified(true) if f.current_index != index
|
163
|
+
f.current_index = index
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Field putc advances cursor when it gives a char so we override this
|
169
|
+
def putc c
|
170
|
+
if c >= 0 and c <= 127
|
171
|
+
ret = putch c.chr
|
172
|
+
if ret == 0
|
173
|
+
addcol 1 if @editable
|
174
|
+
set_modified
|
175
|
+
end
|
176
|
+
end
|
177
|
+
return -1 # always ??? XXX
|
178
|
+
end
|
179
|
+
##
|
180
|
+
# field does not give char to non-editable fields so we override
|
181
|
+
def putch char
|
182
|
+
@current_index ||= 0
|
183
|
+
if @editable
|
184
|
+
super
|
185
|
+
return 0
|
186
|
+
else
|
187
|
+
match = next_match(char)
|
188
|
+
set_buffer match unless match.nil?
|
189
|
+
fire_handler :ENTER_ROW, self
|
190
|
+
end
|
191
|
+
@modified = true
|
192
|
+
fire_handler :CHANGE, self # 2008-12-09 14:51 ???
|
193
|
+
0
|
194
|
+
end
|
195
|
+
##
|
196
|
+
# the sets the next match in the edit field
|
197
|
+
def next_match char
|
198
|
+
start = @current_index
|
199
|
+
start.upto(@list.length-1) do |ix|
|
200
|
+
if @list[ix][0,1].casecmp(char) == 0
|
201
|
+
return @list[ix] unless @list[ix] == @buffer
|
202
|
+
end
|
203
|
+
@current_index += 1
|
204
|
+
end
|
205
|
+
## could not find, start from zero
|
206
|
+
@current_index = 0
|
207
|
+
start = [@list.length()-1, start].min
|
208
|
+
0.upto(start) do |ix|
|
209
|
+
if @list[ix][0,1].casecmp(char) == 0
|
210
|
+
return @list[ix] unless @list[ix] == @buffer
|
211
|
+
end
|
212
|
+
@current_index += 1
|
213
|
+
end
|
214
|
+
@current_index = [@list.length()-1, @current_index].min
|
215
|
+
return nil
|
216
|
+
end
|
217
|
+
##
|
218
|
+
# on leaving the listbox, update the combo/datamodel.
|
219
|
+
# we are using methods of the datamodel. Updating our list will have
|
220
|
+
# no effect on the list, and wont trigger events.
|
221
|
+
# Do not override.
|
222
|
+
def on_leave
|
223
|
+
fire_handler :LEAVE, self
|
224
|
+
end
|
225
|
+
|
226
|
+
def repaint
|
227
|
+
super
|
228
|
+
c = @col + @display_length
|
229
|
+
if @show_symbol # 2009-01-11 18:47
|
230
|
+
# i have changed c +1 to c, since we have no right to print beyond display_length
|
231
|
+
@form.window.mvwaddch @row, c, @COMBO_SYMBOL # Ncurses::ACS_GEQUAL
|
232
|
+
@form.window.mvchgat(y=@row, x=c, max=1, Ncurses::A_REVERSE|Ncurses::A_UNDERLINE, $datacolor, nil)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
end # class ComboBox
|
237
|
+
|
238
|
+
end # module
|
@@ -0,0 +1,415 @@
|
|
1
|
+
=begin
|
2
|
+
* Name: A container that manages components placed in it but
|
3
|
+
is not a form. Thus it can be safely placed as a widget
|
4
|
+
without all the complicatinos of a form embedded inside another.
|
5
|
+
NOTE: Still experimental
|
6
|
+
* Description
|
7
|
+
* Author: rkumar (http://github.com/rkumar/rbcurse/)
|
8
|
+
* Date: 21.10.11 - 00:29
|
9
|
+
* License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
10
|
+
|
11
|
+
* Last update: 23.10.11 - 00:29
|
12
|
+
== CHANGES
|
13
|
+
Focusables so we don't focus on label
|
14
|
+
== TODO
|
15
|
+
How to put blank lines in stack - use a blank label
|
16
|
+
|
17
|
+
- The contaomers and multis need to do their own on_enter and on_leave
|
18
|
+
management, they cannot rely on some other container doing it.
|
19
|
+
We can only rely on handle_key being called. HK should determine
|
20
|
+
whether any set_form row etc needs to be done.
|
21
|
+
- Should have its own stack and flow
|
22
|
+
=end
|
23
|
+
|
24
|
+
require 'rbcurse'
|
25
|
+
|
26
|
+
include RubyCurses
|
27
|
+
module RubyCurses
|
28
|
+
extend self
|
29
|
+
|
30
|
+
# This is an attempt at having a container which can contain multiple
|
31
|
+
# widgets without being a form itself. Having forms within forms
|
32
|
+
# complicates code too much, esp cursor positioning. e.g. tabbedpane
|
33
|
+
|
34
|
+
class Container < Widget
|
35
|
+
|
36
|
+
dsl_accessor :suppress_borders #to_print_borders
|
37
|
+
dsl_accessor :border_attrib, :border_color
|
38
|
+
dsl_accessor :title #set this on top
|
39
|
+
dsl_accessor :title_attrib #bold, reverse, normal
|
40
|
+
# should container stack objects ignoring users row col
|
41
|
+
# this is esp needed since App sets row and col which is too early
|
42
|
+
# This is now the default value, till i can redo things
|
43
|
+
#dsl_accessor :stack
|
44
|
+
dsl_accessor :positioning # absolute, relative, stack
|
45
|
+
attr_reader :current_component
|
46
|
+
|
47
|
+
def initialize form=nil, config={}, &block
|
48
|
+
@suppress_borders = false
|
49
|
+
@row_offset = @col_offset = 1
|
50
|
+
@_events ||= []
|
51
|
+
@stack = true
|
52
|
+
@positioning = :stack
|
53
|
+
super
|
54
|
+
@focusable = true
|
55
|
+
@editable = false
|
56
|
+
@components = [] # all components
|
57
|
+
@focusables = [] # focusable components, makes checks easier
|
58
|
+
|
59
|
+
init_vars
|
60
|
+
end
|
61
|
+
def init_vars
|
62
|
+
@repaint_required = true
|
63
|
+
@row_offset = @col_offset = 0 if @suppress_borders # FIXME supposed to use this !!
|
64
|
+
|
65
|
+
@internal_width = 2
|
66
|
+
@internal_width = 1 if @suppress_borders
|
67
|
+
@name ||= "AContainer"
|
68
|
+
@first_time = true
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
# NOTE: since we are handling the traversal, we delink the object from any
|
73
|
+
# form's widgets array that might have been added. Whenever a form is available,
|
74
|
+
# we set it (without adding widget to it) so it can print using the form's window.
|
75
|
+
#
|
76
|
+
# @param [Widget] to add
|
77
|
+
def add *items
|
78
|
+
items.each do |c|
|
79
|
+
raise ArgumentError, "Nil component passed to add" unless c
|
80
|
+
if c.is_a? Widget
|
81
|
+
if c.form && c.form != @form
|
82
|
+
$log.debug " removing widget VIMSPLIT #{c.class} wr: #{c.row} row:#{@row} ht:#{@height} "
|
83
|
+
c.form.remove_widget c
|
84
|
+
c.form = nil
|
85
|
+
# or should i just stack them myself and screw what you've asked for
|
86
|
+
end
|
87
|
+
# take it out of form's control. We will control it.
|
88
|
+
if c.form
|
89
|
+
c.form.remove_widget c
|
90
|
+
end
|
91
|
+
# shoot, what if at this point the container does not have a form
|
92
|
+
attach_form c if @form
|
93
|
+
end
|
94
|
+
# most likely if you have created both container and widgets
|
95
|
+
# inside app, it would have given row after container
|
96
|
+
|
97
|
+
@components << c
|
98
|
+
if c.focusable
|
99
|
+
@focusables << c
|
100
|
+
@current_component ||= c # only the first else cursor falls on last on enter
|
101
|
+
end
|
102
|
+
|
103
|
+
end # items each
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
# When we get a form, we silently attach it to this object, without the form
|
108
|
+
# knowing. We don't want form managing this object.
|
109
|
+
def attach_form c
|
110
|
+
c.form = @form
|
111
|
+
c.override_graphic @graphic
|
112
|
+
c.parent_component = self
|
113
|
+
end
|
114
|
+
alias :add_widget :add
|
115
|
+
def widgets; @components; end
|
116
|
+
# what of by_name
|
117
|
+
|
118
|
+
|
119
|
+
# correct coordinates of comp esp if App has stacked them after this
|
120
|
+
# container
|
121
|
+
# It is best to use the simple stack feature. The rest could change at any time
|
122
|
+
# and is quite arbitrary. Some folks may set absolute locations if container
|
123
|
+
# is directly on a form, others may set relative locations if it is inside a
|
124
|
+
# tabbed pane or other container. Thus, stacks are best
|
125
|
+
def correct_component c
|
126
|
+
raise "Form is still not set in Container" unless @form
|
127
|
+
attach_form(c) unless c.form
|
128
|
+
@last_row ||= @row + 1
|
129
|
+
inset = 2
|
130
|
+
# 2011-10-20 current default behaviour is to stack
|
131
|
+
if @positioning == :stack
|
132
|
+
c.row = @last_row
|
133
|
+
c.col = @col + inset
|
134
|
+
|
135
|
+
# do not advance row, save col for next row
|
136
|
+
@last_row += 1
|
137
|
+
elsif @positioning == :relative # UNTESTED NOTE
|
138
|
+
if (c.row || 0) <= 0
|
139
|
+
$log.warn "c.row in CONTAINER is #{c.row} "
|
140
|
+
c.row = @last_row
|
141
|
+
@last_row += 1
|
142
|
+
elsif c.row > @row + @height -1
|
143
|
+
$log.warn "c.row in CONTAINER exceeds container. #{c.row} "
|
144
|
+
c.row -= @height - @row_offset
|
145
|
+
else
|
146
|
+
# this is where it should come
|
147
|
+
c.row += @row + @row_offset
|
148
|
+
@last_row = c.row + 1
|
149
|
+
end
|
150
|
+
if (c.col || 0) <= 0
|
151
|
+
c.col = @col + inset + @col_offset
|
152
|
+
elsif c.col > @col + @width -1
|
153
|
+
c.col -= @width
|
154
|
+
elsif c.col == @col
|
155
|
+
c.col += @col_offset + inset
|
156
|
+
else #f c.col < @col
|
157
|
+
c.col += @col+@col_offset
|
158
|
+
end
|
159
|
+
$log.debug "XXX: CORRECT #{c.name} r:#{c.row} c:#{c.col} "
|
160
|
+
end
|
161
|
+
@first_time = false
|
162
|
+
end
|
163
|
+
def check_component c
|
164
|
+
raise "row is less than container #{c.row} #{@row} " if c.row <= @row
|
165
|
+
raise "col is less than container #{c.col} #{@col} " if c.col <= @col
|
166
|
+
end
|
167
|
+
|
168
|
+
public
|
169
|
+
# repaint object
|
170
|
+
# called by Form, and sometimes parent component (if not form).
|
171
|
+
def repaint
|
172
|
+
my_win = @form ? @form.window : @target_window
|
173
|
+
@graphic = my_win unless @graphic
|
174
|
+
raise " #{@name} NO GRAPHIC set as yet CONTAINER paint " unless @graphic
|
175
|
+
@components.each { |e| correct_component e } if @first_time
|
176
|
+
#@components.each { |e| check_component e } # seeme one if printing out
|
177
|
+
|
178
|
+
#return unless @repaint_required
|
179
|
+
|
180
|
+
# if some major change has happened then repaint everything
|
181
|
+
if @repaint_required
|
182
|
+
$log.debug " VIM repaint graphic #{@graphic} "
|
183
|
+
print_borders unless @suppress_borders # do this once only, unless everything changes
|
184
|
+
@components.each { |e| e.repaint_all(true); e.repaint }
|
185
|
+
else
|
186
|
+
@components.each { |e| e.repaint }
|
187
|
+
end # if repaint_required
|
188
|
+
|
189
|
+
@repaint_required = false
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
def print_borders
|
194
|
+
width = @width
|
195
|
+
height = @height-1 # 2010-01-04 15:30 BUFFERED HEIGHT
|
196
|
+
window = @graphic # 2010-01-04 12:37 BUFFERED
|
197
|
+
startcol = @col
|
198
|
+
startrow = @row
|
199
|
+
@color_pair = get_color($datacolor)
|
200
|
+
#$log.debug "rlistb #{name}: window.print_border #{startrow}, #{startcol} , h:#{height}, w:#{width} , @color_pair, @attr "
|
201
|
+
window.print_border startrow, startcol, height, width, @color_pair, @attr
|
202
|
+
print_title
|
203
|
+
end
|
204
|
+
def print_title
|
205
|
+
$log.debug "CONTAINER PRINTING TITLE at #{row} #{col} "
|
206
|
+
@graphic.printstring( @row, @col+(@width-@title.length)/2, @title, @color_pair, @title_attrib) unless @title.nil?
|
207
|
+
end
|
208
|
+
|
209
|
+
public
|
210
|
+
# called by parent or form, otherwise its private
|
211
|
+
def handle_key ch
|
212
|
+
$log.debug " CONTAINER handle_key #{ch} "
|
213
|
+
return if @components.empty?
|
214
|
+
_multiplier = ($multiplier == 0 ? 1 : $multiplier )
|
215
|
+
|
216
|
+
# should this go here 2011-10-19
|
217
|
+
unless @_entered
|
218
|
+
$log.warn "XXX WARN: calling ON_ENTER since in this situation it was not called"
|
219
|
+
on_enter
|
220
|
+
end
|
221
|
+
if ch == KEY_TAB
|
222
|
+
$log.debug "CONTAINER GOTO NEXT TAB"
|
223
|
+
return goto_next_component
|
224
|
+
elsif ch == KEY_BTAB
|
225
|
+
return goto_prev_component
|
226
|
+
end
|
227
|
+
comp = @current_component
|
228
|
+
$log.debug " CONTAINER handle_key #{ch}: #{comp}"
|
229
|
+
if comp
|
230
|
+
ret = comp.handle_key(ch)
|
231
|
+
$log.debug " CONTAINER handle_key#{ch}: #{comp} returned #{ret} "
|
232
|
+
if ret != :UNHANDLED
|
233
|
+
comp.repaint # NOTE: if we don;t do this, then it won't get repainted. I will have to repaint ALL
|
234
|
+
# in repaint of this.
|
235
|
+
return ret
|
236
|
+
end
|
237
|
+
$log.debug "XXX CONTAINER key unhandled by comp #{comp.name} "
|
238
|
+
else
|
239
|
+
$log.warn "XXX CONTAINER key unhandled NULL comp"
|
240
|
+
end
|
241
|
+
case ch
|
242
|
+
when ?\C-c.getbyte(0)
|
243
|
+
$multiplier = 0
|
244
|
+
return 0
|
245
|
+
when ?0.getbyte(0)..?9.getbyte(0)
|
246
|
+
$log.debug " VIM coming here to set multiplier #{$multiplier} "
|
247
|
+
$multiplier *= 10 ; $multiplier += (ch-48)
|
248
|
+
return 0
|
249
|
+
end
|
250
|
+
ret = process_key ch, self
|
251
|
+
# allow user to map left and right if he wants
|
252
|
+
if ret == :UNHANDLED
|
253
|
+
case ch
|
254
|
+
when KEY_UP
|
255
|
+
# form will pick this up and do needful
|
256
|
+
return goto_prev_component #unless on_first_component?
|
257
|
+
when KEY_LEFT
|
258
|
+
# if i don't check for first component, key will go back to form,
|
259
|
+
# but not be processes. so focussed remain here, but be false.
|
260
|
+
# In case of returnign an unhandled TAB, on_leave will happen and cursor will move to
|
261
|
+
# previous component outside of this.
|
262
|
+
return goto_prev_component unless on_first_component?
|
263
|
+
when KEY_RIGHT
|
264
|
+
return goto_next_component #unless on_last_component?
|
265
|
+
when KEY_DOWN
|
266
|
+
return goto_next_component #unless on_last_component?
|
267
|
+
else
|
268
|
+
@_entered = false
|
269
|
+
return :UNHANDLED
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
$multiplier = 0
|
274
|
+
return 0
|
275
|
+
end
|
276
|
+
# Actually we should only go to current component if it accepted
|
277
|
+
# a key stroke. if user tabbed thru it, then no point going back to
|
278
|
+
# it. Go to first or last depending on TAB or BACKTAB otherwise.
|
279
|
+
# NOTE: if user comes in using DOWN or UP, last traversed component will get the focus
|
280
|
+
#
|
281
|
+
def on_enter
|
282
|
+
# if BTAB, the last comp XXX they must be focusable FIXME
|
283
|
+
if $current_key == KEY_BTAB || $current_key == KEY_UP
|
284
|
+
@current_component = @focusables.last
|
285
|
+
else
|
286
|
+
@current_component = @focusables.first
|
287
|
+
end
|
288
|
+
return unless @current_component
|
289
|
+
$log.debug " CONTAINER came to ON_ENTER #{@current_component} "
|
290
|
+
set_form_row
|
291
|
+
@_entered = true
|
292
|
+
end
|
293
|
+
# we cannot be sure that this will be called especially if this is embedded
|
294
|
+
# inside some other component
|
295
|
+
def on_leave
|
296
|
+
@_entered = false
|
297
|
+
super
|
298
|
+
end
|
299
|
+
def goto_next_component
|
300
|
+
if @current_component != nil
|
301
|
+
leave_current_component
|
302
|
+
if on_last_component?
|
303
|
+
#@_entered = false
|
304
|
+
return :UNHANDLED
|
305
|
+
end
|
306
|
+
@current_index = @focusables.index(@current_component)
|
307
|
+
index = @current_index + 1
|
308
|
+
f = @focusables[index]
|
309
|
+
if f
|
310
|
+
@current_index = index
|
311
|
+
@current_component = f
|
312
|
+
return set_form_row
|
313
|
+
end
|
314
|
+
end
|
315
|
+
@_entered = false
|
316
|
+
return :UNHANDLED
|
317
|
+
end
|
318
|
+
def goto_prev_component
|
319
|
+
if @current_component != nil
|
320
|
+
leave_current_component
|
321
|
+
if on_first_component?
|
322
|
+
@_entered = false
|
323
|
+
return :UNHANDLED
|
324
|
+
end
|
325
|
+
@current_index = @focusables.index(@current_component)
|
326
|
+
index = @current_index -= 1
|
327
|
+
f = @focusables[index]
|
328
|
+
if f
|
329
|
+
@current_index = index
|
330
|
+
@current_component = f
|
331
|
+
return set_form_row
|
332
|
+
end
|
333
|
+
end
|
334
|
+
return :UNHANDLED
|
335
|
+
end
|
336
|
+
# private
|
337
|
+
# XXX why are we calling 3 methods in a row, why not OE manages these 3
|
338
|
+
# There's double calling going on.
|
339
|
+
def set_form_row
|
340
|
+
return :UNHANDLED if @current_component.nil?
|
341
|
+
cc = @current_component
|
342
|
+
$log.debug "CONT #{@name} set_form_row calling sfr for #{cc.name}, r #{cc.row} c: #{cc.col} "
|
343
|
+
$log.debug " CONTAINER on enter sfr #{@current_component.name} #{@current_component} "
|
344
|
+
|
345
|
+
# bug caught here. we were printing a field before it had been set, so it printed out
|
346
|
+
@components.each { |e| correct_component e } if @first_time
|
347
|
+
@current_component.on_enter
|
348
|
+
@current_component.set_form_row # why was this missing in vimsplit. is it
|
349
|
+
$log.debug "CONT2 #{@name} set_form_row calling sfr for #{cc.name}, r #{cc.row} c: #{cc.col} "
|
350
|
+
# that on_enter does a set_form_row
|
351
|
+
@current_component.set_form_col # XXX
|
352
|
+
@current_component.repaint # OMG this could happen before we've set row and col
|
353
|
+
# XXX compo should do set_form_row and col if it has that
|
354
|
+
end
|
355
|
+
#
|
356
|
+
def set_form_col
|
357
|
+
return if @current_component.nil?
|
358
|
+
$log.debug " #{@name} CONTAINER EMPTY set_form_col calling sfc for #{@current_component.name} "
|
359
|
+
# already called from above.
|
360
|
+
#@current_component.set_form_col
|
361
|
+
end
|
362
|
+
# leave the component we are on.
|
363
|
+
# This should be followed by all containers, so that the on_leave action
|
364
|
+
# of earlier comp can be displayed, such as dimming components selections
|
365
|
+
def leave_current_component
|
366
|
+
@current_component.on_leave
|
367
|
+
# NOTE this is required, since repaint will just not happen otherwise
|
368
|
+
# Some components are erroneously repainting all, after setting this to true so it is
|
369
|
+
# working there.
|
370
|
+
@current_component.repaint_required true
|
371
|
+
$log.debug " after on_leave RCONT XXX #{@current_component.focussed} #{@current_component.name}"
|
372
|
+
@current_component.repaint
|
373
|
+
end
|
374
|
+
|
375
|
+
# is focus on first component FIXME check for focusable
|
376
|
+
def on_first_component?
|
377
|
+
@current_component == @focusables.first
|
378
|
+
end
|
379
|
+
# is focus on last component FIXME check for focusable
|
380
|
+
def on_last_component?
|
381
|
+
@current_component == @focusables.last
|
382
|
+
end
|
383
|
+
# set focus on given component
|
384
|
+
# Sometimes you have the handle to component, and you want to move focus to it
|
385
|
+
def goto_component comp
|
386
|
+
return if comp == @current_component
|
387
|
+
leave_current_component
|
388
|
+
@current_component = comp
|
389
|
+
set_form_row
|
390
|
+
end
|
391
|
+
|
392
|
+
# ADD HERE ABOVe
|
393
|
+
end # class
|
394
|
+
end # module
|
395
|
+
|
396
|
+
if __FILE__ == $PROGRAM_NAME
|
397
|
+
require 'rbcurse/core/util/app'
|
398
|
+
App.new do
|
399
|
+
f1 = field "name", :maxlen => 20, :display_length => 20, :bgcolor => :white,
|
400
|
+
:color => :black, :text => "abc", :label => " Name: ", :label_color_pair => @datacolor
|
401
|
+
f2 = field "email", :display_length => 20, :bgcolor => :white,
|
402
|
+
:color => :blue, :text => "me@google.com", :label => "Email: ", :label_color_pair => @datacolor
|
403
|
+
f3 = radio :group => :grp, :text => "red", :value => "RED", :color => :red
|
404
|
+
f4 = radio :group => :grp, :text => "blue", :value => "BLUE", :color => :blue
|
405
|
+
f5 = radio :group => :grp, :text => "green", :value => "GREEN", :color => :green
|
406
|
+
stack :margin_top => 2, :margin => 2 do
|
407
|
+
r = container :row => 1, :col => 2, :width => 80, :height => 20, :title => "A container"
|
408
|
+
r.add(f1)
|
409
|
+
r.add(f2)
|
410
|
+
r.add(f3,f4,f5)
|
411
|
+
sl = status_line
|
412
|
+
end # stack
|
413
|
+
|
414
|
+
end # app
|
415
|
+
end # if
|