rbcurse 0.1.3 → 1.1.1
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 +126 -0
- data/Manifest.txt +53 -20
- data/README.markdown +423 -0
- data/Rakefile +3 -1
- data/examples/keytest.rb +177 -0
- data/examples/mpad2.rb +156 -0
- data/examples/newtesttabp.rb +121 -0
- data/examples/rfe.rb +48 -10
- data/examples/rfe_renderer.rb +4 -4
- data/examples/rvimsplit.rb +376 -0
- data/examples/sqlc.rb +97 -106
- data/examples/sqlm.rb +446 -0
- data/examples/test1.rb +4 -4
- data/examples/test2.rb +12 -12
- data/examples/testchars.rb +140 -0
- data/examples/testkeypress.rb +9 -4
- data/examples/testmulticomp.rb +72 -0
- data/examples/testscroller.rb +136 -0
- data/examples/testscrolllb.rb +86 -0
- data/examples/testscrollp.rb +87 -0
- data/examples/testscrollta.rb +80 -0
- data/examples/testscrolltable.rb +166 -0
- data/examples/testsplit.rb +87 -0
- data/examples/testsplit2.rb +123 -0
- data/examples/testsplit3.rb +215 -0
- data/examples/testsplit3_1.rb +244 -0
- data/examples/testsplit3a.rb +215 -0
- data/examples/testsplit3b.rb +237 -0
- data/examples/testsplitta.rb +148 -0
- data/examples/testsplittv.rb +142 -0
- data/examples/testsplittvv.rb +144 -0
- data/examples/testtable.rb +1 -1
- data/examples/testtabp.rb +3 -2
- data/examples/testtestw.rb +69 -0
- data/examples/testtodo.rb +5 -3
- data/examples/testtpane.rb +203 -0
- data/examples/testtpane2.rb +145 -0
- data/examples/testtpanetable.rb +199 -0
- data/examples/viewtodo.rb +5 -3
- data/lib/rbcurse.rb +1 -1
- data/lib/rbcurse/celleditor.rb +2 -2
- data/lib/rbcurse/colormap.rb +5 -5
- data/lib/rbcurse/defaultlistselectionmodel.rb +3 -3
- data/lib/rbcurse/io.rb +663 -0
- data/lib/rbcurse/listeditable.rb +306 -0
- data/lib/rbcurse/listkeys.rb +15 -15
- data/lib/rbcurse/listscrollable.rb +168 -27
- data/lib/rbcurse/mapper.rb +35 -13
- data/lib/rbcurse/rchangeevent.rb +28 -0
- data/lib/rbcurse/rform.rb +845 -0
- data/lib/rbcurse/rlistbox.rb +144 -34
- data/lib/rbcurse/rmessagebox.rb +10 -5
- data/lib/rbcurse/rmulticontainer.rb +325 -0
- data/lib/rbcurse/rmultitextview.rb +306 -0
- data/lib/rbcurse/rscrollform.rb +369 -0
- data/lib/rbcurse/rscrollpane.rb +511 -0
- data/lib/rbcurse/rsplitpane.rb +820 -0
- data/lib/rbcurse/rtabbedpane.rb +737 -109
- data/lib/rbcurse/rtabbedwindow.rb +326 -0
- data/lib/rbcurse/rtable.rb +220 -64
- data/lib/rbcurse/rtextarea.rb +340 -181
- data/lib/rbcurse/rtextview.rb +237 -101
- data/lib/rbcurse/rviewport.rb +203 -0
- data/lib/rbcurse/rwidget.rb +919 -95
- data/lib/rbcurse/scrollable.rb +7 -7
- data/lib/rbcurse/selectable.rb +4 -4
- data/lib/rbcurse/table/tablecellrenderer.rb +3 -0
- data/lib/rbcurse/undomanager.rb +181 -0
- data/lib/rbcurse/vieditable.rb +100 -0
- data/lib/ver/window.rb +471 -21
- metadata +66 -22
- data/README.txt +0 -312
- data/examples/testd.db +0 -0
- data/examples/todocsv.csv +0 -28
data/examples/sqlc.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
|
-
##
|
2
|
-
#
|
3
|
-
#
|
1
|
+
## rkumar, 2009
|
2
|
+
# Sample demo of various widgets and their interaction.
|
3
|
+
# This is a simple sql client which allows table / column selection, construction
|
4
|
+
# of SQL queries, and multiple resultsets.
|
5
|
+
# Use C-q to quit, Alt-Tab to move out of Table to next field.
|
6
|
+
# Please see bind_key statements in this app for some key bindings in table.
|
7
|
+
# There are also key bindings in tabbedpanes and textarea's that will help alot.
|
8
|
+
# This demo uses a tabbedpane so we can have the results of many sql statements and not
|
9
|
+
# need to keep reissuing.
|
4
10
|
#
|
5
11
|
require 'rubygems'
|
6
12
|
require 'ncurses'
|
@@ -11,10 +17,11 @@ require 'rbcurse/rcombo'
|
|
11
17
|
require 'rbcurse/rtextarea'
|
12
18
|
require 'rbcurse/rtable'
|
13
19
|
#require 'rbcurse/table/tablecellrenderer'
|
14
|
-
require 'rbcurse/comboboxcellrenderer'
|
15
|
-
require 'rbcurse/keylabelprinter'
|
20
|
+
#require 'rbcurse/comboboxcellrenderer'
|
21
|
+
#require 'rbcurse/keylabelprinter'
|
16
22
|
require 'rbcurse/applicationheader'
|
17
|
-
require 'rbcurse/action'
|
23
|
+
require 'rbcurse/action' # not used here
|
24
|
+
require 'rbcurse/rtabbedpane'
|
18
25
|
|
19
26
|
# pls get testd.db from
|
20
27
|
# http://www.benegal.org/files/screen/testd.db
|
@@ -137,10 +144,10 @@ end
|
|
137
144
|
class Sqlc
|
138
145
|
def initialize
|
139
146
|
@window = VER::Window.root_window
|
147
|
+
$catch_alt_digits = false # we want to use Alt-1, 2 for tabs.
|
140
148
|
@form = Form.new @window
|
149
|
+
@tab_ctr = 0
|
141
150
|
|
142
|
-
#@todo = Sql.new "todo.yml"
|
143
|
-
#@todo.load
|
144
151
|
@db = Datasource.new
|
145
152
|
@db.connect "testd.db"
|
146
153
|
end
|
@@ -170,8 +177,6 @@ class Sqlc
|
|
170
177
|
end
|
171
178
|
sqlarea << "select * from contacts"
|
172
179
|
buttrow = r+ta_ht+1 #Ncurses.LINES-4
|
173
|
-
#create_table_actions atable, todo, data, categ.getvalue
|
174
|
-
#save_cmd = @save_cmd
|
175
180
|
b_run = Button.new @form do
|
176
181
|
text "&Run"
|
177
182
|
row buttrow
|
@@ -179,14 +184,12 @@ class Sqlc
|
|
179
184
|
help_text "Run query"
|
180
185
|
end
|
181
186
|
## We use Action to create a button: to test out ampersand with MI and Button
|
182
|
-
#clear_act = @clear_act
|
183
187
|
b_clear = Button.new @form do
|
184
188
|
#action new_act
|
185
189
|
text "&Clear"
|
186
190
|
row buttrow
|
187
191
|
col c+10
|
188
192
|
help_text "Clear query entry box "
|
189
|
-
#bind(:ENTER) { status_row.text "New button adds a new row below current " }
|
190
193
|
end
|
191
194
|
b_clear.command {
|
192
195
|
sqlarea.remove_all
|
@@ -199,31 +202,15 @@ class Sqlc
|
|
199
202
|
text "Constr&uct"
|
200
203
|
row buttrow
|
201
204
|
col c+25
|
202
|
-
#bind(:ENTER) { status_row.text "Deletes focussed row" }
|
203
205
|
help_text "Select a table, select columns and press this to construct an SQL"
|
204
206
|
end
|
205
207
|
|
206
208
|
Button.button_layout [b_run, b_clear, b_construct], buttrow, startcol=5, cols=Ncurses.COLS-1, gap=5
|
207
209
|
|
208
|
-
|
209
|
-
|
210
|
-
name "sqltable"
|
211
|
-
row buttrow+1
|
212
|
-
col c
|
213
|
-
width t_width
|
214
|
-
height table_ht
|
215
|
-
#title "A Table"
|
216
|
-
#title_attrib (Ncurses::A_REVERSE | Ncurses::A_BOLD)
|
217
|
-
#set_data data, colnames
|
218
|
-
#cell_editing_allowed true
|
219
|
-
#editing_policy :EDITING_AUTO
|
220
|
-
help_text "M-Tab for next field"
|
221
|
-
end
|
222
|
-
@atable = atable
|
210
|
+
@tp = create_tabbed_pane @form, buttrow, t_width, c
|
211
|
+
@tp.show
|
223
212
|
@data = data
|
224
|
-
#atable.table_model.data = data
|
225
213
|
|
226
|
-
tcm = atable.get_table_column_model
|
227
214
|
b_run.command {
|
228
215
|
query = sqlarea.get_text
|
229
216
|
run_query query
|
@@ -232,45 +219,52 @@ class Sqlc
|
|
232
219
|
## key bindings fo atable
|
233
220
|
# column widths
|
234
221
|
app = self
|
235
|
-
atable.configure() do
|
236
|
-
|
237
|
-
bind_key(?+) {
|
238
|
-
acolumn = atable.column atable.focussed_col()
|
239
|
-
w = acolumn.width + 1
|
240
|
-
acolumn.width w
|
241
|
-
|
242
|
-
}
|
243
|
-
bind_key(?-) {
|
244
|
-
acolumn = atable.column atable.focussed_col()
|
245
|
-
w = acolumn.width - 1
|
246
|
-
if w > 3
|
247
|
-
acolumn.width w
|
248
|
-
|
249
|
-
end
|
250
|
-
}
|
251
|
-
|
252
|
-
|
253
|
-
#atable.
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
#atable.move_column
|
261
|
-
}
|
262
|
-
bind_key(
|
263
|
-
|
222
|
+
#atable.configure() do
|
223
|
+
##bind_key(330) { atable.remove_column(tcm.column(atable.focussed_col)) rescue "" }
|
224
|
+
#bind_key(?+) {
|
225
|
+
#acolumn = atable.column atable.focussed_col()
|
226
|
+
#w = acolumn.width + 1
|
227
|
+
#acolumn.width w
|
228
|
+
##atable.table_structure_changed
|
229
|
+
#}
|
230
|
+
#bind_key(?-) {
|
231
|
+
#acolumn = atable.column atable.focussed_col()
|
232
|
+
#w = acolumn.width - 1
|
233
|
+
#if w > 3
|
234
|
+
#acolumn.width w
|
235
|
+
##atable.table_structure_changed
|
236
|
+
#end
|
237
|
+
#}
|
238
|
+
## added new method on 2009-10-08 00:47
|
239
|
+
#bind_key(?=) {
|
240
|
+
#atable.size_columns_to_fit
|
241
|
+
#}
|
242
|
+
#bind_key(?>) {
|
243
|
+
#tcm = atable.get_table_column_model
|
244
|
+
#colcount = tcm.column_count-1
|
245
|
+
##atable.move_column sel_col.value, sel_col.value+1 unless sel_col.value == colcount
|
246
|
+
#col = atable.focussed_col
|
247
|
+
#atable.move_column col, col+1 unless col == colcount
|
248
|
+
#}
|
249
|
+
#bind_key(?<) {
|
250
|
+
#col = atable.focussed_col
|
251
|
+
#atable.move_column col, col-1 unless col == 0
|
252
|
+
##atable.move_column sel_col.value, sel_col.value-1 unless sel_col.value == 0
|
253
|
+
#}
|
254
|
+
## TODO popup and key labels
|
255
|
+
#bind_key(?\M-h, app) {|tab,td| $log.debug " BIND... #{tab.class}, #{td.class}"; app.make_popup atable}
|
256
|
+
#end
|
264
257
|
#keylabel = RubyCurses::Label.new @form, {'text' => "", "row" => r+table_ht+3, "col" => c, "color" => "yellow", "bgcolor"=>"blue", "display_length"=>60, "height"=>2}
|
265
258
|
#eventlabel = RubyCurses::Label.new @form, {'text' => "Events:", "row" => r+table_ht+6, "col" => c, "color" => "white", "bgcolor"=>"blue", "display_length"=>60, "height"=>2}
|
266
259
|
|
267
260
|
# report some events
|
268
261
|
#atable.table_model.bind(:TABLE_MODEL_EVENT){|e| #eventlabel.text = "Event: #{e}"}
|
269
262
|
#atable.get_table_column_model.bind(:TABLE_COLUMN_MODEL_EVENT){|e| eventlabel.text = "Event: #{e}"}
|
270
|
-
atable.bind(:TABLE_TRAVERSAL_EVENT){|e| @header.text_right "Row #{e.newrow+1} of #{atable.row_count}" }
|
271
263
|
|
272
264
|
tablist_ht = 6
|
273
265
|
mylist = @db.get_data "select name from sqlite_master"
|
266
|
+
# mylist is an Array of SQLite3::ResultSet::ArrayWithTypesAndFields
|
267
|
+
mylist.collect!{|x| x[0] } ## 1.9 hack, but will it run on 1.8 ??
|
274
268
|
$listdata = Variable.new mylist
|
275
269
|
tablelist = Listbox.new @form do
|
276
270
|
name "tablelist"
|
@@ -305,29 +299,35 @@ class Sqlc
|
|
305
299
|
title_attrib 'reverse'
|
306
300
|
help_text "Press ENTER to append columns to sqlarea, Space to select"
|
307
301
|
end
|
302
|
+
## pressing SPACE on a table populates column list with its columns so they can be selected
|
308
303
|
tablelist.bind_key(32) {
|
309
304
|
@status_row.text = "Selected #{tablelist.get_content()[tablelist.current_index]}"
|
310
305
|
table = "#{tablelist.get_content()[tablelist.current_index]}"
|
306
|
+
##table = table[0] if table.class==Array ## 1.9 ???
|
311
307
|
columnlist.list_data_model.remove_all
|
312
308
|
columnlist.list_data_model.insert 0, *@db.get_metadata(table)
|
313
309
|
}
|
310
|
+
## pressing ENTER on a table runs a query on it, no need to type and SQL
|
314
311
|
tablelist.bind_key(13) {
|
315
312
|
@status_row.text = "Selected #{tablelist.get_content()[tablelist.current_index]}"
|
316
313
|
table = "#{tablelist.get_content()[tablelist.current_index]}"
|
314
|
+
##table = table[0] if table.class==Array ## 1.9 ???
|
317
315
|
run_query "select * from #{table}"
|
318
316
|
}
|
319
317
|
columnlist.bind_key(13) {
|
320
|
-
|
318
|
+
## append column name to sqlarea if ENTER pressed
|
321
319
|
column = "#{columnlist.get_content()[columnlist.current_index]}"
|
322
320
|
sqlarea << "#{column},"
|
323
321
|
}
|
324
322
|
columnlist.bind_key(32) {
|
325
|
-
|
323
|
+
## select row - later can press Construct button
|
326
324
|
columnlist.toggle_row_selection
|
327
325
|
column = "#{columnlist.get_content()[columnlist.current_index]}"
|
328
326
|
}
|
327
|
+
## construct an SQL after selecting some columns in the column list
|
329
328
|
b_construct.command {
|
330
329
|
table = "#{tablelist.get_content()[tablelist.current_index]}"
|
330
|
+
#table = table[0] if table.class==Array ## 1.9 ???
|
331
331
|
indexes = columnlist.selected_rows()
|
332
332
|
columns=[]
|
333
333
|
indexes.each do |i|
|
@@ -342,10 +342,9 @@ class Sqlc
|
|
342
342
|
@window.wrefresh
|
343
343
|
Ncurses::Panel.update_panels
|
344
344
|
begin
|
345
|
-
while((ch = @window.getchar()) != ?\C-q )
|
346
|
-
#colcount = tcm.column_count-1
|
345
|
+
while((ch = @window.getchar()) != ?\C-q.getbyte(0) )
|
347
346
|
s = keycode_tos ch
|
348
|
-
|
347
|
+
status_row.text = "Pressed #{ch} , #{s}. Press C-q to quit, Alt-Tab for exiting table "
|
349
348
|
@form.handle_key(ch)
|
350
349
|
|
351
350
|
@form.repaint
|
@@ -355,6 +354,8 @@ class Sqlc
|
|
355
354
|
@window.destroy if !@window.nil?
|
356
355
|
end
|
357
356
|
end
|
357
|
+
## execute the query in the textarea
|
358
|
+
# @param [String] sql string
|
358
359
|
def run_query sql
|
359
360
|
#query = sqlarea.get_text
|
360
361
|
query = sql
|
@@ -365,53 +366,43 @@ class Sqlc
|
|
365
366
|
return
|
366
367
|
end
|
367
368
|
#cw = @db.estimate_column_widths @atable.width, @db.columns
|
368
|
-
|
369
|
-
|
370
|
-
|
369
|
+
atable = create_table @tp, @tab_ctr #, buttrow, t_width, c
|
370
|
+
atable.set_data @content, @db.columns
|
371
|
+
cw = atable.estimate_column_widths @db.columns, @db.datatypes
|
372
|
+
atable.set_column_widths cw
|
371
373
|
rescue => exc
|
374
|
+
$log.debug(exc.backtrace.join("\n"))
|
372
375
|
alert exc.to_s
|
373
376
|
return
|
374
377
|
end
|
375
378
|
@status_row.text = "#{@content.size} rows retrieved"
|
376
|
-
|
379
|
+
atable.repaint
|
377
380
|
end
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
alert("Rewritten yaml file")
|
402
|
-
}
|
403
|
-
@del_cmd = lambda {
|
404
|
-
row = atable.focussed_row
|
405
|
-
if !row.nil?
|
406
|
-
if confirm("Do your really want to delete row #{row+1}?")== :YES
|
407
|
-
tm = atable.table_model
|
408
|
-
tm.delete_at row
|
409
|
-
else
|
410
|
-
@status_row.text = "Delete cancelled"
|
411
|
-
end
|
381
|
+
## create a Table component for populating with data
|
382
|
+
def create_table tp, counter #, buttrow, t_width, c
|
383
|
+
table_ht = 15
|
384
|
+
atable = Table.new do
|
385
|
+
name "sqltable#{counter}"
|
386
|
+
#cell_editing_allowed true
|
387
|
+
#editing_policy :EDITING_AUTO
|
388
|
+
#help_text "M-Tab for next field, M-8 amd M-7 for horiz scroll, + to resize, C-q quit"
|
389
|
+
help_text "M-Tab for next field, C-q quit"
|
390
|
+
end
|
391
|
+
atable.bind(:TABLE_TRAVERSAL_EVENT){|e| @header.text_right "Row #{e.newrow+1} of #{atable.row_count}" }
|
392
|
+
@tab_ctr += 1
|
393
|
+
tab1 = tp.add_tab "Tab&#{@tab_ctr}" , atable
|
394
|
+
return atable
|
395
|
+
end
|
396
|
+
## create the single tabbedpane for populating with resultsets
|
397
|
+
def create_tabbed_pane form, buttrow, t_width, c
|
398
|
+
tp = RubyCurses::TabbedPane.new form do
|
399
|
+
height 16
|
400
|
+
width t_width
|
401
|
+
row buttrow +1
|
402
|
+
col c
|
403
|
+
button_type :ok
|
412
404
|
end
|
413
|
-
|
414
|
-
|
405
|
+
return tp
|
415
406
|
end
|
416
407
|
end
|
417
408
|
if $0 == __FILE__
|
@@ -425,7 +416,7 @@ if $0 == __FILE__
|
|
425
416
|
$log.level = Logger::DEBUG
|
426
417
|
|
427
418
|
colors = Ncurses.COLORS
|
428
|
-
$log.debug "START #{colors} colors
|
419
|
+
$log.debug "START #{colors} colors SQLC demo "
|
429
420
|
|
430
421
|
catch(:close) do
|
431
422
|
t = Sqlc.new
|
data/examples/sqlm.rb
ADDED
@@ -0,0 +1,446 @@
|
|
1
|
+
## rkumar, 2009
|
2
|
+
# Sample demo of various widgets and their interaction.
|
3
|
+
# This is a simple sql client which allows table / column selection, construction
|
4
|
+
# of SQL queries, and multiple resultsets.
|
5
|
+
# Use C-q to quit, Alt-Tab to move out of Table to next field.
|
6
|
+
# Please see bind_key statements in this app for some key bindings in table.
|
7
|
+
# This is an offshoot of sqlc.rb -- this demo uses a multicontainer for tables
|
8
|
+
# instead of a tabbed panes.
|
9
|
+
#
|
10
|
+
require 'rubygems'
|
11
|
+
require 'ncurses'
|
12
|
+
require 'logger'
|
13
|
+
require 'sqlite3'
|
14
|
+
require 'rbcurse'
|
15
|
+
require 'rbcurse/rcombo'
|
16
|
+
require 'rbcurse/rtextarea'
|
17
|
+
require 'rbcurse/rtable'
|
18
|
+
#require 'rbcurse/table/tablecellrenderer'
|
19
|
+
#require 'rbcurse/comboboxcellrenderer'
|
20
|
+
#require 'rbcurse/keylabelprinter'
|
21
|
+
require 'rbcurse/applicationheader'
|
22
|
+
#require 'rbcurse/action' # not used here
|
23
|
+
#require 'rbcurse/rtabbedpane'
|
24
|
+
require 'rbcurse/rmulticontainer'
|
25
|
+
|
26
|
+
# pls get testd.db from
|
27
|
+
# http://www.benegal.org/files/screen/testd.db
|
28
|
+
# or put some other sqlite3 db name there.
|
29
|
+
|
30
|
+
## must give me @content, @columns, @datatypes (opt)
|
31
|
+
class Datasource
|
32
|
+
# attr_reader :field_length # specified by user, length of row in display table
|
33
|
+
attr_accessor :columns # names of columns in array
|
34
|
+
attr_accessor :datatypes # array of datatyps of columns required to align: int, real, float, smallint
|
35
|
+
attr_accessor :content # 2 dim data
|
36
|
+
attr_accessor :user_columns # columnnames provided by user, overrides what is generated for display
|
37
|
+
# attr_reader :sqlstring # specified by user
|
38
|
+
|
39
|
+
# constructor
|
40
|
+
def initialize(config={}, &block)
|
41
|
+
@content = []
|
42
|
+
@columns = nil # actual db columnnames -- needed to figure out datatypes
|
43
|
+
@user_columns = nil # user specified db columnnames, overrides what may be provided
|
44
|
+
@datatypes = nil
|
45
|
+
# @rows = nil
|
46
|
+
# @sqlstring = nil
|
47
|
+
# @command = nil
|
48
|
+
|
49
|
+
instance_eval(&block) if block_given?
|
50
|
+
end
|
51
|
+
def connect dbname
|
52
|
+
@db = SQLite3::Database.new(dbname)
|
53
|
+
end
|
54
|
+
# get columns and datatypes, prefetch
|
55
|
+
def get_data command
|
56
|
+
@columns, *rows = @db.execute2(command)
|
57
|
+
@content = rows
|
58
|
+
return nil if @content.nil? or @content[0].nil?
|
59
|
+
@datatypes = @content[0].types #if @datatypes.nil?
|
60
|
+
@command = command
|
61
|
+
return @content
|
62
|
+
end
|
63
|
+
def get_metadata table
|
64
|
+
get_data "select * from #{table} limit 1"
|
65
|
+
return @columns
|
66
|
+
end
|
67
|
+
##
|
68
|
+
# returns columns_widths, and updates that variable
|
69
|
+
def estimate_column_widths tablewidth, columns
|
70
|
+
colwidths = {}
|
71
|
+
min_column_width = (tablewidth/columns.length) -1
|
72
|
+
$log.debug("min: #{min_column_width}, #{tablewidth}")
|
73
|
+
@content.each_with_index do |row, cix|
|
74
|
+
break if cix >= 20
|
75
|
+
row.each_index do |ix|
|
76
|
+
col = row[ix]
|
77
|
+
colwidths[ix] ||= 0
|
78
|
+
colwidths[ix] = [colwidths[ix], col.length].max
|
79
|
+
end
|
80
|
+
end
|
81
|
+
total = 0
|
82
|
+
colwidths.each_pair do |k,v|
|
83
|
+
name = columns[k.to_i]
|
84
|
+
colwidths[name] = v
|
85
|
+
total += v
|
86
|
+
end
|
87
|
+
colwidths["__TOTAL__"] = total
|
88
|
+
column_widths = colwidths
|
89
|
+
@max_data_widths = column_widths.dup
|
90
|
+
|
91
|
+
columns.each_with_index do | col, i|
|
92
|
+
if @datatypes[i].match(/(real|int)/) != nil
|
93
|
+
wid = column_widths[i]
|
94
|
+
# cw = [column_widths[i], [8,min_column_width].min].max
|
95
|
+
$log.debug("XXX #{wid}. #{columns[i].length}")
|
96
|
+
cw = [wid, columns[i].length].max
|
97
|
+
$log.debug("int #{col} #{column_widths[i]}, #{cw}")
|
98
|
+
elsif @datatypes[i].match(/(date)/) != nil
|
99
|
+
cw = [column_widths[i], [12,min_column_width].min].max
|
100
|
+
#cw = [12,min_column_width].min
|
101
|
+
$log.debug("date #{col} #{column_widths[i]}, #{cw}")
|
102
|
+
else
|
103
|
+
cw = [column_widths[i], min_column_width].max
|
104
|
+
if column_widths[i] <= col.length and col.length <= min_column_width
|
105
|
+
cw = col.length
|
106
|
+
end
|
107
|
+
$log.debug("else #{col} #{column_widths[i]}, #{col.length} #{cw}")
|
108
|
+
end
|
109
|
+
column_widths[i] = cw
|
110
|
+
total += cw
|
111
|
+
end
|
112
|
+
column_widths["__TOTAL__"] = total
|
113
|
+
$log.debug("Estimated col widths: #{column_widths.inspect}")
|
114
|
+
@column_widths = column_widths
|
115
|
+
return column_widths
|
116
|
+
end
|
117
|
+
|
118
|
+
# added to enable query form to allow movement into table only if
|
119
|
+
# there is data 2008-10-08 17:46
|
120
|
+
# returns number of rows fetched
|
121
|
+
def data_length
|
122
|
+
return @content.length
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
def get_key_labels
|
127
|
+
key_labels = [
|
128
|
+
['C-q', 'Exit'], nil,
|
129
|
+
['M-s', 'Save'], ['M-m', 'Move']
|
130
|
+
]
|
131
|
+
return key_labels
|
132
|
+
end
|
133
|
+
def get_key_labels_table
|
134
|
+
key_labels = [
|
135
|
+
['M-n','NewRow'], ['M-d','DelRow'],
|
136
|
+
['C-x','Select'], nil,
|
137
|
+
['M-0', 'Top'], ['M-9', 'End'],
|
138
|
+
['C-p', 'PgUp'], ['C-n', 'PgDn'],
|
139
|
+
['M-Tab','Nxt Fld'], ['Tab','Nxt Col'],
|
140
|
+
['+','Widen'], ['-','Narrow']
|
141
|
+
]
|
142
|
+
return key_labels
|
143
|
+
end
|
144
|
+
class Sqlc
|
145
|
+
def initialize
|
146
|
+
@window = VER::Window.root_window
|
147
|
+
$catch_alt_digits = false # we want to use Alt-1, 2 for tabs.
|
148
|
+
@form = Form.new @window
|
149
|
+
@tab_ctr = 0
|
150
|
+
|
151
|
+
@db = Datasource.new
|
152
|
+
@db.connect "testd.db"
|
153
|
+
end
|
154
|
+
def run
|
155
|
+
title = "rbcurse"
|
156
|
+
@header = ApplicationHeader.new @form, title, {:text2=>"Demo", :text_center=>"SQL Client"}
|
157
|
+
status_row = RubyCurses::Label.new @form, {'text' => "", :row => Ncurses.LINES-4, :col => 0, :display_length=>70}
|
158
|
+
@status_row = status_row
|
159
|
+
# setting ENTER across all objects on a form
|
160
|
+
@form.bind(:ENTER) {|f| status_row.text = f.help_text unless f.help_text.nil? }
|
161
|
+
r = 1; c = 1;
|
162
|
+
@data = [ ["No data"] ]
|
163
|
+
data = @data
|
164
|
+
colnames = %w[ Result ]
|
165
|
+
|
166
|
+
ta_ht = 5
|
167
|
+
t_width = 78
|
168
|
+
sqlarea = TextArea.new @form do
|
169
|
+
name "sqlarea"
|
170
|
+
row r
|
171
|
+
col c
|
172
|
+
width t_width
|
173
|
+
height ta_ht
|
174
|
+
title "Sql Query"
|
175
|
+
title_attrib (Ncurses::A_REVERSE | Ncurses::A_BOLD)
|
176
|
+
help_text "Enter query and press Run or Meta-r"
|
177
|
+
end
|
178
|
+
sqlarea << "select * from contacts"
|
179
|
+
buttrow = r+ta_ht+1 #Ncurses.LINES-4
|
180
|
+
b_run = Button.new @form do
|
181
|
+
text "&Run"
|
182
|
+
row buttrow
|
183
|
+
col c
|
184
|
+
help_text "Run query"
|
185
|
+
end
|
186
|
+
## We use Action to create a button: to test out ampersand with MI and Button
|
187
|
+
b_clear = Button.new @form do
|
188
|
+
#action new_act
|
189
|
+
text "&Clear"
|
190
|
+
row buttrow
|
191
|
+
col c+10
|
192
|
+
help_text "Clear query entry box "
|
193
|
+
end
|
194
|
+
b_clear.command {
|
195
|
+
sqlarea.remove_all
|
196
|
+
sqlarea.focus
|
197
|
+
}
|
198
|
+
|
199
|
+
# using ampersand to set mnemonic
|
200
|
+
|
201
|
+
b_construct = Button.new @form do
|
202
|
+
text "Constr&uct"
|
203
|
+
row buttrow
|
204
|
+
col c+25
|
205
|
+
help_text "Select a table, select columns and press this to construct an SQL"
|
206
|
+
end
|
207
|
+
|
208
|
+
Button.button_layout [b_run, b_clear, b_construct], buttrow, startcol=5, cols=Ncurses.COLS-1, gap=5
|
209
|
+
|
210
|
+
@tp = create_tabbed_pane @form, buttrow, t_width, c
|
211
|
+
@tp.show
|
212
|
+
@data = data
|
213
|
+
|
214
|
+
b_run.command {
|
215
|
+
query = sqlarea.get_text
|
216
|
+
run_query query
|
217
|
+
}
|
218
|
+
#
|
219
|
+
## key bindings fo atable
|
220
|
+
# column widths
|
221
|
+
app = self
|
222
|
+
#atable.configure() do
|
223
|
+
##bind_key(330) { atable.remove_column(tcm.column(atable.focussed_col)) rescue "" }
|
224
|
+
#bind_key(?+) {
|
225
|
+
#acolumn = atable.column atable.focussed_col()
|
226
|
+
#w = acolumn.width + 1
|
227
|
+
#acolumn.width w
|
228
|
+
##atable.table_structure_changed
|
229
|
+
#}
|
230
|
+
#bind_key(?-) {
|
231
|
+
#acolumn = atable.column atable.focussed_col()
|
232
|
+
#w = acolumn.width - 1
|
233
|
+
#if w > 3
|
234
|
+
#acolumn.width w
|
235
|
+
##atable.table_structure_changed
|
236
|
+
#end
|
237
|
+
#}
|
238
|
+
## added new method on 2009-10-08 00:47
|
239
|
+
#bind_key(?=) {
|
240
|
+
#atable.size_columns_to_fit
|
241
|
+
#}
|
242
|
+
#bind_key(?>) {
|
243
|
+
#tcm = atable.get_table_column_model
|
244
|
+
#colcount = tcm.column_count-1
|
245
|
+
##atable.move_column sel_col.value, sel_col.value+1 unless sel_col.value == colcount
|
246
|
+
#col = atable.focussed_col
|
247
|
+
#atable.move_column col, col+1 unless col == colcount
|
248
|
+
#}
|
249
|
+
#bind_key(?<) {
|
250
|
+
#col = atable.focussed_col
|
251
|
+
#atable.move_column col, col-1 unless col == 0
|
252
|
+
##atable.move_column sel_col.value, sel_col.value-1 unless sel_col.value == 0
|
253
|
+
#}
|
254
|
+
## TODO popup and key labels
|
255
|
+
#bind_key(?\M-h, app) {|tab,td| $log.debug " BIND... #{tab.class}, #{td.class}"; app.make_popup atable}
|
256
|
+
#end
|
257
|
+
#keylabel = RubyCurses::Label.new @form, {'text' => "", "row" => r+table_ht+3, "col" => c, "color" => "yellow", "bgcolor"=>"blue", "display_length"=>60, "height"=>2}
|
258
|
+
#eventlabel = RubyCurses::Label.new @form, {'text' => "Events:", "row" => r+table_ht+6, "col" => c, "color" => "white", "bgcolor"=>"blue", "display_length"=>60, "height"=>2}
|
259
|
+
|
260
|
+
# report some events
|
261
|
+
#atable.table_model.bind(:TABLE_MODEL_EVENT){|e| #eventlabel.text = "Event: #{e}"}
|
262
|
+
#atable.get_table_column_model.bind(:TABLE_COLUMN_MODEL_EVENT){|e| eventlabel.text = "Event: #{e}"}
|
263
|
+
|
264
|
+
tablist_ht = 6
|
265
|
+
mylist = @db.get_data "select name from sqlite_master"
|
266
|
+
# mylist is an Array of SQLite3::ResultSet::ArrayWithTypesAndFields
|
267
|
+
mylist.collect!{|x| x[0] } ## 1.9 hack, but will it run on 1.8 ??
|
268
|
+
$listdata = Variable.new mylist
|
269
|
+
tablelist = Listbox.new @form do
|
270
|
+
name "tablelist"
|
271
|
+
row 1
|
272
|
+
col t_width+2
|
273
|
+
width 20
|
274
|
+
height tablist_ht
|
275
|
+
# list mylist
|
276
|
+
list_variable $listdata
|
277
|
+
#selection_mode :SINGLE
|
278
|
+
#show_selector true
|
279
|
+
title "Tables"
|
280
|
+
title_attrib 'reverse'
|
281
|
+
help_text "Press ENTER to run * query, Space to select columns"
|
282
|
+
end
|
283
|
+
#tablelist.bind(:PRESS) { |alist| @status_row.text = "Selected #{alist.current_index}" }
|
284
|
+
tablelist.list_selection_model().bind(:LIST_SELECTION_EVENT,tablelist) { |lsm, alist| @status_row.text = "Selected #{alist.current_index}" }
|
285
|
+
|
286
|
+
collist = []
|
287
|
+
$coldata = Variable.new collist
|
288
|
+
columnlist = Listbox.new @form do
|
289
|
+
name "columnlist"
|
290
|
+
row tablist_ht+2
|
291
|
+
col t_width+2
|
292
|
+
width 20
|
293
|
+
height 15
|
294
|
+
# list mylist
|
295
|
+
list_variable $coldata
|
296
|
+
#selection_mode :SINGLE
|
297
|
+
#show_selector true
|
298
|
+
title "Columns"
|
299
|
+
title_attrib 'reverse'
|
300
|
+
help_text "Press ENTER to append columns to sqlarea, Space to select"
|
301
|
+
end
|
302
|
+
## pressing SPACE on a table populates column list with its columns so they can be selected
|
303
|
+
tablelist.bind_key(32) {
|
304
|
+
@status_row.text = "Selected #{tablelist.get_content()[tablelist.current_index]}"
|
305
|
+
table = "#{tablelist.get_content()[tablelist.current_index]}"
|
306
|
+
##table = table[0] if table.class==Array ## 1.9 ???
|
307
|
+
columnlist.list_data_model.remove_all
|
308
|
+
columnlist.list_data_model.insert 0, *@db.get_metadata(table)
|
309
|
+
}
|
310
|
+
## pressing ENTER on a table runs a query on it, no need to type and SQL
|
311
|
+
tablelist.bind_key(13) {
|
312
|
+
@status_row.text = "Selected #{tablelist.get_content()[tablelist.current_index]}"
|
313
|
+
table = "#{tablelist.get_content()[tablelist.current_index]}"
|
314
|
+
##table = table[0] if table.class==Array ## 1.9 ???
|
315
|
+
run_query "select * from #{table}"
|
316
|
+
}
|
317
|
+
columnlist.bind_key(13) {
|
318
|
+
## append column name to sqlarea if ENTER pressed
|
319
|
+
column = "#{columnlist.get_content()[columnlist.current_index]}"
|
320
|
+
sqlarea << "#{column},"
|
321
|
+
}
|
322
|
+
columnlist.bind_key(32) {
|
323
|
+
## select row - later can press Construct button
|
324
|
+
columnlist.toggle_row_selection
|
325
|
+
column = "#{columnlist.get_content()[columnlist.current_index]}"
|
326
|
+
}
|
327
|
+
## construct an SQL after selecting some columns in the column list
|
328
|
+
b_construct.command {
|
329
|
+
table = "#{tablelist.get_content()[tablelist.current_index]}"
|
330
|
+
#table = table[0] if table.class==Array ## 1.9 ???
|
331
|
+
indexes = columnlist.selected_rows()
|
332
|
+
columns=[]
|
333
|
+
indexes.each do |i|
|
334
|
+
columns << columnlist.get_content()[i]
|
335
|
+
end
|
336
|
+
sql = "select #{columns.join(',')} from #{table}"
|
337
|
+
sqlarea << sql
|
338
|
+
}
|
339
|
+
|
340
|
+
|
341
|
+
@form.repaint
|
342
|
+
@window.wrefresh
|
343
|
+
Ncurses::Panel.update_panels
|
344
|
+
begin
|
345
|
+
while((ch = @window.getchar()) != ?\C-q.getbyte(0) )
|
346
|
+
s = keycode_tos ch
|
347
|
+
status_row.text = "Pressed #{ch} , #{s}. Press C-q to quit, Alt-Tab for exiting table "
|
348
|
+
@form.handle_key(ch)
|
349
|
+
|
350
|
+
@form.repaint
|
351
|
+
@window.wrefresh
|
352
|
+
end
|
353
|
+
ensure
|
354
|
+
@window.destroy if !@window.nil?
|
355
|
+
end
|
356
|
+
end
|
357
|
+
## execute the query in the textarea
|
358
|
+
# @param [String] sql string
|
359
|
+
def run_query sql
|
360
|
+
#query = sqlarea.get_text
|
361
|
+
query = sql
|
362
|
+
begin
|
363
|
+
@content = @db.get_data query
|
364
|
+
if @content.nil?
|
365
|
+
@status_row.text = "0 rows retrieved"
|
366
|
+
return
|
367
|
+
end
|
368
|
+
#cw = @db.estimate_column_widths @atable.width, @db.columns
|
369
|
+
atable = create_table @tp, @tab_ctr #, buttrow, t_width, c
|
370
|
+
atable.set_data @content, @db.columns
|
371
|
+
cw = atable.estimate_column_widths @db.columns, @db.datatypes
|
372
|
+
atable.set_column_widths cw
|
373
|
+
rescue => exc
|
374
|
+
$log.debug(exc.backtrace.join("\n"))
|
375
|
+
alert exc.to_s
|
376
|
+
return
|
377
|
+
end
|
378
|
+
@status_row.text = "#{@content.size} rows retrieved"
|
379
|
+
atable.repaint
|
380
|
+
end
|
381
|
+
## create a Table component for populating with data
|
382
|
+
def create_table tp, counter #, buttrow, t_width, c
|
383
|
+
table_ht = 15
|
384
|
+
atable = Table.new do
|
385
|
+
name "sqltable#{counter}"
|
386
|
+
#cell_editing_allowed true
|
387
|
+
#editing_policy :EDITING_AUTO
|
388
|
+
#help_text "M-Tab for next field, M-8 amd M-7 for horiz scroll, + to resize, C-q quit"
|
389
|
+
help_text "M-Tab for next field, C-q quit"
|
390
|
+
end
|
391
|
+
atable.bind(:TABLE_TRAVERSAL_EVENT){|e| @header.text_right "Row #{e.newrow+1} of #{atable.row_count}" }
|
392
|
+
@tab_ctr += 1
|
393
|
+
#tab1 = tp.add_tab "Tab&#{@tab_ctr}" , atable
|
394
|
+
tab1 = tp.add atable, "Tab&#{@tab_ctr}"
|
395
|
+
return atable
|
396
|
+
end
|
397
|
+
## create the single tabbedpane for populating with resultsets
|
398
|
+
def create_tabbed_pane form, buttrow, t_width, c
|
399
|
+
tp = MultiContainer.new @form do
|
400
|
+
name "multic"
|
401
|
+
height 16
|
402
|
+
width t_width
|
403
|
+
row buttrow +1
|
404
|
+
col c
|
405
|
+
#row r
|
406
|
+
#col c
|
407
|
+
#width 60
|
408
|
+
#height 15
|
409
|
+
title "Results"
|
410
|
+
end
|
411
|
+
#tp = RubyCurses::TabbedPane.new form do
|
412
|
+
#height 16
|
413
|
+
#width t_width
|
414
|
+
#row buttrow +1
|
415
|
+
#col c
|
416
|
+
#button_type :ok
|
417
|
+
#end
|
418
|
+
return tp
|
419
|
+
end
|
420
|
+
end
|
421
|
+
if $0 == __FILE__
|
422
|
+
include RubyCurses
|
423
|
+
include RubyCurses::Utils
|
424
|
+
|
425
|
+
begin
|
426
|
+
# Initialize curses
|
427
|
+
VER::start_ncurses # this is initializing colors via ColorMap.setup
|
428
|
+
$log = Logger.new("view.log")
|
429
|
+
$log.level = Logger::DEBUG
|
430
|
+
|
431
|
+
colors = Ncurses.COLORS
|
432
|
+
$log.debug "START #{colors} colors SQLC demo "
|
433
|
+
|
434
|
+
catch(:close) do
|
435
|
+
t = Sqlc.new
|
436
|
+
t.run
|
437
|
+
end
|
438
|
+
rescue => ex
|
439
|
+
ensure
|
440
|
+
VER::stop_ncurses
|
441
|
+
p ex if ex
|
442
|
+
p(ex.backtrace.join("\n")) if ex
|
443
|
+
$log.debug( ex) if ex
|
444
|
+
$log.debug(ex.backtrace.join("\n")) if ex
|
445
|
+
end
|
446
|
+
end
|