rbcurse 1.5.0 → 1.5.2
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/Makefile +21 -0
- data/Manifest.txt +6 -0
- data/README.markdown +6 -4
- data/TODO +372 -0
- data/TODO2.txt +121 -0
- data/VERSION +1 -1
- data/examples/README.txt +67 -0
- data/examples/abasiclist.rb +33 -0
- data/examples/alpmenu.rb +42 -0
- data/examples/app.rb +859 -0
- data/examples/app.sample +17 -0
- data/examples/appdirtree.rb +74 -0
- data/examples/appemail.rb +191 -0
- data/examples/appemaillb.rb +308 -0
- data/examples/appgcompose.rb +315 -0
- data/examples/atree.rb +64 -0
- data/examples/common/file.rb +40 -0
- data/examples/common/rmail.rb +257 -0
- data/examples/data.txt +683 -0
- data/examples/data/README.markdown +9 -0
- data/examples/data/brew.txt +38 -0
- data/examples/data/color.2 +37 -0
- data/examples/data/gemlist.txt +60 -0
- data/examples/data/lotr.txt +12 -0
- data/examples/data/ports.txt +136 -0
- data/examples/data/tasks.txt +27 -0
- data/examples/data/todocsv.csv +28 -0
- data/examples/data/unix1.txt +21 -0
- data/examples/data/unix2.txt +11 -0
- data/examples/dbdemo.rb +495 -0
- data/examples/deprecated/appgmail.rb +952 -0
- data/examples/deprecated/splitp.rb +56 -0
- data/examples/deprecated/testscrolllb.rb +86 -0
- data/examples/deprecated/testscrollp.rb +88 -0
- data/examples/deprecated/testscrollta.rb +80 -0
- data/examples/deprecated/testscrolltable.rb +165 -0
- data/examples/deprecated/testsplit.rb +87 -0
- data/examples/deprecated/testsplit2.rb +123 -0
- data/examples/deprecated/testsplit3.rb +215 -0
- data/examples/deprecated/testsplit3_1.rb +244 -0
- data/examples/deprecated/testsplit3a.rb +215 -0
- data/examples/deprecated/testsplit3b.rb +237 -0
- data/examples/deprecated/testsplitta.rb +148 -0
- data/examples/deprecated/testsplittv.rb +142 -0
- data/examples/deprecated/testsplittvv.rb +144 -0
- data/examples/deprecated/testtpane.rb +215 -0
- data/examples/deprecated/testtpane2.rb +145 -0
- data/examples/deprecated/testtpanetable.rb +203 -0
- data/examples/dirtree.rb +88 -0
- data/examples/experimental/resultsetdemo.rb +280 -0
- data/examples/experimental/testmform.rb +35 -0
- data/examples/experimental/testscroller.rb +117 -0
- data/examples/experimental/teststackflow.rb +111 -0
- data/examples/menu1.rb +112 -0
- data/examples/multispl.rb +86 -0
- data/examples/newmessagebox.rb +131 -0
- data/examples/newtabbedwindow.rb +100 -0
- data/examples/newtesttabp.rb +121 -0
- data/examples/qdfilechooser.rb +68 -0
- data/examples/rfe.rb +1239 -0
- data/examples/rfe_renderer.rb +121 -0
- data/examples/sqlc.rb +454 -0
- data/examples/sqlm.rb +437 -0
- data/examples/sqlt.rb +408 -0
- data/examples/status.txt +68 -0
- data/examples/table1.rb +24 -0
- data/examples/term2.rb +84 -0
- data/examples/test1.rb +239 -0
- data/examples/test2.rb +674 -0
- data/examples/testapp.rb +44 -0
- data/examples/testapp2.rb +58 -0
- data/examples/testchars.rb +137 -0
- data/examples/testcombo.rb +91 -0
- data/examples/testkeypress.rb +66 -0
- data/examples/testlistbox.rb +113 -0
- data/examples/testmenu.rb +101 -0
- data/examples/testmulticomp.rb +70 -0
- data/examples/testmulticontainer.rb +94 -0
- data/examples/testmultispl.rb +199 -0
- data/examples/testree.rb +106 -0
- data/examples/testtable.rb +264 -0
- data/examples/testtabp.rb +107 -0
- data/examples/testtodo.rb +584 -0
- data/examples/testvimsplit.rb +112 -0
- data/examples/testwsshortcuts.rb +64 -0
- data/examples/testwsshortcuts2.rb +126 -0
- data/examples/todo.db +0 -0
- data/examples/todo.yml +191 -0
- data/examples/viewtodo.rb +574 -0
- data/lib/rbcurse/deprecated/README.markdown +12 -0
- data/lib/rbcurse/deprecated/rpad.rb +375 -0
- data/lib/rbcurse/deprecated/rscrollpane.rb +512 -0
- data/lib/rbcurse/deprecated/rsplitpane.rb +894 -0
- data/lib/rbcurse/deprecated/rsplitpane2.rb +1009 -0
- data/lib/rbcurse/deprecated/rviewport.rb +204 -0
- data/lib/rbcurse/deprecated/widgets/mapper.rb +130 -0
- data/lib/rbcurse/deprecated/widgets/rmessagebox.rb +348 -0
- data/lib/rbcurse/deprecated/widgets/rtabbedpane.rb +1158 -0
- data/lib/rbcurse/deprecated/widgets/rtabbedwindow.rb +167 -0
- data/lib/rbcurse/deprecated/widgets/scrollable.rb +301 -0
- data/lib/rbcurse/deprecated/widgets/stdscrwindow.rb +309 -0
- data/lib/ver/keyboard2.rb +170 -0
- data/test/test_rbcurse.rb +0 -0
- metadata +131 -9
@@ -0,0 +1,1158 @@
|
|
1
|
+
=begin
|
2
|
+
* Name: tabbed pane: can have multiple forms overlapping.
|
3
|
+
* Description:
|
4
|
+
* A tabbed pane, mostly based (iirc) on the Terminal Preferences in OSX PPC 10.5.x
|
5
|
+
* Starting a new version using pads 2009-10-25 12:05
|
6
|
+
|
7
|
+
2011-10-18 : removed ScrollForm since it would not print if window was not at 0,0 top left.
|
8
|
+
|
9
|
+
|
10
|
+
* Author: rkumar
|
11
|
+
|
12
|
+
--------
|
13
|
+
* Date: 2009-10-25 12:05
|
14
|
+
* License:
|
15
|
+
Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
16
|
+
|
17
|
+
* 2010-02-28 09:47 - major cleanup and rewrite.
|
18
|
+
- Allow adding of component (in addition to form)
|
19
|
+
- Ideally, even form should be created and managed itself, why should TP have to repaint it?
|
20
|
+
|
21
|
+
NOTE:
|
22
|
+
Tp now does not create a form by default, since awefun you may want to just put in one component.
|
23
|
+
Pls use tp.form(tab) to get a form associated with the tab.
|
24
|
+
You may add as many tabs as you wish. To scroll tabs, traverse into the tab form and use the usual scroll keys M-l and M-h to scroll left and right.
|
25
|
+
#
|
26
|
+
# TODO : disable/hide tab ???
|
27
|
+
=end
|
28
|
+
require 'rbcurse'
|
29
|
+
require 'ver/rpad'
|
30
|
+
#require 'rbcurse/experimental/widgets/rscrollform' # tried, shows in all cases teh buttons but never gets control
|
31
|
+
|
32
|
+
KEY_TAB ||= 9
|
33
|
+
KEY_BTAB ||= 353
|
34
|
+
#include Ncurses # FFI 2011-09-8
|
35
|
+
include RubyCurses
|
36
|
+
module RubyCurses
|
37
|
+
extend self
|
38
|
+
|
39
|
+
Event = Struct.new( :tab, :index, :event)
|
40
|
+
|
41
|
+
# Multiple independent overlapping forms using the tabbed metaphor.
|
42
|
+
class TabbedButton < RubyCurses::RadioButton
|
43
|
+
attr_accessor :display_tab_on_traversal
|
44
|
+
def getvalue_for_paint
|
45
|
+
@text
|
46
|
+
end
|
47
|
+
def selected?
|
48
|
+
@variable.value == @value
|
49
|
+
end
|
50
|
+
##
|
51
|
+
# highlight abd selected colors and attribs should perhaps be in a
|
52
|
+
# structure, so user can override easily
|
53
|
+
def repaint # tabbedbutton
|
54
|
+
$log.debug("TabbedBUTTon repaint : #{self.class()} fn:#{@form.name} r:#{@row} c:#{@col} #{getvalue_for_paint} gt #{@form.window.top} gl #{@form.window.left}" )
|
55
|
+
r,c = rowcol
|
56
|
+
attribs = @attrs
|
57
|
+
@highlight_foreground ||= $reversecolor
|
58
|
+
@highlight_background ||= $reversecolor # 0
|
59
|
+
_state = @state
|
60
|
+
_state = :SELECTED if @variable.value == @value
|
61
|
+
color = $datacolor
|
62
|
+
case _state
|
63
|
+
when :HIGHLIGHTED
|
64
|
+
$log.debug("TabbedBUTTon repaint : HIGHLIGHTED #{bgcolor}, #{color}, v: #{@value}" )
|
65
|
+
bgcolor = @highlight_background
|
66
|
+
color = @highlight_foreground
|
67
|
+
bgcolor = @bgcolor
|
68
|
+
color = :red #@color
|
69
|
+
attribs = Ncurses::A_BOLD
|
70
|
+
setrowcol r,c # show cursor on highlighted as we tab through
|
71
|
+
## but when tabbing thru selected one, then selected one doesn't show cursor
|
72
|
+
when :SELECTED
|
73
|
+
$log.debug("TabbedBUTTon repaint : SELECTED #{bgcolor}, #{color}")
|
74
|
+
bgcolor = @bgcolor
|
75
|
+
color = @color
|
76
|
+
attribs = Ncurses::A_REVERSE
|
77
|
+
if @state == :HIGHLIGHTED
|
78
|
+
setrowcol r,c # show cursor on highlighted as we tab through
|
79
|
+
end
|
80
|
+
else
|
81
|
+
$log.debug("TabbedBUTTon repaint : ELSE #{bgcolor}, #{color}")
|
82
|
+
bgcolor = @bgcolor
|
83
|
+
color = @color
|
84
|
+
end
|
85
|
+
#bgcolor = @state==:HIGHLIGHTED ? @highlight_background : @bgcolor
|
86
|
+
#color = @state==:HIGHLIGHTED ? @highlight_foreground : @color
|
87
|
+
if bgcolor.is_a? String and color.is_a? String
|
88
|
+
color = ColorMap.get_color(color, bgcolor)
|
89
|
+
end
|
90
|
+
value = getvalue_for_paint
|
91
|
+
$log.debug("button repaint : r:#{r} #{@graphic.top} c:#{c} #{@graphic.left} color:#{color} bg #{bgcolor} v: #{value}, g: #{@graphic} ")
|
92
|
+
len = @display_length || value.length
|
93
|
+
# paint the tabs name in approp place with attribs
|
94
|
+
#@form.window.printstring r, c, "%-*s" % [len, value], color, attribs
|
95
|
+
#@graphic.printstring r+@graphic.top, c+@graphic.left, "%-*s" % [len, value], color, attribs
|
96
|
+
#@graphic.printstring r-@graphic.top, c-@graphic.left, "%-*s" % [len, value], color, attribs
|
97
|
+
|
98
|
+
ro = @graphic.top
|
99
|
+
co = @graphic.left
|
100
|
+
ro = 0
|
101
|
+
co = 0
|
102
|
+
# NOTE after removing scrollform I've replaced check of graphic with 0, Note if we revert
|
103
|
+
if _state == :HIGHLIGHTED
|
104
|
+
color = $datacolor
|
105
|
+
@graphic.printstring r+ro, c-1+co, ">", color, @attrs unless c-1 < 0 #@graphic.left
|
106
|
+
#@graphic.printstring r, c+len+1, "<", color, @attrs
|
107
|
+
else
|
108
|
+
color = $datacolor
|
109
|
+
@graphic.printstring r+ro, c-1+co, " ", color, @attrs unless c-1 < 0 #@graphic.left
|
110
|
+
#@graphic.printstring r, c+len+1, " ", color, @attrs
|
111
|
+
end
|
112
|
+
@graphic.printstring r+ro, c+co, "%-*s" % [len, value], color, attribs
|
113
|
+
@graphic.modified = true
|
114
|
+
# @form.window.mvchgat(y=r, x=c, max=len, Ncurses::A_NORMAL, bgcolor, nil)
|
115
|
+
# underline for the top tab buttons.
|
116
|
+
if @underline != nil
|
117
|
+
# 2011-10-17 cmmented
|
118
|
+
r -= @graphic.top # because of pad, remove if we go back to windows
|
119
|
+
c -= @graphic.left # because of pad, remove if we go back to windows
|
120
|
+
@graphic.mvchgat(y=r, x=c+@underline+0, max=1, Ncurses::A_BOLD|Ncurses::A_UNDERLINE, color, nil)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
# trying to give the option so as we tab through buttons, the relevant tab opens
|
124
|
+
# but this is getting stuck on a tab and not going on
|
125
|
+
# fire() is causing the problem
|
126
|
+
# fire takes the focus into tab area so the next TAB goes back to first button
|
127
|
+
# due to current_tab = tab (so next key stroke goes to tab)
|
128
|
+
def on_enter
|
129
|
+
$log.debug " overridden on_enter of tabbedbutton #{@name}, foc #{@focussed} "
|
130
|
+
super
|
131
|
+
#@state = :HIGHLIGHTED if @focussed
|
132
|
+
$log.debug " overridden on_enter of tabbedbutton #{@name} state #{@state}, foc #{@focussed} "
|
133
|
+
$log.debug " calling fire overridden on_enter of tabbedbutton" if @display_tab_on_traversal
|
134
|
+
fire if @display_tab_on_traversal
|
135
|
+
end
|
136
|
+
# In order to get tab display as we traverse buttons, we need to tamper with KEY_DOWN
|
137
|
+
# since that's the only way of getting down to selected tab in this case.
|
138
|
+
def handle_key ch # tabbed button
|
139
|
+
case ch
|
140
|
+
when KEY_DOWN
|
141
|
+
# form will not do a next_field, it will ignore this
|
142
|
+
@state = :NORMAL # 2011-10-5
|
143
|
+
@form.repaint
|
144
|
+
return :NO_NEXT_FIELD
|
145
|
+
when KEY_RIGHT
|
146
|
+
ret = @form.select_next_field
|
147
|
+
ret = @form.select_first_field if ret == :NO_NEXT_FIELD
|
148
|
+
return ret
|
149
|
+
when KEY_LEFT
|
150
|
+
ret = @form.select_prev_field
|
151
|
+
ret = @form.select_last_field if ret == :NO_PREV_FIELD
|
152
|
+
return ret
|
153
|
+
when KEY_ENTER, 10, 13, 32 # added space bar also
|
154
|
+
if respond_to? :fire
|
155
|
+
fire
|
156
|
+
end
|
157
|
+
else
|
158
|
+
# all thrse will be re-evaluated by form
|
159
|
+
return :UNHANDLED
|
160
|
+
end
|
161
|
+
end
|
162
|
+
0
|
163
|
+
end
|
164
|
+
##
|
165
|
+
# extending Widget from 2009-10-08 18:45
|
166
|
+
# It should extend Widget so we can pop it in a form. In fact it should be in a form,
|
167
|
+
# we should not have tried to make it standalone like messagebox.
|
168
|
+
# This is the main TabbedPane widget that will be slung into a form
|
169
|
+
class TabbedPane < Widget
|
170
|
+
TAB_ROW_OFFSET = 3 # what row should tab start on (was 4 when printing subheader)
|
171
|
+
TAB_COL_OFFSET = 0 # what col should tab start on (to save space, flush on left)
|
172
|
+
dsl_accessor :button_type # ok, ok_cancel, yes_no
|
173
|
+
dsl_accessor :buttons # used if type :custom
|
174
|
+
|
175
|
+
# set to true if you want tabs to show as you traverse the tab buttons
|
176
|
+
dsl_accessor :display_tab_on_traversal
|
177
|
+
|
178
|
+
# creates a scrollable form so many buttons can be placed
|
179
|
+
# NOTE: this only works in a root window, I can't figure out why.
|
180
|
+
dsl_accessor :scrolling_allowed
|
181
|
+
|
182
|
+
attr_reader :selected_index
|
183
|
+
attr_reader :current_tab
|
184
|
+
attr_reader :window
|
185
|
+
def initialize form, aconfig={}, &block
|
186
|
+
@scrolling_allowed = false
|
187
|
+
super
|
188
|
+
@parent = form
|
189
|
+
@parentwin = form.window
|
190
|
+
@visible = true
|
191
|
+
@focusable= true
|
192
|
+
@tabs ||= []
|
193
|
+
@forms ||= []
|
194
|
+
@attr = nil
|
195
|
+
@current_form = nil
|
196
|
+
@current_tab = nil
|
197
|
+
@config = aconfig
|
198
|
+
@col_offset = 2; @row_offset = 1 # added 2010-01-10 22:54
|
199
|
+
@recreate_buttons = true
|
200
|
+
install_keys
|
201
|
+
@_events.push(*[:OPEN, :INSERT, :DELETE])
|
202
|
+
@on_main_form = true # 2011-10-4 we need to know this i think
|
203
|
+
end
|
204
|
+
def install_keys
|
205
|
+
@form.bind_key([?d, ?d]) { ix = highlighted_tab_index; repeatm { remove_tab(ix) } }
|
206
|
+
@form.bind_key(?u) { undelete_tab; }
|
207
|
+
@form.bind_key(?p) { paste_tab 0; } # paste before or at position
|
208
|
+
@form.bind_key(?P) { paste_tab 1; } # paste deleted tab after this one
|
209
|
+
@form.bind_key([?c, ?w]) { change_label }
|
210
|
+
@form.bind_key(?C) { change_label }
|
211
|
+
end
|
212
|
+
##
|
213
|
+
# This is a public, user called method for appending a new tab
|
214
|
+
# This will be called several times for one TP.
|
215
|
+
# when adding tabs, you may use ampersand in text to create hotkey
|
216
|
+
# XXX adding a tab later does not influence buttons array,
|
217
|
+
def add_tab text, component = nil, aconfig={}, &block
|
218
|
+
index = @tabs.size
|
219
|
+
tab = insert_tab text, component, index, aconfig, &block
|
220
|
+
return tab
|
221
|
+
end
|
222
|
+
alias :add :add_tab
|
223
|
+
|
224
|
+
## insert a component at given index
|
225
|
+
# index cannnot be greater than size of tab count
|
226
|
+
def insert_tab text, component, index, aconfig={}, &block
|
227
|
+
$log.debug " TAB insert #{text} at #{index} "
|
228
|
+
@tabs[index] = Tab.new(text, self, aconfig, &block)
|
229
|
+
tab = @tabs[index]
|
230
|
+
|
231
|
+
# trying out setting current tab when created 2011-10-4
|
232
|
+
# This situation happens when someone externally is setting data
|
233
|
+
# in a tab and calling its repaint. When user presses DOWN from button
|
234
|
+
# it was crashing. Old tab is the tab you see below. current_tab takes on
|
235
|
+
# either form or old_tab to distinguish whether we are on top buttons or
|
236
|
+
# inside a tab.
|
237
|
+
@old_tab = tab
|
238
|
+
|
239
|
+
tform = form(tab) # this could go inside Tab constructor now 2011-10-4
|
240
|
+
#tab.component = component unless component.nil? # changed on 2011-10-3 CLEAN
|
241
|
+
#component.form = tform unless component.nil? # changed on 2011-10-3 CLEAN
|
242
|
+
component.set_form( tform) unless component.nil? # changed on 2011-10-3
|
243
|
+
configure_component component unless component.nil?
|
244
|
+
tab.index = index # so i can undelete !!!
|
245
|
+
fire_event tab, index, :INSERT
|
246
|
+
@recreate_buttons = true
|
247
|
+
return tab
|
248
|
+
end
|
249
|
+
## remove given tab based on index
|
250
|
+
# This does not unbind the key mapping, FIXME
|
251
|
+
# Currently, can be invoked by 'dd' over highlighted button
|
252
|
+
# XXX can append to deleted_tabs, then on insert or paste insert with splat.
|
253
|
+
def remove_tab index
|
254
|
+
@recreate_buttons = true
|
255
|
+
$log.debug " inside remove_tab with #{index}, #{@tabs.size} "
|
256
|
+
@deleted_tab = @tabs.delete_at(index) unless @tabs.size < index
|
257
|
+
# note this is the index it was at.
|
258
|
+
fire_event @deleted_tab, index, :DELETE
|
259
|
+
end
|
260
|
+
##
|
261
|
+
# Move this fun stuff to a util class. TODO
|
262
|
+
# If tab deleted accidentally, undelete it
|
263
|
+
# Okay, i just can stop myself from having a little fun
|
264
|
+
def undelete_tab
|
265
|
+
return unless @deleted_tab
|
266
|
+
@recreate_buttons = true
|
267
|
+
@tabs.insert(@deleted_tab.index, @deleted_tab)
|
268
|
+
fire_event @deleted_tab, @deleted_tab.index, :INSERT
|
269
|
+
@deleted_tab = nil
|
270
|
+
$log.debug " undelete over #{@tabs.size} "
|
271
|
+
end
|
272
|
+
def paste_tab pos
|
273
|
+
return unless @deleted_tab
|
274
|
+
ix = highlighted_tab_index
|
275
|
+
return if ix == -1
|
276
|
+
@recreate_buttons = true
|
277
|
+
@deleted_tab.index = ix + pos
|
278
|
+
@tabs.insert(@deleted_tab.index, @deleted_tab)
|
279
|
+
fire_event @deleted_tab, @deleted_tab.index, :INSERT
|
280
|
+
@deleted_tab = nil
|
281
|
+
$log.debug " paste over #{@tabs.size} #{ix} + #{pos} "
|
282
|
+
end
|
283
|
+
|
284
|
+
##
|
285
|
+
# prompts for a new label for a tab - taking care of mnemonics if ampersand present
|
286
|
+
# Currently, mapped to 'C' and 'cw' when cursor is on a label
|
287
|
+
# Perhaps some of this kind of utility stuff needs to go into a util class.
|
288
|
+
#
|
289
|
+
def change_label
|
290
|
+
ix = highlighted_tab_index
|
291
|
+
return if ix < 0
|
292
|
+
prompt = "Enter new label: "
|
293
|
+
label = @buttons[ix].text
|
294
|
+
config = {}
|
295
|
+
config[:default] = label.dup
|
296
|
+
maxlen = 10
|
297
|
+
ret, str = rbgetstr(@graphic, $error_message_row, $error_message_col, prompt, maxlen, config)
|
298
|
+
if ret == 0 and str != "" and str != label
|
299
|
+
@tabs[ix].text = str
|
300
|
+
@buttons[ix].text(str)
|
301
|
+
@recreate_buttons = true
|
302
|
+
end
|
303
|
+
end
|
304
|
+
##
|
305
|
+
# returns the index of the tab cursor is on (not the one that is selected)
|
306
|
+
# @return [0..] index, or -1 if some error
|
307
|
+
def highlighted_tab_index
|
308
|
+
@form.widgets.each_with_index{ |w, ix|
|
309
|
+
return ix if w.state == :HIGHLIGHTED
|
310
|
+
}
|
311
|
+
return -1
|
312
|
+
end
|
313
|
+
def selected_tab_index
|
314
|
+
@form.widgets.each_with_index{ |w, ix|
|
315
|
+
return ix if w.selected?
|
316
|
+
}
|
317
|
+
return -1
|
318
|
+
end
|
319
|
+
## remove all tabs
|
320
|
+
def remove_all
|
321
|
+
if !@buttons.empty?
|
322
|
+
@buttons.each {|e| @form.remove_widget(e) }
|
323
|
+
end
|
324
|
+
@buttons = []
|
325
|
+
@tabs = []
|
326
|
+
@recreate_buttons = true
|
327
|
+
end
|
328
|
+
|
329
|
+
## return a form for use by program - if you want to put multiple items
|
330
|
+
# Otherwise just use add_component
|
331
|
+
# private - can't use externally
|
332
|
+
def configure_component component
|
333
|
+
#component.set_form @parent <<--- definitely NOT
|
334
|
+
#component.form = @parent # changed on 2011-10-2
|
335
|
+
component.rows_panned = component.cols_panned = 0
|
336
|
+
component.parent_component = self # added 2010-02-27 so offsets can go down ?
|
337
|
+
|
338
|
+
$log.debug "XXX: TABBED #{@row} #{@col} #{@height} #{@width} "
|
339
|
+
component.row ||= 0 # 2011-10-3 @row + TAB_ROW_OFFSET # 2
|
340
|
+
component.col ||= 0 #@col + TAB_COL_OFFSET
|
341
|
+
component.width ||= @width #@col + TAB_COL_OFFSET
|
342
|
+
component.height ||= @height - 2 #@col + TAB_COL_OFFSET
|
343
|
+
$log.debug "XXX: TABBED #{component.row} #{component.col} #{component.height} #{component.width} "
|
344
|
+
|
345
|
+
# current_form likely to be nil XXX
|
346
|
+
scr_top = component.row # for Pad, if Pad passed as in SplitPane
|
347
|
+
scr_left = component.col # for Pad, if Pad passed as in SplitPane
|
348
|
+
ho = TAB_ROW_OFFSET + 2 # 5
|
349
|
+
component.set_buffering(:target_window => @target_window || @parentwin, :form => @current_form, :bottom => @height-ho, :right => @width-2, :screen_top => scr_top, :screen_left => scr_left)
|
350
|
+
# if left nil, then we expand the comp
|
351
|
+
component.height ||= @height - (ho - 1) # 1 keeps lower border inside by 1
|
352
|
+
component.width ||= @width - 0 # 0 keeps it flush on right border
|
353
|
+
|
354
|
+
|
355
|
+
end
|
356
|
+
## create a form for tab, if multiple components are to be placed inside tab.
|
357
|
+
# Tabbedpane has no control over placement and width etc of what's inside a form
|
358
|
+
def form tab
|
359
|
+
if !tab.has_form?
|
360
|
+
@forms << create_tab_form(tab)
|
361
|
+
tab.form = @forms.last
|
362
|
+
end
|
363
|
+
return tab.form
|
364
|
+
end
|
365
|
+
|
366
|
+
## returns the index of the current / selected tab
|
367
|
+
## @returns 0.. index of selected tab
|
368
|
+
def selected_tab_index
|
369
|
+
@tabs.index(@current_tab)
|
370
|
+
end
|
371
|
+
|
372
|
+
# private
|
373
|
+
def variable_set var, val
|
374
|
+
var = "@#{var}"
|
375
|
+
instance_variable_set(var, val)
|
376
|
+
end
|
377
|
+
# private
|
378
|
+
def configure(*val , &block)
|
379
|
+
case val.size
|
380
|
+
when 1
|
381
|
+
return @config[val[0]]
|
382
|
+
when 2
|
383
|
+
@config[val[0]] = val[1]
|
384
|
+
variable_set(val[0], val[1])
|
385
|
+
end
|
386
|
+
instance_eval &block if block_given?
|
387
|
+
end
|
388
|
+
## this is a really wierd repaint method.
|
389
|
+
# First time it creates the TP window/form which contains the buttons.
|
390
|
+
# In future calls it really doesn't do anything.
|
391
|
+
# Deos it have nothing to paint, no borders to redraw, no repaint_required ???
|
392
|
+
def repaint
|
393
|
+
$log.debug " tabbedpane repaint "
|
394
|
+
@window || create_window
|
395
|
+
_recreate_buttons if @recreate_buttons
|
396
|
+
$log.debug " tabbedpane repaint #{@window.name} "
|
397
|
+
@window.show
|
398
|
+
@window.wrefresh # trying out FFI 2011-09-19 since form not being refreshed
|
399
|
+
#x set_buffer_modified()
|
400
|
+
end
|
401
|
+
def show
|
402
|
+
repaint
|
403
|
+
end
|
404
|
+
## recreate all buttons
|
405
|
+
# We call this if even one is added : adv is we can space out accordinagly if the numbers increase
|
406
|
+
# We could also expand the pad here.
|
407
|
+
# Test it out with removing tabs to.
|
408
|
+
# XXX have to remove buttons from the form
|
409
|
+
def _recreate_buttons
|
410
|
+
$log.debug " inside recreate_buttons: #{@tabs.size} "
|
411
|
+
r = @row
|
412
|
+
col = @col + 1
|
413
|
+
@buttons ||= []
|
414
|
+
#
|
415
|
+
# NOTE: since we remove buttons, setting any events on them is useless !
|
416
|
+
#
|
417
|
+
if !@buttons.empty?
|
418
|
+
@buttons.each {|e| @form.remove_widget(e) }
|
419
|
+
end
|
420
|
+
@buttons = []
|
421
|
+
button_gap = 4
|
422
|
+
# the next line necessitates a clear on the pad
|
423
|
+
# button_gap = 1 if @tabs.size > 6 # quick dirty fix, we need something that checks fit
|
424
|
+
# we may also need to truncate text to fit
|
425
|
+
|
426
|
+
@buttonpad.wclear if @buttonpad # 2011-10-18
|
427
|
+
## create a button for each tab
|
428
|
+
$tabradio = Variable.new # so we know which is highlighted
|
429
|
+
# 2011-10-17 seems objects on this form do not get windows offset
|
430
|
+
wco = @window.left
|
431
|
+
wro = @window.top
|
432
|
+
@tabs.each do |tab|
|
433
|
+
text = tab.text
|
434
|
+
$log.debug " TABS EACH #{text}, #{wro} #{wco} "
|
435
|
+
@buttons << RubyCurses::TabbedButton.new(@form) do
|
436
|
+
variable $tabradio
|
437
|
+
text text
|
438
|
+
name text
|
439
|
+
value text
|
440
|
+
row r + 1 #+ wro
|
441
|
+
col col #+ wco
|
442
|
+
end
|
443
|
+
col += text.length + button_gap
|
444
|
+
# if col exceeds pad_w then we need to expand pad
|
445
|
+
# but here we don't know that a pad is being used
|
446
|
+
$log.debug " button col #{col} "
|
447
|
+
# form = tab.form # changed 2011 2011-09-26
|
448
|
+
form = form(tab)
|
449
|
+
form.set_parent_buffer(@window) if form
|
450
|
+
|
451
|
+
b = @buttons.last
|
452
|
+
b.display_tab_on_traversal = @display_tab_on_traversal # 2011-10-4
|
453
|
+
tab._button(b) # too late, user needs this when tab is created FIXME
|
454
|
+
b.command(b, @form) {
|
455
|
+
$log.debug " calling tab.repaint,button_form_repaint from button press #{b.name} #{b.state} "
|
456
|
+
# form.rep essentially sees that buttons get correct attributes
|
457
|
+
# when triggering M-<char>. This button should get highlighted.
|
458
|
+
tab.repaint
|
459
|
+
|
460
|
+
# the on_leave of current button does not get fired, so it gets
|
461
|
+
# left in a HIGHLIGHTED state, so two can show highlighted at the same time 2011-10-5
|
462
|
+
#
|
463
|
+
@form.widgets.each { |tb| @form.on_leave(tb) if tb.state == :HIGHLIGHTED }
|
464
|
+
|
465
|
+
button_form_repaint #( b.state == :HIGHLIGHTED )
|
466
|
+
if @display_tab_on_traversal
|
467
|
+
# set as old tab so ONLY on going down this becomes current_tab
|
468
|
+
@old_tab = tab
|
469
|
+
else
|
470
|
+
# next line means next key is IMMED taken by the tab not main form
|
471
|
+
@current_tab = tab
|
472
|
+
end
|
473
|
+
$log.debug "TAB : form #{tab.form}, #{tab.form.widgets.first} "
|
474
|
+
c = tab.form.widgets.first
|
475
|
+
c.set_form_row; c.set_form_col
|
476
|
+
fire_event tab, tab.index, :OPEN
|
477
|
+
}
|
478
|
+
end
|
479
|
+
@recreate_buttons = false
|
480
|
+
# make the buttons visible now, not after next handle_key
|
481
|
+
@form.repaint
|
482
|
+
end
|
483
|
+
## This form is for the tabbed buttons on top
|
484
|
+
def create_window
|
485
|
+
set_buffer_modified() # required still ??
|
486
|
+
# first create the main top window with the tab buttons on it.
|
487
|
+
$log.debug " TPane create_buff Top #{@row}, Left #{@col} H #{@height} W #{@width} "
|
488
|
+
#$log.debug " parentwin #{@parentwin.left} #{@parentwin.top} "
|
489
|
+
|
490
|
+
r = @row
|
491
|
+
c = @col
|
492
|
+
# NOTE: I had to remove ScrollForm since it would not work if
|
493
|
+
# window was not root window. This means I cannot have more buttons
|
494
|
+
# than fit on screen.
|
495
|
+
|
496
|
+
|
497
|
+
# tried out proper ScrollForm, it shows the buttons but does not take
|
498
|
+
# cursor there or show cursor there at all.
|
499
|
+
|
500
|
+
scrolling = @scrolling_allowed
|
501
|
+
if scrolling
|
502
|
+
@form = ScrollForm.new(@parentwin)
|
503
|
+
# 2011-10-18 trying to see why buttons won't print if window is not 0,0
|
504
|
+
#@form.parent_form = @parent # 2011-10-18 trying out why buttons not coming at correct place
|
505
|
+
#@form.add_cols = @parent.window.left
|
506
|
+
#@form.add_rows = @parent.window.top
|
507
|
+
offset = 1
|
508
|
+
@form.set_layout(1, @width, @row+offset, @col+offset)
|
509
|
+
@form.display_h = 1
|
510
|
+
@form.display_w = @width-3
|
511
|
+
@buttonpad = @form.create_pad
|
512
|
+
@buttonpad.name = "Window::TPTOPPAD" # 2010-02-02 20:01
|
513
|
+
|
514
|
+
else
|
515
|
+
@form = Form.new(@parentwin)
|
516
|
+
end
|
517
|
+
|
518
|
+
|
519
|
+
## We will use the parent window, and not a pad. We will write absolute coordinates.
|
520
|
+
@window = @parentwin
|
521
|
+
color = $datacolor
|
522
|
+
# border around button bar. should this not be in scrollform as a border ? XXX
|
523
|
+
@window.print_border @row, @col, 2, @width, color #, Ncurses::A_REVERSE
|
524
|
+
@form.name = "Form::TPTOPFORM"
|
525
|
+
$log.debug("TP WINDOW TOP ? PAD MAIN FORM W:#{@window.name}, F:#{@form.name} ")
|
526
|
+
@form.parent_form = @parent ## 2010-01-21 15:55 TRYING OUT BUFFERED
|
527
|
+
@form.navigation_policy = :NON_CYCLICAL
|
528
|
+
|
529
|
+
Ncurses::Panel.update_panels
|
530
|
+
_recreate_buttons
|
531
|
+
|
532
|
+
@old_tab = @tabs.first
|
533
|
+
@old_tab.repaint if @old_tab
|
534
|
+
button_form_repaint true
|
535
|
+
@window.wrefresh ## ADDED 2009-11-02 23:29
|
536
|
+
#@buttons.first().fire unless @buttons.empty? # make the first form active to start with.
|
537
|
+
#@current_tab = nil # 2011-10-3 otherwise keys go to this form in the beginning
|
538
|
+
@current_tab = @form # 2011-10-4 buttons form should be first
|
539
|
+
end
|
540
|
+
#
|
541
|
+
# Handle placing control in first or last button.
|
542
|
+
# R
|
543
|
+
def on_enter
|
544
|
+
if $current_key == KEY_BTAB
|
545
|
+
c = @form.widgets.count-1
|
546
|
+
@form.select_field c
|
547
|
+
else
|
548
|
+
@form.select_field 0
|
549
|
+
end
|
550
|
+
end
|
551
|
+
def button_form_repaint flag = true
|
552
|
+
$log.debug " INSIDE button_form_repaint #{flag} "
|
553
|
+
if flag
|
554
|
+
# repaint form and refresh pad
|
555
|
+
@form.repaint
|
556
|
+
else
|
557
|
+
# only refresh pad
|
558
|
+
# - 2011-09-19 I don't think this is called, prolly give an error
|
559
|
+
@form.prefresh
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
##
|
564
|
+
# This creates a form for the tab, in case we wish to put many components in it.
|
565
|
+
# Else just pass single components in add_tab.
|
566
|
+
# @params tab tab just created for which a form is required
|
567
|
+
# @return form - a pad based form
|
568
|
+
def create_tab_form tab
|
569
|
+
|
570
|
+
mtop = 0
|
571
|
+
mleft = 0
|
572
|
+
bottom_offset = 2 # 0 will overwrite bottom line, 1 will make another line for inner form
|
573
|
+
layout = { :height => @height-(mtop+bottom_offset), :width => @width, :top => mtop, :left => mleft }
|
574
|
+
window = VER::Pad.create_with_layout(layout)
|
575
|
+
|
576
|
+
form = RubyCurses::Form.new window
|
577
|
+
|
578
|
+
form.parent_form = @parent
|
579
|
+
form.add_cols = @col + 0
|
580
|
+
form.add_rows = @row + 2
|
581
|
+
|
582
|
+
|
583
|
+
form.navigation_policy = :NON_CYCLICAL
|
584
|
+
window.bkgd(Ncurses.COLOR_PAIR($datacolor));
|
585
|
+
window.box(0, 0);
|
586
|
+
window.mvwaddch(0, 0, Ncurses::ACS_LTEE) # beautify the corner 2010-02-06 19:35
|
587
|
+
window.mvwaddch(0, @width-1, Ncurses::ACS_RTEE)
|
588
|
+
window.mvwaddch(layout[:height]-1, 0, Ncurses::ACS_LTEE) # beautify the corner 2010-02-06 19:35
|
589
|
+
window.mvwaddch(layout[:height]-1, @width-1, Ncurses::ACS_RTEE)
|
590
|
+
|
591
|
+
## this prints the tab name on top left
|
592
|
+
window.mvprintw(1,1, tab.text.tr('&', '')) if @print_subheader
|
593
|
+
window.name = "Tab::TAB-#{tab.text}"
|
594
|
+
form.name = "Form::TAB-#{tab.text}"
|
595
|
+
return form
|
596
|
+
end
|
597
|
+
##
|
598
|
+
# added 2009-10-08 19:39 so it can be placed in a form
|
599
|
+
# @form is the top button form
|
600
|
+
# XXX stop this nonsense about current_form and current_tab
|
601
|
+
# TP should only be concerned with tabs. what happens inside is none of its business
|
602
|
+
def handle_key(ch) # tabbed pane TP
|
603
|
+
|
604
|
+
|
605
|
+
if @current_tab == @form # on main form
|
606
|
+
return :UNHANDLED if ch == ?\M-\C-i.getbyte(0) # alt-tab to exit
|
607
|
+
ret = @form.handle_key ch
|
608
|
+
$log.debug "TP HNDLE KEY got ret #{ret}, ch #{ch} "
|
609
|
+
ret ||= :UNHANDLED
|
610
|
+
if ret == :UNHANDLED
|
611
|
+
$log.warn "unhandled key in TP main #{ch} "
|
612
|
+
case ch
|
613
|
+
when KEY_RIGHT, KEY_LEFT, KEY_TAB, KEY_BTAB
|
614
|
+
when KEY_UP
|
615
|
+
when KEY_DOWN
|
616
|
+
when KEY_TAB
|
617
|
+
when KEY_BTAB
|
618
|
+
end
|
619
|
+
elsif ret == :NO_NEXT_FIELD
|
620
|
+
case ch
|
621
|
+
when KEY_TAB
|
622
|
+
return :UNHANDLED
|
623
|
+
when KEY_RIGHT, KEY_DOWN, ?j.getbyte(0)
|
624
|
+
|
625
|
+
@current_tab = @old_tab
|
626
|
+
if @current_tab
|
627
|
+
@current_tab.set_focus :FIRST
|
628
|
+
else
|
629
|
+
alert "Need to press enter on button"
|
630
|
+
if @current_tab.nil? ##or @current_tab.is_a? Form
|
631
|
+
w = @form.widgets.first
|
632
|
+
w.fire if w.respond_to? :fire
|
633
|
+
end
|
634
|
+
end
|
635
|
+
return 0
|
636
|
+
when KEY_UP
|
637
|
+
when KEY_BTAB
|
638
|
+
end
|
639
|
+
return ret
|
640
|
+
elsif ret == :NO_PREV_FIELD
|
641
|
+
case ch
|
642
|
+
when KEY_LEFT, KEY_BTAB, ?k.getbyte(0)
|
643
|
+
$log.debug "LEFT BTAB when no previous field"
|
644
|
+
return :UNHANDLED
|
645
|
+
end
|
646
|
+
return ret
|
647
|
+
end
|
648
|
+
return 0
|
649
|
+
end
|
650
|
+
|
651
|
+
$log.debug " handle_key in tabbed pane got : #{ch}, #{@current_tab}, f: #{@form} "
|
652
|
+
@current_tab ||= @form # first we cycle buttons
|
653
|
+
$log.debug " handle_key in tabbed pane got : #{ch}, giving to #{@current_tab} "
|
654
|
+
# needs to go to component
|
655
|
+
ret = @current_tab.handle_key(ch)
|
656
|
+
$log.debug " -- form.handle_key in tabbed pane got ret : #{ret} , #{@current_tab} , #{ch} "
|
657
|
+
|
658
|
+
# components will usually return UNHANDLED for a tab or btab
|
659
|
+
# We need to convert it so the main form can use it
|
660
|
+
if @current_tab != @form
|
661
|
+
if ret == :UNHANDLED
|
662
|
+
if ch == KEY_TAB #or ch == KEY_DOWN
|
663
|
+
ret = :NO_NEXT_FIELD
|
664
|
+
elsif ch == KEY_BTAB or ch == ?k.getbyte(0) #or ch == KEY_UP # btab
|
665
|
+
ret = :NO_PREV_FIELD
|
666
|
+
end
|
667
|
+
end
|
668
|
+
else
|
669
|
+
# key down pressed in top form, go to tab
|
670
|
+
if ch == KEY_DOWN
|
671
|
+
ret = :NO_NEXT_FIELD
|
672
|
+
end
|
673
|
+
end
|
674
|
+
|
675
|
+
case ret
|
676
|
+
when :NO_NEXT_FIELD
|
677
|
+
#alert "came to no nex field" # CLEAN
|
678
|
+
if @current_tab != @form
|
679
|
+
#alert "case 1 no next field req first" # changed on 2011-10-2 # 2011-10-04 16:12:34
|
680
|
+
## if no next field on a subform go to first button of main form
|
681
|
+
@old_tab = @current_tab
|
682
|
+
@current_tab = @form
|
683
|
+
@form.req_first_field
|
684
|
+
|
685
|
+
else
|
686
|
+
# on top button panel - no more buttons, go to tabs first field
|
687
|
+
if @old_tab # in case of empty tabbed pane old_tab was nil
|
688
|
+
#alert "case 2 no next field set focus" # CLEANUP # 2011-10-04 16:12:46
|
689
|
+
@current_tab = @old_tab
|
690
|
+
@current_tab.set_focus :FIRST
|
691
|
+
end
|
692
|
+
end
|
693
|
+
when :NO_PREV_FIELD
|
694
|
+
if @current_tab != @form
|
695
|
+
$log.debug "TP 1 no prev field - going to last button "
|
696
|
+
@old_tab = @current_tab
|
697
|
+
@current_tab = @form
|
698
|
+
@form.req_last_field
|
699
|
+
else
|
700
|
+
# on top button panel - no prev buttons, go to tabs last field
|
701
|
+
if @old_tab # in case of one tab
|
702
|
+
@current_tab = @old_tab
|
703
|
+
@current_tab.set_focus :LAST
|
704
|
+
end
|
705
|
+
end
|
706
|
+
when :UNHANDLED
|
707
|
+
$log.debug " unhandled in tabbed pane #{ch}"
|
708
|
+
ret = @form.process_key ch, self # field
|
709
|
+
|
710
|
+
return ret if ret == :UNHANDLED
|
711
|
+
end
|
712
|
+
if @buttonpad && @buttonpad.modified
|
713
|
+
button_form_repaint
|
714
|
+
end
|
715
|
+
end
|
716
|
+
##
|
717
|
+
# ensure that the pads are being destroyed, although we've not found a way.
|
718
|
+
def destroy
|
719
|
+
@window.destroy
|
720
|
+
@forms.each { |f| w = f.window; w.destroy unless w.nil? }
|
721
|
+
end
|
722
|
+
def create_buttons
|
723
|
+
case @button_type.to_s.downcase
|
724
|
+
when "ok"
|
725
|
+
make_buttons ["&OK"]
|
726
|
+
when "ok_cancel" #, "input", "list", "field_list"
|
727
|
+
make_buttons %w[&OK &Cancel]
|
728
|
+
when "yes_no"
|
729
|
+
make_buttons %w[&Yes &No]
|
730
|
+
when "yes_no_cancel"
|
731
|
+
make_buttons ["&Yes", "&No", "&Cancel"]
|
732
|
+
when "custom"
|
733
|
+
raise "Blank list of buttons passed to custom" if @buttons.nil? or @buttons.size == 0
|
734
|
+
make_buttons @buttons
|
735
|
+
else
|
736
|
+
$log.debug "No buttontype passed for creating tabbedpane. Using default (OK)"
|
737
|
+
make_buttons ["&OK"]
|
738
|
+
end
|
739
|
+
end
|
740
|
+
def make_buttons names
|
741
|
+
$log.debug "XXX: came to TP make lower buttons FORM #{@form.name} "
|
742
|
+
total = names.inject(0) {|total, item| total + item.length + 4}
|
743
|
+
bcol = center_column total
|
744
|
+
|
745
|
+
brow = @layout[:height]-2 # check for < 0
|
746
|
+
button_ct=0
|
747
|
+
names.each_with_index do |bname, ix|
|
748
|
+
text = bname
|
749
|
+
#underline = @underlines[ix] if !@underlines.nil?
|
750
|
+
|
751
|
+
button = Button.new @form do
|
752
|
+
text text
|
753
|
+
name bname
|
754
|
+
row brow
|
755
|
+
col bcol
|
756
|
+
#underline underline
|
757
|
+
highlight_background $reversecolor
|
758
|
+
color $datacolor
|
759
|
+
bgcolor $datacolor
|
760
|
+
end
|
761
|
+
index = button_ct
|
762
|
+
button.command { |form| @selected_index = index; @stop = true; $log.debug "Pressed Button #{bname}";}
|
763
|
+
button_ct += 1
|
764
|
+
bcol += text.length+6
|
765
|
+
end
|
766
|
+
end
|
767
|
+
def center_column textlen
|
768
|
+
width = @layout[:width] # check for 0 XXX
|
769
|
+
return (width-textlen)/2
|
770
|
+
end
|
771
|
+
def fire_event tab, index, event
|
772
|
+
# experimenting with structs, earlier we've used classes
|
773
|
+
if @tabbedpane_event.nil?
|
774
|
+
@tabbedpane_event = Event.new
|
775
|
+
end
|
776
|
+
@tabbedpane_event.tab = tab
|
777
|
+
@tabbedpane_event.index = index
|
778
|
+
@tabbedpane_event.event = event
|
779
|
+
fire_handler event, @tabbedpane_event
|
780
|
+
end
|
781
|
+
|
782
|
+
##
|
783
|
+
# nested class tab
|
784
|
+
# A user created tab, with its own form
|
785
|
+
class Tab
|
786
|
+
attr_accessor :text
|
787
|
+
attr_reader :config
|
788
|
+
attr_reader :component # 2011-10-4 DEPRECATED
|
789
|
+
#attr_accessor :form
|
790
|
+
attr_accessor :parent_component
|
791
|
+
attr_accessor :index
|
792
|
+
attr_accessor :button # so you can set an event on it 2011-10-4
|
793
|
+
def initialize text, parent_component, aconfig={}, &block
|
794
|
+
@text = text
|
795
|
+
@config = aconfig
|
796
|
+
@parent_component = parent_component
|
797
|
+
@config.each_pair { |k,v| variable_set(k,v) }
|
798
|
+
instance_eval &block if block_given?
|
799
|
+
end
|
800
|
+
## add a single component to the tab
|
801
|
+
# Calling this a second time will overwrite the existing component
|
802
|
+
# @deprecated since 2011-10-4 1.3.1
|
803
|
+
def component=(component)
|
804
|
+
@form.add_widget component
|
805
|
+
raise "Component cannot be null" unless component
|
806
|
+
raise "Component already associated with a form. Do not pass form in constructor." unless component.form.nil?
|
807
|
+
$log.debug " calling configure component "
|
808
|
+
@parent_component.configure_component component
|
809
|
+
#@component = component # changed on 2011-10-3
|
810
|
+
end
|
811
|
+
# @deprecated since 2011-10-4 1.3.1
|
812
|
+
def remove_component
|
813
|
+
@form.widgets.pop # 2011-10-4
|
814
|
+
#@component = nil
|
815
|
+
end
|
816
|
+
# private
|
817
|
+
def variable_set var, val
|
818
|
+
var = "@#{var}"
|
819
|
+
instance_variable_set(var, val)
|
820
|
+
end
|
821
|
+
# tab should handle key instead of TP.
|
822
|
+
# Pass to component or form
|
823
|
+
def handle_key ch # Tab
|
824
|
+
#kh = @component || @form 2011-10-4
|
825
|
+
kh = @form
|
826
|
+
$log.debug "DEBUG : handle_key Tab giving key to ( #{kh} ) "
|
827
|
+
ret = kh.handle_key(ch)
|
828
|
+
$log.debug "DEBUG : handle_key Tab ( #{kh} )got ret #{ret} "
|
829
|
+
# forms seem to returning a nil when the pad has been updated. We need to copy it
|
830
|
+
ret ||= 0
|
831
|
+
if ret == :UNHANDLED
|
832
|
+
$log.debug "tab handle_key returns UNHANDLED" if $log.debug?
|
833
|
+
#alert "Inside unhandled case, tab's for did not handle key"
|
834
|
+
#@component.repaint if @component
|
835
|
+
#@form.window.refresh
|
836
|
+
#$log.debug "DEBUG calling display form(false) from handle_key XXXX" if @form
|
837
|
+
#display_form false if @form # this caused others to go blank 2011-09-26
|
838
|
+
elsif ret != :UNHANDLED # FFI trying out, since forms with components not displaying changes
|
839
|
+
display_form false if @form # required for multi field forms, not single ones
|
840
|
+
end
|
841
|
+
# XXX i need to call repaint of compoent if updated !!
|
842
|
+
return ret
|
843
|
+
end
|
844
|
+
def repaint # Tab
|
845
|
+
if @form
|
846
|
+
$log.debug "DEBUG calling display form(true) from repaint XXX" if $log.debug?
|
847
|
+
display_form
|
848
|
+
else
|
849
|
+
# pls use tp.form(tab) to get form explicity.
|
850
|
+
# It could come here if tab precreated and user is yet to assign a component.
|
851
|
+
# or has removed component and not yet set a new one.
|
852
|
+
$log.error "Got neither component nor form."
|
853
|
+
$log.error "Programmer error. A change in Tabbedpane requires you to create form explicitly using form = tpane.form(tab) syntax"
|
854
|
+
end
|
855
|
+
end
|
856
|
+
## Set focus on a component or form field when a user has tabbed off the last or first button
|
857
|
+
def set_focus first_last
|
858
|
+
if !@form.nil?
|
859
|
+
# move to first field of existing form
|
860
|
+
#@current_form = @current_tab.form # 2010-02-27 20:22
|
861
|
+
$log.debug " calling display form(true) from handle_key NO_NEXT_FIELD: #{first_last} "
|
862
|
+
first_last == :FIRST ? @form.req_first_field : @form.req_last_field
|
863
|
+
display_form
|
864
|
+
if @form.widgets.count == 1 # not working trying to get cursor to show in first form.
|
865
|
+
component = @form.widgets.first
|
866
|
+
component.set_form_row
|
867
|
+
component.set_form_col
|
868
|
+
end
|
869
|
+
else
|
870
|
+
raise "Form nil. Component cannot be directly added any longer. Pls use form"
|
871
|
+
end
|
872
|
+
end
|
873
|
+
# On a tabbed button press, this will display the relevant form
|
874
|
+
# On why I am directyl calling copywin and not using copy_pad_to_win etc
|
875
|
+
#+ those require setting top and left. However, while printing a pad, top and left are reduced and so
|
876
|
+
#+ must be absolute r and c. But inside TP, objects have a relative coord. So the print functions
|
877
|
+
#+ were failing silently, and i was wondering why nothing was printing.
|
878
|
+
# XXX move this tab in tab.repaint and let tab decide based on component or form
|
879
|
+
# if component then pad = component.get_buffer
|
880
|
+
def display_form flag = true
|
881
|
+
return if @form.nil?
|
882
|
+
form = @form
|
883
|
+
if form.is_a? RubyCurses::Form # tempo XXX since there are components
|
884
|
+
pad = form.window
|
885
|
+
else
|
886
|
+
return
|
887
|
+
end
|
888
|
+
pc = @parent_component
|
889
|
+
form.repaint if flag # added 2009-11-03 23:27 paint widgets in inside form
|
890
|
+
$log.debug " TP display form(#{flag}) before pad copy: #{pad.name}, set_backing: #{form}: #{form.name} parent: #{@parent_component} : #{pc.row} , #{pc.col}. #{pc.height} , #{pc.width}: repaint flag #{flag} "
|
891
|
+
ret = -1
|
892
|
+
pminr = pminc = 0
|
893
|
+
r = pc.row + 2
|
894
|
+
c = pc.col + 0
|
895
|
+
border_width = 0
|
896
|
+
maxr = pc.height() - 3
|
897
|
+
maxc = pc.width() - 1
|
898
|
+
$log.debug " ret = pad.copywin(pc.window.get_window, #{pminr}, #{pminc}, #{r}, #{c}, r+ #{maxr} - border_width, c+ #{maxc} -border_width,0). W:#{pc.window}, #{pc.window.get_window} "
|
899
|
+
ret = pad.copywin(pc.window.get_window, pminr, pminc, r, c, r+maxr-border_width, c+maxc-border_width,0)
|
900
|
+
$log.debug " display form after pad copy #{ret}. #{form.name} "
|
901
|
+
#pad.mvhline( 0,0, Ncurses::ACS_HLINE, pad.width-1)
|
902
|
+
pad.mvwaddch(0, 0, Ncurses::ACS_LTEE) # beautify the corner 2010-02-06 19:35
|
903
|
+
pad.mvwaddch(0, pad.width-1, Ncurses::ACS_RTEE)
|
904
|
+
end
|
905
|
+
|
906
|
+
# 2011-09-19 @since 1.3.0 changed so that calling form throws an exception
|
907
|
+
# if it's nil. Please use Tabbedpane's form() to get a form. This throws an exception
|
908
|
+
# so i can catch old programs that use the incorrect method
|
909
|
+
def form(*val)
|
910
|
+
if val.empty?
|
911
|
+
return @form if @form
|
912
|
+
raise "Form is nil. You may be using deprecated method. Use @tp.form, not @tab.form"
|
913
|
+
else
|
914
|
+
#raise ArgumentError "form should be ... " if val[0] ...
|
915
|
+
oldvalue = @form
|
916
|
+
@form = val[0]
|
917
|
+
end
|
918
|
+
self
|
919
|
+
end
|
920
|
+
# used by TP to set form
|
921
|
+
def form=(val)
|
922
|
+
@form = val
|
923
|
+
end
|
924
|
+
# used by TP to check form, since the other methods throws an exception
|
925
|
+
def has_form?
|
926
|
+
!@form.nil?
|
927
|
+
end
|
928
|
+
|
929
|
+
# @private avoid external use
|
930
|
+
def _button(b)
|
931
|
+
@button = b
|
932
|
+
end
|
933
|
+
end # class Tab
|
934
|
+
|
935
|
+
end # class Tabbedpane
|
936
|
+
|
937
|
+
## An extension of Form that only displays and focuses on visible widgets
|
938
|
+
# This is minimal, and is being expanded upon as a separate class in rscrollform.rb
|
939
|
+
#
|
940
|
+
class ScrollForm < RubyCurses::Form
|
941
|
+
attr_accessor :pmincol # advance / scroll columns
|
942
|
+
attr_accessor :pminrow # advance / scroll rows (vertically)
|
943
|
+
attr_accessor :display_w
|
944
|
+
attr_accessor :display_h
|
945
|
+
attr_accessor :scroll_ctr
|
946
|
+
attr_reader :orig_top, :orig_left
|
947
|
+
attr_reader :window
|
948
|
+
attr_accessor :name
|
949
|
+
attr_reader :cols_panned, :rows_panned
|
950
|
+
def initialize win, &block
|
951
|
+
@target_window = win
|
952
|
+
super
|
953
|
+
@pminrow = @pmincol = 0
|
954
|
+
@scroll_ctr = 2
|
955
|
+
@cols_panned = @rows_panned = 0
|
956
|
+
end
|
957
|
+
|
958
|
+
def set_layout(h, w, t, l)
|
959
|
+
@pad_h = h
|
960
|
+
@pad_w = w
|
961
|
+
@top = t
|
962
|
+
@left = l
|
963
|
+
@top += @target_window.top
|
964
|
+
@left += @target_window.left
|
965
|
+
@orig_top = @top
|
966
|
+
@orig_left = @left
|
967
|
+
end
|
968
|
+
def create_pad
|
969
|
+
r = @top
|
970
|
+
c = @left
|
971
|
+
layout = { :height => @pad_h, :width => @pad_w, :top => r, :left => c }
|
972
|
+
@window = VER::Pad.create_with_layout(layout)
|
973
|
+
|
974
|
+
@window.name = "Pad::ScrollPad"
|
975
|
+
@name = "Form::ScrollForm"
|
976
|
+
return @window
|
977
|
+
end
|
978
|
+
|
979
|
+
## ScrollForm handle key, scrolling
|
980
|
+
def handle_key ch
|
981
|
+
#alert("SCROLLFORM #{ch} , ai: #{@active_index} , #{get_current_field.name} ")
|
982
|
+
$log.debug " inside ScrollForm handlekey #{ch} "
|
983
|
+
# do the scrolling thing here top left prow and pcol of pad to be done
|
984
|
+
# # XXX TODO check whether we can scroll before incrementing esp cols_panned etc
|
985
|
+
case ch
|
986
|
+
when ?\M-l.getbyte(0)
|
987
|
+
return false if !validate_scroll_col(@pmincol + @scroll_ctr)
|
988
|
+
@pmincol += @scroll_ctr # some check is required or we'll crash
|
989
|
+
@cols_panned -= @scroll_ctr
|
990
|
+
$log.debug " handled ch M-l in ScrollForm"
|
991
|
+
@window.modified = true
|
992
|
+
return 0
|
993
|
+
when ?\M-h.getbyte(0)
|
994
|
+
return false if !validate_scroll_col(@pmincol - @scroll_ctr)
|
995
|
+
@pmincol -= @scroll_ctr # some check is required or we'll crash
|
996
|
+
@cols_panned += @scroll_ctr
|
997
|
+
$log.debug " handled ch M-h in ScrollForm"
|
998
|
+
@window.modified = true
|
999
|
+
return 0
|
1000
|
+
when ?\M-n.getbyte(0)
|
1001
|
+
return false if !validate_scroll_row(@pminrow + @scroll_ctr)
|
1002
|
+
@pminrow += @scroll_ctr # some check is required or we'll crash
|
1003
|
+
@rows_panned -= @scroll_ctr
|
1004
|
+
@window.modified = true
|
1005
|
+
return 0
|
1006
|
+
when ?\M-p.getbyte(0)
|
1007
|
+
return false if !validate_scroll_row(@pminrow - @scroll_ctr)
|
1008
|
+
@pminrow -= @scroll_ctr # some check is required or we'll crash
|
1009
|
+
@rows_panned += @scroll_ctr
|
1010
|
+
@window.modified = true
|
1011
|
+
return 0
|
1012
|
+
end
|
1013
|
+
case ch
|
1014
|
+
when ?j.getbyte(0)
|
1015
|
+
ch = KEY_DOWN
|
1016
|
+
when ?k.getbyte(0)
|
1017
|
+
ch = KEY_BTAB
|
1018
|
+
@window.ungetch(KEY_BTAB)
|
1019
|
+
return 0
|
1020
|
+
when ?h.getbyte(0)
|
1021
|
+
ch = KEY_LEFT
|
1022
|
+
when ?l.getbyte(0)
|
1023
|
+
ch = KEY_RIGHT
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
super
|
1027
|
+
end
|
1028
|
+
def repaint
|
1029
|
+
$log.debug " scrollForm repaint calling parent"
|
1030
|
+
super
|
1031
|
+
prefresh
|
1032
|
+
@window.modified = false
|
1033
|
+
end
|
1034
|
+
def prefresh
|
1035
|
+
## reduce so we don't go off in top+h and top+w
|
1036
|
+
$log.debug " start ret = @buttonpad.prefresh( #{@pminrow} , #{@pmincol} , #{@top} , #{@left} , top + #{@display_h} left + #{@display_w} ) "
|
1037
|
+
if @pminrow + @display_h > @orig_top + @pad_h
|
1038
|
+
$log.debug " if #{@pminrow} + #{@display_h} > #{@orig_top} +#{@pad_h} "
|
1039
|
+
$log.debug " ERROR 1 "
|
1040
|
+
#return -1
|
1041
|
+
end
|
1042
|
+
if @pmincol + @display_w > @orig_left + @pad_w
|
1043
|
+
$log.debug " if #{@pmincol} + #{@display_w} > #{@orig_left} +#{@pad_w} "
|
1044
|
+
$log.debug " ERROR 2 "
|
1045
|
+
return -1
|
1046
|
+
end
|
1047
|
+
# actually if there is a change in the screen, we may still need to allow update
|
1048
|
+
# but ensure that size does not exceed
|
1049
|
+
if @top + @display_h > @orig_top + @pad_h
|
1050
|
+
$log.debug " if #{@top} + #{@display_h} > #{@orig_top} +#{@pad_h} "
|
1051
|
+
$log.debug " ERROR 3 "
|
1052
|
+
return -1
|
1053
|
+
end
|
1054
|
+
if @left + @display_w > @orig_left + @pad_w
|
1055
|
+
$log.debug " if #{@left} + #{@display_w} > #{@orig_left} +#{@pad_w} "
|
1056
|
+
$log.debug " ERROR 4 "
|
1057
|
+
return -1
|
1058
|
+
end
|
1059
|
+
# maybe we should use copywin to copy onto @target_window
|
1060
|
+
$log.debug " ret = @buttonpad.prefresh( #{@pminrow} , #{@pmincol} , #{@top} , #{@left} , #{@top} + #{@display_h}, #{@left} + #{@display_w} ) "
|
1061
|
+
omit = 0
|
1062
|
+
# this works but if want to avoid copying border
|
1063
|
+
ret = @window.prefresh(@pminrow, @pmincol, @top, @left, @top + @display_h , @left + @display_w)
|
1064
|
+
|
1065
|
+
$log.debug " ret = #{ret} "
|
1066
|
+
# need to refresh the form after repaint over
|
1067
|
+
end
|
1068
|
+
def validate_scroll_row minrow
|
1069
|
+
return false if minrow < 0
|
1070
|
+
if minrow + @display_h > @orig_top + @pad_h
|
1071
|
+
$log.debug " if #{minrow} + #{@display_h} > #{@orig_top} +#{@pad_h} "
|
1072
|
+
$log.debug " ERROR 1 "
|
1073
|
+
return false
|
1074
|
+
end
|
1075
|
+
return true
|
1076
|
+
end
|
1077
|
+
def validate_scroll_col mincol
|
1078
|
+
return false if mincol < 0
|
1079
|
+
if mincol + @display_w > @orig_left + @pad_w
|
1080
|
+
$log.debug " if #{mincol} + #{@display_w} > #{@orig_left} +#{@pad_w} "
|
1081
|
+
$log.debug " ERROR 2 "
|
1082
|
+
return false
|
1083
|
+
end
|
1084
|
+
return true
|
1085
|
+
end
|
1086
|
+
# when tabbing through buttons, we need to account for all that panning/scrolling goin' on
|
1087
|
+
# Either of r or c can be nil (usually one will be)
|
1088
|
+
def setrowcol r, c
|
1089
|
+
# aha ! here's where i can check whether the cursor is falling off the viewable area
|
1090
|
+
if c
|
1091
|
+
if c+@cols_panned < @orig_left
|
1092
|
+
# this essentially means this widget (button) is not in view, its off to the left
|
1093
|
+
$log.debug " setrowcol OVERRIDE #{c} #{@cols_panned} < #{@orig_left} "
|
1094
|
+
$log.debug " aborting settrow col for now"
|
1095
|
+
return
|
1096
|
+
end
|
1097
|
+
if c+@cols_panned > @orig_left + @display_w
|
1098
|
+
# this essentially means this button is not in view, its off to the right
|
1099
|
+
$log.debug " setrowcol OVERRIDE #{c} #{@cols_panned} > #{@orig_left} + #{@display_w} "
|
1100
|
+
$log.debug " aborting settrow col for now"
|
1101
|
+
return
|
1102
|
+
end
|
1103
|
+
end # if c
|
1104
|
+
rr = r.nil? ? nil : r+@rows_panned
|
1105
|
+
cc = c.nil? ? nil : c+@cols_panned
|
1106
|
+
super rr, cc
|
1107
|
+
end
|
1108
|
+
def add_widget w
|
1109
|
+
super
|
1110
|
+
#$log.debug " inside add_widget #{w.name} pad w #{@pad_w} #{w.col} "
|
1111
|
+
if w.col >= @pad_w
|
1112
|
+
@pad_w += 10 # XXX currently just a guess value, we need length and maybe some extra
|
1113
|
+
@window.wresize(@pad_h, @pad_w)
|
1114
|
+
end
|
1115
|
+
end
|
1116
|
+
## Is a component visible, typically used to prevent traversal into the field
|
1117
|
+
# @returns [true, false] false if components has scrolled off
|
1118
|
+
def visible? component
|
1119
|
+
r, c = component.rowcol
|
1120
|
+
return false if c+@cols_panned < @orig_left
|
1121
|
+
return false if c+@cols_panned > @orig_left + @display_w
|
1122
|
+
# XXX TODO for rows UNTESTED for rows
|
1123
|
+
return false if r + @rows_panned < @orig_top
|
1124
|
+
return false if r + @rows_panned > @orig_top + @display_h
|
1125
|
+
|
1126
|
+
return true
|
1127
|
+
end
|
1128
|
+
# returns index of first visible component. Currently using column index
|
1129
|
+
# I am doing this for horizontal scrolling presently
|
1130
|
+
# @return [index, -1] -1 if none visible, else index/offset
|
1131
|
+
def first_visible_component_index
|
1132
|
+
@widgets.each_with_index do |w, ix|
|
1133
|
+
return ix if visible?(w)
|
1134
|
+
end
|
1135
|
+
return -1
|
1136
|
+
end
|
1137
|
+
def last_visible_component_index
|
1138
|
+
ret = -1
|
1139
|
+
@widgets.each_with_index do |w, ix|
|
1140
|
+
$log.debug " reverse last vis #{ix} , #{w} : #{visible?(w)} "
|
1141
|
+
ret = ix if visible?(w)
|
1142
|
+
end
|
1143
|
+
return ret
|
1144
|
+
end
|
1145
|
+
def req_first_field
|
1146
|
+
select_field(first_visible_component_index)
|
1147
|
+
end
|
1148
|
+
def req_last_field
|
1149
|
+
select_field(last_visible_component_index)
|
1150
|
+
end
|
1151
|
+
def focusable?(w)
|
1152
|
+
w.focusable and visible?(w)
|
1153
|
+
end
|
1154
|
+
|
1155
|
+
end # class ScrollF
|
1156
|
+
|
1157
|
+
|
1158
|
+
end # module
|