rbcurse-experimental 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.
- data/README.markdown +75 -0
- data/VERSION +1 -0
- data/examples/teststackflow.rb +113 -0
- data/lib/rbcurse/experimental/widgets/directorylist.rb +467 -0
- data/lib/rbcurse/experimental/widgets/directorytree.rb +69 -0
- data/lib/rbcurse/experimental/widgets/masterdetail.rb +166 -0
- data/lib/rbcurse/experimental/widgets/multiform.rb +330 -0
- data/lib/rbcurse/experimental/widgets/resultsetbrowser.rb +281 -0
- data/lib/rbcurse/experimental/widgets/resultsettextview.rb +586 -0
- data/lib/rbcurse/experimental/widgets/rscrollform.rb +418 -0
- data/lib/rbcurse/experimental/widgets/stackflow.rb +478 -0
- data/lib/rbcurse/experimental/widgets/undomanager.rb +188 -0
- metadata +89 -0
@@ -0,0 +1,418 @@
|
|
1
|
+
=begin
|
2
|
+
* Name: ScrollForm - a form that can take more than the screen and focus only on what's visible
|
3
|
+
* This class originated in TabbedPane for the top button form which only scrolls
|
4
|
+
* horizontally and uses objects that have a ht of 1. Here we have to deal with
|
5
|
+
* large objects and vertical scrolling.
|
6
|
+
* Description:
|
7
|
+
* Author: rkumar
|
8
|
+
|
9
|
+
--------
|
10
|
+
* Date: 2010-03-16 11:32
|
11
|
+
* License:
|
12
|
+
Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
13
|
+
|
14
|
+
|
15
|
+
NOTE:
|
16
|
+
There are going to be tricky cases we have to deal with such as objects that start in the viewable
|
17
|
+
area but finish outside, or vice versa.
|
18
|
+
|
19
|
+
What if we wish some static text to be displayed at top or bottom of ScrollForm
|
20
|
+
=end
|
21
|
+
require 'logger'
|
22
|
+
require 'rbcurse'
|
23
|
+
|
24
|
+
#include Ncurses # FFI 2011-09-8
|
25
|
+
include RubyCurses
|
26
|
+
module RubyCurses
|
27
|
+
extend self
|
28
|
+
class ScrollForm < RubyCurses::Form
|
29
|
+
# the pad prints from this col to window
|
30
|
+
attr_accessor :pmincol # advance / scroll columns
|
31
|
+
# the pad prints from this row to window, usually 0
|
32
|
+
attr_accessor :pminrow # advance / scroll rows (vertically)
|
33
|
+
#attr_accessor :display_w # width of screen display NOW METHODS
|
34
|
+
#attr_accessor :display_h # ht of screen display
|
35
|
+
attr_accessor :row_offset, :col_offset
|
36
|
+
attr_accessor :scroll_unit # by how much should be scroll
|
37
|
+
attr_reader :orig_top, :orig_left
|
38
|
+
attr_reader :window
|
39
|
+
attr_accessor :name
|
40
|
+
attr_reader :cols_panned, :rows_panned
|
41
|
+
def initialize win, &block
|
42
|
+
@target_window = win
|
43
|
+
super
|
44
|
+
@pminrow = @pmincol = 0
|
45
|
+
@row_offset = @col_offset = 0
|
46
|
+
@scroll_unit = 3
|
47
|
+
@cols_panned = @rows_panned = 0
|
48
|
+
@repaint_all = true
|
49
|
+
|
50
|
+
# take display dimensions from window. It is safe to override immediately after form creation
|
51
|
+
@display_h = win.height
|
52
|
+
@display_w = win.width
|
53
|
+
if @display_h == 0
|
54
|
+
@display_h = (Ncurses.LINES - win.top - 2)
|
55
|
+
else
|
56
|
+
@display_h = win.height - 2
|
57
|
+
end
|
58
|
+
if @display_w == 0
|
59
|
+
@display_w = (Ncurses.COLS - win.left - 2)
|
60
|
+
else
|
61
|
+
# copywin fails unless u use rootwindow, so what gives in this case
|
62
|
+
@display_w = win.width - 2
|
63
|
+
end
|
64
|
+
|
65
|
+
init_vars
|
66
|
+
end
|
67
|
+
def init_vars
|
68
|
+
# maybe we should use C-x combinations rather than these keys which might be used
|
69
|
+
# by other widgets, apps
|
70
|
+
bind_key(?\M-h, :scroll_left)
|
71
|
+
bind_key(?\M-l, :scroll_right)
|
72
|
+
bind_key(?\M-n, :scroll_down)
|
73
|
+
bind_key(?\M-p, :scroll_up)
|
74
|
+
end
|
75
|
+
def should_print_border flag=true
|
76
|
+
@print_border_flag = flag
|
77
|
+
@row_offset = @col_offset = 1
|
78
|
+
end
|
79
|
+
# This is how we set size of pad and where it prints on screen
|
80
|
+
# This is all that's needed after constructor.
|
81
|
+
# @param [Fixnum] t top (row on screen to print pad on)
|
82
|
+
# @param [Fixnum] l left (col on screen to print)
|
83
|
+
# @param [Fixnum] h height (how many lines in Pad, usually more that screens actual height)
|
84
|
+
# @param [Fixnum] w width (how many cols in Pad, often more than screens width)
|
85
|
+
#
|
86
|
+
def set_pad_dimensions(t, l, h, w )
|
87
|
+
@pad_h = h
|
88
|
+
@pad_w = w
|
89
|
+
@top = @orig_top = t
|
90
|
+
@left = @orig_left = l
|
91
|
+
create_pad
|
92
|
+
end
|
93
|
+
# old program tabbed pane uses this. fix
|
94
|
+
# @deprecated use set_pad_dimensions
|
95
|
+
def set_layout h,w,t,l
|
96
|
+
set_pad_dimensions t,l,h,w
|
97
|
+
end
|
98
|
+
def display_h(*val)
|
99
|
+
if val.empty?
|
100
|
+
return @display_h
|
101
|
+
else
|
102
|
+
#raise ArgumentError "display_h should be ... " if val[0] ...
|
103
|
+
oldvalue = @display_h
|
104
|
+
@display_h = val[0]
|
105
|
+
$log.debug "XXX:given display_h to #{@display_h} "
|
106
|
+
@display_h = [@display_h, @target_window.height - 2].min unless @target_window.height == 0
|
107
|
+
$log.debug "XXX:set display_h to #{@display_h} "
|
108
|
+
#fire_property_handler(:display_h, oldvalue, @display_h)
|
109
|
+
end
|
110
|
+
self
|
111
|
+
end
|
112
|
+
#
|
113
|
+
# By default we are determining these 2 values based on window's dims.
|
114
|
+
# However, if you use a widget that is smaller than the window, then
|
115
|
+
# you will want to overwrite these values.
|
116
|
+
def display_w(*val)
|
117
|
+
if val.empty?
|
118
|
+
return @display_w
|
119
|
+
else
|
120
|
+
#raise ArgumentError "display_h should be ... " if val[0] ...
|
121
|
+
oldvalue = @display_w
|
122
|
+
@display_w = val[0]
|
123
|
+
@display_w = [@display_w, @target_window.width - 2].min unless @target_window.width == 0
|
124
|
+
$log.debug "XXX:set display_w to #{@display_w} "
|
125
|
+
#fire_property_handler(:display_h, oldvalue, @display_h)
|
126
|
+
end
|
127
|
+
self
|
128
|
+
end
|
129
|
+
##
|
130
|
+
# create a pad to work on.
|
131
|
+
# XXX We reuse window, which is already the main window
|
132
|
+
# So if we try creating pad later, then old external window is used.
|
133
|
+
# However, many methods in superclass operate on window so we needed to overwrite. What do i do ?
|
134
|
+
#private
|
135
|
+
def create_pad
|
136
|
+
#raise "Pad already created" if @pad
|
137
|
+
return @pad if @pad
|
138
|
+
r = @top
|
139
|
+
c = @left
|
140
|
+
layout = { :height => @pad_h, :width => @pad_w, :top => r, :left => c }
|
141
|
+
@window = VER::Pad.create_with_layout(layout)
|
142
|
+
|
143
|
+
@window.name = "Pad::ScrollPad" # 2010-02-02 20:01
|
144
|
+
@name = "Form::ScrollForm"
|
145
|
+
@pad = @window
|
146
|
+
return @window
|
147
|
+
end
|
148
|
+
public
|
149
|
+
def scroll_right
|
150
|
+
s = @scroll_unit + $multiplier
|
151
|
+
$log.debug " scroll_right #{s} m: #{$multiplier} "
|
152
|
+
$multiplier = 0
|
153
|
+
return false if !validate_scroll_col(@pmincol + s)
|
154
|
+
@pmincol += s # some check is required or we'll crash
|
155
|
+
@cols_panned -= s
|
156
|
+
$log.debug " handled ch M-l in ScrollForm"
|
157
|
+
@window.modified = true
|
158
|
+
return 0
|
159
|
+
end
|
160
|
+
##
|
161
|
+
# validate fails once unit + mult > 1. Then it won't go further
|
162
|
+
# unit should be one by default.
|
163
|
+
def scroll_left
|
164
|
+
s = @scroll_unit + $multiplier
|
165
|
+
$log.debug " scroll_left #{s} m: #{$multiplier} "
|
166
|
+
$multiplier = 0
|
167
|
+
#return false if !validate_scroll_col(@pmincol - s)
|
168
|
+
if !validate_scroll_col(@pmincol - s)
|
169
|
+
@pmincol = 0
|
170
|
+
@cols_panned = 0
|
171
|
+
else
|
172
|
+
@pmincol -= s # some check is required or we'll crash
|
173
|
+
@cols_panned += s
|
174
|
+
end
|
175
|
+
@window.modified = true
|
176
|
+
return 0
|
177
|
+
end
|
178
|
+
def scroll_down
|
179
|
+
s = @scroll_unit + $multiplier; $multiplier = 0
|
180
|
+
return false if !validate_scroll_row(@pminrow + s)
|
181
|
+
@pminrow += s # some check is required or we'll crash
|
182
|
+
@rows_panned -= s
|
183
|
+
@window.modified = true
|
184
|
+
#@repaint_all = true
|
185
|
+
return 0
|
186
|
+
end
|
187
|
+
def scroll_up
|
188
|
+
s = @scroll_unit + $multiplier; $multiplier = 0
|
189
|
+
$log.debug " scroll_up #{s} "
|
190
|
+
#return false if !validate_scroll_row(@pminrow - s)
|
191
|
+
if !validate_scroll_row(@pminrow - s)
|
192
|
+
@pminrow = 0
|
193
|
+
@rows_panned = 0
|
194
|
+
$log.debug " !valid #{@pminrow} "
|
195
|
+
else
|
196
|
+
@pminrow -= s # some check is required or we'll crash
|
197
|
+
@rows_panned += s
|
198
|
+
$log.debug " valid #{@pminrow} "
|
199
|
+
end
|
200
|
+
@window.modified = true
|
201
|
+
#@repaint_all = true
|
202
|
+
return 0
|
203
|
+
end
|
204
|
+
# print a border on the main window, just for kicks
|
205
|
+
def print_border
|
206
|
+
$log.debug " SCROLL print_border ..."
|
207
|
+
#@window.print_border_only(@top-@rows_panned, @left+@cols_panned, @display_h, @display_w, $datacolor)
|
208
|
+
@target_window.print_border_only(@top, @left, @display_h, @display_w+1, $datacolor)
|
209
|
+
end
|
210
|
+
def print_footer
|
211
|
+
footer = "Lines %d-%d (%d) Cols %d-%d (%d) " % [ @pminrow, @pminrow + @display_h, @orig_top + @pad_h, @pmincol, @pmincol + @display_w, @orig_left + @pad_w ]
|
212
|
+
@target_window.printstring(@top +@display_h, @left + 3, footer, $datacolor)
|
213
|
+
end
|
214
|
+
# XXX what if we want a static area at bottom ?
|
215
|
+
# maybe we should rename targetwindow to window
|
216
|
+
# and window to pad
|
217
|
+
# super may need target window
|
218
|
+
def repaint
|
219
|
+
print_border if @repaint_all and @print_border_flag
|
220
|
+
print_footer if @print_border_flag
|
221
|
+
$log.debug " scrollForm repaint calling parent #{@row} #{@col}+ #{@cols_panned} #{@col_offset} "
|
222
|
+
super
|
223
|
+
prefresh
|
224
|
+
if @print_border_flag
|
225
|
+
_print_more_data_marker true
|
226
|
+
_print_more_columns_marker true
|
227
|
+
end
|
228
|
+
#$log.debug " @target_window.wmove #{@row+@rows_panned+@row_offset}, #{@col+@cols_panned+@col_offset} "
|
229
|
+
@target_window.wmove @row+@rows_panned+@row_offset, @col+@cols_panned+@col_offset
|
230
|
+
@window.modified = false
|
231
|
+
@repaint_all = false
|
232
|
+
end
|
233
|
+
## refresh pad onto window
|
234
|
+
# I am now copying onto main window, else prefresh has funny effects
|
235
|
+
def prefresh
|
236
|
+
## reduce so we don't go off in top+h and top+w
|
237
|
+
$log.debug " start ret = @buttonpad.prefresh( #{@pminrow} , #{@pmincol} , #{@top} , #{@left} , top + #{@display_h} left + #{@display_w} ) "
|
238
|
+
if @pminrow + @display_h > @orig_top + @pad_h
|
239
|
+
$log.debug " if #{@pminrow} + #{@display_h} > #{@orig_top} +#{@pad_h} "
|
240
|
+
$log.debug " ERROR 1 "
|
241
|
+
#return -1
|
242
|
+
end
|
243
|
+
if @pmincol + @display_w > @orig_left + @pad_w
|
244
|
+
$log.debug " if #{@pmincol} + #{@display_w} > #{@orig_left} +#{@pad_w} "
|
245
|
+
$log.debug " ERROR 2 "
|
246
|
+
return -1
|
247
|
+
end
|
248
|
+
# actually if there is a change in the screen, we may still need to allow update
|
249
|
+
# but ensure that size does not exceed
|
250
|
+
if @top + @display_h > @orig_top + @pad_h
|
251
|
+
$log.debug " if #{@top} + #{@display_h} > #{@orig_top} +#{@pad_h} "
|
252
|
+
$log.debug " ERROR 3 "
|
253
|
+
return -1
|
254
|
+
end
|
255
|
+
if @left + @display_w > @orig_left + @pad_w
|
256
|
+
$log.debug " if #{@left} + #{@display_w} > #{@orig_left} +#{@pad_w} "
|
257
|
+
$log.debug " ERROR 4 "
|
258
|
+
return -1
|
259
|
+
end
|
260
|
+
# maybe we should use copywin to copy onto @target_window
|
261
|
+
$log.debug " ret = @window.prefresh( #{@pminrow} , #{@pmincol} , #{@top} , #{@left} , #{@top} + #{@display_h}, #{@left} + #{@display_w} ) "
|
262
|
+
omit = 0
|
263
|
+
# this works but if want to avoid copying border
|
264
|
+
#ret = @window.prefresh(@pminrow, @pmincol, @top+@row_offset, @left+@col_offset, @top + @display_h - @row_offset , @left + @display_w - @col_offset)
|
265
|
+
#
|
266
|
+
$log.debug "ret = @window.copywin( #{@pminrow} , #{@pmincol} , #{@top+@row_offset} , #{@left+@col_offset} , #{@top} + #{@display_h} - #{@row_offset} , #{@left} + #{@display_w} - #{@col_offset} , 0)"
|
267
|
+
## Haha , we are back to the old notorious copywin which has given mankind
|
268
|
+
# so much grief that it should be removed in the next round of creation.
|
269
|
+
ret = @window.copywin(@target_window.get_window, @pminrow, @pmincol, @top+@row_offset, @left+@col_offset,
|
270
|
+
@top + @display_h - @row_offset , @left + @display_w - @col_offset, 0)
|
271
|
+
|
272
|
+
$log.debug " copywin ret = #{ret} "
|
273
|
+
end
|
274
|
+
private
|
275
|
+
def validate_scroll_row minrow
|
276
|
+
return false if minrow < 0
|
277
|
+
if minrow + @display_h > @orig_top + @pad_h
|
278
|
+
$log.debug " if #{minrow} + #{@display_h} > #{@orig_top} +#{@pad_h} "
|
279
|
+
$log.debug " ERROR 1 "
|
280
|
+
return false
|
281
|
+
end
|
282
|
+
return true
|
283
|
+
end
|
284
|
+
def validate_scroll_col mincol
|
285
|
+
return false if mincol < 0
|
286
|
+
if mincol + @display_w > @orig_left + @pad_w
|
287
|
+
$log.debug " if #{mincol} + #{@display_w} > #{@orig_left} +#{@pad_w} "
|
288
|
+
$log.debug " ERROR 2 "
|
289
|
+
return false
|
290
|
+
end
|
291
|
+
return true
|
292
|
+
end
|
293
|
+
# when tabbing through buttons, we need to account for all that panning/scrolling goin' on
|
294
|
+
# this is typically called by putchar or putc in editable components like field.
|
295
|
+
# XXX DELETE THIS IS SUPPOSE
|
296
|
+
def OLDsetrowcol r, c
|
297
|
+
$log.debug " SCROLL setrowcol #{r}, #{c} + #{@cols_panned}"
|
298
|
+
# aha ! here's where i can check whether the cursor is falling off the viewable area
|
299
|
+
cc = nil
|
300
|
+
rr = nil
|
301
|
+
if c
|
302
|
+
cc = c #+ @cols_panned
|
303
|
+
if c+@cols_panned < @orig_left
|
304
|
+
# this essentially means this widget (button) is not in view, its off to the left
|
305
|
+
$log.debug " setrowcol OVERRIDE #{c} #{@cols_panned} < #{@orig_left} "
|
306
|
+
$log.debug " aborting settrow col for now"
|
307
|
+
return
|
308
|
+
end
|
309
|
+
if c+@cols_panned > @orig_left + @display_w
|
310
|
+
# this essentially means this button is not in view, its off to the right
|
311
|
+
$log.debug " setrowcol OVERRIDE #{c} #{@cols_panned} > #{@orig_left} + #{@display_w} "
|
312
|
+
$log.debug " aborting settrow col for now"
|
313
|
+
return
|
314
|
+
end
|
315
|
+
end
|
316
|
+
if r
|
317
|
+
rr = r+@rows_panned
|
318
|
+
end
|
319
|
+
super rr, cc
|
320
|
+
end
|
321
|
+
public
|
322
|
+
def add_widget w
|
323
|
+
super
|
324
|
+
$log.debug " inside add_widget #{w.name} pad w #{@pad_w} #{w.col}, #{@pad_h} "
|
325
|
+
if w.col >= @pad_w
|
326
|
+
@pad_w += 10 # XXX currently just a guess value, we need length and maybe some extra
|
327
|
+
@window.wresize(@pad_h, @pad_w) if @pad
|
328
|
+
end
|
329
|
+
if w.row >= @pad_h
|
330
|
+
@pad_h += 10 # XXX currently just a guess value, we need length and maybe some extra
|
331
|
+
$log.debug " SCROLL add_widget ..."
|
332
|
+
@window.wresize(@pad_h, @pad_w) if @pad
|
333
|
+
end
|
334
|
+
end
|
335
|
+
## Is a component visible, typically used to prevent traversal into the field
|
336
|
+
# @returns [true, false] false if components has scrolled off
|
337
|
+
def visible? component
|
338
|
+
r, c = component.rowcol
|
339
|
+
return false if c+@cols_panned < @orig_left
|
340
|
+
return false if c+@cols_panned > @orig_left + @display_w
|
341
|
+
# XXX TODO for rows UNTESTED for rows
|
342
|
+
return false if r + @rows_panned < @orig_top
|
343
|
+
return false if r + @rows_panned > @orig_top + @display_h - 2
|
344
|
+
|
345
|
+
return true
|
346
|
+
end
|
347
|
+
# returns index of first visible component. Currently using column index
|
348
|
+
# I am doing this for horizontal scrolling presently
|
349
|
+
# @return [index, -1] -1 if none visible, else index/offset
|
350
|
+
def first_visible_component_index
|
351
|
+
@widgets.each_with_index do |w, ix|
|
352
|
+
return ix if visible?(w) and focusable?(w)
|
353
|
+
end
|
354
|
+
return -1
|
355
|
+
end
|
356
|
+
def last_visible_component_index
|
357
|
+
ret = -1
|
358
|
+
@widgets.each_with_index do |w, ix|
|
359
|
+
ret = ix if visible?(w) and focusable?(w)
|
360
|
+
end
|
361
|
+
return ret
|
362
|
+
end
|
363
|
+
def req_first_field
|
364
|
+
index = first_visible_component_index
|
365
|
+
ret = select_field(index)
|
366
|
+
return ret
|
367
|
+
end
|
368
|
+
def req_last_field
|
369
|
+
select_field(last_visible_component_index)
|
370
|
+
end
|
371
|
+
def focusable?(w)
|
372
|
+
w.focusable and visible?(w)
|
373
|
+
end
|
374
|
+
|
375
|
+
# XXX needs to be called from repaint and print_border
|
376
|
+
# @param [boolean] should marker be printed or not
|
377
|
+
def _print_more_data_marker tf
|
378
|
+
tf = false
|
379
|
+
# the bottom marker meaning there's more data below
|
380
|
+
if @pminrow + @display_h < @pad_h
|
381
|
+
tf = true
|
382
|
+
end
|
383
|
+
marker = tf ? Ncurses::ACS_CKBOARD : Ncurses::ACS_VLINE
|
384
|
+
h = @display_h; w = @display_w
|
385
|
+
r = @orig_top
|
386
|
+
c = @orig_left
|
387
|
+
$log.debug " more data #{r+h-1}, #{c+w-1} : row #{r} h #{h} w #{w} col #{c} "
|
388
|
+
@target_window.mvwaddch r+h-1, c+w-0, marker
|
389
|
+
# the top marker to show that there is data above
|
390
|
+
marker = @pminrow > 0 ? Ncurses::ACS_CKBOARD : Ncurses::ACS_VLINE
|
391
|
+
@target_window.mvwaddch r+1, c+w-0, marker
|
392
|
+
end
|
393
|
+
|
394
|
+
# XXX needs to be called from repaint and print_border
|
395
|
+
# @param [boolean] should marker be printed or not
|
396
|
+
def _print_more_columns_marker tf
|
397
|
+
tf = false
|
398
|
+
if @pmincol + @display_w < @pad_w
|
399
|
+
tf = true
|
400
|
+
end
|
401
|
+
marker = tf ? Ncurses::ACS_CKBOARD : Ncurses::ACS_HLINE
|
402
|
+
h = @display_h; w = @display_w
|
403
|
+
r = @orig_top
|
404
|
+
c = @orig_left
|
405
|
+
@target_window.mvwaddch r+h, c+w-2, marker
|
406
|
+
#
|
407
|
+
# show if columns to left or not
|
408
|
+
marker = @pmincol > 0 ? Ncurses::ACS_CKBOARD : Ncurses::ACS_HLINE
|
409
|
+
@target_window.mvwaddch r+h, c+1, marker
|
410
|
+
end
|
411
|
+
end # class ScrollF
|
412
|
+
|
413
|
+
# the return of the prodigals
|
414
|
+
# The Expanding Heart
|
415
|
+
# The coming together of all those who were
|
416
|
+
|
417
|
+
|
418
|
+
end # module
|
@@ -0,0 +1,478 @@
|
|
1
|
+
=begin
|
2
|
+
* Name: stackflow.rb
|
3
|
+
A version of Container that uses stacks and flows and later grids
|
4
|
+
to place components
|
5
|
+
This is not a form. Thus it can be safely placed as a widget
|
6
|
+
without all the complicatinos of a form embedded inside another.
|
7
|
+
NOTE: Still experimental
|
8
|
+
* Description
|
9
|
+
* Author: rkumar (http://github.com/rkumar/rbcurse/)
|
10
|
+
* Date: 23.10.11 - 19:55
|
11
|
+
* License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
12
|
+
|
13
|
+
* Last update: 30.10.11 - 12:55
|
14
|
+
|
15
|
+
== CHANGES
|
16
|
+
Have moved most things out to a module ModStack, so this is sort of just
|
17
|
+
a skeletal container
|
18
|
+
x take care of margins
|
19
|
+
Resizing components
|
20
|
+
If window or container resized then redo the calc again.
|
21
|
+
Flow to have option of right to left orientation
|
22
|
+
== TODO
|
23
|
+
- If user specifies width, height then to be accounted when calculating weight. Also,
|
24
|
+
in such cases we must try not to overwrite h/w when calculating.
|
25
|
+
- changing an objects config not easy since it is stored in item, user may not have
|
26
|
+
handle to item
|
27
|
+
- weightx weighty
|
28
|
+
- RESET height only if expandable
|
29
|
+
- exceeding 100 will result in exceeding container.
|
30
|
+
- C-a C-e misbehaving in examples
|
31
|
+
|
32
|
+
=end
|
33
|
+
|
34
|
+
require 'rbcurse'
|
35
|
+
require 'rbcurse/core/include/bordertitle'
|
36
|
+
require 'rbcurse/core/util/basestack'
|
37
|
+
|
38
|
+
include RubyCurses
|
39
|
+
module RubyCurses
|
40
|
+
extend self
|
41
|
+
|
42
|
+
# This is a more advanced version of container
|
43
|
+
# which allows user to stack or flow components, including
|
44
|
+
# embedding stacks within flows and viceversa.
|
45
|
+
|
46
|
+
|
47
|
+
class StackFlow < Widget
|
48
|
+
|
49
|
+
include BorderTitle
|
50
|
+
include ModStack
|
51
|
+
# should container stack objects ignoring users row col
|
52
|
+
# this is esp needed since App sets row and col which is too early
|
53
|
+
# This is now the default value, till i can redo things
|
54
|
+
#dsl_accessor :stack
|
55
|
+
attr_reader :current_component
|
56
|
+
attr_reader :components
|
57
|
+
|
58
|
+
def initialize form=nil, config={}, &block
|
59
|
+
@suppress_borders = false
|
60
|
+
@row_offset = @col_offset = 1
|
61
|
+
@_events ||= []
|
62
|
+
@focusable = true
|
63
|
+
@editable = false
|
64
|
+
@components = [] # all components
|
65
|
+
@focusables = [] # focusable components, makes checks easier
|
66
|
+
@active = []
|
67
|
+
super
|
68
|
+
|
69
|
+
init_vars
|
70
|
+
end
|
71
|
+
def init_vars
|
72
|
+
@repaint_required = true
|
73
|
+
@row_offset = @col_offset = 0 if @suppress_borders
|
74
|
+
@ctr = 0
|
75
|
+
|
76
|
+
@internal_width = 2
|
77
|
+
@internal_width = 1 if @suppress_borders
|
78
|
+
@name ||= "a_stackflow"
|
79
|
+
bind_key(?\M-1, :increase_current)
|
80
|
+
bind_key(?\M-2, :decrease_current)
|
81
|
+
#raise "NO components !" if @components.empty?
|
82
|
+
calc_weightages2(@components, self) # FIXME this needs to move to basestack
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
# NOTE this is called by basestack so it cannot be here FIXME
|
88
|
+
|
89
|
+
# NOTE: since we are handling the traversal, we delink the object from any
|
90
|
+
# form's widgets array that might have been added. Whenever a form is available,
|
91
|
+
# we set it (without adding widget to it) so it can print using the form's window.
|
92
|
+
#
|
93
|
+
# @param [Widget] to add
|
94
|
+
private
|
95
|
+
def __add *items
|
96
|
+
items.each do |c|
|
97
|
+
raise ArgumentError, "Nil component passed to add" unless c
|
98
|
+
if c.is_a? Widget
|
99
|
+
if c.form && c.form != @form
|
100
|
+
$log.debug " removing widget VIMSPLIT #{c.class} wr: #{c.row} row:#{@row} ht:#{@height} "
|
101
|
+
c.form.remove_widget c
|
102
|
+
c.form = nil
|
103
|
+
# or should i just stack them myself and screw what you've asked for
|
104
|
+
end
|
105
|
+
# take it out of form's control. We will control it.
|
106
|
+
if c.form
|
107
|
+
c.form.remove_widget c
|
108
|
+
end
|
109
|
+
# shoot, what if at this point the container does not have a form
|
110
|
+
attach_form c if @form
|
111
|
+
end
|
112
|
+
# most likely if you have created both container and widgets
|
113
|
+
# inside app, it would have given row after container
|
114
|
+
|
115
|
+
#@components << c
|
116
|
+
if c.focusable
|
117
|
+
@focusables << c
|
118
|
+
@current_component ||= c # only the first else cursor falls on last on enter
|
119
|
+
end
|
120
|
+
|
121
|
+
end # items each
|
122
|
+
self
|
123
|
+
end
|
124
|
+
|
125
|
+
# When we get a form, we silently attach it to this object, without the form
|
126
|
+
# knowing. We don't want form managing this object.
|
127
|
+
def attach_form c
|
128
|
+
c.form = @form
|
129
|
+
c.override_graphic @graphic
|
130
|
+
c.parent_component = self
|
131
|
+
end
|
132
|
+
public
|
133
|
+
def widgets; @components; end
|
134
|
+
# what of by_name
|
135
|
+
|
136
|
+
|
137
|
+
|
138
|
+
# repaint object
|
139
|
+
# called by Form, and sometimes parent component (if not form).
|
140
|
+
def repaint # stackflow
|
141
|
+
my_win = @form ? @form.window : @target_window
|
142
|
+
@graphic = my_win unless @graphic
|
143
|
+
raise " #{@name} NO GRAPHIC set as yet STACKFLOW paint " unless @graphic
|
144
|
+
# actually at this level we don't have margins set -- not yet.
|
145
|
+
@margin_left ||= 0
|
146
|
+
@margin_right ||= 0
|
147
|
+
@margin_top ||= 0
|
148
|
+
@margin_bottom ||= 0
|
149
|
+
r = @row + @row_offset + @margin_top
|
150
|
+
c = @col + @col_offset + @margin_left
|
151
|
+
ht = @height-2-(@margin_top + @margin_bottom)
|
152
|
+
wd = @width -2-(@margin_left + @margin_right)
|
153
|
+
# should this not happen only if repaint_required ?
|
154
|
+
@components.each { |e|
|
155
|
+
e.parent_component = self
|
156
|
+
e.row = r
|
157
|
+
e.col = c
|
158
|
+
# check that we are not trying to print outside bounds
|
159
|
+
# by default we are stacking top level comps regardless of stack or flow
|
160
|
+
# otherwise too complicated
|
161
|
+
if e.is_a? BaseStack
|
162
|
+
# using ||= allows us to use overrides given by user
|
163
|
+
# but disallows us from calculating if size changes
|
164
|
+
e.height = (ht) * (e.weight * 0.01)
|
165
|
+
e.height = e.height.round
|
166
|
+
e.width = wd
|
167
|
+
if e.row + e.height >= @row + @height
|
168
|
+
#alert "is exceeding #{e.row} #{e.height} > #{@row} + #{@height} "
|
169
|
+
e.height = @height - e.row - 1
|
170
|
+
end
|
171
|
+
r += e.height
|
172
|
+
$log.debug "XXX: STACK r:#{e.row} e.h: #{e.height} w:#{e.weight} h: #{@height} "
|
173
|
+
#if e.type == :flow
|
174
|
+
#e.height ||= (@height-2) * (e.weight * 0.01)
|
175
|
+
#e.height = e.height.round
|
176
|
+
#e.width ||= (@width-2)
|
177
|
+
#r += e.height
|
178
|
+
#elsif e.type == :stack
|
179
|
+
#e.width ||= (@width-2) * (e.weight * 0.01)
|
180
|
+
#e.width = e.width.round
|
181
|
+
#e.height ||= (@height-2)
|
182
|
+
#c += e.width
|
183
|
+
#end
|
184
|
+
end
|
185
|
+
check_coords e
|
186
|
+
attach_form e unless e.form
|
187
|
+
} # seeme one if printing out
|
188
|
+
last = @components.last
|
189
|
+
if last.row + last.height < @row + @height
|
190
|
+
last.height += 1 # @row + @height - last.row + last.height
|
191
|
+
end
|
192
|
+
|
193
|
+
# if some major change has happened then repaint everything
|
194
|
+
# if multiple components then last row and col needs to be stored or else overlap will happen FIXME
|
195
|
+
if @repaint_required
|
196
|
+
$log.debug " STACKFLOW repaint graphic #{@graphic}, size:#{@components.size} "
|
197
|
+
print_borders unless @suppress_borders # do this once only, unless everything changes
|
198
|
+
@components.each { |e| e.repaint_all(true); e.repaint }
|
199
|
+
else
|
200
|
+
@components.each { |e| e.repaint }
|
201
|
+
end # if repaint_required
|
202
|
+
|
203
|
+
@repaint_required = false
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
207
|
+
def check_coords e # container
|
208
|
+
r = e.row
|
209
|
+
c = e.col
|
210
|
+
if r >= @row + @height
|
211
|
+
$log.warn "XXX: WARN #{e.class} is out of bounds row #{r} "
|
212
|
+
e.visible = false
|
213
|
+
end
|
214
|
+
if c >= @col + @width
|
215
|
+
$log.warn "XXX: WARN #{e.class} is out of bounds col #{c} "
|
216
|
+
e.visible = false
|
217
|
+
end
|
218
|
+
if e.row + e.height >= @height
|
219
|
+
$log.warn "XXX: WARN #{e.class} is out of bounds row #{e.row} + h #{e.height} >= #{@height} "
|
220
|
+
#e.visible = false
|
221
|
+
end
|
222
|
+
if e.col + e.width >= @width
|
223
|
+
$log.warn "XXX: WARN #{e.class} is out of bounds col #{e.col} + w #{e.width} >= #{@width} "
|
224
|
+
#e.visible = false
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
public
|
229
|
+
# called by parent or form, otherwise its private
|
230
|
+
def handle_key ch
|
231
|
+
$log.debug " STACKFLOW handle_key #{ch} "
|
232
|
+
return if @components.empty?
|
233
|
+
_multiplier = ($multiplier == 0 ? 1 : $multiplier )
|
234
|
+
|
235
|
+
# should this go here 2011-10-19
|
236
|
+
unless @_entered
|
237
|
+
$log.warn "XXX WARN: calling ON_ENTER since in this situation it was not called"
|
238
|
+
on_enter
|
239
|
+
end
|
240
|
+
if ch == KEY_TAB
|
241
|
+
$log.debug "STACKFLOW GOTO NEXT TAB"
|
242
|
+
return goto_next_component
|
243
|
+
elsif ch == KEY_BTAB
|
244
|
+
return goto_prev_component
|
245
|
+
end
|
246
|
+
comp = @current_component
|
247
|
+
$log.debug " STACKFLOW handle_key #{ch}: #{comp}"
|
248
|
+
if comp
|
249
|
+
ret = comp.handle_key(ch)
|
250
|
+
$log.debug " STACKFLOW handle_key#{ch}: #{comp} returned #{ret} "
|
251
|
+
if ret != :UNHANDLED
|
252
|
+
comp.repaint # NOTE: if we don;t do this, then it won't get repainted. I will have to repaint ALL
|
253
|
+
# in repaint of this.
|
254
|
+
return ret
|
255
|
+
end
|
256
|
+
$log.debug "XXX STACKFLOW key unhandled by comp #{comp.name} "
|
257
|
+
else
|
258
|
+
$log.warn "XXX STACKFLOW key unhandled NULL comp"
|
259
|
+
end
|
260
|
+
case ch
|
261
|
+
when ?\C-c.getbyte(0)
|
262
|
+
$multiplier = 0
|
263
|
+
return 0
|
264
|
+
when ?0.getbyte(0)..?9.getbyte(0)
|
265
|
+
$log.debug " VIM coming here to set multiplier #{$multiplier} "
|
266
|
+
$multiplier *= 10 ; $multiplier += (ch-48)
|
267
|
+
return 0
|
268
|
+
end
|
269
|
+
ret = process_key ch, self
|
270
|
+
# allow user to map left and right if he wants
|
271
|
+
if ret == :UNHANDLED
|
272
|
+
case ch
|
273
|
+
when KEY_UP
|
274
|
+
# form will pick this up and do needful
|
275
|
+
return goto_prev_component #unless on_first_component?
|
276
|
+
when KEY_LEFT
|
277
|
+
# if i don't check for first component, key will go back to form,
|
278
|
+
# but not be processes. so focussed remain here, but be false.
|
279
|
+
# In case of returnign an unhandled TAB, on_leave will happen and cursor will move to
|
280
|
+
# previous component outside of this.
|
281
|
+
return goto_prev_component unless on_first_component?
|
282
|
+
when KEY_RIGHT
|
283
|
+
return goto_next_component #unless on_last_component?
|
284
|
+
when KEY_DOWN
|
285
|
+
return goto_next_component #unless on_last_component?
|
286
|
+
else
|
287
|
+
@_entered = false
|
288
|
+
return :UNHANDLED
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
$multiplier = 0
|
293
|
+
return 0
|
294
|
+
end
|
295
|
+
# Actually we should only go to current component if it accepted
|
296
|
+
# a key stroke. if user tabbed thru it, then no point going back to
|
297
|
+
# it. Go to first or last depending on TAB or BACKTAB otherwise.
|
298
|
+
# NOTE: if user comes in using DOWN or UP, last traversed component will get the focus
|
299
|
+
#
|
300
|
+
def on_enter
|
301
|
+
# if BTAB, the last comp XXX they must be focusable FIXME
|
302
|
+
if $current_key == KEY_BTAB || $current_key == KEY_UP
|
303
|
+
@current_component = @focusables.last
|
304
|
+
elsif $current_key == KEY_TAB || $current_key == KEY_DOWN
|
305
|
+
@current_component = @focusables.first
|
306
|
+
else
|
307
|
+
# let current component be, since an unhandled key may have resulted
|
308
|
+
# in on_enter being called again
|
309
|
+
end
|
310
|
+
return unless @current_component
|
311
|
+
$log.debug " STACKFLOW came to ON_ENTER #{@current_component} "
|
312
|
+
set_form_row
|
313
|
+
@_entered = true
|
314
|
+
end
|
315
|
+
# we cannot be sure that this will be called especially if this is embedded
|
316
|
+
# inside some other component
|
317
|
+
def on_leave
|
318
|
+
@_entered = false
|
319
|
+
super
|
320
|
+
end
|
321
|
+
def goto_next_component
|
322
|
+
if @current_component != nil
|
323
|
+
leave_current_component
|
324
|
+
if on_last_component?
|
325
|
+
#@_entered = false
|
326
|
+
return :UNHANDLED
|
327
|
+
end
|
328
|
+
@current_index = @focusables.index(@current_component)
|
329
|
+
index = @current_index + 1
|
330
|
+
f = @focusables[index]
|
331
|
+
if f
|
332
|
+
@current_index = index
|
333
|
+
@current_component = f
|
334
|
+
return set_form_row
|
335
|
+
end
|
336
|
+
end
|
337
|
+
@_entered = false
|
338
|
+
return :UNHANDLED
|
339
|
+
end
|
340
|
+
def goto_prev_component
|
341
|
+
if @current_component != nil
|
342
|
+
leave_current_component
|
343
|
+
if on_first_component?
|
344
|
+
@_entered = false
|
345
|
+
return :UNHANDLED
|
346
|
+
end
|
347
|
+
@current_index = @focusables.index(@current_component)
|
348
|
+
index = @current_index -= 1
|
349
|
+
f = @focusables[index]
|
350
|
+
if f
|
351
|
+
@current_index = index
|
352
|
+
@current_component = f
|
353
|
+
return set_form_row
|
354
|
+
end
|
355
|
+
end
|
356
|
+
return :UNHANDLED
|
357
|
+
end
|
358
|
+
# private
|
359
|
+
# XXX why are we calling 3 methods in a row, why not OE manages these 3
|
360
|
+
# There's double calling going on.
|
361
|
+
def set_form_row
|
362
|
+
return :UNHANDLED if @current_component.nil?
|
363
|
+
cc = @current_component
|
364
|
+
$log.debug "STACKFLOW #{@name} set_form_row calling sfr for #{cc.name}, r #{cc.row} c: #{cc.col} "
|
365
|
+
$log.debug " STACKFLOW on enter sfr #{@current_component.name} #{@current_component} "
|
366
|
+
|
367
|
+
@current_component.on_enter
|
368
|
+
@current_component.set_form_row # why was this missing in vimsplit. is it
|
369
|
+
$log.debug "STACKFLOW #{@name} set_form_row calling sfr for #{cc.name}, r #{cc.row} c: #{cc.col} "
|
370
|
+
# that on_enter does a set_form_row
|
371
|
+
@current_component.set_form_col # XXX
|
372
|
+
@current_component.repaint # OMG this could happen before we've set row and col
|
373
|
+
# XXX compo should do set_form_row and col if it has that
|
374
|
+
end
|
375
|
+
#
|
376
|
+
def set_form_col
|
377
|
+
# override widget
|
378
|
+
end
|
379
|
+
# leave the component we are on.
|
380
|
+
# This should be followed by all containers, so that the on_leave action
|
381
|
+
# of earlier comp can be displayed, such as dimming components selections
|
382
|
+
def leave_current_component
|
383
|
+
begin
|
384
|
+
@current_component.on_leave
|
385
|
+
rescue FieldValidationException => fve
|
386
|
+
alert fve.to_s
|
387
|
+
end
|
388
|
+
# NOTE this is required, since repaint will just not happen otherwise
|
389
|
+
# Some components are erroneously repainting all, after setting this to true so it is
|
390
|
+
# working there.
|
391
|
+
@current_component.repaint_required true
|
392
|
+
$log.debug " after on_leave STACKFLOW XXX #{@current_component.focussed} #{@current_component.name}"
|
393
|
+
@current_component.repaint
|
394
|
+
end
|
395
|
+
|
396
|
+
# is focus on first component FIXME check for focusable
|
397
|
+
def on_first_component?
|
398
|
+
@current_component == @focusables.first
|
399
|
+
end
|
400
|
+
# is focus on last component FIXME check for focusable
|
401
|
+
def on_last_component?
|
402
|
+
@current_component == @focusables.last
|
403
|
+
end
|
404
|
+
# set focus on given component
|
405
|
+
# Sometimes you have the handle to component, and you want to move focus to it
|
406
|
+
def goto_component comp
|
407
|
+
return if comp == @current_component
|
408
|
+
leave_current_component
|
409
|
+
@current_component = comp
|
410
|
+
set_form_row
|
411
|
+
end
|
412
|
+
|
413
|
+
def increase_current
|
414
|
+
c = @current_component
|
415
|
+
p = c.config[:parent]
|
416
|
+
$log.debug "XXX: INC increase current #{c} , #{p} "
|
417
|
+
p.increase c
|
418
|
+
end
|
419
|
+
def decrease_current
|
420
|
+
c = @current_component
|
421
|
+
p = c.config[:parent]
|
422
|
+
$log.debug "XXX: INC increase current #{c} , #{p} "
|
423
|
+
p.decrease c
|
424
|
+
end
|
425
|
+
# ADD HERE ABOVe
|
426
|
+
end # class
|
427
|
+
|
428
|
+
end # module
|
429
|
+
|
430
|
+
# NOTE this is now giving an error ni basestack.rb 87 reg margin_top.
|
431
|
+
# @see teststackflow.rb in examples dir
|
432
|
+
#
|
433
|
+
if __FILE__ == $PROGRAM_NAME
|
434
|
+
require 'rbcurse/core/util/app'
|
435
|
+
App.new do
|
436
|
+
|
437
|
+
lb = Listbox.new nil, :list => ["ruby","perl","lisp","jaava", "c-blunt"] , :name => "mylist"
|
438
|
+
lb1 = Listbox.new nil, :list => ["roger","borg","haas","tsonga", "kolya","delpotro"] , :name => "mylist1"
|
439
|
+
|
440
|
+
lb2 = Listbox.new nil, :list => `gem list --local`.split("\n") , :name => "mylist2"
|
441
|
+
|
442
|
+
alist = %w[ ruby perl python java jruby macruby rubinius rails rack sinatra pylons django cakephp grails]
|
443
|
+
str = "Hello people of this world.\nThis is a textbox.\nUse arrow keys, j/k/h/l/gg/G/C-a/C-e/C-n/C-p\n"
|
444
|
+
str << alist.join("\n")
|
445
|
+
require 'rbcurse/core/widgets/rtextview'
|
446
|
+
tv = TextView.new nil, :name => "text"
|
447
|
+
tv.set_content str
|
448
|
+
=begin
|
449
|
+
f1 = field "name", :maxlen => 20, :display_length => 20, :bgcolor => :white,
|
450
|
+
:color => :black, :text => "abc", :label => " Name: ", :label_color_pair => @datacolor
|
451
|
+
f2 = field "email", :display_length => 20, :bgcolor => :white,
|
452
|
+
:color => :blue, :text => "me@google.com", :label => "Email: ", :label_color_pair => @datacolor
|
453
|
+
f3 = radio :group => :grp, :text => "red", :value => "RED", :color => :red
|
454
|
+
f4 = radio :group => :grp, :text => "blue", :value => "BLUE", :color => :blue
|
455
|
+
f5 = radio :group => :grp, :text => "green", :value => "GREEN", :color => :green
|
456
|
+
=end
|
457
|
+
|
458
|
+
f1 = Field.new nil, :maxlen => 20, :display_length => 20, :bgcolor => :white,
|
459
|
+
:color => :black, :text => "abc", :label => " Name: ", :label_color_pair => @datacolor
|
460
|
+
r = StackFlow.new @form, :row => 1, :col => 2, :width => 80, :height => 25, :title => "A container" do
|
461
|
+
stack :margin_left => 1 do
|
462
|
+
add tv, :weight => 30, :margin_left => 2
|
463
|
+
add lb, :weight => 30
|
464
|
+
flow :weight => 30 do
|
465
|
+
add lb1, :weight => 40
|
466
|
+
add lb2, :weight => 60
|
467
|
+
end
|
468
|
+
add f1
|
469
|
+
end # stack
|
470
|
+
end # r
|
471
|
+
|
472
|
+
#r.add(f1)
|
473
|
+
#r.add(f2)
|
474
|
+
#r.add(f3,f4,f5)
|
475
|
+
#sl = status_line
|
476
|
+
|
477
|
+
end # app
|
478
|
+
end # if
|