rbcurse-extras 0.0.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.
Files changed (38) hide show
  1. data/README.md +75 -0
  2. data/VERSION +1 -0
  3. data/examples/data/list.txt +300 -0
  4. data/examples/data/lotr.txt +12 -0
  5. data/examples/data/table.txt +36 -0
  6. data/examples/data/tasks.txt +27 -0
  7. data/examples/data/unix1.txt +21 -0
  8. data/examples/inc/qdfilechooser.rb +70 -0
  9. data/examples/inc/rfe_renderer.rb +121 -0
  10. data/examples/newtabbedwindow.rb +100 -0
  11. data/examples/rfe.rb +1236 -0
  12. data/examples/test2.rb +670 -0
  13. data/examples/testeditlist.rb +78 -0
  14. data/examples/testtable.rb +270 -0
  15. data/examples/testvimsplit.rb +141 -0
  16. data/lib/rbcurse/extras/include/celleditor.rb +112 -0
  17. data/lib/rbcurse/extras/include/checkboxcellrenderer.rb +57 -0
  18. data/lib/rbcurse/extras/include/comboboxcellrenderer.rb +30 -0
  19. data/lib/rbcurse/extras/include/defaultlistselectionmodel.rb +79 -0
  20. data/lib/rbcurse/extras/include/listkeys.rb +37 -0
  21. data/lib/rbcurse/extras/include/listselectable.rb +144 -0
  22. data/lib/rbcurse/extras/include/tableextended.rb +40 -0
  23. data/lib/rbcurse/extras/widgets/horizlist.rb +203 -0
  24. data/lib/rbcurse/extras/widgets/menutree.rb +63 -0
  25. data/lib/rbcurse/extras/widgets/multilinelabel.rb +142 -0
  26. data/lib/rbcurse/extras/widgets/rcomboedit.rb +256 -0
  27. data/lib/rbcurse/extras/widgets/rlink.rb.moved +27 -0
  28. data/lib/rbcurse/extras/widgets/rlistbox.rb +1247 -0
  29. data/lib/rbcurse/extras/widgets/rmenulink.rb.moved +21 -0
  30. data/lib/rbcurse/extras/widgets/rmulticontainer.rb +304 -0
  31. data/lib/rbcurse/extras/widgets/rmultisplit.rb +722 -0
  32. data/lib/rbcurse/extras/widgets/rmultitextview.rb +306 -0
  33. data/lib/rbcurse/extras/widgets/rpopupmenu.rb +755 -0
  34. data/lib/rbcurse/extras/widgets/rtable.rb +1758 -0
  35. data/lib/rbcurse/extras/widgets/rvimsplit.rb +800 -0
  36. data/lib/rbcurse/extras/widgets/table/tablecellrenderer.rb +86 -0
  37. data/lib/rbcurse/extras/widgets/table/tabledatecellrenderer.rb +98 -0
  38. metadata +94 -0
@@ -0,0 +1,256 @@
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
+ TODO:
12
+ Simplify completely. we don't need to use popup list, use something simpler do
13
+ we can control keys.
14
+ Keys: ignore down arrow in field. Use space to popup and space to select from popup.
15
+ Or keep that as default.
16
+ That v character does not position correctly if label used.
17
+ =end
18
+ require 'rbcurse'
19
+ require 'rbcurse/extras/widgets/rlistbox'
20
+
21
+ include RubyCurses
22
+ module RubyCurses
23
+ extend self
24
+
25
+ # TODO :
26
+ # i no longer use values, i now use "list" or better "list_data_model"
27
+ # try to make it so values gets converted to list.
28
+ # NOTE: 2010-10-01 13:25 spacebar and enter will popup in addition to Alt-Down
29
+ class ComboBoxEdit < Field
30
+ include RubyCurses::EventHandler
31
+ dsl_accessor :list_config
32
+ dsl_accessor :insert_policy # NO_INSERT, INSERT_AT_TOP, INSERT_AT_BOTTOM, INSERT_AT_CURRENT
33
+ # INSERT_AFTER_CURRENT, INSERT_BEFORE_CURRENT,INSERT_ALPHABETICALLY
34
+
35
+ attr_accessor :current_index
36
+ # the symbol you want to use for combos
37
+ attr_accessor :COMBO_SYMBOL
38
+ attr_accessor :show_symbol # show that funny symbol after a combo to signify its a combo
39
+ dsl_accessor :arrow_key_policy # :IGNORE :NEXT_ROW :POPUP
40
+
41
+ def initialize form, config={}, &block
42
+ @arrow_key_policy = :ignore
43
+ @show_symbol = true
44
+ @COMBO_SYMBOL = "v".ord # trying this out
45
+ super
46
+ @current_index ||= 0
47
+ # added if check since it was overriding set_buffer in creation. 2009-01-18 00:03
48
+ set_buffer @list[@current_index].dup if @buffer.nil? or @buffer.empty?
49
+ init_vars
50
+ @_events.push(*[:CHANGE, :ENTER_ROW, :LEAVE_ROW])
51
+ end
52
+ def init_vars
53
+ super
54
+ #@show_symbol = false if @label # commented out 2011-11-13 maybe it doesn't place properly if label
55
+ @COMBO_SYMBOL ||= FFI::NCurses::ACS_DARROW #GEQUAL # now this won't work since i've set it above
56
+ bind_key(KEY_UP) { previous_row }
57
+ bind_key(KEY_DOWN) { next_row }
58
+ end
59
+ def selected_item
60
+ @list[@current_index]
61
+ end
62
+ def selected_index
63
+ @current_index
64
+ end
65
+
66
+ ##
67
+ # convert given list to datamodel
68
+ def list alist=nil
69
+ return @list if alist.nil?
70
+ @list = RubyCurses::ListDataModel.new(alist)
71
+ end
72
+ ##
73
+ # set given datamodel
74
+ def list_data_model ldm
75
+ raise "Expecting list_data_model" unless ldm.is_a? RubyCurses::ListDataModel
76
+ @list = ldm
77
+ end
78
+ ##
79
+ # combo edit box key handling
80
+ # removed UP and DOWN and bound it, so it can be unbound
81
+ def handle_key(ch)
82
+ @current_index ||= 0
83
+ # added 2009-01-18 22:44 no point moving horiz or passing up to Field if not edit
84
+ if !@editable
85
+ if ch == KEY_LEFT or ch == KEY_RIGHT
86
+ return :UNHANDLED
87
+ end
88
+ end
89
+ case @arrow_key_policy
90
+ when :ignore
91
+ if ch == KEY_DOWN or ch == KEY_UP
92
+ return :UNHANDLED
93
+ end
94
+ when :popup
95
+ if ch == KEY_DOWN or ch == KEY_UP
96
+ popup
97
+ end
98
+ end
99
+ case ch
100
+ #when KEY_UP # show previous value
101
+ # previous_row
102
+ #when KEY_DOWN # show previous value
103
+ # next_row
104
+ # adding spacebar to popup combo, as in microemacs 2010-10-01 13:21
105
+ when 32, KEY_DOWN+ META_KEY # alt down
106
+ popup # pop up the popup
107
+ else
108
+ super
109
+ end
110
+ end
111
+ def previous_row
112
+ @current_index -= 1 if @current_index > 0
113
+ set_buffer @list[@current_index].dup
114
+ set_modified(true) ## ??? not required
115
+ fire_handler :ENTER_ROW, self
116
+ @list.on_enter_row self
117
+ end
118
+ def next_row
119
+ @current_index += 1 if @current_index < @list.length()-1
120
+ set_buffer @list[@current_index].dup
121
+ set_modified(true) ## ??? not required
122
+ fire_handler :ENTER_ROW, self
123
+ @list.on_enter_row self
124
+ end
125
+ ##
126
+ # calls a popup list
127
+ # TODO: should not be positioned so that it goes off edge
128
+ # user's customizations of list should be passed in
129
+ # The dup of listconfig is due to a tricky feature/bug.
130
+ # I try to keep the config hash and instance variables in synch. So
131
+ # this config hash is sent to popuplist which updates its row col and
132
+ # next time we pop up the popup row and col are zero.
133
+ #
134
+ #
135
+ # added dup in PRESS since editing edit field mods this
136
+ # on pressing ENTER, value set back and current_index updated
137
+ def popup
138
+ listconfig = (@list_config && @list_config.dup) || {}
139
+ dm = @list
140
+ # current item in edit box will be focussed when list pops up
141
+ #$log.debug "XXX POPUP: #{dm.selected_index} = #{@current_index}, value #{@buffer}"
142
+ # we are having some problms when using this in a list. it retains earlier value
143
+ _index = dm.index @buffer
144
+ dm.selected_index = _index # @current_index
145
+ poprow = @row+0 # one row below the edit box
146
+ popcol = @col
147
+ dlength = @display_length
148
+ f = self
149
+ @popup = RubyCurses::PopupList.new do
150
+ row poprow
151
+ col popcol
152
+ width dlength
153
+ list_data_model dm
154
+ list_selection_mode 'single'
155
+ relative_to f
156
+ list_config listconfig
157
+ bind(:PRESS) do |index|
158
+ f.set_buffer dm[index].dup
159
+ f.set_modified(true) if f.current_index != index
160
+ f.current_index = index
161
+ end
162
+ end
163
+ end
164
+
165
+ # Field putc advances cursor when it gives a char so we override this
166
+ def putc c
167
+ if c >= 0 and c <= 127
168
+ ret = putch c.chr
169
+ if ret == 0
170
+ addcol 1 if @editable
171
+ set_modified
172
+ end
173
+ end
174
+ return -1 # always ??? XXX
175
+ end
176
+ ##
177
+ # field does not give char to non-editable fields so we override
178
+ def putch char
179
+ @current_index ||= 0
180
+ if @editable
181
+ super
182
+ return 0
183
+ else
184
+ match = next_match(char)
185
+ set_buffer match unless match.nil?
186
+ fire_handler :ENTER_ROW, self
187
+ end
188
+ @modified = true
189
+ fire_handler :CHANGE, self # 2008-12-09 14:51 ???
190
+ 0
191
+ end
192
+ ##
193
+ # the sets the next match in the edit field
194
+ def next_match char
195
+ start = @current_index
196
+ start.upto(@list.length-1) do |ix|
197
+ if @list[ix][0,1].casecmp(char) == 0
198
+ return @list[ix] unless @list[ix] == @buffer
199
+ end
200
+ @current_index += 1
201
+ end
202
+ ## could not find, start from zero
203
+ @current_index = 0
204
+ start = [@list.length()-1, start].min
205
+ 0.upto(start) do |ix|
206
+ if @list[ix][0,1].casecmp(char) == 0
207
+ return @list[ix] unless @list[ix] == @buffer
208
+ end
209
+ @current_index += 1
210
+ end
211
+ @current_index = [@list.length()-1, @current_index].min
212
+ return nil
213
+ end
214
+ ##
215
+ # on leaving the listbox, update the combo/datamodel.
216
+ # we are using methods of the datamodel. Updating our list will have
217
+ # no effect on the list, and wont trigger events.
218
+ # Do not override.
219
+ def on_leave
220
+ if !@list.include? @buffer and !@buffer.strip.empty?
221
+ _insert_policy = @insert_policy || :INSERT_AT_BOTTOM
222
+ case _insert_policy
223
+ when :INSERT_AT_BOTTOM, :INSERT_AT_END
224
+ @list.append @buffer
225
+ when :INSERT_AT_TOP
226
+ @list.insert(0, @buffer)
227
+ when :INSERT_AFTER_CURRENT
228
+ @current_index += 1
229
+ @list.insert(@current_index, @buffer)
230
+
231
+ when :INSERT_BEFORE_CURRENT
232
+ #_index = @current_index-1 if @current_index>0
233
+ _index = @current_index
234
+ @list.insert(_index, @buffer)
235
+ when :INSERT_AT_CURRENT
236
+ @list[@current_index]=@buffer
237
+ when :NO_INSERT
238
+ ; # take a break
239
+ end
240
+ end
241
+ fire_handler :LEAVE, self
242
+ end
243
+
244
+ def repaint
245
+ super
246
+ c = @col + @display_length
247
+ if @show_symbol # 2009-01-11 18:47
248
+ # i have changed c +1 to c, since we have no right to print beyond display_length
249
+ @form.window.mvwaddch @row, c, @COMBO_SYMBOL # Ncurses::ACS_GEQUAL
250
+ @form.window.mvchgat(y=@row, x=c, max=1, Ncurses::A_REVERSE|Ncurses::A_UNDERLINE, $datacolor, nil)
251
+ end
252
+ end
253
+
254
+ end # class ComboBox
255
+
256
+ end # module
@@ -0,0 +1,27 @@
1
+ require 'rbcurse'
2
+ ##
3
+ module RubyCurses
4
+ class Link < Button
5
+ dsl_property :description
6
+
7
+
8
+ def initialize form, config={}, &block
9
+ super
10
+ @text_offset = 0
11
+ # haha we've never done this, pin the cursor up on 0,0
12
+ @col_offset = -1
13
+ if @mnemonic
14
+ form.bind_key(@mnemonic.downcase, self){ self.fire }
15
+ end
16
+ @display_length = config[:width]
17
+ end
18
+ def fire
19
+ super
20
+ self.focus
21
+ end
22
+ def getvalue_for_paint
23
+ getvalue()
24
+ end
25
+ ##
26
+ end # class
27
+ end # module
@@ -0,0 +1,1247 @@
1
+ =begin
2
+ * Name: rlistbox: editable scrollable lists
3
+ * Description
4
+ * Author: rkumar (arunachalesha)
5
+ * Date: 2008-11-19 12:49
6
+ * License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
7
+ * This file started on 2009-01-13 22:18 (broken off rwidgets.rb)
8
+ NOTE: listbox now traps RETURN/ENTER/13 so if you are trapping it, please use bind :PRESS
9
+ # Changes:
10
+ # Added edit_toggle_mode and edit_toggle_key so on can use vim keys for navigating
11
+ # and go into edit mode (currently using C-e). This way ENTER can still be used
12
+ # for PRESS. Esc and esc-esc closes edit mode whereas C-c or C-g reverts edit.
13
+
14
+ TODO
15
+ =end
16
+ require 'rbcurse'
17
+ require 'rbcurse/core/include/listcellrenderer'
18
+ require 'rbcurse/extras/include/listkeys'
19
+ require 'forwardable'
20
+
21
+
22
+ #include Ncurses # FFI 2011-09-8
23
+ module RubyCurses
24
+ extend self
25
+ ##
26
+ # When an event is fired by Listbox, contents are changed, then this object will be passed
27
+ # to trigger
28
+ # shamelessly plugged from a legacy language best unnamed
29
+ # type is CONTENTS_CHANGED, INTERVAL_ADDED, INTERVAL_REMOVED
30
+ class ListDataEvent
31
+ attr_accessor :index0, :index1, :source, :type
32
+ def initialize index0, index1, source, type
33
+ @index0 = index0
34
+ @index1 = index1
35
+ @source = source
36
+ @type = type
37
+ end
38
+ def to_s
39
+ "#{@type.to_s}, #{@source}, #{@index0}, #{@index1}"
40
+ end
41
+ def inspect
42
+ "#{@type.to_s}, #{@source}, #{@index0}, #{@index1}"
43
+ end
44
+ end
45
+ # http://www.java2s.com/Code/JavaAPI/javax.swing.event/ListDataEventCONTENTSCHANGED.htm
46
+ # should we extend array of will that open us to misuse
47
+ class ListDataModel
48
+ include Enumerable
49
+ include RubyCurses::EventHandler
50
+ attr_accessor :selected_index
51
+ attr_reader :last_regex # should i really keep here as public or maintain in listbox
52
+
53
+ def initialize anarray=[]
54
+ @list = anarray.dup
55
+ @_events = [:LIST_DATA_EVENT, :ENTER_ROW]
56
+ end
57
+ # changd on 2009-01-14 12:28 based on ..
58
+ # http://www.ruby-forum.com/topic/175637#769030
59
+ def each(&blk)
60
+ @list.each(&blk)
61
+ end
62
+ # not sure how to do this XXX removed on 2009-01-14 12:28
63
+ #def <=>(other)
64
+ # @list <=> other
65
+ #end
66
+ def index obj
67
+ @list.index(obj)
68
+ end
69
+ def length ; @list.length; end
70
+ alias :size :length
71
+
72
+ def insert off0, *data
73
+ @list.insert off0, *data
74
+ lde = ListDataEvent.new(off0, off0+data.length-1, self, :INTERVAL_ADDED)
75
+ fire_handler :LIST_DATA_EVENT, lde
76
+ end
77
+ def append data
78
+ @list << data
79
+ lde = ListDataEvent.new(@list.length-1, @list.length-1, self, :INTERVAL_ADDED)
80
+ fire_handler :LIST_DATA_EVENT, lde
81
+ end
82
+ def update off0, data
83
+ @list[off0] = data
84
+ lde = ListDataEvent.new(off0, off0, self, :CONTENTS_CHANGED)
85
+ fire_handler :LIST_DATA_EVENT, lde
86
+ end
87
+ def []=(off0, data)
88
+ update off0, data
89
+ end
90
+ def [](off0)
91
+ @list[off0]
92
+ end
93
+ def delete_at off0
94
+ ret=@list.delete_at off0
95
+ lde = ListDataEvent.new(off0, off0, self, :INTERVAL_REMOVED)
96
+ fire_handler :LIST_DATA_EVENT, lde
97
+ return ret
98
+ end
99
+ def remove_all
100
+ return if @list.nil? || @list.empty? # 2010-09-21 13:25
101
+ lde = ListDataEvent.new(0, @list.size, self, :INTERVAL_REMOVED)
102
+ @list = []
103
+ @current_index = 0
104
+ fire_handler :LIST_DATA_EVENT, lde
105
+ end
106
+ def delete obj
107
+ off0 = @list.index obj
108
+ return nil if off0.nil?
109
+ ret=@list.delete_at off0
110
+ lde = ListDataEvent.new(off0, off0, self, :INTERVAL_REMOVED)
111
+ fire_handler :LIST_DATA_EVENT, lde
112
+ return ret
113
+ end
114
+ def include?(obj)
115
+ return @list.include?(obj)
116
+ end
117
+ # returns a `dup()` of the list
118
+ def values
119
+ @list.dup
120
+ end
121
+ # why do we have this here in data, we should remove this
122
+ # @deprecated this was just eye candy for some demo
123
+ def on_enter_row object
124
+ $log.debug " XXX on_enter_row of list_data"
125
+ fire_handler :ENTER_ROW, object
126
+ end
127
+ # ##
128
+ # added 2009-01-14 01:00
129
+ # searches between given range of rows (def 0 and end)
130
+ # returns row index of first match of given regex (or nil if not found)
131
+ def find_match regex, ix0=0, ix1=length()
132
+ $log.debug " find_match got #{regex} #{ix0} #{ix1}"
133
+ @last_regex = regex
134
+ @search_start_ix = ix0
135
+ @search_end_ix = ix1
136
+ #@search_found_ix = nil
137
+ @list.each_with_index do |row, ix|
138
+ next if ix < ix0
139
+ break if ix > ix1
140
+ if !row.match(regex).nil?
141
+ @search_found_ix = ix
142
+ return ix
143
+ end
144
+ end
145
+ return nil
146
+ end
147
+ ##
148
+ # continues previous search
149
+ def find_next
150
+ raise "No previous search" if @last_regex.nil?
151
+ start = @search_found_ix && @search_found_ix+1 || 0
152
+ return find_match @last_regex, start, @search_end_ix
153
+ end
154
+ ##
155
+ # find backwards, list_data_model
156
+ # Using this to start a search or continue search
157
+ def find_prev regex=@last_regex, start = @search_found_ix
158
+ raise "No previous search" if regex.nil? # @last_regex.nil?
159
+ $log.debug " find_prev #{@search_found_ix} : #{@current_index}"
160
+ start -= 1 unless start == 0
161
+ @last_regex = regex
162
+ @search_start_ix = start
163
+ start.downto(0) do |ix|
164
+ row = @list[ix]
165
+ if !row.match(regex).nil?
166
+ @search_found_ix = ix
167
+ return ix
168
+ end
169
+ end
170
+ return nil
171
+ #return find_match @last_regex, start, @search_end_ix
172
+ end
173
+ ##
174
+ # added 2010-05-23 12:10 for listeditable
175
+ def slice!(line, howmany)
176
+ ret = @list.slice!(line, howmany)
177
+ lde = ListDataEvent.new(line, line+howmany-1, self, :INTERVAL_REMOVED)
178
+ fire_handler :LIST_DATA_EVENT, lde
179
+ return ret
180
+ end
181
+
182
+ alias :to_array :values
183
+ end # class ListDataModel
184
+ ##
185
+ # scrollable, selectable list of items
186
+ # TODO Add events for item add/remove and selection change
187
+ # added event LIST_COMBO_SELECT fired whenever a select/deselect is done.
188
+ # - I do not know how this works in Tk so only the name is copied..
189
+ # - @selected contains indices of selected objects.
190
+ # - currently the first argument of event is row (the row selected/deselected). Should it
191
+ # be the object.
192
+ # - this event could change when range selection is allowed.
193
+ #
194
+
195
+ ##
196
+ # TODO CAN WE MOVE THIS OUT TO ANOTHER FILE as confusing me
197
+ # pops up a list of values for selection
198
+ # 2008-12-10
199
+ class PopupList
200
+ include RubyCurses::EventHandler
201
+ dsl_accessor :title
202
+ dsl_accessor :row, :col, :height, :width
203
+ dsl_accessor :layout
204
+ attr_reader :config
205
+ attr_reader :selected_index # button index selected by user
206
+ attr_reader :window # required for keyboard
207
+ dsl_accessor :list_selection_mode # true or false allow multiple selection
208
+ dsl_accessor :relative_to # a widget, if given row and col are relative to widgets windows
209
+ # layout
210
+ dsl_accessor :max_visible_items # how many to display
211
+ dsl_accessor :list_config # hash with values for the list to use
212
+ dsl_accessor :valign
213
+ attr_reader :listbox
214
+
215
+ def initialize aconfig={}, &block
216
+ @config = aconfig
217
+ @selected_index = -1
218
+ @list_config ||= {}
219
+ @config.each_pair { |k,v| instance_variable_set("@#{k}",v) }
220
+ instance_eval &block if block_given?
221
+ @list_config.each_pair { |k,v| instance_variable_set("@#{k}",v) }
222
+ @height ||= [@max_visible_items || 10, @list.length].min
223
+ $log.debug " POPUP XXX #{@max_visible_items} ll:#{@list.length} h:#{@height}"
224
+ # get widgets absolute coords
225
+ if @relative_to
226
+ layout = @relative_to.form.window.layout
227
+ @row = @row + layout[:top]
228
+ @col = @col + layout[:left]
229
+ end
230
+ if !@valign.nil?
231
+ case @valign.to_sym
232
+ when :BELOW
233
+ @row += 1
234
+ when :ABOVE
235
+ @row -= @height+1
236
+ @row = 0 if @row < 0
237
+ when :CENTER
238
+ @row -= @height/2
239
+ @row = 0 if @row < 0
240
+ else
241
+ end
242
+ end
243
+
244
+ layout(1+height, @width+4, @row, @col) # changed 2 to 1, 2008-12-17 13:48
245
+ @window = VER::Window.new(@layout)
246
+ @form = RubyCurses::Form.new @window
247
+ @window.bkgd(Ncurses.COLOR_PAIR($reversecolor));
248
+ @window.wrefresh
249
+ #@panel = @window.panel # useless line ?
250
+ Ncurses::Panel.update_panels
251
+ print_input # creates the listbox
252
+ @form.repaint
253
+ @window.wrefresh
254
+ handle_keys
255
+ end
256
+ # class popup
257
+ def list alist=nil
258
+ return @list if alist.nil?
259
+ @list = ListDataModel.new(alist)
260
+ @repaint_required = true
261
+ # will we need this ? listbox made each time so data should be fresh
262
+ #@list.bind(:LIST_DATA_EVENT) { |e| list_data_changed() }
263
+ end
264
+ # class popup
265
+ def list_data_model ldm
266
+ raise "Expecting list_data_model" unless ldm.is_a? RubyCurses::ListDataModel
267
+ @list = ldm
268
+ # will we need this ? listbox made each time so data should be fresh
269
+ #@list.bind(:LIST_DATA_EVENT) { |e| list_data_changed() }
270
+ end
271
+ ##
272
+ def input_value
273
+ #return @listbox.getvalue if !@listbox.nil?
274
+ return @listbox.focussed_index if !@listbox.nil?
275
+ end
276
+ ## popuplist
277
+ def stopping?
278
+ @stop
279
+ end
280
+ ## popuplist
281
+ def handle_keys
282
+ begin
283
+ while((ch = @window.getchar()) != 999 )
284
+ case ch
285
+ when -1
286
+ next
287
+ else
288
+ press ch
289
+ break if @stop
290
+ end
291
+ end
292
+ ensure
293
+ destroy
294
+ end
295
+ return 0 #@selected_index
296
+ end
297
+ ##
298
+ # TODO get next match for key
299
+ def press ch
300
+ $log.debug "popup handle_keys : #{ch}" if ch != -1
301
+ case ch
302
+ when -1
303
+ return
304
+ when KEY_F1, 27, ?\C-q # 27/ESC does not come here since gobbled by keyboard.rb
305
+ @stop = true
306
+ return
307
+ when KEY_ENTER, 10, 13
308
+ # if you press ENTER without selecting, it won't come here
309
+ # it will fire button OK's fire, if that's the default button
310
+
311
+ # returns an array of indices if multiple selection
312
+ if @listbox.selection_mode == :multiple
313
+ fire_handler :PRESS, @listbox
314
+ else
315
+ fire_handler :PRESS, @listbox.focussed_index
316
+ end
317
+ # since Listbox is handling enter, COMBO_SELECT will not be fired
318
+ # $log.debug "popup ENTER : #{field.name}" if !field.nil?
319
+ @stop = true
320
+ return
321
+ when KEY_TAB
322
+ @form.select_next_field
323
+ else
324
+ # fields must return unhandled else we will miss hotkeys.
325
+ # On messageboxes, often if no edit field, then O and C are hot.
326
+ field = @form.get_current_field
327
+ handled = field.handle_key ch
328
+
329
+ if handled == :UNHANDLED
330
+ @stop = true
331
+ return
332
+ end
333
+ end
334
+ @form.repaint
335
+ Ncurses::Panel.update_panels();
336
+ Ncurses.doupdate();
337
+ @window.wrefresh
338
+ end
339
+ def print_input
340
+ r = c = 0
341
+ width = @layout[:width]
342
+ #$log.debug " print_input POPUP ht:#{@height} lh:#{@layout[:height]} "
343
+ height = @layout[:height]
344
+ #height = @height # 2010-01-06 12:52 why was this overriding previous line. its one less than layout
345
+ # i am now using layout height since it gives a closer size to whats asked for.
346
+ parent = @relative_to
347
+ defaultvalue = @default_value || ""
348
+ list = @list
349
+ selection_mode = @list_selection_mode
350
+ default_values = @default_values
351
+ @list_config['color'] ||= 'black'
352
+ @list_config['bgcolor'] ||= 'yellow'
353
+ @listbox = RubyCurses::Listbox.new @form, @list_config do
354
+ name "input"
355
+ row r
356
+ col c
357
+ # attr 'reverse'
358
+ width width
359
+ height height
360
+ list_data_model list
361
+ # ?? XXX display_length 30
362
+ # set_buffer defaultvalue
363
+ selection_mode selection_mode
364
+ default_values default_values
365
+ is_popup true
366
+ #add_observer parent
367
+ end
368
+ end
369
+ # may need to be upgraded to new one XXX FIXME
370
+ def configure(*val , &block)
371
+ case val.size
372
+ when 1
373
+ return @config[val[0]]
374
+ when 2
375
+ @config[val[0]] = val[1]
376
+ instance_variable_set("@#{val[0]}", val[1])
377
+ end
378
+ instance_eval &block if block_given?
379
+ end
380
+ def cget param
381
+ @config[param]
382
+ end
383
+
384
+ def layout(height=0, width=0, top=0, left=0)
385
+ @layout = { :height => height, :width => width, :top => top, :left => left }
386
+ end
387
+ def destroy
388
+ @window.destroy if !@window.nil?
389
+ end
390
+ end # class PopupList
391
+ ##
392
+ # A control for displaying a list of data or values.
393
+ # The list will be editable if @cell_editing_allowed
394
+ # is set to true when creating. By default, multiple selection is allowed, but may be set to :single.
395
+ # TODO: were we not going to force creation of datamodel and listener on startup by putting a blank
396
+ # :list in config, if no list or list_variable or model is there ?
397
+ # Or at end of constructor check, if no listdatamodel then create default.
398
+ # TODO : perhaps when datamodel created, attach listener to it, so we can fire to callers when
399
+ # they want to be informed of changes. As we did with selection listeners.
400
+ #
401
+ class Listbox < Widget
402
+
403
+ require 'rbcurse/core/include/listscrollable'
404
+ require 'rbcurse/extras/include/listselectable'
405
+ require 'rbcurse/extras/include/defaultlistselectionmodel'
406
+ require 'rbcurse/extras/include/celleditor'
407
+ include ListScrollable
408
+ include ListSelectable
409
+ include RubyCurses::ListKeys
410
+ extend Forwardable
411
+ dsl_accessor :height
412
+ dsl_accessor :title
413
+ dsl_property :title_attrib # bold, reverse, normal
414
+ # dsl_accessor :list # the array of data to be sent by user
415
+ attr_reader :toprow
416
+ # attr_reader :prow
417
+ # attr_reader :winrow
418
+ # dsl_accessor :selection_mode # allow multiple select or not
419
+ # dsl_accessor :list_variable # a variable values are shown from this
420
+ dsl_accessor :default_values # array of default values
421
+ dsl_accessor :is_popup # if it is in a popup and single select, selection closes
422
+ attr_accessor :current_index
423
+ #dsl_accessor :cell_renderer
424
+ dsl_accessor :selected_color, :selected_bgcolor, :selected_attr
425
+ dsl_accessor :max_visible_items # how many to display 2009-01-11 16:15
426
+ dsl_accessor :cell_editing_allowed
427
+ dsl_property :show_selector
428
+ dsl_property :row_selected_symbol # 2009-01-12 12:01 changed from selector to selected
429
+ dsl_property :row_unselected_symbol # added 2009-01-12 12:00
430
+ dsl_property :left_margin
431
+ # please set these in he constructor block. Settin them later will have no effect
432
+ # since i would have bound them to actions
433
+ dsl_accessor :KEY_ROW_SELECTOR # editable lists may want to use 0 or some other key
434
+ dsl_accessor :KEY_GOTO_TOP # this is going to go
435
+ dsl_accessor :KEY_GOTO_BOTTOM # this is going to go
436
+ dsl_accessor :KEY_CLEAR_SELECTION # this is going to go
437
+ dsl_accessor :KEY_NEXT_SELECTION # this is going to go
438
+ dsl_accessor :KEY_PREV_SELECTION # this is going to go
439
+ dsl_accessor :valign # 2009-01-17 18:32 vertical alignment used in combos
440
+ dsl_accessor :justify # 2010-09-27 12:41 used by renderer
441
+ attr_accessor :one_key_selection # will pressing a single key select or not
442
+ dsl_accessor :border_attrib, :border_color #
443
+ dsl_accessor :sanitization_required
444
+ dsl_accessor :suppress_borders #to_print_borders
445
+
446
+
447
+ def initialize form, config={}, &block
448
+ @focusable = true
449
+ @editable = false
450
+ @sanitization_required = true
451
+ @one_key_selection = false # allow vim like keys
452
+ @row = 0
453
+ @col = 0
454
+ # data of listbox this is not an array, its a pointer to the listdatamodel
455
+ @list = nil
456
+ # any special attribs such as status to be printed in col1, or color (selection)
457
+ @list_attribs = {}
458
+ @suppress_borders = false
459
+ @row_offset = @col_offset = 1 # for borders
460
+ super
461
+ @_events.push(*[:ENTER_ROW, :LEAVE_ROW, :LIST_SELECTION_EVENT, :PRESS])
462
+ @current_index ||= 0
463
+ @selection_mode ||= :multiple # default is multiple, anything else given becomes single
464
+ @win = @graphic # 2010-01-04 12:36 BUFFERED replace form.window with graphic
465
+ # moving down to repaint so that scrollpane can set should_buffered
466
+ # added 2010-02-17 23:05 RFED16 so we don't need a form.
467
+
468
+ # next 2 lines carry a redundancy
469
+ select_default_values
470
+ # when the combo box has a certain row in focus, the popup should have the same row in focus
471
+
472
+ install_keys
473
+ install_list_keys
474
+ init_vars
475
+ init_actions
476
+ # OMG What about people whove installed custom renders such as rfe.rb 2011-10-15
477
+ #bind(:PROPERTY_CHANGE){|e| @cell_renderer = nil } # will be recreated if anything changes 2011-09-28 V1.3.1
478
+ bind(:PROPERTY_CHANGE){|e|
479
+ # I can't delete the cell renderer, but this may not have full effect if one color is passed.
480
+ if @cell_renderer.respond_to? e.property_name
481
+ @cell_renderer.send(e.property_name.to_sym, e.newvalue)
482
+ end
483
+ } # will be recreated if anything changes 2011-09-28 V1.3.1
484
+
485
+ if @list && !@list.selected_index.nil?
486
+ set_focus_on @list.selected_index # the new version
487
+ end
488
+ end
489
+ # this is called several times, from constructor
490
+ # and when list data changed, so only put relevant resets here.
491
+ # why can't current_index be set to 0 here
492
+ def init_vars
493
+ @repaint_required = true
494
+ @toprow = @pcol = 0
495
+
496
+ @row_offset = @col_offset = 0 if @suppress_borders
497
+ if @show_selector
498
+ @row_selected_symbol ||= '>'
499
+ @row_unselected_symbol ||= ' '
500
+ @left_margin ||= @row_selected_symbol.length
501
+ end
502
+ @left_margin ||= 0
503
+ # we reduce internal_width from width while printing
504
+ @internal_width = 2 # taking into account borders accounting for 2 cols
505
+ @internal_width = 0 if @suppress_borders # should it be 0 ???
506
+
507
+ # toggle editable state using ENTER key
508
+ @edit_toggle = false
509
+ @edit_toggle_key = ?\C-e.getbyte(0)
510
+ # has editing started using edit_toggle
511
+ @is_editing = !@edit_toggle
512
+ map_keys unless @keys_mapped # moved here so users can remap
513
+
514
+ end
515
+ def map_keys
516
+ return if @keys_mapped
517
+ bind_key(?f, 'next row starting with ...'){ ask_selection_for_char() }
518
+ bind_key(?\M-v, 'one key toggle'){ @one_key_selection = !@one_key_selection }
519
+ bind_key(?j, 'next row'){ next_row() }
520
+ bind_key(?k, 'prev row'){ previous_row() }
521
+ bind_key(?\C-n, 'next row'){ next_row() }
522
+ bind_key(?\C-p, 'prev row'){ previous_row() }
523
+ bind_key(?\C-d, :scroll_forward)
524
+ bind_key(?\C-b, :scroll_backward)
525
+ bind_key(?G, 'goto bottom'){ goto_bottom() }
526
+ bind_key([?g,?g], 'goto top'){ goto_top() }
527
+ bind_key(?\M-<, :goto_top )
528
+ bind_key(?\M->, :goto_bottom )
529
+ bind_key(?/, 'find'){ ask_search() }
530
+ bind_key(?n, 'find next'){ find_more() }
531
+ #bind_key(32){ toggle_row_selection() } # some guys may want another selector
532
+ if @cell_editing_allowed && @KEY_ROW_SELECTOR == 32
533
+ @KEY_ROW_SELECTOR = 0 # Ctrl-Space
534
+ end
535
+ bind_key(@KEY_ROW_SELECTOR, 'select a row'){ toggle_row_selection() }
536
+ bind_key(10, 'fire action'){ fire_action_event }
537
+ # I had commented this but we need ENTER for cases like a directory browser where ENTER opens dir
538
+ bind_key(KEY_ENTER, 'fire action'){ fire_action_event }
539
+ #bind_key(13){ @is_editing = !@is_editing }
540
+ #bind_key(?\M-: , :_show_menu)
541
+ @keys_mapped = true
542
+ end
543
+
544
+ ##
545
+ # getter and setter for selection_mode
546
+ # Must be called after creating model, so no duplicate. Since one may set in model directly.
547
+ def selection_mode(*val)
548
+ #raise "ListSelectionModel not yet created!" if @list_selection_model.nil?
549
+
550
+ if val.empty?
551
+ if @list_selection_model
552
+ return @list_selection_model.selection_mode
553
+ else
554
+ return @tmp_selection_mode
555
+ end
556
+ else
557
+ if @list_selection_model.nil?
558
+ @tmp_selection_mode = val[0]
559
+ else
560
+ @list_selection_model.selection_mode = val[0].to_sym
561
+ end
562
+ end
563
+ end
564
+ def row_count
565
+ return 0 if @list.nil?
566
+ @list.length
567
+ end
568
+ # added 2009-01-07 13:05 so new scrollable can use
569
+ def scrollatrow
570
+ if @suppress_borders
571
+ return @height - 1
572
+ else
573
+ return @height - 3
574
+ end
575
+ end
576
+ # provide data to List in the form of an Array or Variable or
577
+ # ListDataModel. This will create a default ListSelectionModel.
578
+ #
579
+ # CHANGE as on 2010-09-21 12:53:
580
+ # If explicit nil passed then dummy datamodel and selection model created
581
+ # From now on, constructor will call this, so this can always
582
+ # happen.
583
+ #
584
+ # NOTE: sometimes this can be added much after its painted.
585
+ # Do not expect this to be called from constructor, although that
586
+ # is the usual case. it can be dependent on some other list or tree.
587
+ # @param [Array, Variable, ListDataModel] data to populate list with
588
+ # @return [ListDataModel] just created or assigned
589
+
590
+ def list *val
591
+ return @list if val.empty?
592
+ clear_selection if @list # clear previous selections if any
593
+ @default_values = nil if @list # clear previous selections if any
594
+ alist = val[0]
595
+ case alist
596
+ when Array
597
+ if @list
598
+ @list.remove_all
599
+ @list.insert 0, *alist
600
+ @current_index = 0
601
+ else
602
+ @list = RubyCurses::ListDataModel.new(alist)
603
+ end
604
+ when NilClass
605
+ if @list
606
+ @list.remove_all
607
+ else
608
+ @list = RubyCurses::ListDataModel.new(alist)
609
+ end
610
+ when Variable
611
+ @list = RubyCurses::ListDataModel.new(alist.value)
612
+ when RubyCurses::ListDataModel
613
+ @list = alist
614
+ else
615
+ raise ArgumentError, "Listbox list(): do not know how to handle #{alist.class} "
616
+ end
617
+ # added on 2009-01-13 23:19 since updates are not automatic now
618
+ @list.bind(:LIST_DATA_EVENT) { |e| list_data_changed() }
619
+ create_default_list_selection_model
620
+ @list_selection_model.selection_mode = @tmp_selection_mode if @tmp_selection_mode
621
+ @repaint_required = true
622
+ @list
623
+ end
624
+ # populate using a Variable which should contain a list
625
+ # NOTE: This explicilty overwrites any existing datamodel such as the
626
+ # default one. You may lose any events you have bound to the listbox
627
+ # prior to this call.
628
+ def list_variable alist=nil
629
+ return @list if alist.nil?
630
+ @list = RubyCurses::ListDataModel.new(alist.value)
631
+ # added on 2009-01-13 23:19 since updates are not automatic now
632
+ @list.bind(:LIST_DATA_EVENT) { |e| list_data_changed() }
633
+ create_default_list_selection_model
634
+ end
635
+ # populate using a custom data model
636
+ # NOTE: This explicilty overwrites any existing datamodel such as the
637
+ # default one. You may lose any events you have bound to the listbox
638
+ # prior to this call.
639
+
640
+ def list_data_model ldm=nil
641
+ return @list if ldm.nil?
642
+ raise "Expecting list_data_model" unless ldm.is_a? RubyCurses::ListDataModel
643
+ @list = ldm
644
+ # added on 2009-01-13 23:19 since updates are not automatic now
645
+ @list.bind(:LIST_DATA_EVENT) { |e| list_data_changed() }
646
+ create_default_list_selection_model
647
+ end
648
+ # create a default list selection model and set it
649
+ # NOTE: I am now checking if one is not already created, since
650
+ # a second creation would wipe out any listeners on it.
651
+ # @see ListSelectable
652
+ # @see DefaultListSelectionModel
653
+ def create_default_list_selection_model
654
+ if @list_selection_model.nil?
655
+ list_selection_model DefaultListSelectionModel.new(self)
656
+ end
657
+ end
658
+ # added 2010-09-15 00:11 to make life easier
659
+ def_delegators :@list, :insert, :remove_all, :delete_at, :include?, :each, :values, :size
660
+ # get element at
661
+ # @param [Fixnum] index for element
662
+ # @return [Object] element
663
+ # @since 1.2.0 2010-09-06 14:33 making life easier for others.
664
+ def [](off0)
665
+ @list[off0]
666
+ end
667
+ # return object under cursor
668
+ # Note: this should not be confused with selected row/s. User may not have selected this.
669
+ # This is only useful since in some demos we like to change a status bar as a user scrolls down
670
+ # @since 1.2.0 2010-09-06 14:33 making life easier for others.
671
+ def current_value
672
+ @list[@current_index]
673
+ end
674
+ # avoid using "row", i'd rather stick with "index" and "value".
675
+ alias :current_row :current_value
676
+ alias :text :current_value # thanks to shoes, not sure how this will impact since widget has text.
677
+
678
+ # XXX can this not be done at repaint
679
+ def select_default_values
680
+ return if @default_values.nil?
681
+ @default_values.each do |val|
682
+ row = @list.index val
683
+ add_row_selection_interval row, row unless row.nil?
684
+ end
685
+ end
686
+ def print_borders
687
+ raise "Width not supplied" unless @width
688
+ raise "Height not supplied" unless @height
689
+ width = @width
690
+ height = @height-1 # 2010-01-04 15:30 BUFFERED HEIGHT
691
+ window = @graphic # 2010-01-04 12:37 BUFFERED
692
+ startcol = @col
693
+ startrow = @row
694
+ @color_pair = get_color($datacolor)
695
+ # bordercolor = @border_color || $datacolor # changed 2011 dts
696
+ bordercolor = @border_color || @color_pair
697
+ borderatt = @border_attrib || Ncurses::A_NORMAL
698
+
699
+ #$log.debug "rlistb #{name}: window.print_border #{startrow}, #{startcol} , h:#{height}, w:#{width} , @color_pair, @attr "
700
+ window.print_border startrow, startcol, height, width, bordercolor, borderatt
701
+ print_title
702
+ end
703
+ def print_title
704
+ @color_pair ||= get_color($datacolor)
705
+ # check title.length and truncate if exceeds width
706
+ return unless @title
707
+ _title = @title
708
+ if @title.length > @width - 2
709
+ _title = @title[0..@width-2]
710
+ end
711
+ @graphic.printstring( @row, @col+(@width-_title.length)/2, _title, @color_pair, @title_attrib) unless @title.nil?
712
+ end
713
+ ### START FOR scrollable ###
714
+ def get_content
715
+ #@list 2008-12-01 23:13
716
+ # NOTE: we never stored the listvariable, so its redundant, we used its value to set list
717
+ @list_variable && @list_variable.value || @list
718
+ end
719
+ def get_window
720
+ @graphic # 2010-01-04 12:37 BUFFERED
721
+ end
722
+ ### END FOR scrollable ###
723
+ # override widgets text
724
+ # returns indices of selected rows
725
+ def getvalue
726
+ selected_rows
727
+ end
728
+ # Listbox
729
+ def handle_key(ch)
730
+ @current_index ||= 0
731
+ @toprow ||= 0
732
+ h = scrollatrow()
733
+ rc = row_count
734
+ $log.debug " listbox got ch #{ch}"
735
+ # not sure we should do something like this
736
+ # If editing is happening don't use space for selection, let it be using in editing
737
+ # and make 0 (ctrl-space) the selector. If no editing, let space be selector
738
+ if ch == 32
739
+ if @KEY_ROW_SELECTOR == 32
740
+ if @is_editing
741
+ key_row_selector = 0 # Ctrl-Space
742
+ else
743
+ key_row_selector = 32
744
+ end
745
+ end
746
+ end
747
+ case ch
748
+ when KEY_UP # show previous value
749
+ return previous_row
750
+ when KEY_DOWN # show previous value
751
+ return next_row
752
+ when key_row_selector # 32
753
+ return if is_popup && @selection_mode != :multiple # not allowing select this way since there will be a difference
754
+ toggle_row_selection @current_index #, @current_index
755
+ @repaint_required = true
756
+ when @KEY_GOTO_TOP # 48, ?\C-[
757
+ # please note that C-[ gives 27, same as esc so will respond after ages
758
+ goto_top
759
+ when @KEY_GOTO_BOTTOM # ?\C-]
760
+ goto_bottom
761
+ when @KEY_NEXT_SELECTION # ?'
762
+ $log.debug "insdie next selection"
763
+ @oldrow = @current_index
764
+ do_next_selection
765
+ bounds_check
766
+ when @KEY_PREV_SELECTION # ?"
767
+ @oldrow = @current_index
768
+ $log.debug "insdie prev selection"
769
+ do_prev_selection
770
+ bounds_check
771
+ when @KEY_CLEAR_SELECTION
772
+ clear_selection
773
+ @repaint_required = true
774
+ when 3, ?\C-c.getbyte(0), ?\C-g.getbyte(0)
775
+ editing_canceled @current_index if @cell_editing_allowed
776
+ cancel_block # block
777
+ @repaint_required = true
778
+ $multiplier = 0
779
+ when 27, 2727
780
+ # ESC or ESC ESC completes edit along with toggle key
781
+ if @is_editing
782
+ editing_completed @current_index
783
+ end
784
+ @repaint_required = true
785
+ when @edit_toggle_key
786
+ # I am currently allowing this to work even if edit_toggle is false
787
+ # You can check for edit_toggle here, if you don't like this behaviour
788
+ @is_editing = !@is_editing
789
+ if !@is_editing
790
+ editing_completed @current_index
791
+ end
792
+ @repaint_required = true
793
+ when @KEY_ASK_FIND_FORWARD
794
+ # ask_search_forward
795
+ when @KEY_ASK_FIND_BACKWARD
796
+ # ask_search_backward
797
+ when @KEY_FIND_NEXT
798
+ # find_next
799
+ when @KEY_FIND_PREV
800
+ # find_prev
801
+ when @KEY_ASK_FIND
802
+ ask_search
803
+ when @KEY_FIND_MORE
804
+ find_more
805
+ when @KEY_BLOCK_SELECTOR
806
+ mark_block #selection
807
+ #when ?\C-u.getbyte(0)
808
+ # multiplier. Series is 4 16 64
809
+ # TESTING @multiplier = (@multiplier == 0 ? 4 : @multiplier *= 4)
810
+ # return 0
811
+ when ?\C-c.getbyte(0)
812
+ @multiplier = 0
813
+ return 0
814
+ else
815
+ # this has to be fixed, if compo does not handle key it has to continue into next part FIXME
816
+ ret = :UNHANDLED # changed on 2009-01-27 13:14 not going into unhandled, tab not released
817
+ if @cell_editing_allowed
818
+ #if !@edit_toggle || (@edit_toggle && @is_editing)
819
+ if @is_editing
820
+ @repaint_required = true
821
+ # hack - on_enter_row should fire when this widget gets focus. first row that is DONE
822
+ begin
823
+ ret = @cell_editor.component.handle_key(ch)
824
+ rescue
825
+ on_enter_row @current_index
826
+ ret = @cell_editor.component.handle_key(ch)
827
+ end
828
+ end
829
+ end
830
+ if ret == :UNHANDLED
831
+ # beware one-key eats up numbers. we'll be wondering why
832
+ if @one_key_selection
833
+ case ch
834
+ #when ?A.getbyte(0)..?Z.getbyte(0), ?a.getbyte(0)..?z.getbyte(0), ?0.getbyte(0)..?9.getbyte(0)
835
+ when ?A.getbyte(0)..?Z.getbyte(0), ?a.getbyte(0)..?z.getbyte(0)
836
+ # simple motion, key press defines motion
837
+ ret = set_selection_for_char ch.chr
838
+ else
839
+ ret = process_key ch, self
840
+ $log.debug "111 listbox #{@current_index} "
841
+ @multiplier = 0
842
+ return :UNHANDLED if ret == :UNHANDLED
843
+ end
844
+ else
845
+ # no motion on single key, we can freak out like in vim, pref f <char> for set_selection
846
+ case ch
847
+ when ?0.getbyte(0)..?9.getbyte(0)
848
+ $multiplier *= 10 ; $multiplier += (ch-48)
849
+ #$log.debug " setting mult to #{$multiplier} in list "
850
+ return 0
851
+ end
852
+ ret = process_key ch, self
853
+ return :UNHANDLED if ret == :UNHANDLED
854
+ end
855
+ end
856
+ end
857
+ $multiplier = 0
858
+ end
859
+ # fire handler when user presses ENTER/RETURN
860
+ # @since 1.2.0
861
+ # listbox now traps ENTER key and fires action event
862
+ # to trap please bind :PRESS
863
+ #
864
+ def fire_action_event
865
+ # this does not select the row ???? FIXME ??
866
+ require 'rbcurse/core/include/ractionevent'
867
+ # should have been callled :ACTION_EVENT !!!
868
+ fire_handler :PRESS, ActionEvent.new(self, :PRESS, text)
869
+ end
870
+ # get a keystroke from user and go to first item starting with that key
871
+ def ask_selection_for_char
872
+ ch = @graphic.getch
873
+ if ch < 0 || ch > 255
874
+ return :UNHANDLED
875
+ end
876
+ ret = set_selection_for_char ch.chr
877
+ end
878
+ def ask_search_forward
879
+ regex = get_string("Enter regex to search")
880
+ ix = @list.find_match regex
881
+ if ix.nil?
882
+ alert("No matching data for: #{regex}")
883
+ else
884
+ set_focus_on(ix)
885
+ end
886
+ end
887
+ # gets string to search and calls data models find prev
888
+ def ask_search_backward
889
+ regex = get_string("Enter regex to search (backward)")
890
+ @last_regex = regex
891
+ ix = @list.find_prev regex, @current_index
892
+ if ix.nil?
893
+ alert("No matching data for: #{regex}")
894
+ else
895
+ set_focus_on(ix)
896
+ end
897
+ end
898
+ # please check for error before proceeding
899
+ # @return [Boolean] false if no data
900
+ def on_enter
901
+ if @list.nil? || @list.size == 0
902
+ #Ncurses.beep
903
+ get_window.ungetch($current_key) # 2011-10-4 push key back so form can go next
904
+ return :UNHANDLED
905
+ end
906
+ on_enter_row @current_index
907
+ set_form_row # added 2009-01-11 23:41
908
+ #$log.debug " ONE ENTER LIST #{@current_index}, #{@form.row}"
909
+ @repaint_required = true
910
+ super
911
+ #fire_handler :ENTER, self
912
+ true
913
+ end
914
+ def on_enter_row arow
915
+ #$log.debug " Listbox #{self} ENTER_ROW with curr #{@current_index}. row: #{arow} H: #{@handler.keys}"
916
+ #fire_handler :ENTER_ROW, arow
917
+ fire_handler :ENTER_ROW, self
918
+ @list.on_enter_row self ## XXX WHY THIS ???
919
+ edit_row_at arow
920
+ @repaint_required = true
921
+ end
922
+ def edit_row_at arow
923
+ if @cell_editing_allowed
924
+ #$log.debug " cell editor on enter #{arow} val of list[row]: #{@list[arow]}"
925
+ editor = cell_editor
926
+ prepare_editor editor, arow
927
+ end
928
+ end
929
+ ##
930
+ # private
931
+ def prepare_editor editor, row
932
+ r,c = rowcol
933
+ value = @list[row] # .chomp
934
+ value = value.dup rescue value # so we can cancel
935
+ row = r + (row - @toprow) # @form.row
936
+ col = c+@left_margin # @form.col
937
+ # unfortunately 2009-01-11 19:47 combo boxes editable allows changing value
938
+ editor.prepare_editor self, row, col, value
939
+ editor.component.curpos = 0 # reset it after search, if user scrols down
940
+ #editor.component.graphic = @graphic # 2010-01-05 00:36 TRYING OUT BUFFERED
941
+ ## override is required if the listbox uses a buffer
942
+ #if @should_create_buffer # removed on 2011-09-29
943
+ #$log.debug " overriding editors comp with GRAPHIC #{@graphic} "
944
+ #editor.component.override_graphic(@graphic) # 2010-01-05 00:36 TRYING OUT BUFFERED
945
+ #end
946
+ set_form_col 0 #@left_margin
947
+
948
+ # set original value so we can cancel
949
+ # set row and col,
950
+ # set value and other things, color and bgcolor
951
+ end
952
+ def on_leave_row arow
953
+ #$log.debug " Listbox #{self} leave with (cr: #{@current_index}) #{arow}: list[row]:#{@list[arow]}"
954
+ #$log.debug " Listbox #{self} leave with (cr: #{@current_index}) #{arow}: "
955
+ #fire_handler :LEAVE_ROW, arow
956
+ fire_handler :LEAVE_ROW, self
957
+ editing_completed arow
958
+ end
959
+ def editing_completed arow
960
+ if @cell_editing_allowed
961
+ if !@cell_editor.nil?
962
+ # $log.debug " cell editor (leave) setting value row: #{arow} val: #{@cell_editor.getvalue}"
963
+ $log.debug " cell editor #{@cell_editor.component.form.window} (leave) setting value row: #{arow} val: #{@cell_editor.getvalue}"
964
+ @list[arow] = @cell_editor.getvalue #.dup 2009-01-10 21:42 boolean can't duplicate
965
+ else
966
+ $log.debug "CELL EDITOR WAS NIL, #{arow} "
967
+ end
968
+ if @edit_toggle
969
+ @is_editing = false
970
+ end
971
+ end
972
+ @repaint_required = true
973
+ end
974
+ def editing_canceled arow=@current_index
975
+ return unless @cell_editing_allowed
976
+ prepare_editor @cell_editor, arow
977
+ @repaint_required = true
978
+ end
979
+
980
+ ##
981
+ # getter and setter for cell_editor
982
+ def cell_editor(*val)
983
+ if val.empty?
984
+ @cell_editor ||= create_default_cell_editor
985
+ else
986
+ @cell_editor = val[0]
987
+ end
988
+ end
989
+ def create_default_cell_editor
990
+ return RubyCurses::CellEditor.new RubyCurses::Field.new nil, {"focusable"=>false, "visible"=>false, "display_length"=> @width-@internal_width-@left_margin}
991
+ end
992
+ ##
993
+ # getter and setter for cell_renderer
994
+ def cell_renderer(*val)
995
+ if val.empty?
996
+ @cell_renderer ||= create_default_cell_renderer
997
+ else
998
+ @cell_renderer = val[0]
999
+ end
1000
+ end
1001
+ def create_default_cell_renderer
1002
+ return RubyCurses::ListCellRenderer.new "", {"color"=>@color, "bgcolor"=>@bgcolor, "parent" => self, "display_length"=> @width-@internal_width-@left_margin}
1003
+ end
1004
+ ##
1005
+ # this method chops the data to length before giving it to the
1006
+ # renderer, this can cause problems if the renderer does some
1007
+ # processing. also, it pans the data horizontally giving the renderer
1008
+ # a section of it.
1009
+ def repaint
1010
+ return unless @repaint_required
1011
+ return if (@height || 0) < 3 || @width == 0
1012
+ # not sure where to put this, once for all or repeat 2010-02-17 23:07 RFED16
1013
+ my_win = @form ? @form.window : @target_window
1014
+ @graphic = my_win unless @graphic
1015
+ raise " #{@name} neither form, nor target window given LB paint " unless my_win
1016
+ raise " #{@name} NO GRAPHIC set as yet LB paint " unless @graphic
1017
+
1018
+ #$log.debug "rlistbox repaint #{@name} r,c, #{@row} #{@col} , width: #{@width} "
1019
+ print_borders unless @suppress_borders # do this once only, unless everything changes
1020
+ #maxlen = @maxlen || @width-@internal_width
1021
+ renderer = cell_renderer()
1022
+ renderer.display_length(@width-@internal_width-@left_margin) # just in case resizing of listbox
1023
+ tm = list()
1024
+ rc = row_count
1025
+ @longest_line = @width
1026
+ $log.debug " rlistbox repaint #{row_count} #{name} "
1027
+ if rc > 0 # just added in case no data passed
1028
+ tr = @toprow
1029
+ acolor = get_color $datacolor # should be set once, if color or bgcolor changs TODO FIXME
1030
+ h = scrollatrow()
1031
+ r,c = rowcol
1032
+ 0.upto(h) do |hh|
1033
+ crow = tr+hh
1034
+ if crow < rc
1035
+ _focussed = @current_index == crow ? true : false # row focussed ?
1036
+ focus_type = _focussed
1037
+ # added 2010-09-02 14:39 so inactive fields don't show a bright focussed line
1038
+ #focussed = false if focussed && !@focussed
1039
+ focus_type = :SOFT_FOCUS if _focussed && !@focussed
1040
+ selected = is_row_selected crow
1041
+ content = tm[crow] # 2009-01-17 18:37 chomp giving error in some cases says frozen
1042
+ content = convert_value_to_text content, crow # 2010-09-23 20:12
1043
+ # by now it has to be a String
1044
+ if content.is_a? String
1045
+ content = content.dup
1046
+ sanitize content if @sanitization_required
1047
+ truncate content
1048
+ end
1049
+ ## set the selector symbol if requested
1050
+ selection_symbol = ''
1051
+ if @show_selector
1052
+ if selected
1053
+ selection_symbol = @row_selected_symbol
1054
+ else
1055
+ selection_symbol = @row_unselected_symbol
1056
+ end
1057
+ @graphic.printstring r+hh, c, selection_symbol, acolor,@attr
1058
+ end
1059
+ #renderer = get_default_cell_renderer_for_class content.class.to_s
1060
+ renderer.repaint @graphic, r+hh, c+@left_margin, crow, content, focus_type, selected
1061
+ else
1062
+ # clear rows
1063
+ @graphic.printstring r+hh, c, " " * (@width-@internal_width), acolor,@attr
1064
+ end
1065
+ end
1066
+ if @cell_editing_allowed && @is_editing
1067
+ @cell_editor.component.repaint unless @cell_editor.nil? or @cell_editor.component.form.nil?
1068
+ end
1069
+ end # rc == 0
1070
+ #@table_changed = false
1071
+ @repaint_required = false
1072
+ end
1073
+ # the idea here is to allow users who subclass Listbox to easily override parts of the cumbersome repaint
1074
+ # method. This assumes your List has some data, but you print a lot more. Now you don't need to
1075
+ # change the data in the renderer, or keep formatted data in the list itself.
1076
+ # e.g. @list contains file names, or File objects, and this converts to a long listing.
1077
+ # If the renderer did that, the truncation would be on wrong data.
1078
+ # @since 1.2.0
1079
+ def convert_value_to_text value, crow
1080
+ case value
1081
+ when TrueClass, FalseClass
1082
+ value
1083
+ else
1084
+ value.to_s if value
1085
+ end
1086
+ end
1087
+ # takes a block, this way anyone extending this class can just pass a block to do his job
1088
+ # This modifies the string
1089
+ def sanitize content
1090
+ if content.is_a? String
1091
+ content.chomp!
1092
+ content.gsub!(/[\t\n\r]/, ' ') # don't display tab
1093
+ content.gsub!(/[^[:print:]]/, '') # don't display non print characters
1094
+ else
1095
+ content
1096
+ end
1097
+ end
1098
+ # returns only the visible portion of string taking into account display length
1099
+ # and horizontal scrolling. MODIFIES STRING
1100
+ # if you;ve truncated the data, it could stay truncated even if lb is increased. be careful
1101
+ # FIXME if _maxlen becomes 0 then maxlen -1 will print whole string again
1102
+ def truncate content
1103
+ _maxlen = @maxlen || @width-@internal_width
1104
+ _maxlen = @width-@internal_width if _maxlen > @width-@internal_width
1105
+ #$log.debug "TRUNCATE: listbox maxlen #{@maxlen}, #{_maxlen} width #{@width}: #{content} "
1106
+ if !content.nil?
1107
+ if content.length > _maxlen # only show maxlen
1108
+ @longest_line = content.length if content.length > @longest_line
1109
+ #content = content[@pcol..@pcol+maxlen-1]
1110
+ content.replace content[@pcol..@pcol+_maxlen-1]
1111
+ else
1112
+ # can this be avoided if pcol is 0 XXX
1113
+ content.replace content[@pcol..-1] if @pcol > 0
1114
+ end
1115
+ end
1116
+ #$log.debug " content: #{content}"
1117
+ content
1118
+ end
1119
+
1120
+ def list_data_changed
1121
+ if row_count == 0 # added on 2009-02-02 17:13 so cursor not hanging on last row which could be empty
1122
+ init_vars
1123
+ @current_index = 0
1124
+ # I had placed this at some time to get cursor correct. But if this listbox is updated
1125
+ # during entry in another field, then this steals the row. e.g. test1.rb 5
1126
+ #set_form_row
1127
+ end
1128
+ @repaint_required = true
1129
+ end
1130
+ # set cursor column position
1131
+ # if i set col1 to @curpos, i can move around left right if key mapped
1132
+ def set_form_col col1=0
1133
+ # TODO BUFFERED use setrowcol @form.row, col
1134
+ # TODO BUFFERED use cols_panned
1135
+ #col1 ||= 0
1136
+ @cols_panned ||= 0 # RFED16 2010-02-17 23:40
1137
+ # editable listboxes will involve changing cursor and the form issue
1138
+ ## added win_col on 2010-01-04 23:28 for embedded forms BUFFERED TRYING OUT
1139
+ #win_col=@form.window.left
1140
+ win_col = 0 # 2010-02-17 23:19 RFED16
1141
+ #col = win_col + @orig_col + @col_offset + @curpos + @form.cols_panned
1142
+ col2 = win_col + @col + @col_offset + col1 + @cols_panned + @left_margin
1143
+ $log.debug " set_form_col in rlistbox #{@col}+ left_margin #{@left_margin} ( #{col2} ) "
1144
+ #super col+@left_margin
1145
+ #@form.setrowcol @form.row, col2 # added 2009-12-29 18:50 BUFFERED
1146
+ setrowcol nil, col2 # 2010-02-17 23:19 RFED16
1147
+ end
1148
+ #def rowcol
1149
+ ## $log.debug "rlistbox rowcol : #{@row+@row_offset+@winrow}, #{@col+@col_offset}"
1150
+ #win_col=@form.window.left
1151
+ #col2 = win_col + @col + @col_offset + @form.cols_panned + @left_margin
1152
+ #return @row+@row_offset, col2
1153
+ #end
1154
+ # experimental selection of multiple rows via block
1155
+ # specify a block start and then a block end
1156
+ # usage: bind mark_selection to a key. It works as a toggle.
1157
+ # C-c will cancel any selection that has begun.
1158
+ # added on 2009-02-19 22:37
1159
+ def mark_block #selection
1160
+ if @inside_block
1161
+ @inside_block = false
1162
+ end_block #selection
1163
+ else
1164
+ @inside_block = true
1165
+ start_block #selection
1166
+ end
1167
+ end
1168
+ # added on 2009-02-19 22:37
1169
+ def cancel_block
1170
+ @first_index = @last_index = nil
1171
+ @inside_block = false
1172
+ end
1173
+ # sets marker for start of block
1174
+ # added on 2009-02-19 22:37
1175
+ def start_block #selection
1176
+ @first_index = @current_index
1177
+ @last_index = nil
1178
+ end
1179
+ # sets marker for end of block
1180
+ # added on 2009-02-19 22:37
1181
+ def end_block #selection
1182
+ @last_index = current_index
1183
+ lower = [@first_index, @last_index].min
1184
+ higher = [@first_index, @last_index].max
1185
+ #lower.upto(higher) do |i| @list.toggle_row_selection i; end
1186
+ add_row_selection_interval(lower, higher)
1187
+ @repaint_required = true
1188
+ end
1189
+ # 2010-02-18 11:40
1190
+ # TRYING OUT - canceling editing if resized otherwise drawing errors can occur
1191
+ # the earlier painted edited comp in yellow keeps showing until a key is pressed
1192
+
1193
+ def set_buffering params
1194
+ $log.warn "CALLED set_buffering in LISTBOX listbox " if $log.debug?
1195
+ super # removed from widget 2011-09-29
1196
+ ## Ensuring that changes to top get reflect in editing comp
1197
+ #+ otherwise it raises an exception. Still the earlier cell_edit is being
1198
+ #+ printed where it was , until a key is moved
1199
+ # FIXME - do same for col
1200
+ if @cell_editor
1201
+ r,c = rowcol
1202
+ if @cell_editor.component.row < @row_offset + @buffer_params[:screen_top]
1203
+ @cell_editor.component.row = @row_offset + @buffer_params[:screen_top]
1204
+ end
1205
+ # TODO next block to be tested by placing a listbox in right split of vertical
1206
+ if @cell_editor.component.col < @col_offset + @buffer_params[:screen_left]
1207
+ @cell_editor.component.col = @col_offset + @buffer_params[:screen_left]
1208
+ end
1209
+ #editing_canceled @current_index if @cell_editing_allowed and @cell_editor
1210
+ end
1211
+ #set_form_row
1212
+ @repaint_required = true
1213
+ end
1214
+
1215
+ # trying to simplify usage. The Java way has made listboxes very difficult to use
1216
+ # Returns selected indices
1217
+ # Indices are often required since the renderer may modify the values displayed
1218
+ #
1219
+ def get_selected_indices
1220
+ @list_selection_model.get_selected_indices
1221
+ end
1222
+
1223
+ # Returns selected values
1224
+ #
1225
+ def get_selected_values
1226
+ selected = []
1227
+ @list_selection_model.get_selected_indices.each { |i| selected << list_data_model[i] }
1228
+ return selected
1229
+ end
1230
+ alias :selected_values :get_selected_values
1231
+ alias :selected_indices :get_selected_indices
1232
+
1233
+ def init_actions
1234
+ return if @actions_added
1235
+ @actions_added = true
1236
+ am = action_manager()
1237
+ am.add_action(Action.new("&One Key Selection toggle ") { @one_key_selection = !@one_key_selection} )
1238
+ am.add_action(Action.new("&Edit Toggle") { @edit_toggle = !@edit_toggle; $status_message.value = "Edit toggle is #{@edit_toggle}" })
1239
+
1240
+ # this is a little more involved due to list selection model
1241
+ am.add_action(Action.new("&Disable selection") { @selection_mode = :none; unbind_key(32); bind_key(32, :scroll_forward); })
1242
+ end
1243
+ # ADD HERE
1244
+ end # class listb
1245
+
1246
+
1247
+ end # module