rbcurse 0.1.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/CHANGELOG +1570 -0
- data/History.txt +6 -0
- data/Manifest.txt +54 -0
- data/README.txt +304 -0
- data/Rakefile +28 -0
- data/examples/qdfilechooser.rb +68 -0
- data/examples/rfe.rb +853 -0
- data/examples/rfe_renderer.rb +69 -0
- data/examples/test1.rb +242 -0
- data/examples/test2.rb +498 -0
- data/examples/testcombo.rb +95 -0
- data/examples/testkeypress.rb +61 -0
- data/examples/testmenu.rb +105 -0
- data/examples/testtable.rb +266 -0
- data/examples/testtabp.rb +106 -0
- data/examples/testtodo.rb +532 -0
- data/examples/viewtodo.rb +512 -0
- data/lib/rbcurse/action.rb +31 -0
- data/lib/rbcurse/applicationheader.rb +57 -0
- data/lib/rbcurse/celleditor.rb +120 -0
- data/lib/rbcurse/checkboxcellrenderer.rb +69 -0
- data/lib/rbcurse/colormap.rb +133 -0
- data/lib/rbcurse/comboboxcellrenderer.rb +45 -0
- data/lib/rbcurse/defaultlistselectionmodel.rb +49 -0
- data/lib/rbcurse/keylabelprinter.rb +143 -0
- data/lib/rbcurse/listcellrenderer.rb +99 -0
- data/lib/rbcurse/listkeys.rb +33 -0
- data/lib/rbcurse/listscrollable.rb +216 -0
- data/lib/rbcurse/listselectable.rb +67 -0
- data/lib/rbcurse/mapper.rb +108 -0
- data/lib/rbcurse/orderedhash.rb +77 -0
- data/lib/rbcurse/rcombo.rb +243 -0
- data/lib/rbcurse/rdialogs.rb +183 -0
- data/lib/rbcurse/rform.rb +845 -0
- data/lib/rbcurse/rinputdataevent.rb +36 -0
- data/lib/rbcurse/rlistbox.rb +804 -0
- data/lib/rbcurse/rmenu.rb +666 -0
- data/lib/rbcurse/rmessagebox.rb +325 -0
- data/lib/rbcurse/rpopupmenu.rb +754 -0
- data/lib/rbcurse/rtabbedpane.rb +259 -0
- data/lib/rbcurse/rtable.rb +1296 -0
- data/lib/rbcurse/rtextarea.rb +673 -0
- data/lib/rbcurse/rtextview.rb +335 -0
- data/lib/rbcurse/rwidget.rb +1731 -0
- data/lib/rbcurse/scrollable.rb +301 -0
- data/lib/rbcurse/selectable.rb +94 -0
- data/lib/rbcurse/table/tablecellrenderer.rb +85 -0
- data/lib/rbcurse/table/tabledatecellrenderer.rb +102 -0
- data/lib/rbcurse.rb +7 -0
- data/lib/ver/keyboard.rb +150 -0
- data/lib/ver/keyboard2.rb +170 -0
- data/lib/ver/ncurses.rb +102 -0
- data/lib/ver/window.rb +369 -0
- data/test/test_rbcurse.rb +0 -0
- metadata +118 -0
@@ -0,0 +1,1296 @@
|
|
1
|
+
=begin
|
2
|
+
* Name: table widget
|
3
|
+
* Description:
|
4
|
+
* Author: rkumar (arunachalesha)
|
5
|
+
|
6
|
+
|
7
|
+
TODO: NOTE: Needs to be tested under conditions such as no data, deleting all data
|
8
|
+
A few higher level methods check for no data but lower level ones do not.
|
9
|
+
|
10
|
+
--------
|
11
|
+
* Date: 2008-12-27 21:33
|
12
|
+
* License:
|
13
|
+
Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
14
|
+
|
15
|
+
=end
|
16
|
+
require 'rubygems'
|
17
|
+
require 'ncurses'
|
18
|
+
require 'logger'
|
19
|
+
require 'rbcurse'
|
20
|
+
require 'rbcurse/table/tablecellrenderer'
|
21
|
+
require 'rbcurse/table/tabledatecellrenderer'
|
22
|
+
require 'rbcurse/checkboxcellrenderer'
|
23
|
+
require 'rbcurse/listselectable'
|
24
|
+
require 'rbcurse/listkeys'
|
25
|
+
|
26
|
+
include Ncurses
|
27
|
+
include RubyCurses
|
28
|
+
module RubyCurses
|
29
|
+
extend self
|
30
|
+
|
31
|
+
# ------ NOTE ------------------ #
|
32
|
+
# Table contains a TableModel
|
33
|
+
# Table contains a TableColumnModel (which contains TableColumn instances)
|
34
|
+
# TableColumn contains 2 TableCellRenderer: column and header
|
35
|
+
# ------------------------ #
|
36
|
+
#
|
37
|
+
#
|
38
|
+
# Due to not having method overloading, after usig new, use set_data or set_model
|
39
|
+
#
|
40
|
+
# This is a widget that displays tabular data. We will get into editing after this works out.
|
41
|
+
# This uses the MVC architecture and is WIP as of 2009-01-04 18:37
|
42
|
+
# TODO cellrenderers should be able to get parents bgcolor and color (Jtables) if none defined for them.
|
43
|
+
class Table < Widget
|
44
|
+
#include RubyCurses::EventHandler # widget does 2009-01-15 15:38
|
45
|
+
include RubyCurses::ListSelectable
|
46
|
+
include RubyCurses::ListKeys
|
47
|
+
|
48
|
+
dsl_accessor :height
|
49
|
+
dsl_accessor :title
|
50
|
+
dsl_accessor :title_attrib
|
51
|
+
dsl_accessor :selected_color, :selected_bgcolor, :selected_attr
|
52
|
+
attr_accessor :current_index # the row index universally
|
53
|
+
#attr_accessor :current_column # index of column (usually in current row )
|
54
|
+
# a changed event of an editor component can utitlize this if it wishes to know
|
55
|
+
# the row or col that was exited.
|
56
|
+
attr_reader :editing_col, :editing_row # r and col being edited, set to nil on leave
|
57
|
+
attr_accessor :is_editing # boolean is only true if cell_editing_allowed
|
58
|
+
dsl_accessor :editing_policy # :EDITING_AUTO
|
59
|
+
|
60
|
+
# A table should only be editable if this is true regardless of other variables
|
61
|
+
# In addition, A column to be editable must either have editable as nil or true
|
62
|
+
dsl_accessor :cell_editing_allowed # 2009-01-16 22:55
|
63
|
+
|
64
|
+
def initialize form, config={}, &block
|
65
|
+
super
|
66
|
+
init_vars
|
67
|
+
install_list_keys
|
68
|
+
install_keys_bindings
|
69
|
+
end
|
70
|
+
|
71
|
+
def init_vars
|
72
|
+
@col_offset = @row_offset = 1
|
73
|
+
@focusable= true
|
74
|
+
@current_index = 0
|
75
|
+
@current_column = 0
|
76
|
+
@oldrow = @oldcol = 0
|
77
|
+
@current_column_offset ||= 0 # added 2009-01-12 19:06 current_column's offset
|
78
|
+
@toprow = 0
|
79
|
+
@to_print_borders ||= 1
|
80
|
+
@show_grid ||= 1
|
81
|
+
@curpos = 0
|
82
|
+
# @selected_color ||= 'yellow'
|
83
|
+
# @selected_bgcolor ||= 'black'
|
84
|
+
@table_changed = true
|
85
|
+
@repaint_required = true
|
86
|
+
end
|
87
|
+
def install_keys_bindings
|
88
|
+
|
89
|
+
# alt-tab next column
|
90
|
+
# alt-shift-tab prev column
|
91
|
+
#bind_key(?\M-\C-i) { next_column }
|
92
|
+
#bind_key(481) { previous_column }
|
93
|
+
bind_key(9) { next_column }
|
94
|
+
bind_key(353) { previous_column }
|
95
|
+
bind_key(KEY_RIGHT) { next_column }
|
96
|
+
bind_key(KEY_LEFT) { previous_column }
|
97
|
+
bind_key(@KEY_ASK_FIND_FORWARD) { ask_search_forward }
|
98
|
+
bind_key(@KEY_ASK_FIND_BACKWARD) { ask_search_backward }
|
99
|
+
bind_key(@KEY_FIND_NEXT) { find_next }
|
100
|
+
bind_key(@KEY_FIND_PREV) { find_prev }
|
101
|
+
end
|
102
|
+
|
103
|
+
def focussed_row
|
104
|
+
#raise "No data in table" if row_count < 1
|
105
|
+
return nil if row_count < 1
|
106
|
+
@current_index
|
107
|
+
end
|
108
|
+
def focussed_col
|
109
|
+
return nil if row_count < 1
|
110
|
+
#raise "No data in table" if row_count < 1
|
111
|
+
@current_column
|
112
|
+
end
|
113
|
+
# added 2009-01-07 13:05 so new scrollable can use
|
114
|
+
def row_count
|
115
|
+
@table_model.row_count
|
116
|
+
end
|
117
|
+
# added 2009-01-07 13:05 so new scrollable can use
|
118
|
+
def scrollatrow
|
119
|
+
@height -3
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# Sets the data in models
|
124
|
+
# Should replace if these are created. TODO FIXME
|
125
|
+
def set_data data, colnames_array
|
126
|
+
if data.is_a? Array
|
127
|
+
model = RubyCurses::DefaultTableModel.new data, colnames_array
|
128
|
+
table_model model
|
129
|
+
elsif data.is_a? RubyCurses::TableModel
|
130
|
+
table_model data
|
131
|
+
end
|
132
|
+
if colnames_array.is_a? Array
|
133
|
+
model = DefaultTableColumnModel.new colnames_array
|
134
|
+
table_column_model model
|
135
|
+
elsif colnames_array.is_a? RubyCurses::TableColumnModel
|
136
|
+
table_column_model colnames_array
|
137
|
+
end
|
138
|
+
create_default_list_selection_model
|
139
|
+
create_table_header
|
140
|
+
end
|
141
|
+
def set_model tm, tcm=nil, lsm=nil
|
142
|
+
table_model tm
|
143
|
+
if tcm.nil?
|
144
|
+
create_default_table_column_model
|
145
|
+
else
|
146
|
+
table_column_model tcm
|
147
|
+
end
|
148
|
+
if lsm.nil?
|
149
|
+
create_default_list_selection_model
|
150
|
+
else
|
151
|
+
list_selection_model lsm
|
152
|
+
end
|
153
|
+
create_table_header
|
154
|
+
end
|
155
|
+
|
156
|
+
# getter and setter for table_model
|
157
|
+
def table_model(*val)
|
158
|
+
if val.empty?
|
159
|
+
@table_model
|
160
|
+
else
|
161
|
+
raise "data error" if !val[0].is_a? RubyCurses::TableModel
|
162
|
+
@table_model = val[0]
|
163
|
+
## table registers as a listener, or rather binds to event
|
164
|
+
@table_model.bind(:TABLE_MODEL_EVENT){|lde| table_data_changed(lde) }
|
165
|
+
end
|
166
|
+
end
|
167
|
+
def table_column_model tcm
|
168
|
+
raise "data error" if !tcm.is_a? RubyCurses::TableColumnModel
|
169
|
+
@table_column_model = tcm
|
170
|
+
@table_column_model.bind(:TABLE_COLUMN_MODEL_EVENT) {|e|
|
171
|
+
table_structure_changed e
|
172
|
+
}
|
173
|
+
@table_column_model.bind(:PROPERTY_CHANGE){|e| column_property_changed(e)}
|
174
|
+
|
175
|
+
#@table_header.column_model(tcm) unless @table_header.nil?
|
176
|
+
@table_header.table_column_model=(tcm) unless @table_header.nil?
|
177
|
+
end
|
178
|
+
# FIX THIS SO NO GET_ XXX
|
179
|
+
def get_table_column_model
|
180
|
+
@table_column_model
|
181
|
+
end
|
182
|
+
#
|
183
|
+
def create_default_table_column_model
|
184
|
+
table_column_model DefaultTableColumnModel.new
|
185
|
+
end
|
186
|
+
def create_table_header
|
187
|
+
@table_header = TableHeader.new @table_column_model
|
188
|
+
end
|
189
|
+
|
190
|
+
#--- selection methods ---#
|
191
|
+
def is_column_selected col
|
192
|
+
raise "TODO "
|
193
|
+
end
|
194
|
+
def is_cell_selected row, col
|
195
|
+
raise "TODO "
|
196
|
+
end
|
197
|
+
def add_column_selection_interval ix0, ix1
|
198
|
+
raise "TODO "
|
199
|
+
# if column_selection_allowed
|
200
|
+
end
|
201
|
+
def remove_column_selection_interval ix0, ix1
|
202
|
+
raise "TODO "
|
203
|
+
end
|
204
|
+
|
205
|
+
def selected_column
|
206
|
+
@table_column_model.selected_columns[0]
|
207
|
+
end
|
208
|
+
def selected_columns
|
209
|
+
@table_column_model.selected_columns
|
210
|
+
end
|
211
|
+
def selected_column_count
|
212
|
+
@table_column_model.selected_column_count
|
213
|
+
end
|
214
|
+
|
215
|
+
#--- row and column methods ---#
|
216
|
+
|
217
|
+
##
|
218
|
+
# getter and setter for current_column index
|
219
|
+
def current_column(*val)
|
220
|
+
if val.empty?
|
221
|
+
@current_column || 0
|
222
|
+
else
|
223
|
+
@oldcol = @current_column
|
224
|
+
v = val[0]
|
225
|
+
v = 0 if v < 0
|
226
|
+
v = @table_column_model.column_count-1 if v > @table_column_model.column_count-1
|
227
|
+
@current_column = v
|
228
|
+
if @current_column != @oldcol
|
229
|
+
on_leave_column @oldcol
|
230
|
+
on_enter_column @current_column
|
231
|
+
end
|
232
|
+
set_form_col
|
233
|
+
@oldcol = @current_column # added on 2009-01-16 19:40 for next_col
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
def add_column tc
|
239
|
+
@table_column_model << tc
|
240
|
+
#table_structure_changed # this should be called by tcm TODO with object
|
241
|
+
end
|
242
|
+
def remove_column tc
|
243
|
+
@table_column_model.remove_column tc
|
244
|
+
#table_structure_changed # this should be called by tcm TODO with object
|
245
|
+
end
|
246
|
+
def get_column identifier
|
247
|
+
ix = @table_column_model.column_index identifier
|
248
|
+
return @table_column_model.column ix
|
249
|
+
end
|
250
|
+
##
|
251
|
+
# returns col by col ix added on 2009-01-16 23:45
|
252
|
+
def column ix
|
253
|
+
@table_column_model.column(ix)
|
254
|
+
end
|
255
|
+
def get_column_name ix
|
256
|
+
@table_column_model.column(ix).identifier
|
257
|
+
end
|
258
|
+
def move_column ix, newix
|
259
|
+
@table_column_model.move_column ix, newix
|
260
|
+
#table_structure_changed # this should be called by tcm TODO with object
|
261
|
+
end
|
262
|
+
|
263
|
+
#--- row and column methods of Table ---#
|
264
|
+
# must not give wrong results when columns switched!
|
265
|
+
def get_value_at row, col
|
266
|
+
model_index = @table_column_model.column(col).model_index
|
267
|
+
@table_model.get_value_at row, model_index
|
268
|
+
end
|
269
|
+
# must not give wrong results when columns switched!
|
270
|
+
def set_value_at row, col, value
|
271
|
+
model_index = @table_column_model.column(col).model_index
|
272
|
+
@table_model.set_value_at row, model_index, value
|
273
|
+
end
|
274
|
+
|
275
|
+
#--- event listener support methods (p521) TODO ---#
|
276
|
+
|
277
|
+
def table_data_changed tabmodev
|
278
|
+
#$log.debug " def table_data_changed got #{tabmodev}"
|
279
|
+
@repaint_required = true
|
280
|
+
# next was required otherwise on_enter would bomb if data changed from outside
|
281
|
+
if row_count == 0
|
282
|
+
init_vars
|
283
|
+
set_form_col # added 2009-01-24 14:32 since cursor was still showing on existing col
|
284
|
+
return # added 2009-01-23 15:15
|
285
|
+
end
|
286
|
+
# the next block to be only called if user is inside editing. Often data will be refreshed by
|
287
|
+
# a search field and this gets called.
|
288
|
+
if @is_editing
|
289
|
+
@is_editing = false # 2009-01-19 18:18 testing this out XXX
|
290
|
+
# we need to refresh the editor if you deleted a row while sitting on it
|
291
|
+
# otherwise it shows the old value
|
292
|
+
editing_started
|
293
|
+
end
|
294
|
+
end
|
295
|
+
def table_structure_changed tablecolmodelevent
|
296
|
+
$log.debug " def table_structure_changed #{tablecolmodelevent}"
|
297
|
+
@table_changed = true
|
298
|
+
@repaint_required = true
|
299
|
+
init_vars
|
300
|
+
end
|
301
|
+
def column_property_changed evt
|
302
|
+
$log.debug "JT def column_property_changed #{evt} "
|
303
|
+
@table_changed = true
|
304
|
+
@repaint_required = true
|
305
|
+
end
|
306
|
+
=begin
|
307
|
+
# this could be complicating things. I don't need it in here.
|
308
|
+
def column_added tabcolmodev
|
309
|
+
@repaint_required = true
|
310
|
+
end
|
311
|
+
def column_removed tabcolmodev
|
312
|
+
@repaint_required = true
|
313
|
+
end
|
314
|
+
def column_moved tabcolmodev
|
315
|
+
@repaint_required = true
|
316
|
+
end
|
317
|
+
=end
|
318
|
+
## to do for TrueClass and FalseClass
|
319
|
+
def prepare_renderers
|
320
|
+
@crh = Hash.new
|
321
|
+
@crh['String'] = TableCellRenderer.new "", {"parent" => self }
|
322
|
+
@crh['Fixnum'] = TableCellRenderer.new "", { "justify" => :right, "parent" => self}
|
323
|
+
@crh['Float'] = TableCellRenderer.new "", {"justify" => :right, "parent" => self}
|
324
|
+
@crh['TrueClass'] = CheckBoxCellRenderer.new "", {"parent" => self, "display_length"=>7}
|
325
|
+
@crh['FalseClass'] = CheckBoxCellRenderer.new "", {"parent" => self, "display_length"=>7}
|
326
|
+
@crh['Time'] = TableDateCellRenderer.new "", {"parent" => self, "display_length"=>16}
|
327
|
+
#@crh['String'] = TableCellRenderer.new "", {"bgcolor" => "cyan", "color"=>"white", "parent" => self}
|
328
|
+
#@crh['Fixnum'] = TableCellRenderer.new "", {"display_length" => 6, "justify" => :right, "color"=>"blue","bgcolor"=>"cyan" }
|
329
|
+
#@crh['Float'] = TableCellRenderer.new "", {"display_length" => 6, "justify" => :right, "color"=>"blue", "bgcolor"=>"cyan" }
|
330
|
+
end
|
331
|
+
# this is vry temporary and will change as we begin to use models - i need to pick
|
332
|
+
# columns renderer
|
333
|
+
def get_default_cell_renderer_for_class cname
|
334
|
+
@crh || prepare_renderers
|
335
|
+
@crh[cname] || @crh['String']
|
336
|
+
end
|
337
|
+
def set_default_cell_renderer_for_class cname, rend
|
338
|
+
@crh ||= {}
|
339
|
+
@crh[cname]=rend
|
340
|
+
end
|
341
|
+
## override for cell or row behaviour
|
342
|
+
def get_cell_renderer row, col
|
343
|
+
# get columns renderer else class default
|
344
|
+
column = @table_column_model.column(col)
|
345
|
+
rend = column.cell_renderer
|
346
|
+
return rend # can be nil
|
347
|
+
end
|
348
|
+
#
|
349
|
+
# ------- editing methods---------- #
|
350
|
+
def get_cell_editor row, col
|
351
|
+
$log.debug " def get_cell_editor #{row}, #{col}"
|
352
|
+
column = @table_column_model.column(col)
|
353
|
+
return nil if column.editable == false or (column.editable.nil? and @cell_editing_allowed!=true)
|
354
|
+
editor = column.cell_editor
|
355
|
+
return editor # can be nil
|
356
|
+
end
|
357
|
+
def edit_cell_at row, col
|
358
|
+
acolumn = column(col)
|
359
|
+
if acolumn.editable == false or (acolumn.editable.nil? and @cell_editing_allowed!=true)
|
360
|
+
$log.debug " editing not allowed in #{col}"
|
361
|
+
@is_editing = false
|
362
|
+
return nil
|
363
|
+
end
|
364
|
+
return nil if row >= row_count
|
365
|
+
value = get_value_at row, col
|
366
|
+
editor = get_cell_editor row, col
|
367
|
+
@old_cell_value = value # for event
|
368
|
+
if editor.nil?
|
369
|
+
|
370
|
+
cls = value.nil? ? get_value_at(0,col).class.to_s : value.class.to_s
|
371
|
+
if value.nil?
|
372
|
+
case cls
|
373
|
+
when 'String'
|
374
|
+
value = value.to_s
|
375
|
+
when 'Fixnum'
|
376
|
+
value = value.to_i
|
377
|
+
when 'Float'
|
378
|
+
value = value.to_f
|
379
|
+
else
|
380
|
+
value = value.to_s
|
381
|
+
end
|
382
|
+
end
|
383
|
+
editor = get_default_cell_editor_for_class cls
|
384
|
+
editor.component.display_length = @table_column_model.column(col).width
|
385
|
+
editor.component.maxlen = editor.component.display_length if editor.component.respond_to? :maxlen and editor.component.maxlen.nil? # 2009-01-18 00:59 XXX don't overwrite if user has set
|
386
|
+
#$log.debug "EDIT_CELL_AT: #{cls} #{editor.component.display_length} = #{@table_column_model.column(col).width}i maxlen #{editor.component.maxlen}"
|
387
|
+
end
|
388
|
+
$log.debug " got an EDITOR #{editor} :: #{editor.component} "
|
389
|
+
# by now we should have something to edit with. We just need to prepare the widgey.
|
390
|
+
prepare_editor editor, row, col, value
|
391
|
+
|
392
|
+
end
|
393
|
+
def prepare_editor editor, row, col, value
|
394
|
+
r,c = rowcol
|
395
|
+
row = r + (row - @toprow) +1 # @form.row , 1 added for header row!
|
396
|
+
col = c+get_column_offset()
|
397
|
+
editor.prepare_editor self, row, col, value
|
398
|
+
@cell_editor = editor
|
399
|
+
@repaint_required = true
|
400
|
+
set_form_col
|
401
|
+
end
|
402
|
+
## Its too late to call components on_leave here
|
403
|
+
# since cursor would have moved elsewhere.
|
404
|
+
# Prior to moving out of a field, the on_leave should be called and exceptions caught FIXME
|
405
|
+
def cancel_editor
|
406
|
+
# not really required, the refresh was required. Ok, now i call components on_leave inside
|
407
|
+
#@cell_editor.cancel_editor
|
408
|
+
@editing_row, @editing_col = nil, nil
|
409
|
+
@is_editing = false
|
410
|
+
@repaint_required = true
|
411
|
+
end
|
412
|
+
def get_default_cell_editor_for_class cname
|
413
|
+
@ceh ||= {}
|
414
|
+
cname = 'Boolean' if cname == 'TrueClass' or cname == 'FalseClass'
|
415
|
+
if @ceh.include? cname
|
416
|
+
return @ceh[cname]
|
417
|
+
else
|
418
|
+
case cname
|
419
|
+
when 'String'
|
420
|
+
# I do not know cell width here, you will have toset display_length NOTE
|
421
|
+
ce = RubyCurses::CellEditor.new RubyCurses::Field.new nil, {"focusable"=>false, "visible"=>false, "display_length"=> 8}
|
422
|
+
@ceh['String'] = ce
|
423
|
+
return ce
|
424
|
+
when 'Fixnum'
|
425
|
+
ce = RubyCurses::CellEditor.new RubyCurses::Field.new nil, {"focusable"=>false, "visible"=>false, "display_length"=> 5}
|
426
|
+
@ceh[cname] = ce
|
427
|
+
return ce
|
428
|
+
when 'Float'
|
429
|
+
ce = RubyCurses::CellEditor.new RubyCurses::Field.new nil, {"focusable"=>false, "visible"=>false, "display_length"=> 5}
|
430
|
+
@ceh[cname] = ce
|
431
|
+
return ce
|
432
|
+
when "Boolean" #'TrueClass', 'FalseClass'
|
433
|
+
ce = RubyCurses::CellEditor.new(RubyCurses::CheckBox.new nil, {"display_length"=> 0})
|
434
|
+
@ceh[cname] = ce
|
435
|
+
return ce
|
436
|
+
else
|
437
|
+
$log.debug " get_default_cell_editor_for_class UNKNOWN #{cname}"
|
438
|
+
ce = RubyCurses::CellEditor.new RubyCurses::Field.new nil, {"focusable"=>false, "visible"=>false, "display_length"=> 6}
|
439
|
+
@ceh[cname] = ce
|
440
|
+
return ce
|
441
|
+
end
|
442
|
+
end
|
443
|
+
end
|
444
|
+
# returns true if editing is occurring
|
445
|
+
#def is_editing?
|
446
|
+
# @editing
|
447
|
+
#end
|
448
|
+
|
449
|
+
# ----------------- #
|
450
|
+
|
451
|
+
##
|
452
|
+
# key handling
|
453
|
+
# make separate methods so callable programmatically
|
454
|
+
def handle_key(ch)
|
455
|
+
@current_index ||= 0
|
456
|
+
@toprow ||= 0
|
457
|
+
h = scrollatrow()
|
458
|
+
rc = @table_model.row_count
|
459
|
+
if @is_editing and (ch != 27 and ch != ?\C-c and ch != 13)
|
460
|
+
$log.debug " sending ch #{ch} to cell editor"
|
461
|
+
ret = @cell_editor.component.handle_key(ch)
|
462
|
+
@repaint_required = true
|
463
|
+
$log.debug "RET #{ret} got from to cell editor"
|
464
|
+
return if ret != :UNHANDLED
|
465
|
+
end
|
466
|
+
case ch
|
467
|
+
when KEY_UP # show previous value
|
468
|
+
editing_stopped if @is_editing # 2009-01-16 16:06
|
469
|
+
previous_row
|
470
|
+
when KEY_DOWN # show previous value
|
471
|
+
editing_stopped if @is_editing # 2009-01-16 16:06
|
472
|
+
next_row
|
473
|
+
when 27, ?\C-c:
|
474
|
+
editing_canceled
|
475
|
+
when KEY_ENTER, 10, 13:
|
476
|
+
# actually it should fall through to the else
|
477
|
+
return :UNHANDLED unless @cell_editing_allowed
|
478
|
+
toggle_cell_editing
|
479
|
+
|
480
|
+
when @KEY_ROW_SELECTOR # ?\C-x #32:
|
481
|
+
#add_row_selection_interval @current_index, @current_index
|
482
|
+
toggle_row_selection @current_index #, @current_index
|
483
|
+
@repaint_required = true
|
484
|
+
when ?\C-n:
|
485
|
+
editing_stopped if @is_editing # 2009-01-16 16:06
|
486
|
+
scroll_forward
|
487
|
+
when ?\C-p:
|
488
|
+
editing_stopped if @is_editing # 2009-01-16 16:06
|
489
|
+
scroll_backward
|
490
|
+
when 48, @KEY_GOTO_TOP
|
491
|
+
# please note that C-[ gives 27, same as esc so will respond after ages
|
492
|
+
editing_stopped if @is_editing # 2009-01-16 16:06
|
493
|
+
goto_top
|
494
|
+
when @KEY_GOTO_BOTTOM
|
495
|
+
editing_stopped if @is_editing # 2009-01-16 16:06
|
496
|
+
goto_bottom
|
497
|
+
else
|
498
|
+
# there could be a case of editing here too!
|
499
|
+
ret = process_key ch, self
|
500
|
+
return :UNHANDLED if ret == :UNHANDLED
|
501
|
+
end
|
502
|
+
end
|
503
|
+
def editing_canceled
|
504
|
+
return unless @cell_editing_allowed
|
505
|
+
@is_editing = false if @is_editing
|
506
|
+
cancel_editor
|
507
|
+
end
|
508
|
+
def toggle_cell_editing
|
509
|
+
return unless @cell_editing_allowed
|
510
|
+
@is_editing = !@is_editing
|
511
|
+
if @is_editing
|
512
|
+
editing_started
|
513
|
+
else
|
514
|
+
editing_stopped
|
515
|
+
end
|
516
|
+
end
|
517
|
+
def editing_started
|
518
|
+
return if !@cell_editing_allowed or row_count < 1
|
519
|
+
@is_editing = true # 2009-01-16 16:14
|
520
|
+
$log.debug " turning on editing cell at #{focussed_row}, #{focussed_col}"
|
521
|
+
# on deleting last row, we need to go back 2009-01-19 18:31
|
522
|
+
if focussed_row >= row_count
|
523
|
+
bounds_check
|
524
|
+
end
|
525
|
+
@editing_row, @editing_col = focussed_row(), focussed_col()
|
526
|
+
edit_cell_at focussed_row(), focussed_col()
|
527
|
+
end
|
528
|
+
# EDST
|
529
|
+
# the defualt values are useful when user is ON the field and pressed ENTER
|
530
|
+
# when leaving a cell, this should have oldrow and oldcol, not default values
|
531
|
+
# this throws an exception if validation on field fails NOTE
|
532
|
+
def editing_stopped row=focussed_row(), col=focussed_col()
|
533
|
+
return unless @cell_editing_allowed or @is_editing == false or column(col).editable == false
|
534
|
+
return if row_count < 1
|
535
|
+
$log.debug "editing_stopped set_value_at(#{row}, #{col}: #{@cell_editor.getvalue}"
|
536
|
+
# next line should be in on_leave_cell but that's not being called FIXME from everywhere
|
537
|
+
@cell_editor.on_leave row,col # added here since this is called whenever a cell is exited
|
538
|
+
|
539
|
+
value = @cell_editor.getvalue
|
540
|
+
if value != @old_cell_value
|
541
|
+
set_value_at(row, col, @cell_editor.getvalue) #.dup 2009-01-10 21:42 boolean can't duplicate
|
542
|
+
if @table_editing_event.nil?
|
543
|
+
@table_editing_event ||= TableEditingEvent.new row, col, self, @old_cell_value, value, :EDITING_STOPPED
|
544
|
+
else
|
545
|
+
@table_editing_event.set row, col, self, @old_cell_value, value, :EDITING_STOPPED
|
546
|
+
end
|
547
|
+
fire_handler :TABLE_EDITING_EVENT, @table_editing_event
|
548
|
+
end
|
549
|
+
cancel_editor
|
550
|
+
end
|
551
|
+
##
|
552
|
+
def previous_row
|
553
|
+
@oldrow = @current_index
|
554
|
+
@current_index -= 1 if @current_index > 0
|
555
|
+
bounds_check
|
556
|
+
end
|
557
|
+
def next_row
|
558
|
+
rc = row_count
|
559
|
+
@oldrow = @current_index
|
560
|
+
# don't go on if rc 2009-01-16 19:55 XXX
|
561
|
+
if @current_index < rc
|
562
|
+
@current_index += 1
|
563
|
+
bounds_check
|
564
|
+
end
|
565
|
+
end
|
566
|
+
def next_column
|
567
|
+
v = @current_column+1
|
568
|
+
if v < @table_column_model.column_count
|
569
|
+
$log.debug " if v < #{@table_column_model.column_count} "
|
570
|
+
current_column v
|
571
|
+
else
|
572
|
+
if @current_index < row_count()-1
|
573
|
+
$log.debug " GOING TO NEXT ROW FROM NEXT COL : #{@current_index} : #{row_count}"
|
574
|
+
@current_column = 0
|
575
|
+
next_row
|
576
|
+
else
|
577
|
+
return :UNHANDLED
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
def previous_column
|
582
|
+
v = @current_column-1
|
583
|
+
# returning unhandled so focus can go to prev field auto
|
584
|
+
if v < 0 and @current_index <= 0
|
585
|
+
return :UNHANDLED
|
586
|
+
end
|
587
|
+
if v < 0 and @current_index > 0
|
588
|
+
@current_column = @table_column_model.column_count-1
|
589
|
+
previous_row
|
590
|
+
else
|
591
|
+
current_column @current_column-1
|
592
|
+
end
|
593
|
+
end
|
594
|
+
def goto_bottom
|
595
|
+
@oldrow = @current_index
|
596
|
+
rc = row_count
|
597
|
+
@current_index = rc -1
|
598
|
+
bounds_check
|
599
|
+
end
|
600
|
+
def goto_top
|
601
|
+
@oldrow = @current_index
|
602
|
+
@current_index = 0
|
603
|
+
bounds_check
|
604
|
+
end
|
605
|
+
def scroll_backward
|
606
|
+
@oldrow = @current_index
|
607
|
+
h = scrollatrow()
|
608
|
+
@current_index -= h
|
609
|
+
bounds_check
|
610
|
+
end
|
611
|
+
def scroll_forward
|
612
|
+
@oldrow = @current_index
|
613
|
+
h = scrollatrow()
|
614
|
+
rc = row_count
|
615
|
+
# more rows than box
|
616
|
+
if h < rc
|
617
|
+
@toprow += h+1 #if @current_index+h < rc
|
618
|
+
@current_index = @toprow
|
619
|
+
else
|
620
|
+
# fewer rows than box
|
621
|
+
@current_index = rc -1
|
622
|
+
end
|
623
|
+
#@current_index += h+1 #if @current_index+h < rc
|
624
|
+
bounds_check
|
625
|
+
end
|
626
|
+
|
627
|
+
def bounds_check
|
628
|
+
h = scrollatrow()
|
629
|
+
rc = row_count
|
630
|
+
#$log.debug " PRE CURR:#{@current_index}, TR: #{@toprow} RC: #{rc} H:#{h}"
|
631
|
+
@current_index = 0 if @current_index < 0 # not lt 0
|
632
|
+
@current_index = rc-1 if @current_index >= rc # not gt rowcount
|
633
|
+
@toprow = rc-h-1 if rc > h and @toprow > rc - h - 1 # toprow shows full page if possible
|
634
|
+
# curr has gone below table, move toprow forward
|
635
|
+
if @current_index - @toprow > h
|
636
|
+
@toprow = @current_index - h
|
637
|
+
elsif @current_index < @toprow
|
638
|
+
# curr has gone above table, move toprow up
|
639
|
+
@toprow = @current_index
|
640
|
+
end
|
641
|
+
#$log.debug " POST CURR:#{@current_index}, TR: #{@toprow} RC: #{rc} H:#{h}"
|
642
|
+
if @oldrow != @current_index
|
643
|
+
#$log.debug "going to call on leave and on enter"
|
644
|
+
on_leave_row @oldrow #if respond_to? :on_leave_row # to be defined by widget that has included this
|
645
|
+
on_enter_row @current_index #if respond_to? :on_enter_row # to be defined by widget that has included this
|
646
|
+
end
|
647
|
+
set_form_row
|
648
|
+
@oldrow = @current_index # added 2009-01-16 19:43 XXX
|
649
|
+
@repaint_required = true
|
650
|
+
end
|
651
|
+
def on_leave_row arow
|
652
|
+
#$log.debug " def on_leave_row #{arow}"
|
653
|
+
#on_leave_cell arow, @current_column
|
654
|
+
on_leave_cell arow, @oldcol # 2009-01-16 19:41 XXX trying outt
|
655
|
+
end
|
656
|
+
def on_leave_column acol
|
657
|
+
#$log.debug " def on_leave_column #{acol}"
|
658
|
+
#on_leave_cell @current_index, acol
|
659
|
+
on_leave_cell @oldrow, acol
|
660
|
+
end
|
661
|
+
def on_enter_row arow
|
662
|
+
#$log.debug " def on_enter_row #{arow}"
|
663
|
+
on_enter_cell arow, @current_column
|
664
|
+
end
|
665
|
+
def on_enter_column acol
|
666
|
+
#$log.debug " def on_enter_column #{acol}"
|
667
|
+
on_enter_cell @current_index, acol
|
668
|
+
end
|
669
|
+
## OLCE
|
670
|
+
def on_leave_cell arow, acol
|
671
|
+
$log.debug " def on_leave_cell #{arow}, #{acol}"
|
672
|
+
#if @editing_policy == :EDITING_AUTO # actually this should happen in all cases
|
673
|
+
if @is_editing # 2009-01-17 00:49
|
674
|
+
editing_stopped arow, acol
|
675
|
+
end
|
676
|
+
end
|
677
|
+
## OECE
|
678
|
+
def on_enter_cell arow, acol
|
679
|
+
$log.debug " def on_enter_cell #{arow}, #{acol}"
|
680
|
+
if @table_traversal_event.nil?
|
681
|
+
@table_traversal_event ||= TableTraversalEvent.new @oldrow, @oldcol, arow, acol, self
|
682
|
+
else
|
683
|
+
@table_traversal_event.set(@oldrow, @oldcol, arow, acol, self)
|
684
|
+
end
|
685
|
+
fire_handler :TABLE_TRAVERSAL_EVENT, @table_traversal_event
|
686
|
+
if @editing_policy == :EDITING_AUTO
|
687
|
+
editing_started
|
688
|
+
end
|
689
|
+
end
|
690
|
+
# on enter of widget
|
691
|
+
# the cursor should be appropriately positioned
|
692
|
+
def on_enter
|
693
|
+
super
|
694
|
+
set_form_row
|
695
|
+
set_form_col # 2009-01-17 01:35
|
696
|
+
on_enter_cell focussed_row(), focussed_col()
|
697
|
+
end
|
698
|
+
def on_leave
|
699
|
+
super
|
700
|
+
$log.debug " on leave of table 2009-01-16 21:58 "
|
701
|
+
editing_stopped if @is_editing # 2009-01-16 21:58
|
702
|
+
end
|
703
|
+
def set_form_row
|
704
|
+
r,c = rowcol
|
705
|
+
# +1 is due to header
|
706
|
+
@form.row = r + (@current_index-@toprow) + 1
|
707
|
+
end
|
708
|
+
# set cursor on correct column, widget
|
709
|
+
def set_form_col col=@curpos
|
710
|
+
@curpos = col
|
711
|
+
@current_column_offset = get_column_offset
|
712
|
+
@form.col = @col + @col_offset + @curpos + @current_column_offset
|
713
|
+
end
|
714
|
+
# protected
|
715
|
+
def get_column_offset columnid=@current_column
|
716
|
+
return @table_column_model.column(columnid).column_offset || 0
|
717
|
+
end
|
718
|
+
|
719
|
+
|
720
|
+
def repaint
|
721
|
+
return unless @repaint_required
|
722
|
+
print_border @form.window if @to_print_borders == 1 # do this once only, unless everything changes
|
723
|
+
cc = @table_model.column_count
|
724
|
+
rc = @table_model.row_count
|
725
|
+
tcm = @table_column_model
|
726
|
+
tm = @table_model
|
727
|
+
tr = @toprow
|
728
|
+
acolor = get_color $datacolor
|
729
|
+
h = scrollatrow()
|
730
|
+
r,c = rowcol
|
731
|
+
# each cell should print itself, however there is a width issue.
|
732
|
+
# Then thee
|
733
|
+
print_header # do this once, unless columns changed
|
734
|
+
# TCM should give modelindex of col which is used to fetch data from TM
|
735
|
+
r += 1 # save for header
|
736
|
+
0.upto(h) do |hh|
|
737
|
+
crow = tr+hh
|
738
|
+
if crow < rc
|
739
|
+
offset = 0
|
740
|
+
# 0.upto(cc-1) do |colix|
|
741
|
+
# we loop through column_model and fetch data based on model index
|
742
|
+
# FIXED better to call table.get_value_at since we may now
|
743
|
+
# introduce a view - 2009-01-18 18:21
|
744
|
+
tcm.each_with_index do |acolumn, colix|
|
745
|
+
#acolumn = tcm.column(colix)
|
746
|
+
#model_index = acolumn.model_index
|
747
|
+
focussed = @current_index == crow ? true : false
|
748
|
+
selected = is_row_selected crow
|
749
|
+
content = get_value_at(crow, colix) # tables
|
750
|
+
#renderer = get_default_cell_renderer_for_class content.class.to_s
|
751
|
+
renderer = get_cell_renderer(crow, colix)
|
752
|
+
if renderer.nil?
|
753
|
+
renderer = get_default_cell_renderer_for_class(content.class.to_s) if renderer.nil?
|
754
|
+
renderer.display_length acolumn.width unless acolumn.nil?
|
755
|
+
end
|
756
|
+
width = renderer.display_length + 1
|
757
|
+
#renderer.repaint @form.window, r+hh, c+(colix*11), content, focussed, selected
|
758
|
+
acolumn.column_offset = offset
|
759
|
+
renderer.repaint @form.window, r+hh, c+(offset), content, focussed, selected
|
760
|
+
offset += width
|
761
|
+
end
|
762
|
+
else
|
763
|
+
@form.window.printstring r+hh, c, " " * (@width-2), acolor,@attr
|
764
|
+
# clear rows
|
765
|
+
end
|
766
|
+
end
|
767
|
+
if @is_editing
|
768
|
+
@cell_editor.component.repaint unless @cell_editor.nil? or @cell_editor.component.form.nil?
|
769
|
+
end
|
770
|
+
@table_changed = false
|
771
|
+
@repaint_required = false
|
772
|
+
end
|
773
|
+
def print_border g
|
774
|
+
return unless @table_changed
|
775
|
+
g.print_border @row, @col, @height, @width, $datacolor
|
776
|
+
end
|
777
|
+
def print_header
|
778
|
+
return unless @table_changed
|
779
|
+
r,c = rowcol
|
780
|
+
header_model = @table_header.table_column_model
|
781
|
+
tcm = @table_column_model ## could have been overridden, should we use this at all
|
782
|
+
offset = 0
|
783
|
+
header_model.each_with_index do |tc, colix|
|
784
|
+
acolumn = tcm.column colix
|
785
|
+
renderer = tc.cell_renderer
|
786
|
+
renderer = @table_header.default_renderer if renderer.nil?
|
787
|
+
renderer.display_length acolumn.width unless acolumn.nil?
|
788
|
+
width = renderer.display_length + 1
|
789
|
+
content = tc.header_value
|
790
|
+
renderer.repaint @form.window, r, c+(offset), content, false, false
|
791
|
+
offset += width
|
792
|
+
end
|
793
|
+
end
|
794
|
+
# 2009-01-17 13:25
|
795
|
+
def set_focus_on arow
|
796
|
+
@oldrow = @current_index
|
797
|
+
@current_index = arow
|
798
|
+
bounds_check if @oldrow != @current_index
|
799
|
+
end
|
800
|
+
attr_accessor :toprow # top visible
|
801
|
+
def ask_search_backward
|
802
|
+
regex = get_string("Enter regex to search (backward)")
|
803
|
+
ix = @table_model.find_prev regex, @current_index
|
804
|
+
if ix.nil?
|
805
|
+
alert("No matching data for: #{regex}")
|
806
|
+
else
|
807
|
+
set_focus_on(ix)
|
808
|
+
end
|
809
|
+
end
|
810
|
+
def find_prev
|
811
|
+
ix = @table_model.find_prev
|
812
|
+
regex = @table_model.last_regex
|
813
|
+
if ix.nil?
|
814
|
+
alert("No previous matching data for: #{regex}")
|
815
|
+
else
|
816
|
+
set_focus_on(ix)
|
817
|
+
end
|
818
|
+
end
|
819
|
+
def ask_search_forward
|
820
|
+
regex = get_string("Enter regex to search (forward)")
|
821
|
+
#ix = @table_model.find_next regex, @current_index
|
822
|
+
ix = @table_model.find_match regex, @current_index
|
823
|
+
if ix.nil?
|
824
|
+
alert("No matching data for: #{regex}")
|
825
|
+
else
|
826
|
+
set_focus_on(ix)
|
827
|
+
end
|
828
|
+
end
|
829
|
+
# table find_next
|
830
|
+
def find_next
|
831
|
+
ix = @table_model.find_next
|
832
|
+
regex = @table_model.last_regex
|
833
|
+
if ix.nil?
|
834
|
+
alert("No more matching data for: #{regex}")
|
835
|
+
else
|
836
|
+
set_focus_on(ix)
|
837
|
+
end
|
838
|
+
end
|
839
|
+
end # class Table
|
840
|
+
|
841
|
+
## TC
|
842
|
+
# All column changes take place in ColumnModel not in data. TC keeps pointer to col in data via
|
843
|
+
# TODO - can't change width beyond min and max if set
|
844
|
+
# resizable - user can't resize but programatically can
|
845
|
+
# model_index
|
846
|
+
class TableColumn
|
847
|
+
include RubyCurses::EventHandler # 2009-01-15 22:49
|
848
|
+
attr_reader :identifier
|
849
|
+
attr_accessor :min_width, :max_width, :is_resizable
|
850
|
+
attr_accessor :cell_renderer
|
851
|
+
attr_accessor :model_index # index inside TableModel
|
852
|
+
# user may override or set for this column, else headers default will be used
|
853
|
+
attr_accessor :header_renderer
|
854
|
+
dsl_property :header_value
|
855
|
+
dsl_property :width
|
856
|
+
# some columns may not be editable. e.g in a Finder, file size or time not editable
|
857
|
+
# whereas name is.
|
858
|
+
|
859
|
+
# is this column editable. Set to false to disable a column from editing IF the table
|
860
|
+
# allows editing. Setting to true not required.
|
861
|
+
dsl_accessor :editable # if not set takes tables value 2009-01-16 22:49
|
862
|
+
## added column_offset on 2009-01-12 19:01
|
863
|
+
attr_accessor :column_offset # where we've place this guy. in case we need to position cursor
|
864
|
+
attr_accessor :cell_editor
|
865
|
+
|
866
|
+
|
867
|
+
def initialize model_index, identifier, header_value, width, config={}, &block
|
868
|
+
@width = width
|
869
|
+
@model_index = model_index
|
870
|
+
@identifier = identifier
|
871
|
+
@header_value = header_value
|
872
|
+
@config={}
|
873
|
+
instance_eval &block if block_given?
|
874
|
+
end
|
875
|
+
def fire_property_change(text, oldval, newval)
|
876
|
+
#$log.debug "TC: def fire_property_change(#{text}, #{oldval}, #{newval})"
|
877
|
+
# need to send changeevent FIXME XXX maybe dsl_prop should do this.
|
878
|
+
fire_handler :PROPERTY_CHANGE, self
|
879
|
+
end
|
880
|
+
end # class tc
|
881
|
+
|
882
|
+
## TCM
|
883
|
+
#
|
884
|
+
class TableColumnModel
|
885
|
+
def column ix
|
886
|
+
nil
|
887
|
+
end
|
888
|
+
def columns
|
889
|
+
nil
|
890
|
+
end
|
891
|
+
def column_count
|
892
|
+
0
|
893
|
+
end
|
894
|
+
def column_selection_allowed
|
895
|
+
false
|
896
|
+
end
|
897
|
+
def selected_column_count
|
898
|
+
0
|
899
|
+
end
|
900
|
+
def selected_columns
|
901
|
+
nil
|
902
|
+
end
|
903
|
+
def total_column_width
|
904
|
+
0
|
905
|
+
end
|
906
|
+
def get_selection_model
|
907
|
+
nil
|
908
|
+
end
|
909
|
+
def set_selection_model lsm
|
910
|
+
end
|
911
|
+
def add_column tc
|
912
|
+
end
|
913
|
+
def remove_column tc
|
914
|
+
end
|
915
|
+
def move_column ix, newix
|
916
|
+
end
|
917
|
+
def column_index identifier
|
918
|
+
nil
|
919
|
+
end
|
920
|
+
# add tcm listener
|
921
|
+
end
|
922
|
+
## DTCM DCM
|
923
|
+
class DefaultTableColumnModel < TableColumnModel
|
924
|
+
include Enumerable
|
925
|
+
include RubyCurses::EventHandler # widget does 2009-01-15 15:38
|
926
|
+
attr_accessor :column_selection_allowed
|
927
|
+
|
928
|
+
##
|
929
|
+
# takes a column names array
|
930
|
+
def initialize cols=[]
|
931
|
+
@columns = []
|
932
|
+
##cols.each_with_index {|c, index| @columns << TableColumn.new(index, c, c, 10) }
|
933
|
+
cols.each_with_index {|c, index| add_column(TableColumn.new(index, c, c, 10)) }
|
934
|
+
@selected_columns = []
|
935
|
+
end
|
936
|
+
def column ix
|
937
|
+
raise "Invalid arg #{ix}" if ix < 0 or ix > (@columns.length() -1)
|
938
|
+
@columns[ix]
|
939
|
+
end
|
940
|
+
def columns; @columns; end
|
941
|
+
##
|
942
|
+
# yields a table column
|
943
|
+
def each
|
944
|
+
@columns.each { |c|
|
945
|
+
yield c
|
946
|
+
}
|
947
|
+
end
|
948
|
+
def column_count
|
949
|
+
@columns.length
|
950
|
+
end
|
951
|
+
def selected_column_count
|
952
|
+
@selected_columns.length
|
953
|
+
end
|
954
|
+
def selected_columns
|
955
|
+
@selected_columns
|
956
|
+
end
|
957
|
+
def clear_selection
|
958
|
+
@selected_columns = []
|
959
|
+
end
|
960
|
+
def total_column_width
|
961
|
+
0
|
962
|
+
end
|
963
|
+
def set_selection_model lsm
|
964
|
+
@column_selection_model = lsm
|
965
|
+
end
|
966
|
+
def add_column tc
|
967
|
+
@columns << tc
|
968
|
+
tc.bind(:PROPERTY_CHANGE){|e| column_property_changed(e)}
|
969
|
+
tmce = TableColumnModelEvent.new(nil, @columns.length-1, self, :INSERT)
|
970
|
+
fire_handler :TABLE_COLUMN_MODEL_EVENT, tmce
|
971
|
+
end
|
972
|
+
def column_property_changed evt
|
973
|
+
$log.debug "DTCM def column_property_changed #{evt} "
|
974
|
+
# need to send changeevent FIXME XXX
|
975
|
+
fire_handler :PROPERTY_CHANGE, self
|
976
|
+
end
|
977
|
+
def remove_column tc
|
978
|
+
ix = @columns.index tc
|
979
|
+
@columns.delete tc
|
980
|
+
tmce = TableColumnModelEvent.new(ix, nil, self, :DELETE)
|
981
|
+
fire_handler :TABLE_COLUMN_MODEL_EVENT, tmce
|
982
|
+
end
|
983
|
+
def move_column ix, newix
|
984
|
+
# acol = remove_column column(ix)
|
985
|
+
acol = @columns.delete_at ix
|
986
|
+
@columns.insert newix, acol
|
987
|
+
tmce = TableColumnModelEvent.new(ix, newix, self, :MOVE)
|
988
|
+
fire_handler :TABLE_COLUMN_MODEL_EVENT, tmce
|
989
|
+
end
|
990
|
+
##
|
991
|
+
# return index of column identified with identifier
|
992
|
+
def column_index identifier
|
993
|
+
@columns.each_with_index {|c, i| return i if c.identifier == identifier }
|
994
|
+
return nil
|
995
|
+
end
|
996
|
+
## TODO - if we get into column selection somewhen
|
997
|
+
def get_selection_model
|
998
|
+
@lsm
|
999
|
+
end
|
1000
|
+
def set_selection_model lsm
|
1001
|
+
@lsm = lsm
|
1002
|
+
end
|
1003
|
+
# add tcm listener
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
## TM
|
1007
|
+
class TableModel
|
1008
|
+
def column_count
|
1009
|
+
end
|
1010
|
+
def row_count
|
1011
|
+
end
|
1012
|
+
def set_value_at row, col, val
|
1013
|
+
end
|
1014
|
+
def get_value_at row, col
|
1015
|
+
end
|
1016
|
+
=begin
|
1017
|
+
def << obj
|
1018
|
+
end
|
1019
|
+
def insert row, obj
|
1020
|
+
end
|
1021
|
+
def delete obj
|
1022
|
+
end
|
1023
|
+
def delete_at row
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
=end
|
1027
|
+
end # class
|
1028
|
+
|
1029
|
+
##
|
1030
|
+
# DTM
|
1031
|
+
class DefaultTableModel < TableModel
|
1032
|
+
attr_reader :last_regex
|
1033
|
+
include RubyCurses::EventHandler # 2009-01-15 15:38
|
1034
|
+
def initialize data, colnames_array
|
1035
|
+
@data = data
|
1036
|
+
@column_identifiers = colnames_array
|
1037
|
+
end
|
1038
|
+
def column_count
|
1039
|
+
@column_identifiers.count
|
1040
|
+
end
|
1041
|
+
def row_count
|
1042
|
+
@data.length
|
1043
|
+
end
|
1044
|
+
#
|
1045
|
+
# please avoid directly hitting this. Suggested to use get_value_at of jtable
|
1046
|
+
# since columns could have been switched.
|
1047
|
+
def set_value_at row, col, val
|
1048
|
+
# $log.debug " def set_value_at #{row}, #{col}, #{val} "
|
1049
|
+
# if editing allowed
|
1050
|
+
@data[row][col] = val
|
1051
|
+
tme = TableModelEvent.new(row, row, col, self, :UPDATE)
|
1052
|
+
fire_handler :TABLE_MODEL_EVENT, tme
|
1053
|
+
end
|
1054
|
+
##
|
1055
|
+
# please avoid directly hitting this. Suggested to use get_value_at of jtable
|
1056
|
+
# since columns could have been switched.
|
1057
|
+
def get_value_at row, col
|
1058
|
+
#$log.debug " def get_value_at #{row}, #{col} "
|
1059
|
+
|
1060
|
+
raise "IndexError get_value_at #{row}, #{col}" if @data.nil? or row >= @data.size
|
1061
|
+
return @data[row][ col]
|
1062
|
+
end
|
1063
|
+
def << obj
|
1064
|
+
@data << obj
|
1065
|
+
tme = TableModelEvent.new(@data.length-1,@data.length-1, :ALL_COLUMNS, self, :INSERT)
|
1066
|
+
fire_handler :TABLE_MODEL_EVENT, tme
|
1067
|
+
# create tablemodelevent and fire_table_changed for all listeners
|
1068
|
+
end
|
1069
|
+
def insert row, obj
|
1070
|
+
@data.insert row, obj
|
1071
|
+
tme = TableModelEvent.new(row, row,:ALL_COLUMNS, self, :INSERT)
|
1072
|
+
fire_handler :TABLE_MODEL_EVENT, tme
|
1073
|
+
# create tablemodelevent and fire_table_changed for all listeners
|
1074
|
+
end
|
1075
|
+
def delete obj
|
1076
|
+
row = @data.index obj
|
1077
|
+
return if row.nil?
|
1078
|
+
ret = @data.delete obj
|
1079
|
+
tme = TableModelEvent.new(row, row,:ALL_COLUMNS, self, :DELETE)
|
1080
|
+
fire_handler :TABLE_MODEL_EVENT, tme
|
1081
|
+
# create tablemodelevent and fire_table_changed for all listeners
|
1082
|
+
return ret
|
1083
|
+
end
|
1084
|
+
def delete_at row
|
1085
|
+
ret = @data.delete_at row
|
1086
|
+
# create tablemodelevent and fire_table_changed for all listeners
|
1087
|
+
tme = TableModelEvent.new(row, row,:ALL_COLUMNS, self, :DELETE)
|
1088
|
+
fire_handler :TABLE_MODEL_EVENT, tme
|
1089
|
+
return ret
|
1090
|
+
end
|
1091
|
+
##
|
1092
|
+
# added 2009-01-17 21:36
|
1093
|
+
# Use with caution, does not call events per row
|
1094
|
+
def delete_all
|
1095
|
+
len = @data.length-1
|
1096
|
+
@data=[]
|
1097
|
+
tme = TableModelEvent.new(0, len,:ALL_COLUMNS, self, :DELETE)
|
1098
|
+
fire_handler :TABLE_MODEL_EVENT, tme
|
1099
|
+
end
|
1100
|
+
##
|
1101
|
+
# for those quick cases when you wish to replace all the data
|
1102
|
+
# and not have an event per row being generated
|
1103
|
+
def data=(data)
|
1104
|
+
raise "Data nil or invalid" if data.nil? or data.size == 0
|
1105
|
+
delete_all
|
1106
|
+
@data = data
|
1107
|
+
tme = TableModelEvent.new(0, @data.length-1,:ALL_COLUMNS, self, :INSERT)
|
1108
|
+
fire_handler :TABLE_MODEL_EVENT, tme
|
1109
|
+
end
|
1110
|
+
def ask_search_forward
|
1111
|
+
regex = get_string "Enter regex to search for:"
|
1112
|
+
ix = get_list_data_model.find_match regex
|
1113
|
+
if ix.nil?
|
1114
|
+
alert("No matching data for: #{regex}")
|
1115
|
+
else
|
1116
|
+
set_focus_on(ix)
|
1117
|
+
end
|
1118
|
+
end
|
1119
|
+
# continues previous search
|
1120
|
+
##
|
1121
|
+
def find_match regex, ix0=0, ix1=row_count()
|
1122
|
+
$log.debug " find_match got #{regex} #{ix0} #{ix1}"
|
1123
|
+
@last_regex = regex
|
1124
|
+
@search_start_ix = ix0
|
1125
|
+
@search_end_ix = ix1
|
1126
|
+
@data.each_with_index do |row, ix|
|
1127
|
+
next if ix < ix0
|
1128
|
+
break if ix > ix1
|
1129
|
+
if row.grep(/#{regex}/) != []
|
1130
|
+
#if !row.match(regex).nil?
|
1131
|
+
@search_found_ix = ix
|
1132
|
+
return ix
|
1133
|
+
end
|
1134
|
+
end
|
1135
|
+
return nil
|
1136
|
+
end
|
1137
|
+
def find_prev regex=@last_regex, start = @search_found_ix
|
1138
|
+
raise "No previous search" if @last_regex.nil?
|
1139
|
+
$log.debug " find_prev #{@search_found_ix} : #{@current_index}"
|
1140
|
+
start -= 1 unless start == 0
|
1141
|
+
@last_regex = regex
|
1142
|
+
@search_start_ix = start
|
1143
|
+
start.downto(0) do |ix|
|
1144
|
+
row = @data[ix]
|
1145
|
+
if row.grep(/#{regex}/) != []
|
1146
|
+
@search_found_ix = ix
|
1147
|
+
return ix
|
1148
|
+
end
|
1149
|
+
end
|
1150
|
+
return nil
|
1151
|
+
#return find_match @last_regex, start, @search_end_ix
|
1152
|
+
end
|
1153
|
+
## dtm findnext
|
1154
|
+
def find_next
|
1155
|
+
raise "No more search" if @last_regex.nil?
|
1156
|
+
start = @search_found_ix && @search_found_ix+1 || 0
|
1157
|
+
return find_match @last_regex, start, @search_end_ix
|
1158
|
+
end
|
1159
|
+
end # class DTC
|
1160
|
+
|
1161
|
+
##
|
1162
|
+
# LSM
|
1163
|
+
#
|
1164
|
+
class DefaultListSelectionModel
|
1165
|
+
include RubyCurses::EventHandler
|
1166
|
+
attr_accessor :selection_mode
|
1167
|
+
attr_reader :anchor_selection_index
|
1168
|
+
attr_reader :lead_selection_index
|
1169
|
+
def initialize
|
1170
|
+
@selected_indices=[]
|
1171
|
+
@anchor_selection_index = -1
|
1172
|
+
@lead_selection_index = -1
|
1173
|
+
@selection_mode = :MULTIPLE
|
1174
|
+
end
|
1175
|
+
|
1176
|
+
def clear_selection
|
1177
|
+
@selected_indices=[]
|
1178
|
+
end
|
1179
|
+
def is_selected_index ix
|
1180
|
+
@selected_indices.include? ix
|
1181
|
+
end
|
1182
|
+
def get_max_selection_index
|
1183
|
+
@selected_indices[-1]
|
1184
|
+
end
|
1185
|
+
def get_min_selection_index
|
1186
|
+
@selected_indices[0]
|
1187
|
+
end
|
1188
|
+
def get_selected_rows
|
1189
|
+
@selected_indices
|
1190
|
+
end
|
1191
|
+
## TODO should go in sorted, and no dupes
|
1192
|
+
def add_selection_interval ix0, ix1
|
1193
|
+
@anchor_selection_index = ix0
|
1194
|
+
@lead_selection_index = ix1
|
1195
|
+
ix0.upto(ix1) {|i| @selected_indices << i unless @selected_indices.include? i }
|
1196
|
+
end
|
1197
|
+
def remove_selection_interval ix0, ix1
|
1198
|
+
@anchor_selection_index = ix0
|
1199
|
+
@lead_selection_index = ix1
|
1200
|
+
@selected_indices.delete_if {|x| x >= ix0 and x <= ix1}
|
1201
|
+
end
|
1202
|
+
def insert_index_interval ix0, len
|
1203
|
+
@anchor_selection_index = ix0
|
1204
|
+
@lead_selection_index = ix0+len
|
1205
|
+
add_selection_interval @anchor_selection_index, @lead_selection_index
|
1206
|
+
end
|
1207
|
+
end # class DefaultListSelectionModel
|
1208
|
+
##
|
1209
|
+
#
|
1210
|
+
class TableHeader
|
1211
|
+
attr_accessor :default_renderer
|
1212
|
+
attr_accessor :table_column_model
|
1213
|
+
def initialize table_column_model
|
1214
|
+
@table_column_model = table_column_model
|
1215
|
+
create_default_renderer
|
1216
|
+
end
|
1217
|
+
def create_default_renderer
|
1218
|
+
#@default_renderer = TableCellRenderer.new "", {"display_length" => 10, "justify" => :center}
|
1219
|
+
@default_renderer = TableCellRenderer.new "", {"display_length" => 10, "justify" => :center, "color"=>"white", "bgcolor"=>"blue"}
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
end
|
1223
|
+
##
|
1224
|
+
# When an event is fired by TableModel, contents are changed, then this object will be passed
|
1225
|
+
# to trigger
|
1226
|
+
# type is :INSERT :UPDATE :DELETE :HEADER_ROW
|
1227
|
+
# columns: number or :ALL_COLUMNS
|
1228
|
+
class TableModelEvent
|
1229
|
+
attr_accessor :firstrow, :lastrow, :column, :source, :type
|
1230
|
+
def initialize firstrow, lastrow, column, source, type
|
1231
|
+
@firstrow = firstrow
|
1232
|
+
@lastrow = lastrow
|
1233
|
+
@column = column
|
1234
|
+
@source = source
|
1235
|
+
@type = type
|
1236
|
+
end
|
1237
|
+
def to_s
|
1238
|
+
"#{@type.to_s}, firstrow: #{@firstrow}, lastrow: #{@lastrow}, column: #{@column}, source: #{@source}"
|
1239
|
+
end
|
1240
|
+
def inspect
|
1241
|
+
to_s
|
1242
|
+
end
|
1243
|
+
end
|
1244
|
+
##
|
1245
|
+
# event sent when a column is added, removed or moved
|
1246
|
+
# type :INSERT :DELETE :MOVE
|
1247
|
+
# in the case of add query first col, for removed query second
|
1248
|
+
class TableColumnModelEvent
|
1249
|
+
attr_accessor :from_col, :to_col, :source, :type
|
1250
|
+
def initialize from_col, to_col, source, type
|
1251
|
+
@from_col = from_col
|
1252
|
+
@to_col = to_col
|
1253
|
+
@source = source
|
1254
|
+
@type = type
|
1255
|
+
end
|
1256
|
+
def to_s
|
1257
|
+
"#{@type.to_s}, from_col: #{@from_col}, to_col: #{@to_col}, source: #{@source}"
|
1258
|
+
end
|
1259
|
+
def inspect
|
1260
|
+
to_s
|
1261
|
+
end
|
1262
|
+
end
|
1263
|
+
## caller can create one and reuse NOTE TODO
|
1264
|
+
class TableTraversalEvent
|
1265
|
+
attr_accessor :oldrow, :oldcol, :newrow, :newcol, :source
|
1266
|
+
def initialize oldrow, oldcol, newrow, newcol, source
|
1267
|
+
@oldrow, @oldcol, @newrow, @newcol, @source = oldrow, oldcol, newrow, newcol, source
|
1268
|
+
end
|
1269
|
+
def set oldrow, oldcol, newrow, newcol, source
|
1270
|
+
@oldrow, @oldcol, @newrow, @newcol, @source = oldrow, oldcol, newrow, newcol, source
|
1271
|
+
end
|
1272
|
+
def to_s
|
1273
|
+
"TRAVERSAL oldrow: #{@oldrow}, oldcol: #{@oldcol}, newrow: #{@newrow}, newcol: #{@newcol}, source: #{@source}"
|
1274
|
+
end
|
1275
|
+
def inspect
|
1276
|
+
to_s
|
1277
|
+
end
|
1278
|
+
end
|
1279
|
+
## caller can create one and reuse NOTE TODO
|
1280
|
+
class TableEditingEvent
|
1281
|
+
attr_accessor :row, :col, :source, :oldvalue, :newvalue, :type
|
1282
|
+
def initialize row, col, source, oldvalue, newvalue, type
|
1283
|
+
set row, col, source, oldvalue, newvalue, type
|
1284
|
+
end
|
1285
|
+
def set row, col, source, oldvalue, newvalue, type
|
1286
|
+
@row, @col, @source, @oldvalue, @newvalue, @type = row, col, source, oldvalue, newvalue, type
|
1287
|
+
end
|
1288
|
+
def to_s
|
1289
|
+
"TABLEDITING #{@type} row: #{@row}, col: #{@col}, oldval: #{@oldvalue}, newvalue: #{@newvalue}, source: #{@source}"
|
1290
|
+
end
|
1291
|
+
def inspect
|
1292
|
+
to_s
|
1293
|
+
end
|
1294
|
+
end
|
1295
|
+
|
1296
|
+
end # module
|