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