rbcurse 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|