Annotation of embedaddon/sqlite3/contrib/sqlitecon.tcl, revision 1.1.1.1
1.1 misho 1: # A Tk console widget for SQLite. Invoke sqlitecon::create with a window name,
2: # a prompt string, a title to set a new top-level window, and the SQLite
3: # database handle. For example:
4: #
5: # sqlitecon::create .sqlcon {sql:- } {SQL Console} db
6: #
7: # A toplevel window is created that allows you to type in SQL commands to
8: # be processed on the spot.
9: #
10: # A limited set of dot-commands are supported:
11: #
12: # .table
13: # .schema ?TABLE?
14: # .mode list|column|multicolumn|line
15: # .exit
16: #
17: # In addition, a new SQL function named "edit()" is created. This function
18: # takes a single text argument and returns a text result. Whenever the
19: # the function is called, it pops up a new toplevel window containing a
20: # text editor screen initialized to the argument. When the "OK" button
21: # is pressed, whatever revised text is in the text editor is returned as
22: # the result of the edit() function. This allows text fields of SQL tables
23: # to be edited quickly and easily as follows:
24: #
25: # UPDATE table1 SET dscr = edit(dscr) WHERE rowid=15;
26: #
27:
28:
29: # Create a namespace to work in
30: #
31: namespace eval ::sqlitecon {
32: # do nothing
33: }
34:
35: # Create a console widget named $w. The prompt string is $prompt.
36: # The title at the top of the window is $title. The database connection
37: # object is $db
38: #
39: proc sqlitecon::create {w prompt title db} {
40: upvar #0 $w.t v
41: if {[winfo exists $w]} {destroy $w}
42: if {[info exists v]} {unset v}
43: toplevel $w
44: wm title $w $title
45: wm iconname $w $title
46: frame $w.mb -bd 2 -relief raised
47: pack $w.mb -side top -fill x
48: menubutton $w.mb.file -text File -menu $w.mb.file.m
49: menubutton $w.mb.edit -text Edit -menu $w.mb.edit.m
50: pack $w.mb.file $w.mb.edit -side left -padx 8 -pady 1
51: set m [menu $w.mb.file.m -tearoff 0]
52: $m add command -label {Close} -command "destroy $w"
53: sqlitecon::create_child $w $prompt $w.mb.edit.m
54: set v(db) $db
55: $db function edit ::sqlitecon::_edit
56: }
57:
58: # This routine creates a console as a child window within a larger
59: # window. It also creates an edit menu named "$editmenu" if $editmenu!="".
60: # The calling function is responsible for posting the edit menu.
61: #
62: proc sqlitecon::create_child {w prompt editmenu} {
63: upvar #0 $w.t v
64: if {$editmenu!=""} {
65: set m [menu $editmenu -tearoff 0]
66: $m add command -label Cut -command "sqlitecon::Cut $w.t"
67: $m add command -label Copy -command "sqlitecon::Copy $w.t"
68: $m add command -label Paste -command "sqlitecon::Paste $w.t"
69: $m add command -label {Clear Screen} -command "sqlitecon::Clear $w.t"
70: $m add separator
71: $m add command -label {Save As...} -command "sqlitecon::SaveFile $w.t"
72: catch {$editmenu config -postcommand "sqlitecon::EnableEditMenu $w"}
73: }
74: scrollbar $w.sb -orient vertical -command "$w.t yview"
75: pack $w.sb -side right -fill y
76: text $w.t -font fixed -yscrollcommand "$w.sb set"
77: pack $w.t -side right -fill both -expand 1
78: bindtags $w.t Sqlitecon
79: set v(editmenu) $editmenu
80: set v(history) 0
81: set v(historycnt) 0
82: set v(current) -1
83: set v(prompt) $prompt
84: set v(prior) {}
85: set v(plength) [string length $v(prompt)]
86: set v(x) 0
87: set v(y) 0
88: set v(mode) column
89: set v(header) on
90: $w.t mark set insert end
91: $w.t tag config ok -foreground blue
92: $w.t tag config err -foreground red
93: $w.t insert end $v(prompt)
94: $w.t mark set out 1.0
95: after idle "focus $w.t"
96: }
97:
98: bind Sqlitecon <1> {sqlitecon::Button1 %W %x %y}
99: bind Sqlitecon <B1-Motion> {sqlitecon::B1Motion %W %x %y}
100: bind Sqlitecon <B1-Leave> {sqlitecon::B1Leave %W %x %y}
101: bind Sqlitecon <B1-Enter> {sqlitecon::cancelMotor %W}
102: bind Sqlitecon <ButtonRelease-1> {sqlitecon::cancelMotor %W}
103: bind Sqlitecon <KeyPress> {sqlitecon::Insert %W %A}
104: bind Sqlitecon <Left> {sqlitecon::Left %W}
105: bind Sqlitecon <Control-b> {sqlitecon::Left %W}
106: bind Sqlitecon <Right> {sqlitecon::Right %W}
107: bind Sqlitecon <Control-f> {sqlitecon::Right %W}
108: bind Sqlitecon <BackSpace> {sqlitecon::Backspace %W}
109: bind Sqlitecon <Control-h> {sqlitecon::Backspace %W}
110: bind Sqlitecon <Delete> {sqlitecon::Delete %W}
111: bind Sqlitecon <Control-d> {sqlitecon::Delete %W}
112: bind Sqlitecon <Home> {sqlitecon::Home %W}
113: bind Sqlitecon <Control-a> {sqlitecon::Home %W}
114: bind Sqlitecon <End> {sqlitecon::End %W}
115: bind Sqlitecon <Control-e> {sqlitecon::End %W}
116: bind Sqlitecon <Return> {sqlitecon::Enter %W}
117: bind Sqlitecon <KP_Enter> {sqlitecon::Enter %W}
118: bind Sqlitecon <Up> {sqlitecon::Prior %W}
119: bind Sqlitecon <Control-p> {sqlitecon::Prior %W}
120: bind Sqlitecon <Down> {sqlitecon::Next %W}
121: bind Sqlitecon <Control-n> {sqlitecon::Next %W}
122: bind Sqlitecon <Control-k> {sqlitecon::EraseEOL %W}
123: bind Sqlitecon <<Cut>> {sqlitecon::Cut %W}
124: bind Sqlitecon <<Copy>> {sqlitecon::Copy %W}
125: bind Sqlitecon <<Paste>> {sqlitecon::Paste %W}
126: bind Sqlitecon <<Clear>> {sqlitecon::Clear %W}
127:
128: # Insert a single character at the insertion cursor
129: #
130: proc sqlitecon::Insert {w a} {
131: $w insert insert $a
132: $w yview insert
133: }
134:
135: # Move the cursor one character to the left
136: #
137: proc sqlitecon::Left {w} {
138: upvar #0 $w v
139: scan [$w index insert] %d.%d row col
140: if {$col>$v(plength)} {
141: $w mark set insert "insert -1c"
142: }
143: }
144:
145: # Erase the character to the left of the cursor
146: #
147: proc sqlitecon::Backspace {w} {
148: upvar #0 $w v
149: scan [$w index insert] %d.%d row col
150: if {$col>$v(plength)} {
151: $w delete {insert -1c}
152: }
153: }
154:
155: # Erase to the end of the line
156: #
157: proc sqlitecon::EraseEOL {w} {
158: upvar #0 $w v
159: scan [$w index insert] %d.%d row col
160: if {$col>=$v(plength)} {
161: $w delete insert {insert lineend}
162: }
163: }
164:
165: # Move the cursor one character to the right
166: #
167: proc sqlitecon::Right {w} {
168: $w mark set insert "insert +1c"
169: }
170:
171: # Erase the character to the right of the cursor
172: #
173: proc sqlitecon::Delete w {
174: $w delete insert
175: }
176:
177: # Move the cursor to the beginning of the current line
178: #
179: proc sqlitecon::Home w {
180: upvar #0 $w v
181: scan [$w index insert] %d.%d row col
182: $w mark set insert $row.$v(plength)
183: }
184:
185: # Move the cursor to the end of the current line
186: #
187: proc sqlitecon::End w {
188: $w mark set insert {insert lineend}
189: }
190:
191: # Add a line to the history
192: #
193: proc sqlitecon::addHistory {w line} {
194: upvar #0 $w v
195: if {$v(historycnt)>0} {
196: set last [lindex $v(history) [expr $v(historycnt)-1]]
197: if {[string compare $last $line]} {
198: lappend v(history) $line
199: incr v(historycnt)
200: }
201: } else {
202: set v(history) [list $line]
203: set v(historycnt) 1
204: }
205: set v(current) $v(historycnt)
206: }
207:
208: # Called when "Enter" is pressed. Do something with the line
209: # of text that was entered.
210: #
211: proc sqlitecon::Enter w {
212: upvar #0 $w v
213: scan [$w index insert] %d.%d row col
214: set start $row.$v(plength)
215: set line [$w get $start "$start lineend"]
216: $w insert end \n
217: $w mark set out end
218: if {$v(prior)==""} {
219: set cmd $line
220: } else {
221: set cmd $v(prior)\n$line
222: }
223: if {[string index $cmd 0]=="." || [$v(db) complete $cmd]} {
224: regsub -all {\n} [string trim $cmd] { } cmd2
225: addHistory $w $cmd2
226: set rc [catch {DoCommand $w $cmd} res]
227: if {![winfo exists $w]} return
228: if {$rc} {
229: $w insert end $res\n err
230: } elseif {[string length $res]>0} {
231: $w insert end $res\n ok
232: }
233: set v(prior) {}
234: $w insert end $v(prompt)
235: } else {
236: set v(prior) $cmd
237: regsub -all {[^ ]} $v(prompt) . x
238: $w insert end $x
239: }
240: $w mark set insert end
241: $w mark set out {insert linestart}
242: $w yview insert
243: }
244:
245: # Execute a single SQL command. Pay special attention to control
246: # directives that begin with "."
247: #
248: # The return value is the text output from the command, properly
249: # formatted.
250: #
251: proc sqlitecon::DoCommand {w cmd} {
252: upvar #0 $w v
253: set mode $v(mode)
254: set header $v(header)
255: if {[regexp {^(\.[a-z]+)} $cmd all word]} {
256: if {$word==".mode"} {
257: regexp {^.[a-z]+ +([a-z]+)} $cmd all v(mode)
258: return {}
259: } elseif {$word==".exit"} {
260: destroy [winfo toplevel $w]
261: return {}
262: } elseif {$word==".header"} {
263: regexp {^.[a-z]+ +([a-z]+)} $cmd all v(header)
264: return {}
265: } elseif {$word==".tables"} {
266: set mode multicolumn
267: set cmd {SELECT name FROM sqlite_master WHERE type='table'
268: UNION ALL
269: SELECT name FROM sqlite_temp_master WHERE type='table'}
270: $v(db) eval {PRAGMA database_list} {
271: if {$name!="temp" && $name!="main"} {
272: append cmd "UNION ALL SELECT name FROM $name.sqlite_master\
273: WHERE type='table'"
274: }
275: }
276: append cmd { ORDER BY 1}
277: } elseif {$word==".fullschema"} {
278: set pattern %
279: regexp {^.[a-z]+ +([^ ]+)} $cmd all pattern
280: set mode list
281: set header 0
282: set cmd "SELECT sql FROM sqlite_master WHERE tbl_name LIKE '$pattern'
283: AND sql NOT NULL UNION ALL SELECT sql FROM sqlite_temp_master
284: WHERE tbl_name LIKE '$pattern' AND sql NOT NULL"
285: $v(db) eval {PRAGMA database_list} {
286: if {$name!="temp" && $name!="main"} {
287: append cmd " UNION ALL SELECT sql FROM $name.sqlite_master\
288: WHERE tbl_name LIKE '$pattern' AND sql NOT NULL"
289: }
290: }
291: } elseif {$word==".schema"} {
292: set pattern %
293: regexp {^.[a-z]+ +([^ ]+)} $cmd all pattern
294: set mode list
295: set header 0
296: set cmd "SELECT sql FROM sqlite_master WHERE name LIKE '$pattern'
297: AND sql NOT NULL UNION ALL SELECT sql FROM sqlite_temp_master
298: WHERE name LIKE '$pattern' AND sql NOT NULL"
299: $v(db) eval {PRAGMA database_list} {
300: if {$name!="temp" && $name!="main"} {
301: append cmd " UNION ALL SELECT sql FROM $name.sqlite_master\
302: WHERE name LIKE '$pattern' AND sql NOT NULL"
303: }
304: }
305: } else {
306: return \
307: ".exit\n.mode line|list|column\n.schema ?TABLENAME?\n.tables"
308: }
309: }
310: set res {}
311: if {$mode=="list"} {
312: $v(db) eval $cmd x {
313: set sep {}
314: foreach col $x(*) {
315: append res $sep$x($col)
316: set sep |
317: }
318: append res \n
319: }
320: if {[info exists x(*)] && $header} {
321: set sep {}
322: set hdr {}
323: foreach col $x(*) {
324: append hdr $sep$col
325: set sep |
326: }
327: set res $hdr\n$res
328: }
329: } elseif {[string range $mode 0 2]=="col"} {
330: set y {}
331: $v(db) eval $cmd x {
332: foreach col $x(*) {
333: if {![info exists cw($col)] || $cw($col)<[string length $x($col)]} {
334: set cw($col) [string length $x($col)]
335: }
336: lappend y $x($col)
337: }
338: }
339: if {[info exists x(*)] && $header} {
340: set hdr {}
341: set ln {}
342: set dash ---------------------------------------------------------------
343: append dash ------------------------------------------------------------
344: foreach col $x(*) {
345: if {![info exists cw($col)] || $cw($col)<[string length $col]} {
346: set cw($col) [string length $col]
347: }
348: lappend hdr $col
349: lappend ln [string range $dash 1 $cw($col)]
350: }
351: set y [concat $hdr $ln $y]
352: }
353: if {[info exists x(*)]} {
354: set format {}
355: set arglist {}
356: set arglist2 {}
357: set i 0
358: foreach col $x(*) {
359: lappend arglist x$i
360: append arglist2 " \$x$i"
361: incr i
362: append format " %-$cw($col)s"
363: }
364: set format [string trimleft $format]\n
365: if {[llength $arglist]>0} {
366: foreach $arglist $y "append res \[format [list $format] $arglist2\]"
367: }
368: }
369: } elseif {$mode=="multicolumn"} {
370: set y [$v(db) eval $cmd]
371: set max 0
372: foreach e $y {
373: if {$max<[string length $e]} {set max [string length $e]}
374: }
375: set ncol [expr {int(80/($max+2))}]
376: if {$ncol<1} {set ncol 1}
377: set nelem [llength $y]
378: set nrow [expr {($nelem+$ncol-1)/$ncol}]
379: set format "%-${max}s"
380: for {set i 0} {$i<$nrow} {incr i} {
381: set j $i
382: while 1 {
383: append res [format $format [lindex $y $j]]
384: incr j $nrow
385: if {$j>=$nelem} break
386: append res { }
387: }
388: append res \n
389: }
390: } else {
391: $v(db) eval $cmd x {
392: foreach col $x(*) {append res "$col = $x($col)\n"}
393: append res \n
394: }
395: }
396: return [string trimright $res]
397: }
398:
399: # Change the line to the previous line
400: #
401: proc sqlitecon::Prior w {
402: upvar #0 $w v
403: if {$v(current)<=0} return
404: incr v(current) -1
405: set line [lindex $v(history) $v(current)]
406: sqlitecon::SetLine $w $line
407: }
408:
409: # Change the line to the next line
410: #
411: proc sqlitecon::Next w {
412: upvar #0 $w v
413: if {$v(current)>=$v(historycnt)} return
414: incr v(current) 1
415: set line [lindex $v(history) $v(current)]
416: sqlitecon::SetLine $w $line
417: }
418:
419: # Change the contents of the entry line
420: #
421: proc sqlitecon::SetLine {w line} {
422: upvar #0 $w v
423: scan [$w index insert] %d.%d row col
424: set start $row.$v(plength)
425: $w delete $start end
426: $w insert end $line
427: $w mark set insert end
428: $w yview insert
429: }
430:
431: # Called when the mouse button is pressed at position $x,$y on
432: # the console widget.
433: #
434: proc sqlitecon::Button1 {w x y} {
435: global tkPriv
436: upvar #0 $w v
437: set v(mouseMoved) 0
438: set v(pressX) $x
439: set p [sqlitecon::nearestBoundry $w $x $y]
440: scan [$w index insert] %d.%d ix iy
441: scan $p %d.%d px py
442: if {$px==$ix} {
443: $w mark set insert $p
444: }
445: $w mark set anchor $p
446: focus $w
447: }
448:
449: # Find the boundry between characters that is nearest
450: # to $x,$y
451: #
452: proc sqlitecon::nearestBoundry {w x y} {
453: set p [$w index @$x,$y]
454: set bb [$w bbox $p]
455: if {![string compare $bb ""]} {return $p}
456: if {($x-[lindex $bb 0])<([lindex $bb 2]/2)} {return $p}
457: $w index "$p + 1 char"
458: }
459:
460: # This routine extends the selection to the point specified by $x,$y
461: #
462: proc sqlitecon::SelectTo {w x y} {
463: upvar #0 $w v
464: set cur [sqlitecon::nearestBoundry $w $x $y]
465: if {[catch {$w index anchor}]} {
466: $w mark set anchor $cur
467: }
468: set anchor [$w index anchor]
469: if {[$w compare $cur != $anchor] || (abs($v(pressX) - $x) >= 3)} {
470: if {$v(mouseMoved)==0} {
471: $w tag remove sel 0.0 end
472: }
473: set v(mouseMoved) 1
474: }
475: if {[$w compare $cur < anchor]} {
476: set first $cur
477: set last anchor
478: } else {
479: set first anchor
480: set last $cur
481: }
482: if {$v(mouseMoved)} {
483: $w tag remove sel 0.0 $first
484: $w tag add sel $first $last
485: $w tag remove sel $last end
486: update idletasks
487: }
488: }
489:
490: # Called whenever the mouse moves while button-1 is held down.
491: #
492: proc sqlitecon::B1Motion {w x y} {
493: upvar #0 $w v
494: set v(y) $y
495: set v(x) $x
496: sqlitecon::SelectTo $w $x $y
497: }
498:
499: # Called whenever the mouse leaves the boundries of the widget
500: # while button 1 is held down.
501: #
502: proc sqlitecon::B1Leave {w x y} {
503: upvar #0 $w v
504: set v(y) $y
505: set v(x) $x
506: sqlitecon::motor $w
507: }
508:
509: # This routine is called to automatically scroll the window when
510: # the mouse drags offscreen.
511: #
512: proc sqlitecon::motor w {
513: upvar #0 $w v
514: if {![winfo exists $w]} return
515: if {$v(y)>=[winfo height $w]} {
516: $w yview scroll 1 units
517: } elseif {$v(y)<0} {
518: $w yview scroll -1 units
519: } else {
520: return
521: }
522: sqlitecon::SelectTo $w $v(x) $v(y)
523: set v(timer) [after 50 sqlitecon::motor $w]
524: }
525:
526: # This routine cancels the scrolling motor if it is active
527: #
528: proc sqlitecon::cancelMotor w {
529: upvar #0 $w v
530: catch {after cancel $v(timer)}
531: catch {unset v(timer)}
532: }
533:
534: # Do a Copy operation on the stuff currently selected.
535: #
536: proc sqlitecon::Copy w {
537: if {![catch {set text [$w get sel.first sel.last]}]} {
538: clipboard clear -displayof $w
539: clipboard append -displayof $w $text
540: }
541: }
542:
543: # Return 1 if the selection exists and is contained
544: # entirely on the input line. Return 2 if the selection
545: # exists but is not entirely on the input line. Return 0
546: # if the selection does not exist.
547: #
548: proc sqlitecon::canCut w {
549: set r [catch {
550: scan [$w index sel.first] %d.%d s1x s1y
551: scan [$w index sel.last] %d.%d s2x s2y
552: scan [$w index insert] %d.%d ix iy
553: }]
554: if {$r==1} {return 0}
555: if {$s1x==$ix && $s2x==$ix} {return 1}
556: return 2
557: }
558:
559: # Do a Cut operation if possible. Cuts are only allowed
560: # if the current selection is entirely contained on the
561: # current input line.
562: #
563: proc sqlitecon::Cut w {
564: if {[sqlitecon::canCut $w]==1} {
565: sqlitecon::Copy $w
566: $w delete sel.first sel.last
567: }
568: }
569:
570: # Do a paste opeation.
571: #
572: proc sqlitecon::Paste w {
573: if {[sqlitecon::canCut $w]==1} {
574: $w delete sel.first sel.last
575: }
576: if {[catch {selection get -displayof $w -selection CLIPBOARD} topaste]
577: && [catch {selection get -displayof $w -selection PRIMARY} topaste]} {
578: return
579: }
580: if {[info exists ::$w]} {
581: set prior 0
582: foreach line [split $topaste \n] {
583: if {$prior} {
584: sqlitecon::Enter $w
585: update
586: }
587: set prior 1
588: $w insert insert $line
589: }
590: } else {
591: $w insert insert $topaste
592: }
593: }
594:
595: # Enable or disable entries in the Edit menu
596: #
597: proc sqlitecon::EnableEditMenu w {
598: upvar #0 $w.t v
599: set m $v(editmenu)
600: if {$m=="" || ![winfo exists $m]} return
601: switch [sqlitecon::canCut $w.t] {
602: 0 {
603: $m entryconf Copy -state disabled
604: $m entryconf Cut -state disabled
605: }
606: 1 {
607: $m entryconf Copy -state normal
608: $m entryconf Cut -state normal
609: }
610: 2 {
611: $m entryconf Copy -state normal
612: $m entryconf Cut -state disabled
613: }
614: }
615: }
616:
617: # Prompt the user for the name of a writable file. Then write the
618: # entire contents of the console screen to that file.
619: #
620: proc sqlitecon::SaveFile w {
621: set types {
622: {{Text Files} {.txt}}
623: {{All Files} *}
624: }
625: set f [tk_getSaveFile -filetypes $types -title "Write Screen To..."]
626: if {$f!=""} {
627: if {[catch {open $f w} fd]} {
628: tk_messageBox -type ok -icon error -message $fd
629: } else {
630: puts $fd [string trimright [$w get 1.0 end] \n]
631: close $fd
632: }
633: }
634: }
635:
636: # Erase everything from the console above the insertion line.
637: #
638: proc sqlitecon::Clear w {
639: $w delete 1.0 {insert linestart}
640: }
641:
642: # An in-line editor for SQL
643: #
644: proc sqlitecon::_edit {origtxt {title {}}} {
645: for {set i 0} {[winfo exists .ed$i]} {incr i} continue
646: set w .ed$i
647: toplevel $w
648: wm protocol $w WM_DELETE_WINDOW "$w.b.can invoke"
649: wm title $w {Inline SQL Editor}
650: frame $w.b
651: pack $w.b -side bottom -fill x
652: button $w.b.can -text Cancel -width 6 -command [list set ::$w 0]
653: button $w.b.ok -text OK -width 6 -command [list set ::$w 1]
654: button $w.b.cut -text Cut -width 6 -command [list ::sqlitecon::Cut $w.t]
655: button $w.b.copy -text Copy -width 6 -command [list ::sqlitecon::Copy $w.t]
656: button $w.b.paste -text Paste -width 6 -command [list ::sqlitecon::Paste $w.t]
657: set ::$w {}
658: pack $w.b.cut $w.b.copy $w.b.paste $w.b.can $w.b.ok\
659: -side left -padx 5 -pady 5 -expand 1
660: if {$title!=""} {
661: label $w.title -text $title
662: pack $w.title -side top -padx 5 -pady 5
663: }
664: text $w.t -bg white -fg black -yscrollcommand [list $w.sb set]
665: pack $w.t -side left -fill both -expand 1
666: scrollbar $w.sb -orient vertical -command [list $w.t yview]
667: pack $w.sb -side left -fill y
668: $w.t insert end $origtxt
669:
670: vwait ::$w
671:
672: if {[set ::$w]} {
673: set txt [string trimright [$w.t get 1.0 end]]
674: } else {
675: set txt $origtxt
676: }
677: destroy $w
678: return $txt
679: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>