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,21 @@
1
+ require 'rbcurse/extras/widgets/rlink'
2
+ ##
3
+ module RubyCurses
4
+ class MenuLink < Link
5
+ dsl_property :description
6
+
7
+ def initialize form, config={}, &block
8
+ super
9
+ @col_offset = -1 * @col
10
+ @row_offset = -1 * @row
11
+ end
12
+ # added for some standardization 2010-09-07 20:28
13
+ # alias :text :getvalue # NEXT VERSION
14
+ # change existing text to label
15
+
16
+ def getvalue_for_paint
17
+ "%s %-12s - %-s" % [ @mnemonic , getvalue(), @description ]
18
+ end
19
+ ##
20
+ end # class
21
+ end # module
@@ -0,0 +1,304 @@
1
+ =begin
2
+ * Name: MultiContainer
3
+ * Description View (cycle) multiple components in one container using a key or menu
4
+ * Author: rkumar (arunachalesha)
5
+ * file created 2010-03-15 10:40
6
+ --------
7
+ * License:
8
+ Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
9
+
10
+ =end
11
+ require 'rbcurse'
12
+
13
+ include RubyCurses
14
+ module RubyCurses
15
+ extend self
16
+
17
+ ##
18
+ # Extends TextView with ability to load more than one file or content
19
+ # and switch between files (buffers).
20
+ # NOTE: ideally, i should be able to dynamically add this functionality to either Textview
21
+ # or TextArea or even ListBox or Table someday. Should then be a Module rather than a class.
22
+ class MultiContainer < Widget
23
+ dsl_accessor :title
24
+
25
+
26
+ def initialize form = nil, config={}, &block
27
+ @focusable = true
28
+ @row_offset = @col_offset = 1
29
+ super
30
+ @bmanager = BufferManager.new self
31
+ init_vars
32
+
33
+ end
34
+ def init_vars
35
+ super
36
+ # the following allows us to navigate buffers with :bn :bp etc (with Alt pressed)
37
+ bind_key(?\M-:, :buffer_menu)
38
+ bind_key(?\M-;, :buffer_menu)
39
+ # bind_key([?\C-x, ?f], :file_edit)
40
+ bind_key([?\C-x, ?k], :delete_component)
41
+ bind_key([?\C-x, ?\C-b], :list_components)
42
+ bind_key(?\M-n, :goto_next_component)
43
+ bind_key(?\M-p, :goto_prev_component)
44
+ bind_key(?\M-1, :goto_first_component)
45
+ # easily cycle using p. n is used for next search.
46
+ #bind_key(?p, :buffer_previous)
47
+ @suppress_borders = false
48
+ @repaint_all = true
49
+ @name ||= "multicontainer"
50
+ end
51
+ ## returns current buffer
52
+ # @return [RBuffer] current buffer
53
+ def current_component
54
+ @bmanager.current
55
+ end
56
+ ##
57
+ # Add a component with a title
58
+ # @param [Widget] component
59
+ # @param [String] title
60
+ def add component, title
61
+ component.row = @row+@row_offset+0 # FFI changed 1 to 0 2011-09-12
62
+ component.col = @col+@col_offset+0 # FFI changed 1 to 0 2011-09-12
63
+ component.width = @width-2
64
+ component.height = @height-2
65
+ component.form = @form
66
+ component.override_graphic(@graphic)
67
+ @current_buffer = @bmanager.add component, title
68
+ @current_component = @current_buffer.component
69
+ #set_current_component
70
+ #set_form_row ## FFI added 2011-09-12 to get cursor at start when adding
71
+ $log.debug "MULTICONT ADD got cb : #{@current_component} "
72
+ end
73
+ def set_current_component
74
+ @current_component = @current_buffer.component
75
+ @current_title = @current_component.title # NOTE: unused, don't knw what for
76
+ set_form_row
77
+ @current_component.repaint_all true
78
+ end
79
+ # required otherwise some components may not get correct cursor position on entry
80
+ # e.g. table
81
+ def on_enter
82
+ set_form_row
83
+ end
84
+ def set_form_row #:nodoc:
85
+ if !@current_component.nil?
86
+ cc = @current_component
87
+
88
+ @current_component.on_enter # 2011-10-19 why was this not there earlier
89
+
90
+ # 2011-10-21 I've tried removing next 2 lines but there are certain case
91
+ # that do need them. See testmulticontainer.rb
92
+
93
+ @current_component.set_form_row
94
+ @current_component.set_form_col
95
+
96
+ end
97
+ end
98
+ def set_form_col
99
+ # deliberately empty since Form will call this and Widgets one is unsuitable
100
+ # for us
101
+ end
102
+ ##
103
+ # multi-container
104
+ def handle_key ch #:nodoc:
105
+ $log.debug " MULTI handlekey #{ch}, #{@current_component}"
106
+ ret = :UNHANDLED
107
+ return :UNHANDLED unless @current_component
108
+
109
+ ret = @current_component.handle_key(ch)
110
+ $log.debug " MULTI = current comp #{@current_component} returned #{ret} "
111
+ if ret == :UNHANDLED
112
+ # check for bindings, these cannot override above keys since placed at end
113
+ begin
114
+ ret = process_key ch, self
115
+ $log.debug " MULTI = process_key returned #{ret} "
116
+ if ch > 177 && ch < 187
117
+ n = ch - 177
118
+
119
+ component_at(n)
120
+ ret = 0 # other unhandled goes back
121
+ # go to component n
122
+ end
123
+ rescue => err
124
+ $error_message.value = err.to_s
125
+ $log.error " Multicomponent process_key #{err} "
126
+ $log.debug(err.backtrace.join("\n"))
127
+ alert err.to_s
128
+ end
129
+ return :UNHANDLED if ret == :UNHANDLED
130
+ end
131
+ # check for any keys not handled and check our own ones
132
+ return ret #
133
+ end
134
+ def repaint
135
+ print_border if (@suppress_borders == false && @repaint_all) # do this once only, unless everything changes
136
+ return unless @current_component
137
+ $log.debug " MULTI REPAINT - calling current_comps repaint #{@current_component} "
138
+ ret = @current_component.repaint
139
+ end
140
+ def print_border #:nodoc:
141
+ #$log.debug " #{@name} print_borders, #{@graphic.name} "
142
+ color = $datacolor
143
+ @graphic.print_border_only @row, @col, @height-1, @width, color #, Ncurses::A_REVERSE
144
+ print_title
145
+ end
146
+ def print_title #:nodoc:
147
+ #$log.debug " print_title #{@row}, #{@col}, #{@width} "
148
+ _title = @title || "" + @current_title
149
+ @graphic.printstring( @row, @col+(@width-_title.length)/2, _title, $datacolor, @title_attrib) unless _title.nil?
150
+ end
151
+ # this is just a test of the simple "most" menu
152
+ # can use this for next, prev, first, last, new, delete, overwrite etc
153
+ def buffer_menu
154
+ menu = PromptMenu.new self
155
+ menu.add(menu.create_mitem( 'l', "list buffers", "list buffers ", :list_components ))
156
+ item = menu.create_mitem( 'b', "Buffer Options", "Buffer Options" )
157
+ menu1 = PromptMenu.new( self, "Buffer Options")
158
+ menu1.add(menu1.create_mitem( 'n', "Next", "Switched to next buffer", :goto_next_component ))
159
+ menu1.add(menu1.create_mitem( 'p', "Prev", "Switched to previous buffer", :goto_prev_component ))
160
+ menu1.add(menu1.create_mitem( 'f', "First", "Switched to first buffer", :goto_first_component ))
161
+ menu1.add(menu1.create_mitem( 'l', "Last", "Switched to last buffer", :goto_last_component ))
162
+ menu1.add(menu1.create_mitem( 'd', "Delete", "Deleted buffer", :delete_component ))
163
+ item.action = menu1
164
+ menu.add(item)
165
+ # how do i know what's available. the application or window should know where to place
166
+ menu.display @form.window, $error_message_row, $error_message_col, $datacolor #, menu
167
+ end
168
+
169
+
170
+ def goto_next_component
171
+ perror "No other buffer" and return if @bmanager.size < 2
172
+
173
+ @current_buffer = @bmanager.next
174
+ set_current_component
175
+ end
176
+
177
+ def goto_prev_component
178
+ perror "No other buffer" and return if @bmanager.size < 2
179
+
180
+ @current_buffer = @bmanager.previous
181
+ $log.debug " buffer_prev got #{@current_buffer} "
182
+ set_current_component
183
+ end
184
+ def goto_first_component
185
+ @current_buffer = @bmanager.first
186
+ $log.debug " buffer_first got #{@current_buffer} "
187
+ set_current_component
188
+ end
189
+ def goto_last_component
190
+ @current_buffer = @bmanager.last
191
+ $log.debug " buffer_last got #{@current_buffer} "
192
+ set_current_component
193
+ end
194
+ def delete_component
195
+ if @bmanager.size > 1
196
+ @bmanager.delete_at
197
+ @current_component = @bmanager.previous
198
+ set_current_component
199
+ else
200
+ perror "Only one buffer. Cannot delete."
201
+ end
202
+ end
203
+
204
+ def component_at index
205
+ cc = @bmanager.element_at index
206
+ return unless cc
207
+ @current_component = cc
208
+ #$log.debug " buffer_last got #{@current_component} "
209
+ set_current_component
210
+ end
211
+ def perror errmess
212
+ alert errmess
213
+ #@form.window.print_error_message errmess
214
+ end
215
+ def list_components
216
+ $log.debug " TODO buffers_list: #{@bmanager.size} "
217
+ menu = PromptMenu.new self
218
+ @bmanager.each_with_index{ |b, ix|
219
+ aproc = Proc.new { component_at(ix) }
220
+ name = b.title
221
+ num = ix + 1
222
+ menu.add(menu.create_mitem( num.to_s, name, "Switched to buffer #{ix}", aproc ))
223
+ }
224
+ menu.display @form.window, $error_message_row, $error_message_col, $datacolor
225
+ end
226
+ end # class multicontainer
227
+ ##
228
+ # Handles multiple buffers, navigation, maintenance etc
229
+ # Instantiated at startup of
230
+ #
231
+ class BufferManager
232
+ include Enumerable
233
+ def initialize source
234
+ @source = source
235
+ @buffers = [] # contains RBuffer
236
+ @counter = 0
237
+ # for each buffer i need to store data, current_index (row), curpos (col offset) and title (filename).
238
+ end
239
+ def element_at index
240
+ @buffers[index]
241
+ end
242
+ def each
243
+ @buffers.each {|k| yield(k)}
244
+ end
245
+
246
+ ##
247
+ # @return [RBuffer] current buffer/file
248
+ ##
249
+ def current
250
+ @buffers[@counter]
251
+ end
252
+ ##
253
+ # Would have liked to just return next buffer and not get lost in details of caller
254
+ #
255
+ # @return [RBuffer] next buffer/file
256
+ ##
257
+ def next
258
+ @counter += 1
259
+ @counter = 0 if @counter >= @buffers.size
260
+ @buffers[@counter]
261
+ end
262
+ ##
263
+ # @return [RBuffer] previous buffer/file
264
+ ##
265
+ def previous
266
+ $log.debug " previous bs: #{@buffers.size}, #{@counter} "
267
+ @counter -= 1
268
+ return last() if @counter < 0
269
+ $log.debug " previous ctr #{@counter} "
270
+ @buffers[@counter]
271
+ end
272
+ def first
273
+ @counter = 0
274
+ @buffers[@counter]
275
+ end
276
+ def last
277
+ @counter = @buffers.size - 1
278
+ @buffers[@counter]
279
+ end
280
+ ##
281
+ def delete_at index=@counter
282
+ @buffers.delete_at index
283
+ end
284
+ def delete_by_name name
285
+ @buffers.delete_if {|b| b.filename == name }
286
+ end
287
+ def insert component, position, title=nil
288
+ anew = RComponents.new(component, title)
289
+ @buffers.insert position, anew
290
+ @counter = position
291
+ return anew
292
+ end
293
+ def add component, title=nil
294
+ $log.debug " ADD H: #{component.height} C: #{component.width} "
295
+ insert component, @buffers.size, title
296
+ end
297
+ def size
298
+ @buffers.size
299
+ end
300
+ alias :count :size
301
+ end
302
+ RComponents = Struct.new(:component, :title)
303
+
304
+ end # modul
@@ -0,0 +1,722 @@
1
+ =begin
2
+ * Name: MultiSplit
3
+ * Description: allows user to create multiple splits
4
+ * This diverges from the standard SplitPane which allowed one split only.
5
+ This is inspired by the column-browse pattern as in when we view rdoc in a browser.
6
+ A user does not need to create multiple split panes embedded inside each other, we
7
+ don't have that kind of space, and expanding can be tricky since there is no mouse
8
+ to select panes. Mostly, this makes creating apps with this pattern easy for user.
9
+
10
+
11
+ * NOTE that VERTICAL_SPLIT means the *divider* is vertical.
12
+ * Author: rkumar (arunachalesha)
13
+ * file created 2010-08-31 20:18
14
+ Todo:
15
+ --------
16
+ * License:
17
+ Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
18
+
19
+ =end
20
+ #require 'rubygems'
21
+ #require 'ncurses'
22
+ require 'logger'
23
+ require 'rbcurse'
24
+
25
+ #include Ncurses # FFI 2011-09-8
26
+ include RubyCurses
27
+ module RubyCurses
28
+ extend self
29
+
30
+ ##
31
+ # A MultiSplit allows user to split N components vertically or horizontally.
32
+ # such as 3 listboxes, each dependent on what is selected in previous.
33
+ # This is the column-browse pattern, as in ruby's rdoc when seen in a browser.
34
+ # Also, this can be used for directory browsing, as in OSX Finder.
35
+ # One can keep adding components, and scroll
36
+ # back and forth, so we can have more components than are visible. See testmultispl.rb
37
+ # for a demo of this.
38
+ #
39
+ # This class allows for adding components in one direction, vertical or horizontal. It differs
40
+ # from Vimsplit in that you can have offscreen windows and scroll through. Vimsplit shows
41
+ # all windows but allows stacking and flowing of components.
42
+ #
43
+ # @since 1.1.5
44
+ # TODO -
45
+ # [ ] Don't print title if width less than title XXX or truncate - listbox
46
+ # x user specify max panes to show (beyond that hide and pan)
47
+ # x how many can be created
48
+ # - to squeeze panes and fit all or hide and pan
49
+ # x allow resize of panes
50
+ # - allow orientation change or not
51
+ # x some anamoly reg LEAVE and ENTER from last object
52
+ # x should we not be managing on_enter of listboxes when tabbing ?
53
+ # x how many panes to show, max to create
54
+ # x increase size - currently i recalc each time!
55
+ # x print more marker
56
+ # - allow user to specify preferred sizes and respect that
57
+ # x don't move to an empty list, can have a crash
58
+ #
59
+
60
+ class MultiSplit < Widget
61
+ dsl_property :orientation # :VERTICAL_SPLIT or :HORIZONTAL_SPLIT
62
+ dsl_accessor :border_color
63
+ dsl_accessor :border_attrib
64
+ # if no components have been added at time of repainting
65
+ #+ we could use this. This idea if that a user may want to
66
+ #+ show blank splits.
67
+ dsl_property :split_count
68
+ # should we allow adding more than split_count
69
+ # currently, we don't scroll, we narrow what is shown
70
+ dsl_accessor :unlimited
71
+ # allow user to resize components, default true
72
+ dsl_accessor :allow_resizing # XXX unused
73
+ # allow user to flip / exhange 2 components or not, default false
74
+ dsl_accessor :allow_exchanging # XXX unused
75
+ # when focus reenters this component, should it focus
76
+ # on first internal component, or last focused component.
77
+ # True means it will focus on first component (or last, if backtabbing)
78
+ dsl_accessor :cyclic_behavior
79
+ # maximum to show, if less than split_count then scrolling
80
+ dsl_property :max_visible
81
+ attr_reader :components
82
+ # should borders be suppressed or printed
83
+ dsl_accessor :suppress_borders
84
+
85
+ #attr_accessor :one_touch_expandable # boolean, default true # XXX
86
+
87
+ def initialize form, config={}, &block
88
+ @focusable = true
89
+ @editable = false
90
+ @cyclic_behavior = true
91
+ @row = 0
92
+ @col = 0
93
+ @split_count = nil
94
+ # this is the list of components
95
+ @components = []
96
+ # need to recalculate offsets and dimensions of all comps since new one added
97
+ # to be done once in repaint, and whenever a new one added (in repaint)
98
+ @recalc_required = true
99
+ @row_offset = @col_offset = 1
100
+ @suppress_borders = false
101
+ super
102
+ @orig_col = @col
103
+ @use_absolute = true; # set to true if not using subwins XXX CLEAN THIS
104
+ init_vars
105
+ end
106
+ def init_vars #:nodoc:
107
+ @_first_column_print = 0 # added 2009-10-07 11:25
108
+ @max_visible ||= @split_count
109
+ @_last_column_print = @_first_column_print + @max_visible - 1
110
+
111
+ # cascade_changes keeps the child exactly sized as per the pane which looks nice
112
+ #+ but may not really be what you want.
113
+ @cascade_changes=true
114
+ ## if this splp is increased (ht or wid) then expand the child
115
+ @cascade_boundary_changes = true
116
+ @orientation ||= :HORIZONTAL_SPLIT # added 2010-01-13 15:05 since not set
117
+
118
+ # true means will request child to create a buffer, since cropping will be needed
119
+ # FIXME: if true then increases in size are not having effect !!!
120
+ @_child_buffering = false # private, internal. not to be changed by callers.
121
+ #@one_touch_expandable = true
122
+ #@is_expanding = false
123
+ @row_offset = @col_offset = 0 if @suppress_borders
124
+
125
+ #bind_key([?\C-w, ?o], :expand)
126
+ #bind_key([?\C-w, ?1], :expand)
127
+ #bind_key([?\C-w, ?2], :unexpand)
128
+ #bind_key([?\C-w, ?x], :exchange)
129
+ bind_key(?w, :goto_next_component)
130
+ bind_key(?b, :goto_prev_component)
131
+ bind_key([?\C-w, ?-], :decrease)
132
+ bind_key([?\C-w, ?+], :increase)
133
+ bind_key([?\C-w, ?=], :same)
134
+
135
+ end
136
+ ##
137
+ # adds a component to the multisplit
138
+ # When you add a component to a container such as multisplit, be sure
139
+ # you create it with a nil form object, or else the main form will try to manage it.
140
+ # Containers typically manage their own components such as navigation and they
141
+ # give it the form/graphic object they were created with.
142
+ # @param [widget] a widget object to stack in a pane
143
+ def add comp
144
+ # for starters to make life simple, we force user to specify how many splits
145
+ # This is largely because i don;t know much about the buffering thing, is it still
146
+ # needed here or what. If we can postpone it, then we can compute this in a loop
147
+ # in repaint
148
+ raise "split_count must be given first. How many splits there will be." unless @split_count
149
+ $log.debug " multisplit: Adding a component #{@components.size} "
150
+
151
+ # until we hide those outside bounds, or are able to scroll, lets not allow add if
152
+ # exceeds
153
+ if @components.size >= @split_count
154
+ if @unlimited
155
+ #@split_count = @components.size + 1
156
+ # calc of width depending on ths
157
+ else
158
+ Ncurses.beep
159
+ return
160
+ end
161
+ end
162
+ @recalc_required = true
163
+ @components = [] if @components.nil?
164
+ @components << comp
165
+ #comp.height = nil # nuking listboxes height since it gets calculated
166
+ comp.parent_component = self
167
+ # dang ! this can go out of bounds ! XXX tab goes out
168
+ index = @components.size - 1 # expected as base 0 in compute
169
+ #index = @max_visible - 1 if index > @max_visible - 1
170
+ # all this ado to prevent cursor going out in the first display
171
+ # when index exceeds visible, since compute now uses running balance
172
+ if index > @max_visible - 1
173
+ # we copy the coords of the previous one
174
+ prev = @components[index-1]
175
+ comp.row = prev.row
176
+ comp.col = prev.col
177
+ comp.width = prev.width
178
+ comp.height = prev.height
179
+ else
180
+ compute_component comp, index
181
+ end
182
+ comp.set_buffering(:target_window => @target_window || @form.window, :bottom => comp.height-1, :right => comp.width-1, :form => @form ) # removed on 2011-09-29
183
+ comp.min_height ||= 5
184
+ comp.min_width ||= 5
185
+ return self
186
+ end
187
+ alias :<< :add
188
+
189
+ def [](index)
190
+ raise "MultiSplit: Please add components first" unless @components
191
+ @components[index]
192
+ end
193
+ def size
194
+ @components.size
195
+ end
196
+ alias :length :size
197
+ ##
198
+ # compute component dimensions in one place
199
+ # @param [widget] a widget
200
+ # @param [Fixnum] offset in list of components
201
+ # XXX if called from outside balance can have last value !!!
202
+ # FIXME for last component, take as much as is left height or width
203
+ # otherwise odd figures will leave on row unoccupied
204
+ def compute_component comp, index
205
+ @balance ||= 0
206
+ if @orientation == :HORIZONTAL_SPLIT
207
+ # XXX NOT TESTED TODO
208
+ @comp_height = (@height / @split_count) - 0
209
+ @comp_width = @width
210
+ h = @comp_height
211
+ if @recalc_required
212
+ comp.height = h # listboxes etal calculate a height so that will stand !! XXX
213
+ else
214
+ comp.height ||= h # listboxes etal calculate a height so that will stand !! XXX
215
+ end
216
+ w = @comp_width
217
+ #r = @row + ( comp.height * index)
218
+ r = @row + @balance
219
+ if r > @row + @height
220
+ r = @row + @height
221
+ end
222
+ #alert "r #{@row} h #{@height} ::: comp row #{r} h #{h} bal:#{@balance} "
223
+ @balance += comp.height
224
+ c = @col
225
+ comp.width = w
226
+ comp.row = r
227
+ comp.col = c
228
+ else
229
+ @comp_height = @height
230
+ @comp_width = (@width / @split_count) - 0
231
+ h = @comp_height
232
+ w = @comp_width
233
+ if @recalc_required
234
+ comp.width = w
235
+ else
236
+ comp.width ||= w
237
+ end
238
+ #c = @col + ( w * index) # this makes them all equal
239
+ c = @col + @balance
240
+ if c > @col + @width
241
+ c = @col + @width
242
+ end
243
+ $log.debug "XXXX index #{index} , w #{comp.width} , c = #{c} , bal #{@balance} c+w:#{@col+@width} "
244
+ #if index < @max_visible - 1
245
+ @balance += comp.width
246
+ #end
247
+ r = @row
248
+ comp.height = h
249
+ comp.row = r
250
+ comp.col = c
251
+ #$log.debug " XXXX index r c #{r} #{c} "
252
+ end
253
+ comp
254
+ end
255
+ def increase
256
+ _multiplier = ($multiplier == 0 ? 1 : $multiplier )
257
+ delta = _multiplier
258
+ c = @current_component
259
+ n = get_next_component
260
+ n = get_prev_component unless n
261
+ return unless n
262
+ if @orientation == :HORIZONTAL_SPLIT
263
+ if n.height > 3 + delta
264
+ c.height += delta
265
+ n.height -= delta
266
+ end
267
+ else
268
+ if n.width > 3 + delta
269
+ c.width += delta
270
+ n.width -= delta
271
+ end
272
+ end
273
+ @repaint_required = true
274
+ self
275
+ end
276
+ # decrease size of current component.
277
+ # if last one, then border printing exceeds right boundary. values look okay
278
+ # dunno why XXX FIXME
279
+ def decrease
280
+ _multiplier = ($multiplier == 0 ? 1 : $multiplier )
281
+ delta = _multiplier
282
+ $log.debug "XXXX decrease got mult #{$_multiplier} "
283
+ c = @current_component
284
+ # if decreasing last component then increase previous
285
+ # otherwise always increase the next
286
+ n = get_next_component || get_prev_component
287
+ return unless n # if no other, don't allow
288
+ if @orientation == :HORIZONTAL_SPLIT
289
+ c.height -= delta
290
+ n.height += delta
291
+ # TODO
292
+ else
293
+ if c.width > 3 + delta
294
+ c.width -= delta
295
+ n.width += delta
296
+ end
297
+ end
298
+ @repaint_required = true
299
+ self
300
+ end
301
+ def same
302
+ @components.each do |comp|
303
+ comp.height = @comp_height
304
+ comp.width = @comp_width
305
+ end
306
+ @repaint_required = true
307
+ self
308
+ end
309
+ # @return [widget] next component or nil if no next
310
+ def get_next_component
311
+ return @components[@current_index+1]
312
+ end
313
+ # @return [widget] prev component or nil if no next
314
+ def get_prev_component
315
+ return nil if @current_index == 0
316
+ return @components[@current_index-1]
317
+ end
318
+ ##
319
+ #
320
+ # change height of splitpane
321
+ # @param val [int] new height of splitpane
322
+ # @return [int] old ht if nil passed
323
+ def height(*val)
324
+ return @height if val.empty?
325
+ oldvalue = @height || 0
326
+ super
327
+ @height = val[0]
328
+ return if @components.nil? || @components.empty?
329
+ delta = @height - oldvalue
330
+ @repaint_required = true
331
+ if !@cascade_boundary_changes.nil?
332
+ # must tell children if height changed which will happen in nested splitpanes
333
+ # must adjust to components own offsets too
334
+ if @orientation == :VERTICAL_SPLIT
335
+ @components.each do |e|
336
+ e.height += delta
337
+ end
338
+ else
339
+ e = @components.first
340
+ e.height += delta
341
+ end
342
+ end
343
+ end
344
+ ##
345
+ # change width of splitpane
346
+ # @param val [int, nil] new width of splitpane
347
+ # @return [int] old width if nil passed
348
+ # NOTE: if VERTICAL, then expand or contract only second
349
+ # If HORIZ then expand / contract both
350
+ # Actually this is very complicated since reducing should take into account min_width
351
+ def width(*val)
352
+ return @width if val.empty?
353
+ # must tell children if height changed which will happen in nested splitpanes
354
+ oldvalue = @width || 0
355
+ super
356
+ @width = val[0]
357
+ delta = @width - oldvalue
358
+ $log.debug " SPLP #{@name} width #{oldvalue}, #{@width}, #{delta} "
359
+ @repaint_required = true
360
+ if !@cascade_boundary_changes.nil?
361
+ # must adjust to components own offsets too
362
+ # NOTE: 2010-01-10 20:11 if we increase width by one, each time will both components get increased by one.
363
+ if @orientation == :HORIZONTAL_SPLIT
364
+ @components.each do |e|
365
+ e.width += delta
366
+ end
367
+ else
368
+ # any change in width must effect col of others too ! 2010-08-31 21:57 AUG2010
369
+ # which is why this should be done in repaint and not here
370
+ # ## next change should only happen if sc w < ...
371
+ # if @second_component.width < @width - (rc + @col_offset + @divider_offset + 1)
372
+ last = @components.last
373
+ last.width += delta
374
+ end
375
+ end
376
+ end
377
+ ##
378
+ # resets divider location based on preferred size of first component
379
+ # @return :ERROR if min sizes failed
380
+ # You may want to check for ERROR and if so, resize_weight to 0.50
381
+ def reset_to_preferred_sizes
382
+ alert "TODO THIS reset_to "
383
+ return if @components.nil?
384
+ @repaint_required = true
385
+ end
386
+ # recalculates components and calls repaint
387
+ def update_components # #:nodoc:
388
+ @balance = 0
389
+ @max_visible ||= @split_count
390
+ @_first_column_print ||= 0
391
+ @_last_column_print = @_first_column_print + @max_visible - 1
392
+ $log.debug " XXXX #{@_first_column_print} , last print #{@_last_column_print} "
393
+ @components.each_with_index do |comp,index|
394
+ next if index < @_first_column_print
395
+ break if index > @_last_column_print
396
+ compute_component comp, index
397
+ comp.set_buffering(:target_window => @target_window || @form.window, :form => @form ) # 2011-09-29
398
+ #comp.set_buffering(:target_window => @target_window || @form.window, :bottom => comp.height-1, :right => comp.width-1, :form => @form )
399
+ comp.repaint
400
+ end
401
+ #@balance = 0
402
+ @recalc_required = false
403
+ end
404
+ def repaint # multisplitpane #:nodoc:
405
+ if @graphic.nil?
406
+ @graphic = @target_window || @form.window
407
+ raise "graphic nil in rsplitpane #{@name} " unless @graphic
408
+ end
409
+
410
+ if @repaint_required
411
+ # repaint all ?
412
+ @components.each { |e| e.repaint_all(true) }
413
+ end
414
+ if @repaint_required
415
+ ## paint border and divider
416
+ $log.debug "MULTISPLP #{@name} repaint split H #{@height} W #{@width} "
417
+ bordercolor = @border_color || $datacolor
418
+ borderatt = @border_attrib || Ncurses::A_NORMAL
419
+ absrow = abscol = 0
420
+ if @use_absolute
421
+ absrow = @row
422
+ abscol = @col
423
+ end
424
+ if @use_absolute
425
+ $log.debug " #{@graphic} #{name} calling print_border #{@row} #{@col} #{@height}-1 #{@width}-1 "
426
+ @graphic.print_border(@row, @col, @height-1, @width-1, bordercolor, borderatt) if !@suppress_borders
427
+ else
428
+ $log.debug " #{@graphic} calling print_border 0,0"
429
+ @graphic.print_border(0, 0, @height-1, @width-1, bordercolor, borderatt) unless @suppress_borders
430
+ end
431
+ rc = -1
432
+
433
+ @graphic.attron(Ncurses.COLOR_PAIR(bordercolor) | borderatt)
434
+ # 2010-02-14 18:23 - non buffered, have to make relative coords into absolute
435
+ #+ by adding row and col
436
+ count = @components.nil? ? @split_count : @components.size
437
+ count = @components.empty? ? @split_count : @components.size
438
+ if @orientation == :VERTICAL_SPLIT
439
+ @comp_height ||= @height
440
+ @comp_width ||= (@width / @split_count) - 0
441
+ $log.debug "SPLP #{@name} prtingign split vline divider 1, rc: #{rc}, h:#{@height} - 2 "
442
+ #@graphic.mvvline(absrow+1, rc+abscol, 0, @height-2)
443
+ # (1...count).each(){|i| @graphic.mvvline(absrow+1, (i*@comp_width)+abscol, 0, @height-2) }
444
+ # TODO put vlines here
445
+ # commented off since it uses fixed values and we are increaseing and dec
446
+
447
+ else
448
+ @comp_height ||= (@height / @split_count) - 1
449
+ @comp_width ||= @width
450
+ #$log.debug "SPLP #{@name} prtingign split hline divider rc: #{rc} , 1 , w:#{@width} - 2"
451
+ #@graphic.mvhline(rc+absrow, abscol+1, 0, @width-2)
452
+ # XXX in next line -2 at end was causing an overlap into final border col,
453
+ # this need correction in splitpane XXX
454
+ #(1...count).each(){|i| @graphic.mvhline((i*@comp_height)+absrow, abscol+1, 0, @width-3) }
455
+ # TODO put hlines here
456
+ end
457
+ @graphic.attroff(Ncurses.COLOR_PAIR(bordercolor) | borderatt)
458
+ update_components
459
+ _print_more_columns_marker true
460
+ @graphic.wrefresh # 2010-02-14 20:18 SUBWIN ONLY ??? what is this doing here ? XXX
461
+ else
462
+ # repaint only those components that may have changed
463
+ @components.each { |e| e.repaint }
464
+ end
465
+ ## XXX do not paint what is outside of bounds. See tabbedpane or scrollform
466
+ #paint
467
+ @repaint_required = false
468
+ end
469
+ def getvalue #:nodoc:
470
+ # TODO
471
+ end
472
+ # take focus to next pane (component in it)
473
+ # if its the last, return UNHANDLED so form can take to next field
474
+ # @return [0, :UNHANDLED] success, or last component
475
+
476
+ def goto_next_component
477
+ if @current_component != nil
478
+ @current_component.on_leave
479
+ if on_last_component?
480
+ return :UNHANDLED
481
+ end
482
+ @current_index += 1
483
+ @current_component = @components[@current_index]
484
+ # is it visible
485
+ #@current_index.between?(_first_column_print, _last_column_print)
486
+ if @current_index > @_last_column_print
487
+ # TODO need to check for exceeding
488
+ @_first_column_print += 1
489
+ @_last_column_print += 1
490
+ @repaint_required = true
491
+ end
492
+ # shoot if this this put on a form with other widgets
493
+ # we would never get out, should return nil -1 in handle key
494
+ unless @current_component
495
+ $log.debug " CAME HERE unless @current_component setting to first"
496
+ raise " CAME HERE unless @current_component setting to first"
497
+ @current_index = 0
498
+ @current_component = @components[@current_index]
499
+ end
500
+ else
501
+ # this happens in one_tab_expand
502
+ #@current_component = @second_component if @first_component.nil?
503
+ #@current_component = @first_component if @second_component.nil?
504
+ # XXX not sure what to do here, will it come
505
+ $log.debug " CAME HERE in else clause MSP setting to first"
506
+ raise" CAME HERE in else clause MSP setting to first"
507
+ @current_index = 0
508
+ @current_component = @components[@current_index]
509
+ end
510
+ return set_form_row
511
+ end
512
+
513
+ # take focus to prev pane (component in it)
514
+ # if its the first, return UNHANDLED so form can take to prev field
515
+ # @return [0, :UNHANDLED] success, or first component
516
+ def goto_prev_component
517
+ if @current_component != nil
518
+ @current_component.on_leave
519
+ if on_first_component?
520
+ return :UNHANDLED
521
+ end
522
+ @current_index -= 1
523
+ @current_component = @components[@current_index]
524
+ if @current_index < @_first_column_print
525
+ # TODO need to check for zero
526
+ @_first_column_print -= 1
527
+ @_last_column_print -= 1
528
+ @repaint_required = true
529
+ end
530
+ # shoot if this this put on a form with other widgets
531
+ # we would never get out, should return nil -1 in handle key
532
+ unless @current_component
533
+ @current_index = 0
534
+ @current_component = @components[@current_index]
535
+ end
536
+ else
537
+ # this happens in one_tab_expand
538
+ #@current_component = @second_component if @first_component.nil?
539
+ #@current_component = @first_component if @second_component.nil?
540
+ # XXX not sure what to do here, will it come
541
+ @current_index = 0
542
+ @current_component = @components[@current_index]
543
+ end
544
+ set_form_row
545
+ return 0
546
+ end
547
+ def on_first_component?
548
+ @current_component == @components.first
549
+ end
550
+ def on_last_component?
551
+ @current_component == @components.last
552
+ end
553
+ ## Handles key for splitpanes
554
+ ## By default, first component gets focus, not the SPL itself.
555
+ ##+ Mostly passing to child, and handling child's left-overs.
556
+ # please use bind_key for all mappings.
557
+ # Avoid adding code in here. Let this be generic
558
+ def handle_key ch #:nodoc:
559
+ _multiplier = ($multiplier == 0 ? 1 : $multiplier )
560
+ @current_component ||= @first_component
561
+ @current_index ||= 0
562
+ ## 2010-01-15 12:57 this helps me switch between highest level
563
+ ## However, i should do as follows:
564
+ ## If tab on second component, return UNHA so form can take to next field
565
+ ## If B_tab on second comp, switch to first
566
+ ## If B_tab on first comp, return UNHA so form can take to prev field
567
+ if ch == KEY_TAB
568
+ return goto_next_component
569
+ #return 0
570
+ elsif ch == KEY_BTAB
571
+ return goto_prev_component
572
+ end
573
+
574
+ if @current_component != nil
575
+ ret = @current_component.handle_key ch
576
+ return ret if ret != :UNHANDLED
577
+ else
578
+ ## added 2010-01-07 18:59 in case nothing in there.
579
+ $log.debug " SPLP #{@name} - no component installed in splitpane"
580
+ #return :UNHANDLED
581
+ end
582
+ $log.debug " mmplitpane #{@name} gets KEY #{ch}"
583
+ case ch
584
+ when ?\C-c.getbyte(0)
585
+ $multiplier = 0
586
+ return 0
587
+ when ?0.getbyte(0)..?9.getbyte(0)
588
+ $multiplier *= 10 ; $multiplier += (ch-48)
589
+ return 0
590
+ end
591
+ ret = process_key ch, self
592
+ return :UNHANDLED if ret == :UNHANDLED
593
+
594
+ $multiplier = 0
595
+ return 0
596
+ end
597
+ def paint #:nodoc:
598
+ #@repaint_required = false
599
+ end
600
+ # this is executed when the component gets focus
601
+ # and will happen each time on traversal
602
+ # Used to place the focus on correct internal component
603
+ # and place cursor where component should have it.
604
+ # User can press tab, to come here, or it could be first field of form,
605
+ # or he could press a mnemonic.
606
+ def on_enter
607
+ return if @components.nil?
608
+ # cyclic means it always lands into first comp just as in rdoc
609
+ # otherwise it will always land in last visited component
610
+ if @cyclic_behavior
611
+ # if user backtabbed in place him on last comp
612
+ # else place him in first.
613
+ if $current_key == KEY_BTAB
614
+ @current_component = @components[@_last_column_print]
615
+ @current_index = @_last_column_print
616
+ else
617
+ @current_component = @components[@_first_column_print]
618
+ @current_index = @_first_column_print
619
+ end
620
+ end
621
+ @current_component ||= @components.first
622
+ set_form_row
623
+ end
624
+ # sets cursor on correct row, col
625
+ # should we raise error or throw exception if can;t enter
626
+ def set_form_row #:nodoc:
627
+ if !@current_component.nil?
628
+ c=@current_component
629
+ $log.debug "XXXXX #{@name} set_form_row calling sfr for #{@current_component.name}, #{c.row}, #{c.col} "
630
+ #@current_component.set_form_row
631
+ # trigger the on_enter handler
632
+ # my god XXX this assumes a listbox !! FIXME
633
+ # on enter should return a false or error so we don't proceed
634
+ # or throw exception
635
+ if @current_component.row_count > 0
636
+ @current_component.on_enter # typically on enter does a set_form_row
637
+ # XXX another assumption that is has this !!!
638
+ @current_component.set_form_col
639
+ return 0
640
+ end
641
+ #
642
+ end
643
+ return :UNHANDLED
644
+ end
645
+ # added 2010-02-09 10:10
646
+ # sets the forms cursor column correctly
647
+ # earlier the super was being called which missed out on child's column.
648
+ # Note: splitpane does not use the cursor, so it does not know where cursor should be displayed,
649
+ #+ the child has to decide where it should be displayed.
650
+ def set_form_col #:nodoc:
651
+ return if @current_component.nil?
652
+ #$log.debug " #{@name} set_form_col calling sfc for #{@current_component.name} "
653
+ @current_component.set_form_col
654
+ end
655
+ ## expand a split to maximum. This is the one_touch_expandable feature
656
+ # Currently mapped to C-w 1 (mnemonic for one touch), or C-w o (vim's only)
657
+ # To revert, you have to unexpand
658
+ # Note: basically, i nil the component that we don't want to see
659
+ def expand
660
+ return unless @one_touch_expandable
661
+ # TODO
662
+ #@is_expanding = true # this is required so i don't check for min_width later
663
+ #$log.debug " callign expand "
664
+ #if @current_component == @first_component
665
+ #@saved_component = @second_component
666
+ #@second_component = nil
667
+ #if @orientation == :VERTICAL_SPLIT
668
+ #set_divider_location @width - 1
669
+ #else
670
+ #set_divider_location @height - 1
671
+ #end
672
+ #$log.debug " callign expand 2 nil #{@divider_location}, h:#{@height} w: #{@width} "
673
+ #else
674
+ #@saved_component = @first_component
675
+ #@first_component = nil
676
+ #set_divider_location 1
677
+ #$log.debug " callign expand 1 nil #{@divider_location}, h:#{@height} w: #{@width} "
678
+ #end
679
+ #@repaint_required = true
680
+ end
681
+ # after expanding one split, revert to original - actually i reset, rather than revert
682
+ # This only works after expand has been done
683
+ def unexpand
684
+ #$log.debug " inside unexpand "
685
+ #return unless @saved_component
686
+ #if @first_component.nil?
687
+ #@first_component = @saved_component
688
+ #else
689
+ #@second_component = @saved_component
690
+ #end
691
+ #@saved_component = nil
692
+ #@repaint_required = true
693
+ #reset_to_preferred_sizes
694
+ end
695
+
696
+ # exchange 2 splits, bound to C-w x
697
+ # TODO
698
+ def exchange
699
+ alert "TODO"
700
+ #tmp = @first_component
701
+ #@first_component = @second_component
702
+ #@second_component = tmp
703
+ #@repaint_required = true
704
+ #reset_to_preferred_sizes
705
+ end
706
+ def tile
707
+ return unless @tiling_allowed
708
+ # TODO
709
+ end
710
+ private
711
+ def _print_more_columns_marker tf
712
+ # this marker shows that there are more columns to right
713
+ tf = @_last_column_print < @components.size - 1
714
+ marker = tf ? Ncurses::ACS_CKBOARD : Ncurses::ACS_HLINE
715
+ #@graphic.mvwaddch @row+@height-1, @col+@width-2, marker
716
+ @graphic.mvwaddch @row+@height-1, @col+@width-3, marker
717
+ # show if columns to left or not
718
+ marker = @_first_column_print > 0 ? Ncurses::ACS_CKBOARD : Ncurses::ACS_HLINE
719
+ @graphic.mvwaddch @row+@height-1, @col+@_first_column_print+1, marker
720
+ end
721
+ end # class
722
+ end # module