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