rbcurse 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +1570 -0
- data/History.txt +6 -0
- data/Manifest.txt +54 -0
- data/README.txt +304 -0
- data/Rakefile +28 -0
- data/examples/qdfilechooser.rb +68 -0
- data/examples/rfe.rb +853 -0
- data/examples/rfe_renderer.rb +69 -0
- data/examples/test1.rb +242 -0
- data/examples/test2.rb +498 -0
- data/examples/testcombo.rb +95 -0
- data/examples/testkeypress.rb +61 -0
- data/examples/testmenu.rb +105 -0
- data/examples/testtable.rb +266 -0
- data/examples/testtabp.rb +106 -0
- data/examples/testtodo.rb +532 -0
- data/examples/viewtodo.rb +512 -0
- data/lib/rbcurse/action.rb +31 -0
- data/lib/rbcurse/applicationheader.rb +57 -0
- data/lib/rbcurse/celleditor.rb +120 -0
- data/lib/rbcurse/checkboxcellrenderer.rb +69 -0
- data/lib/rbcurse/colormap.rb +133 -0
- data/lib/rbcurse/comboboxcellrenderer.rb +45 -0
- data/lib/rbcurse/defaultlistselectionmodel.rb +49 -0
- data/lib/rbcurse/keylabelprinter.rb +143 -0
- data/lib/rbcurse/listcellrenderer.rb +99 -0
- data/lib/rbcurse/listkeys.rb +33 -0
- data/lib/rbcurse/listscrollable.rb +216 -0
- data/lib/rbcurse/listselectable.rb +67 -0
- data/lib/rbcurse/mapper.rb +108 -0
- data/lib/rbcurse/orderedhash.rb +77 -0
- data/lib/rbcurse/rcombo.rb +243 -0
- data/lib/rbcurse/rdialogs.rb +183 -0
- data/lib/rbcurse/rform.rb +845 -0
- data/lib/rbcurse/rinputdataevent.rb +36 -0
- data/lib/rbcurse/rlistbox.rb +804 -0
- data/lib/rbcurse/rmenu.rb +666 -0
- data/lib/rbcurse/rmessagebox.rb +325 -0
- data/lib/rbcurse/rpopupmenu.rb +754 -0
- data/lib/rbcurse/rtabbedpane.rb +259 -0
- data/lib/rbcurse/rtable.rb +1296 -0
- data/lib/rbcurse/rtextarea.rb +673 -0
- data/lib/rbcurse/rtextview.rb +335 -0
- data/lib/rbcurse/rwidget.rb +1731 -0
- data/lib/rbcurse/scrollable.rb +301 -0
- data/lib/rbcurse/selectable.rb +94 -0
- data/lib/rbcurse/table/tablecellrenderer.rb +85 -0
- data/lib/rbcurse/table/tabledatecellrenderer.rb +102 -0
- data/lib/rbcurse.rb +7 -0
- data/lib/ver/keyboard.rb +150 -0
- data/lib/ver/keyboard2.rb +170 -0
- data/lib/ver/ncurses.rb +102 -0
- data/lib/ver/window.rb +369 -0
- data/test/test_rbcurse.rb +0 -0
- metadata +118 -0
@@ -0,0 +1,216 @@
|
|
1
|
+
# Provides the ability to scroll content, typically an array
|
2
|
+
# widget that includes may override on_enter_row and on_leave_row
|
3
|
+
# Caller should have
|
4
|
+
# row_count()
|
5
|
+
# scrollatrow() typically @height - 2 (unless a header row, then -3)
|
6
|
+
# @current_index (row of current index, starting with 0 usually)
|
7
|
+
# @toprow : set to 0 for starters, top row to be displayed
|
8
|
+
# @pcol (used for horiz scrolling, starts at 0)
|
9
|
+
#
|
10
|
+
module ListScrollable
|
11
|
+
def previous_row
|
12
|
+
@oldrow = @current_index
|
13
|
+
@current_index -= 1 if @current_index > 0
|
14
|
+
bounds_check
|
15
|
+
end
|
16
|
+
alias :up :previous_row
|
17
|
+
def next_row
|
18
|
+
@oldrow = @current_index
|
19
|
+
rc = row_count
|
20
|
+
@current_index += 1 if @current_index < rc
|
21
|
+
bounds_check
|
22
|
+
end
|
23
|
+
alias :down :next_row
|
24
|
+
def goto_bottom
|
25
|
+
@oldrow = @current_index
|
26
|
+
rc = row_count
|
27
|
+
@current_index = rc -1
|
28
|
+
bounds_check
|
29
|
+
end
|
30
|
+
alias :goto_end :goto_bottom
|
31
|
+
def goto_top
|
32
|
+
@oldrow = @current_index
|
33
|
+
@current_index = 0
|
34
|
+
bounds_check
|
35
|
+
end
|
36
|
+
alias :goto_start :goto_top
|
37
|
+
def scroll_backward
|
38
|
+
@oldrow = @current_index
|
39
|
+
h = scrollatrow()
|
40
|
+
@current_index -= h
|
41
|
+
bounds_check
|
42
|
+
end
|
43
|
+
def scroll_forward
|
44
|
+
@oldrow = @current_index
|
45
|
+
h = scrollatrow()
|
46
|
+
rc = row_count
|
47
|
+
# more rows than box
|
48
|
+
if h < rc
|
49
|
+
@toprow += h+1 #if @current_index+h < rc
|
50
|
+
@current_index = @toprow
|
51
|
+
else
|
52
|
+
# fewer rows than box
|
53
|
+
@current_index = rc -1
|
54
|
+
end
|
55
|
+
#@current_index += h+1 #if @current_index+h < rc
|
56
|
+
bounds_check
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# please set oldrow before calling this. Store current_index as oldrow before changing. NOTE
|
61
|
+
def bounds_check
|
62
|
+
h = scrollatrow()
|
63
|
+
rc = row_count
|
64
|
+
$log.debug " PRE CURR:#{@current_index}, TR: #{@toprow} RC: #{rc} H:#{h}"
|
65
|
+
@current_index = 0 if @current_index < 0 # not lt 0
|
66
|
+
@current_index = rc-1 if @current_index >= rc # not gt rowcount
|
67
|
+
@toprow = rc-h-1 if rc > h and @toprow > rc - h - 1 # toprow shows full page if possible
|
68
|
+
# curr has gone below table, move toprow forward
|
69
|
+
if @current_index - @toprow > h
|
70
|
+
@toprow = @current_index - h
|
71
|
+
elsif @current_index < @toprow
|
72
|
+
# curr has gone above table, move toprow up
|
73
|
+
@toprow = @current_index
|
74
|
+
end
|
75
|
+
#$log.debug " POST CURR:#{@current_index}, TR: #{@toprow} RC: #{rc} H:#{h}"
|
76
|
+
if @oldrow != @current_index
|
77
|
+
$log.debug "going to call on leave and on enter"
|
78
|
+
on_leave_row @oldrow if respond_to? :on_leave_row # to be defined by widget that has included this
|
79
|
+
on_enter_row @current_index if respond_to? :on_enter_row # to be defined by widget that has included this
|
80
|
+
end
|
81
|
+
set_form_row
|
82
|
+
@repaint_required = true
|
83
|
+
end
|
84
|
+
# the cursor should be appropriately positioned
|
85
|
+
def set_form_row
|
86
|
+
r,c = rowcol
|
87
|
+
@form.row = r + (@current_index-@toprow)
|
88
|
+
end
|
89
|
+
def right
|
90
|
+
@hscrollcols ||= @cols/2
|
91
|
+
@pcol += @hscrollcols if @pcol + @hscrollcols < @padcols
|
92
|
+
# window_erase @win XXX
|
93
|
+
end
|
94
|
+
def left
|
95
|
+
@hscrollcols ||= @cols/2
|
96
|
+
@pcol -= @hscrollcols if @pcol > 0
|
97
|
+
@pcol = 0 if @pcol < 0
|
98
|
+
end
|
99
|
+
# not that saving content_rows is buggy since we add rows.
|
100
|
+
##
|
101
|
+
# caution, this now uses winrow not prow
|
102
|
+
## for user to know which row is being focussed on
|
103
|
+
def focussed_index
|
104
|
+
@current_index # 2009-01-07 14:35
|
105
|
+
end
|
106
|
+
# only to be used in single selection cases as focussed item FIXME.
|
107
|
+
# best not to use, as can be implementation dep, use current_index
|
108
|
+
def selected_item
|
109
|
+
get_content()[focussed_index()]
|
110
|
+
end
|
111
|
+
#alias :current_index :focussed_index
|
112
|
+
alias :selected_index :focussed_index
|
113
|
+
def OLDscrollable_handle_key ch
|
114
|
+
begin
|
115
|
+
###pre_key # 2009-01-07 13:23
|
116
|
+
case ch
|
117
|
+
when ?\C-n
|
118
|
+
scroll_forward
|
119
|
+
when 32
|
120
|
+
scroll_forward
|
121
|
+
when ?\C-p
|
122
|
+
scroll_backward
|
123
|
+
when ?0
|
124
|
+
#goto_start
|
125
|
+
goto_top
|
126
|
+
when ?9
|
127
|
+
#goto_end
|
128
|
+
goto_bottom
|
129
|
+
when KEY_UP
|
130
|
+
#select_prev_row
|
131
|
+
#up
|
132
|
+
$log.debug " GOT KEY UP NEW SCROLL"
|
133
|
+
previous_row
|
134
|
+
when KEY_LEFT
|
135
|
+
when KEY_RIGHT
|
136
|
+
when KEY_DOWN
|
137
|
+
#down
|
138
|
+
$log.debug " GOT KEY DOWN NEW SCROLL"
|
139
|
+
next_row
|
140
|
+
when KEY_ENTER, 10, 13
|
141
|
+
if respond_to? :fire
|
142
|
+
fire
|
143
|
+
end
|
144
|
+
when ?A..?Z, ?a..?z
|
145
|
+
ret = set_selection_for_char ch.chr
|
146
|
+
else
|
147
|
+
return :UNHANDLED #if ret == -1
|
148
|
+
end
|
149
|
+
ensure
|
150
|
+
#post_key
|
151
|
+
end
|
152
|
+
end # handle_k listb
|
153
|
+
## 2008-12-18 18:03
|
154
|
+
# finds the next match for the char pressed
|
155
|
+
# returning the index
|
156
|
+
def next_match char
|
157
|
+
data = get_content
|
158
|
+
row = focussed_index + 1
|
159
|
+
row.upto(data.length-1) do |ix|
|
160
|
+
val = data[ix].chomp
|
161
|
+
#if val[0,1] == char #and val != currval
|
162
|
+
if val[0,1].casecmp(char) == 0 #AND VAL != CURRval
|
163
|
+
return ix
|
164
|
+
end
|
165
|
+
end
|
166
|
+
row = focussed_index - 1
|
167
|
+
0.upto(row) do |ix|
|
168
|
+
val = data[ix].chomp
|
169
|
+
#if val[0,1] == char #and val != currval
|
170
|
+
if val[0,1].casecmp(char) == 0 #and val != currval
|
171
|
+
return ix
|
172
|
+
end
|
173
|
+
end
|
174
|
+
return -1
|
175
|
+
end
|
176
|
+
## 2008-12-18 18:03
|
177
|
+
# sets the selection to the next row starting with char
|
178
|
+
def set_selection_for_char char
|
179
|
+
@oldrow = @current_index
|
180
|
+
ix = next_match char
|
181
|
+
@current_index = ix if ix != -1
|
182
|
+
bounds_check
|
183
|
+
return ix
|
184
|
+
end
|
185
|
+
|
186
|
+
##
|
187
|
+
# ensures that the given row is focussed
|
188
|
+
# new version of older one that was not perfect.
|
189
|
+
# 2009-01-17 13:25
|
190
|
+
def set_focus_on arow
|
191
|
+
@oldrow = @current_index
|
192
|
+
@current_index = arow
|
193
|
+
bounds_check if @oldrow != @current_index
|
194
|
+
end
|
195
|
+
##
|
196
|
+
# 2008-12-18 18:05
|
197
|
+
# set focus on given index
|
198
|
+
def OLDset_focus_on arow
|
199
|
+
return if arow > row_count()-1 or arow < 0
|
200
|
+
@oldrow = @current_index
|
201
|
+
total = row_count()
|
202
|
+
@current_index = arow
|
203
|
+
sar = scrollatrow + 1
|
204
|
+
@toprow = (@current_index / sar) * sar
|
205
|
+
|
206
|
+
$log.debug "1 set_focus #{total}, sar #{sar}, toprow #{@toprow}, current_index #{@current_index}"
|
207
|
+
if total - @toprow < sar
|
208
|
+
@toprow = (total - sar)
|
209
|
+
end
|
210
|
+
$log.debug "2 set_focus #{total}, sar #{sar}, toprow #{@toprow}, current_index #{@current_index}"
|
211
|
+
set_form_row # 2009-01-17 12:44
|
212
|
+
@repaint_required = true
|
213
|
+
#bounds_check
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# this is a companion file to defaultlistselectionmodel
|
2
|
+
# if you use that, include this to get all the methods to use it
|
3
|
+
module RubyCurses
|
4
|
+
module ListSelectable
|
5
|
+
|
6
|
+
def list_selection_model lsm
|
7
|
+
@list_selection_model = lsm
|
8
|
+
#@list_selection_model.selection_mode = @selection_mode || :MULTIPLE
|
9
|
+
end
|
10
|
+
def create_default_list_selection_model
|
11
|
+
list_selection_model DefaultListSelectionModel.new
|
12
|
+
end
|
13
|
+
def is_row_selected row
|
14
|
+
@list_selection_model.is_selected_index row
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_row_selection_interval ix0, ix1
|
18
|
+
# if row_selection_allowed
|
19
|
+
@list_selection_model.add_selection_interval ix0, ix1
|
20
|
+
end
|
21
|
+
def remove_row_selection_interval ix0, ix1
|
22
|
+
@list_selection_model.remove_selection_interval ix0, ix1
|
23
|
+
end
|
24
|
+
def toggle_row_selection row=@current_index
|
25
|
+
if is_row_selected row
|
26
|
+
$log.debug " deleting row #{row}"
|
27
|
+
remove_row_selection_interval(row, row)
|
28
|
+
else
|
29
|
+
$log.debug " adding row #{row}"
|
30
|
+
add_row_selection_interval(row, row)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def clear_selection
|
35
|
+
@list_selection_model.clear_selection
|
36
|
+
end
|
37
|
+
def selected_item
|
38
|
+
# @list[@current_index]
|
39
|
+
end
|
40
|
+
def selected_rows
|
41
|
+
@list_selection_model.get_selected_rows
|
42
|
+
end
|
43
|
+
def selected_row_count
|
44
|
+
selected_rows.size
|
45
|
+
end
|
46
|
+
def selected_row
|
47
|
+
@list_selection_model.get_min_selection_index
|
48
|
+
end
|
49
|
+
def do_next_selection
|
50
|
+
return if selected_rows().length == 0
|
51
|
+
row = selected_rows().sort.find { |i| i > @current_index }
|
52
|
+
row ||= @current_index
|
53
|
+
@current_index = row
|
54
|
+
@repaint_required = true # fire list_select XXX
|
55
|
+
end
|
56
|
+
def do_prev_selection
|
57
|
+
return if selected_rows().length == 0
|
58
|
+
row = selected_rows().sort{|a,b| b <=> a}.find { |i| i < @current_index }
|
59
|
+
row ||= @current_index
|
60
|
+
@current_index = row
|
61
|
+
@repaint_required = true # fire list_select XXX
|
62
|
+
end
|
63
|
+
alias :selected_index :selected_row
|
64
|
+
attr_accessor :row_selection_allowed
|
65
|
+
attr_accessor :column_selection_allowed
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'rbcurse/orderedhash'
|
2
|
+
class Mapper
|
3
|
+
attr_reader :keymap
|
4
|
+
attr_reader :view
|
5
|
+
attr_accessor :mode
|
6
|
+
attr_reader :keys
|
7
|
+
def initialize handler
|
8
|
+
#@handler = handler
|
9
|
+
@view = handler # caller program
|
10
|
+
@keys = {}
|
11
|
+
@mode = nil # used when defining
|
12
|
+
@pendingkeys = nil
|
13
|
+
@prevkey = nil # in case of a key sequence such as C-x C-c, will have C-x
|
14
|
+
@arg = nil # regex matched this key.
|
15
|
+
end
|
16
|
+
def let mode, &block
|
17
|
+
h = OrderedHash.new
|
18
|
+
@keys[mode] = h
|
19
|
+
@mode = mode
|
20
|
+
instance_eval(&block)
|
21
|
+
$log.debug("KEYS: #{@keys[mode].inspect}")
|
22
|
+
end
|
23
|
+
def map(*args, &block)
|
24
|
+
if block_given?
|
25
|
+
# We check for cases like C-x C-c etc. Only 2 levels.
|
26
|
+
#args = arg.split(/ +/)
|
27
|
+
if args.length == 2
|
28
|
+
@keys[@mode][args[0]] ||= OrderedHash.new
|
29
|
+
@keys[@mode][args[0]][args[1]]=block
|
30
|
+
else
|
31
|
+
# single key or control key
|
32
|
+
@keys[@mode][args[0]]=block
|
33
|
+
end
|
34
|
+
else
|
35
|
+
#no block, last arg shold be a symbol
|
36
|
+
symb = args.pop
|
37
|
+
raise "If block not passed, last arg should be a method symbol" if !symb.is_a? Symbol
|
38
|
+
if args.length == 2
|
39
|
+
@keys[@mode][args[0]] ||= OrderedHash.new
|
40
|
+
@keys[@mode][args[0]][args[1]]=symb
|
41
|
+
else
|
42
|
+
# single key or control key
|
43
|
+
@keys[@mode][args[0]]=symb
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
## manages key pressing
|
49
|
+
# takes care of multiple key combos too
|
50
|
+
def press key
|
51
|
+
$log.debug("press Got: #{key}")
|
52
|
+
# for a double key combination such as C-x C-c this has the set of pending keys to check against
|
53
|
+
if @pendingkeys != nil
|
54
|
+
blk = @pendingkeys[key]
|
55
|
+
else
|
56
|
+
# this is the regular single key mode
|
57
|
+
#blk = @keys[@view.mode][key]
|
58
|
+
blk = match(key)
|
59
|
+
end
|
60
|
+
# this means this key expects more keys to follow such as C-x could
|
61
|
+
if blk.is_a? OrderedHash
|
62
|
+
@pendingkeys = blk
|
63
|
+
@prevkey = key
|
64
|
+
return
|
65
|
+
end
|
66
|
+
if blk.nil? # this should go up XXX
|
67
|
+
if !@pendingkeys.nil?
|
68
|
+
# this error message to be modified if using numeric keys -- need to convert to char
|
69
|
+
view.info("%p not valid in %p. Try: #{@pendingkeys.keys.join(', ')}" % [key, @prevkey]) # XXX
|
70
|
+
else
|
71
|
+
view.info("%p not valid in %p. " % [key, @view.mode])
|
72
|
+
end
|
73
|
+
return
|
74
|
+
end
|
75
|
+
# call the block or symbol - our user defined key mappings use symbols
|
76
|
+
if blk.is_a? Symbol
|
77
|
+
@view.send(blk)
|
78
|
+
else
|
79
|
+
blk.call
|
80
|
+
end
|
81
|
+
@prevkey = nil
|
82
|
+
@pendingkeys = nil
|
83
|
+
end
|
84
|
+
def match key
|
85
|
+
# $log.debug "MATCH #key "
|
86
|
+
#blk = @keys[@view.mode][key]
|
87
|
+
@keys[@view.mode].each_pair do |k,v|
|
88
|
+
# $log.debug "LOOP #{k.class}, #{k}, #{v} "
|
89
|
+
case k.class.to_s
|
90
|
+
when "String"
|
91
|
+
return v if k == key
|
92
|
+
when "Fixnum" # for keyboard2
|
93
|
+
$log.debug "FIXNUM LOOP #{k.class}, #{k}, #{v} "
|
94
|
+
return v if k == key
|
95
|
+
when "Regexp"
|
96
|
+
# $log.debug "REGEX #key , #k, #{k.match(key)}"
|
97
|
+
key = key.chr if key.is_a? Fixnum
|
98
|
+
if !k.match(key).nil?
|
99
|
+
@arg = key
|
100
|
+
return v
|
101
|
+
end
|
102
|
+
else
|
103
|
+
$log.error "MATCH: Unhandled class #{k.class} "
|
104
|
+
end
|
105
|
+
end
|
106
|
+
return nil
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
## Insert order preserving hash
|
2
|
+
# Thanks to Bill Kelly, posted on http://www.ruby-forum.com/topic/166075
|
3
|
+
#
|
4
|
+
class OrderedHash
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(*args, &block)
|
8
|
+
@h = Hash.new(*args, &block)
|
9
|
+
@ordered_keys = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def []=(key, val)
|
13
|
+
@ordered_keys << key unless @h.has_key? key
|
14
|
+
@h[key] = val
|
15
|
+
end
|
16
|
+
|
17
|
+
def each
|
18
|
+
@ordered_keys.each {|k| yield(k, @h[k])}
|
19
|
+
end
|
20
|
+
alias :each_pair :each
|
21
|
+
|
22
|
+
def each_value
|
23
|
+
@ordered_keys.each {|k| yield(@h[k])}
|
24
|
+
end
|
25
|
+
|
26
|
+
def each_key
|
27
|
+
@ordered_keys.each {|k| yield k}
|
28
|
+
end
|
29
|
+
|
30
|
+
def keys
|
31
|
+
@ordered_keys
|
32
|
+
end
|
33
|
+
|
34
|
+
def values
|
35
|
+
@ordered_keys.map {|k| @h[k]}
|
36
|
+
end
|
37
|
+
|
38
|
+
def clear
|
39
|
+
@ordered_keys.clear
|
40
|
+
@h.clear
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete(k, &block)
|
44
|
+
@ordered_keys.delete k
|
45
|
+
@h.delete(k, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def reject!
|
49
|
+
del = []
|
50
|
+
each_pair {|k,v| del << k if yield k,v}
|
51
|
+
del.each {|k| delete k}
|
52
|
+
del.empty? ? nil : self
|
53
|
+
end
|
54
|
+
|
55
|
+
def delete_if(&block)
|
56
|
+
reject!(&block)
|
57
|
+
self
|
58
|
+
end
|
59
|
+
## added since the normal hash will give it in unordered. so debugging sucks
|
60
|
+
def inspect
|
61
|
+
out = []
|
62
|
+
each do | k,v |
|
63
|
+
out << " #{k} => #{v} "
|
64
|
+
end
|
65
|
+
res = %Q[ { #{out.join(",\n ")} } ]
|
66
|
+
end
|
67
|
+
|
68
|
+
%w(merge!).each do |name|
|
69
|
+
define_method(name) do |*args|
|
70
|
+
raise NotImplementedError, "#{name} not implemented"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def method_missing(*args)
|
75
|
+
@h.send(*args)
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
=begin
|
2
|
+
* Name: combo box
|
3
|
+
* Description:
|
4
|
+
* Author: rkumar
|
5
|
+
|
6
|
+
--------
|
7
|
+
* Date: 2008-12-16 22:03
|
8
|
+
* License:
|
9
|
+
Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
10
|
+
|
11
|
+
=end
|
12
|
+
require 'rubygems'
|
13
|
+
require 'ncurses'
|
14
|
+
require 'logger'
|
15
|
+
require 'rbcurse'
|
16
|
+
require 'rbcurse/rlistbox'
|
17
|
+
|
18
|
+
include Ncurses
|
19
|
+
include RubyCurses
|
20
|
+
module RubyCurses
|
21
|
+
META_KEY = 128
|
22
|
+
extend self
|
23
|
+
|
24
|
+
# TODO :
|
25
|
+
# i no longer use values, i now use "list" or better "list_data_model"
|
26
|
+
# try to make it so values gets converted to list.
|
27
|
+
class ComboBox < Field
|
28
|
+
include RubyCurses::EventHandler
|
29
|
+
dsl_accessor :list_config
|
30
|
+
dsl_accessor :insert_policy # NO_INSERT, INSERT_AT_TOP, INSERT_AT_BOTTOM, INSERT_AT_CURRENT
|
31
|
+
# INSERT_AFTER_CURRENT, INSERT_BEFORE_CURRENT,INSERT_ALPHABETICALLY
|
32
|
+
|
33
|
+
attr_accessor :current_index
|
34
|
+
# the symbol you want to use for combos
|
35
|
+
attr_accessor :COMBO_SYMBOL
|
36
|
+
attr_accessor :show_symbol # show that funny symbol after a combo to signify its a combo
|
37
|
+
|
38
|
+
def initialize form, config={}, &block
|
39
|
+
super
|
40
|
+
@current_index ||= 0
|
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
|
+
end
|
45
|
+
def init_vars
|
46
|
+
super
|
47
|
+
@show_symbol ||= true
|
48
|
+
@COMBO_SYMBOL ||= Ncurses::ACS_GEQUAL
|
49
|
+
bind_key(KEY_UP) { previous_row }
|
50
|
+
bind_key(KEY_DOWN) { next_row }
|
51
|
+
end
|
52
|
+
def selected_item
|
53
|
+
@list[@current_index]
|
54
|
+
end
|
55
|
+
def selected_index
|
56
|
+
@current_index
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# convert given list to datamodel
|
61
|
+
def list alist=nil
|
62
|
+
return @list if alist.nil?
|
63
|
+
@list = RubyCurses::ListDataModel.new(alist)
|
64
|
+
end
|
65
|
+
##
|
66
|
+
# set given datamodel
|
67
|
+
def list_data_model ldm
|
68
|
+
raise "Expecting list_data_model" unless ldm.is_a? RubyCurses::ListDataModel
|
69
|
+
@list = ldm
|
70
|
+
end
|
71
|
+
##
|
72
|
+
# combo edit box key handling
|
73
|
+
# removed UP and DOWN and bound it, so it can be unbound
|
74
|
+
def handle_key(ch)
|
75
|
+
@current_index ||= 0
|
76
|
+
# added 2009-01-18 22:44 no point moving horiz or passing up to Field if not edit
|
77
|
+
if !@editable
|
78
|
+
if ch == KEY_LEFT or ch == KEY_RIGHT
|
79
|
+
return :UNHANDLED
|
80
|
+
end
|
81
|
+
end
|
82
|
+
case ch
|
83
|
+
#when KEY_UP # show previous value
|
84
|
+
# previous_row
|
85
|
+
#when KEY_DOWN # show previous value
|
86
|
+
# next_row
|
87
|
+
when KEY_DOWN+ RubyCurses::META_KEY # alt down
|
88
|
+
popup # pop up the popup
|
89
|
+
else
|
90
|
+
super
|
91
|
+
end
|
92
|
+
end
|
93
|
+
def previous_row
|
94
|
+
@current_index -= 1 if @current_index > 0
|
95
|
+
set_buffer @list[@current_index].dup
|
96
|
+
set_modified(true) ## ??? not required
|
97
|
+
fire_handler :ENTER_ROW, self
|
98
|
+
@list.on_enter_row self
|
99
|
+
end
|
100
|
+
def next_row
|
101
|
+
@current_index += 1 if @current_index < @list.length()-1
|
102
|
+
set_buffer @list[@current_index].dup
|
103
|
+
set_modified(true) ## ??? not required
|
104
|
+
fire_handler :ENTER_ROW, self
|
105
|
+
@list.on_enter_row self
|
106
|
+
end
|
107
|
+
##
|
108
|
+
# calls a popup list
|
109
|
+
# TODO: should not be positioned so that it goes off edge
|
110
|
+
# user's customizations of list should be passed in
|
111
|
+
# The dup of listconfig is due to a tricky feature/bug.
|
112
|
+
# I try to keep the config hash and instance variables in synch. So
|
113
|
+
# this config hash is sent to popuplist which updates its row col and
|
114
|
+
# next time we pop up the popup row and col are zero.
|
115
|
+
#
|
116
|
+
#
|
117
|
+
# added dup in PRESS since editing edit field mods this
|
118
|
+
# on pressing ENTER, value set back and current_index updated
|
119
|
+
def popup
|
120
|
+
listconfig = (@list_config && @list_config.dup) || {}
|
121
|
+
dm = @list
|
122
|
+
# current item in edit box will be focussed when list pops up
|
123
|
+
#$log.debug "XXX POPUP: #{dm.selected_index} = #{@current_index}, value #{@buffer}"
|
124
|
+
# we are having some problms when using this in a list. it retains earlier value
|
125
|
+
_index = dm.index @buffer
|
126
|
+
dm.selected_index = _index # @current_index
|
127
|
+
poprow = @row+0 # one row below the edit box
|
128
|
+
popcol = @col
|
129
|
+
dlength = @display_length
|
130
|
+
f = self
|
131
|
+
@popup = RubyCurses::PopupList.new do
|
132
|
+
row poprow
|
133
|
+
col popcol
|
134
|
+
width dlength
|
135
|
+
list_data_model dm
|
136
|
+
list_selection_mode 'single'
|
137
|
+
relative_to f
|
138
|
+
list_config listconfig
|
139
|
+
bind(:PRESS) do |index|
|
140
|
+
f.set_buffer dm[index].dup
|
141
|
+
f.set_modified(true) if f.current_index != index
|
142
|
+
f.current_index = index
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Field putc advances cursor when it gives a char so we override this
|
148
|
+
def putc c
|
149
|
+
if c >= 0 and c <= 127
|
150
|
+
ret = putch c.chr
|
151
|
+
if ret == 0
|
152
|
+
addcol 1 if @editable
|
153
|
+
set_modified
|
154
|
+
end
|
155
|
+
end
|
156
|
+
return -1 # always ??? XXX
|
157
|
+
end
|
158
|
+
##
|
159
|
+
# field does not give char to non-editable fields so we override
|
160
|
+
def putch char
|
161
|
+
@current_index ||= 0
|
162
|
+
if @editable
|
163
|
+
super
|
164
|
+
return 0
|
165
|
+
else
|
166
|
+
match = next_match(char)
|
167
|
+
set_buffer match unless match.nil?
|
168
|
+
fire_handler :ENTER_ROW, self
|
169
|
+
end
|
170
|
+
@modified = true
|
171
|
+
fire_handler :CHANGE, self # 2008-12-09 14:51 ???
|
172
|
+
0
|
173
|
+
end
|
174
|
+
##
|
175
|
+
# the sets the next match in the edit field
|
176
|
+
def next_match char
|
177
|
+
start = @current_index
|
178
|
+
start.upto(@list.length-1) do |ix|
|
179
|
+
if @list[ix][0,1].casecmp(char) == 0
|
180
|
+
return @list[ix] unless @list[ix] == @buffer
|
181
|
+
end
|
182
|
+
@current_index += 1
|
183
|
+
end
|
184
|
+
## could not find, start from zero
|
185
|
+
@current_index = 0
|
186
|
+
start = [@list.length()-1, start].min
|
187
|
+
0.upto(start) do |ix|
|
188
|
+
if @list[ix][0,1].casecmp(char) == 0
|
189
|
+
return @list[ix] unless @list[ix] == @buffer
|
190
|
+
end
|
191
|
+
@current_index += 1
|
192
|
+
end
|
193
|
+
@current_index = [@list.length()-1, @current_index].min
|
194
|
+
return nil
|
195
|
+
end
|
196
|
+
##
|
197
|
+
# on leaving the listbox, update the combo/datamodel.
|
198
|
+
# we are using methods of the datamodel. Updating our list will have
|
199
|
+
# no effect on the list, and wont trigger events.
|
200
|
+
# Do not override.
|
201
|
+
def on_leave
|
202
|
+
if !@list.include? @buffer and !@buffer.strip.empty?
|
203
|
+
_insert_policy = @insert_policy || :INSERT_AT_BOTTOM
|
204
|
+
case _insert_policy
|
205
|
+
when :INSERT_AT_BOTTOM, :INSERT_AT_END
|
206
|
+
@list.append @buffer
|
207
|
+
when :INSERT_AT_TOP
|
208
|
+
@list.insert(0, @buffer)
|
209
|
+
when :INSERT_AFTER_CURRENT
|
210
|
+
@current_index += 1
|
211
|
+
@list.insert(@current_index, @buffer)
|
212
|
+
|
213
|
+
when :INSERT_BEFORE_CURRENT
|
214
|
+
#_index = @current_index-1 if @current_index>0
|
215
|
+
_index = @current_index
|
216
|
+
@list.insert(_index, @buffer)
|
217
|
+
when :INSERT_AT_CURRENT
|
218
|
+
@list[@current_index]=@buffer
|
219
|
+
when :NO_INSERT
|
220
|
+
; # take a break
|
221
|
+
end
|
222
|
+
end
|
223
|
+
fire_handler :LEAVE, self
|
224
|
+
end
|
225
|
+
|
226
|
+
def repaint
|
227
|
+
super
|
228
|
+
c = @col + @display_length
|
229
|
+
# @form.window.mvwvline( @row, c, ACS_VLINE, 1)
|
230
|
+
if @show_symbol # 2009-01-11 18:47
|
231
|
+
# i have changed c +1 to c, since we have no right to print beyond display_length
|
232
|
+
@form.window.mvwaddch @row, c, @COMBO_SYMBOL # Ncurses::ACS_GEQUAL
|
233
|
+
end
|
234
|
+
# @form.window.mvwvline( @row, c+2, ACS_VLINE, 1)
|
235
|
+
# @form.window.mvwaddch @row, c+2, Ncurses::ACS_S1
|
236
|
+
# @form.window.mvwaddch @row, c+3, Ncurses::ACS_S9
|
237
|
+
# @form.window.mvwaddch @row, c+4, Ncurses::ACS_LRCORNER
|
238
|
+
# @form.window.mvwhline( @row, c+5, ACS_HLINE, 2)
|
239
|
+
end
|
240
|
+
|
241
|
+
end # class ComboBox
|
242
|
+
|
243
|
+
end # module
|