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.
Files changed (104) hide show
  1. data/Makefile +21 -0
  2. data/Manifest.txt +6 -0
  3. data/README.markdown +6 -4
  4. data/TODO +372 -0
  5. data/TODO2.txt +121 -0
  6. data/VERSION +1 -1
  7. data/examples/README.txt +67 -0
  8. data/examples/abasiclist.rb +33 -0
  9. data/examples/alpmenu.rb +42 -0
  10. data/examples/app.rb +859 -0
  11. data/examples/app.sample +17 -0
  12. data/examples/appdirtree.rb +74 -0
  13. data/examples/appemail.rb +191 -0
  14. data/examples/appemaillb.rb +308 -0
  15. data/examples/appgcompose.rb +315 -0
  16. data/examples/atree.rb +64 -0
  17. data/examples/common/file.rb +40 -0
  18. data/examples/common/rmail.rb +257 -0
  19. data/examples/data.txt +683 -0
  20. data/examples/data/README.markdown +9 -0
  21. data/examples/data/brew.txt +38 -0
  22. data/examples/data/color.2 +37 -0
  23. data/examples/data/gemlist.txt +60 -0
  24. data/examples/data/lotr.txt +12 -0
  25. data/examples/data/ports.txt +136 -0
  26. data/examples/data/tasks.txt +27 -0
  27. data/examples/data/todocsv.csv +28 -0
  28. data/examples/data/unix1.txt +21 -0
  29. data/examples/data/unix2.txt +11 -0
  30. data/examples/dbdemo.rb +495 -0
  31. data/examples/deprecated/appgmail.rb +952 -0
  32. data/examples/deprecated/splitp.rb +56 -0
  33. data/examples/deprecated/testscrolllb.rb +86 -0
  34. data/examples/deprecated/testscrollp.rb +88 -0
  35. data/examples/deprecated/testscrollta.rb +80 -0
  36. data/examples/deprecated/testscrolltable.rb +165 -0
  37. data/examples/deprecated/testsplit.rb +87 -0
  38. data/examples/deprecated/testsplit2.rb +123 -0
  39. data/examples/deprecated/testsplit3.rb +215 -0
  40. data/examples/deprecated/testsplit3_1.rb +244 -0
  41. data/examples/deprecated/testsplit3a.rb +215 -0
  42. data/examples/deprecated/testsplit3b.rb +237 -0
  43. data/examples/deprecated/testsplitta.rb +148 -0
  44. data/examples/deprecated/testsplittv.rb +142 -0
  45. data/examples/deprecated/testsplittvv.rb +144 -0
  46. data/examples/deprecated/testtpane.rb +215 -0
  47. data/examples/deprecated/testtpane2.rb +145 -0
  48. data/examples/deprecated/testtpanetable.rb +203 -0
  49. data/examples/dirtree.rb +88 -0
  50. data/examples/experimental/resultsetdemo.rb +280 -0
  51. data/examples/experimental/testmform.rb +35 -0
  52. data/examples/experimental/testscroller.rb +117 -0
  53. data/examples/experimental/teststackflow.rb +111 -0
  54. data/examples/menu1.rb +112 -0
  55. data/examples/multispl.rb +86 -0
  56. data/examples/newmessagebox.rb +131 -0
  57. data/examples/newtabbedwindow.rb +100 -0
  58. data/examples/newtesttabp.rb +121 -0
  59. data/examples/qdfilechooser.rb +68 -0
  60. data/examples/rfe.rb +1239 -0
  61. data/examples/rfe_renderer.rb +121 -0
  62. data/examples/sqlc.rb +454 -0
  63. data/examples/sqlm.rb +437 -0
  64. data/examples/sqlt.rb +408 -0
  65. data/examples/status.txt +68 -0
  66. data/examples/table1.rb +24 -0
  67. data/examples/term2.rb +84 -0
  68. data/examples/test1.rb +239 -0
  69. data/examples/test2.rb +674 -0
  70. data/examples/testapp.rb +44 -0
  71. data/examples/testapp2.rb +58 -0
  72. data/examples/testchars.rb +137 -0
  73. data/examples/testcombo.rb +91 -0
  74. data/examples/testkeypress.rb +66 -0
  75. data/examples/testlistbox.rb +113 -0
  76. data/examples/testmenu.rb +101 -0
  77. data/examples/testmulticomp.rb +70 -0
  78. data/examples/testmulticontainer.rb +94 -0
  79. data/examples/testmultispl.rb +199 -0
  80. data/examples/testree.rb +106 -0
  81. data/examples/testtable.rb +264 -0
  82. data/examples/testtabp.rb +107 -0
  83. data/examples/testtodo.rb +584 -0
  84. data/examples/testvimsplit.rb +112 -0
  85. data/examples/testwsshortcuts.rb +64 -0
  86. data/examples/testwsshortcuts2.rb +126 -0
  87. data/examples/todo.db +0 -0
  88. data/examples/todo.yml +191 -0
  89. data/examples/viewtodo.rb +574 -0
  90. data/lib/rbcurse/deprecated/README.markdown +12 -0
  91. data/lib/rbcurse/deprecated/rpad.rb +375 -0
  92. data/lib/rbcurse/deprecated/rscrollpane.rb +512 -0
  93. data/lib/rbcurse/deprecated/rsplitpane.rb +894 -0
  94. data/lib/rbcurse/deprecated/rsplitpane2.rb +1009 -0
  95. data/lib/rbcurse/deprecated/rviewport.rb +204 -0
  96. data/lib/rbcurse/deprecated/widgets/mapper.rb +130 -0
  97. data/lib/rbcurse/deprecated/widgets/rmessagebox.rb +348 -0
  98. data/lib/rbcurse/deprecated/widgets/rtabbedpane.rb +1158 -0
  99. data/lib/rbcurse/deprecated/widgets/rtabbedwindow.rb +167 -0
  100. data/lib/rbcurse/deprecated/widgets/scrollable.rb +301 -0
  101. data/lib/rbcurse/deprecated/widgets/stdscrwindow.rb +309 -0
  102. data/lib/ver/keyboard2.rb +170 -0
  103. data/test/test_rbcurse.rb +0 -0
  104. 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