123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680 |
- # A Tk console widget for SQLite. Invoke sqlitecon::create with a window name,
- # a prompt string, a title to set a new top-level window, and the SQLite
- # database handle. For example:
- #
- # sqlitecon::create .sqlcon {sql:- } {SQL Console} db
- #
- # A toplevel window is created that allows you to type in SQL commands to
- # be processed on the spot.
- #
- # A limited set of dot-commands are supported:
- #
- # .table
- # .schema ?TABLE?
- # .mode list|column|multicolumn|line
- # .exit
- #
- # In addition, a new SQL function named "edit()" is created. This function
- # takes a single text argument and returns a text result. Whenever the
- # the function is called, it pops up a new toplevel window containing a
- # text editor screen initialized to the argument. When the "OK" button
- # is pressed, whatever revised text is in the text editor is returned as
- # the result of the edit() function. This allows text fields of SQL tables
- # to be edited quickly and easily as follows:
- #
- # UPDATE table1 SET dscr = edit(dscr) WHERE rowid=15;
- #
- # Create a namespace to work in
- #
- namespace eval ::sqlitecon {
- # do nothing
- }
- # Create a console widget named $w. The prompt string is $prompt.
- # The title at the top of the window is $title. The database connection
- # object is $db
- #
- proc sqlitecon::create {w prompt title db} {
- upvar #0 $w.t v
- if {[winfo exists $w]} {destroy $w}
- if {[info exists v]} {unset v}
- toplevel $w
- wm title $w $title
- wm iconname $w $title
- frame $w.mb -bd 2 -relief raised
- pack $w.mb -side top -fill x
- menubutton $w.mb.file -text File -menu $w.mb.file.m
- menubutton $w.mb.edit -text Edit -menu $w.mb.edit.m
- pack $w.mb.file $w.mb.edit -side left -padx 8 -pady 1
- set m [menu $w.mb.file.m -tearoff 0]
- $m add command -label {Close} -command "destroy $w"
- sqlitecon::create_child $w $prompt $w.mb.edit.m
- set v(db) $db
- $db function edit ::sqlitecon::_edit
- }
- # This routine creates a console as a child window within a larger
- # window. It also creates an edit menu named "$editmenu" if $editmenu!="".
- # The calling function is responsible for posting the edit menu.
- #
- proc sqlitecon::create_child {w prompt editmenu} {
- upvar #0 $w.t v
- if {$editmenu!=""} {
- set m [menu $editmenu -tearoff 0]
- $m add command -label Cut -command "sqlitecon::Cut $w.t"
- $m add command -label Copy -command "sqlitecon::Copy $w.t"
- $m add command -label Paste -command "sqlitecon::Paste $w.t"
- $m add command -label {Clear Screen} -command "sqlitecon::Clear $w.t"
- $m add separator
- $m add command -label {Save As...} -command "sqlitecon::SaveFile $w.t"
- catch {$editmenu config -postcommand "sqlitecon::EnableEditMenu $w"}
- }
- scrollbar $w.sb -orient vertical -command "$w.t yview"
- pack $w.sb -side right -fill y
- text $w.t -font fixed -yscrollcommand "$w.sb set"
- pack $w.t -side right -fill both -expand 1
- bindtags $w.t Sqlitecon
- set v(editmenu) $editmenu
- set v(history) 0
- set v(historycnt) 0
- set v(current) -1
- set v(prompt) $prompt
- set v(prior) {}
- set v(plength) [string length $v(prompt)]
- set v(x) 0
- set v(y) 0
- set v(mode) column
- set v(header) on
- $w.t mark set insert end
- $w.t tag config ok -foreground blue
- $w.t tag config err -foreground red
- $w.t insert end $v(prompt)
- $w.t mark set out 1.0
- after idle "focus $w.t"
- }
- bind Sqlitecon <1> {sqlitecon::Button1 %W %x %y}
- bind Sqlitecon <B1-Motion> {sqlitecon::B1Motion %W %x %y}
- bind Sqlitecon <B1-Leave> {sqlitecon::B1Leave %W %x %y}
- bind Sqlitecon <B1-Enter> {sqlitecon::cancelMotor %W}
- bind Sqlitecon <ButtonRelease-1> {sqlitecon::cancelMotor %W}
- bind Sqlitecon <KeyPress> {sqlitecon::Insert %W %A}
- bind Sqlitecon <Left> {sqlitecon::Left %W}
- bind Sqlitecon <Control-b> {sqlitecon::Left %W}
- bind Sqlitecon <Right> {sqlitecon::Right %W}
- bind Sqlitecon <Control-f> {sqlitecon::Right %W}
- bind Sqlitecon <BackSpace> {sqlitecon::Backspace %W}
- bind Sqlitecon <Control-h> {sqlitecon::Backspace %W}
- bind Sqlitecon <Delete> {sqlitecon::Delete %W}
- bind Sqlitecon <Control-d> {sqlitecon::Delete %W}
- bind Sqlitecon <Home> {sqlitecon::Home %W}
- bind Sqlitecon <Control-a> {sqlitecon::Home %W}
- bind Sqlitecon <End> {sqlitecon::End %W}
- bind Sqlitecon <Control-e> {sqlitecon::End %W}
- bind Sqlitecon <Return> {sqlitecon::Enter %W}
- bind Sqlitecon <KP_Enter> {sqlitecon::Enter %W}
- bind Sqlitecon <Up> {sqlitecon::Prior %W}
- bind Sqlitecon <Control-p> {sqlitecon::Prior %W}
- bind Sqlitecon <Down> {sqlitecon::Next %W}
- bind Sqlitecon <Control-n> {sqlitecon::Next %W}
- bind Sqlitecon <Control-k> {sqlitecon::EraseEOL %W}
- bind Sqlitecon <<Cut>> {sqlitecon::Cut %W}
- bind Sqlitecon <<Copy>> {sqlitecon::Copy %W}
- bind Sqlitecon <<Paste>> {sqlitecon::Paste %W}
- bind Sqlitecon <<Clear>> {sqlitecon::Clear %W}
- # Insert a single character at the insertion cursor
- #
- proc sqlitecon::Insert {w a} {
- $w insert insert $a
- $w yview insert
- }
- # Move the cursor one character to the left
- #
- proc sqlitecon::Left {w} {
- upvar #0 $w v
- scan [$w index insert] %d.%d row col
- if {$col>$v(plength)} {
- $w mark set insert "insert -1c"
- }
- }
- # Erase the character to the left of the cursor
- #
- proc sqlitecon::Backspace {w} {
- upvar #0 $w v
- scan [$w index insert] %d.%d row col
- if {$col>$v(plength)} {
- $w delete {insert -1c}
- }
- }
- # Erase to the end of the line
- #
- proc sqlitecon::EraseEOL {w} {
- upvar #0 $w v
- scan [$w index insert] %d.%d row col
- if {$col>=$v(plength)} {
- $w delete insert {insert lineend}
- }
- }
- # Move the cursor one character to the right
- #
- proc sqlitecon::Right {w} {
- $w mark set insert "insert +1c"
- }
- # Erase the character to the right of the cursor
- #
- proc sqlitecon::Delete w {
- $w delete insert
- }
- # Move the cursor to the beginning of the current line
- #
- proc sqlitecon::Home w {
- upvar #0 $w v
- scan [$w index insert] %d.%d row col
- $w mark set insert $row.$v(plength)
- }
- # Move the cursor to the end of the current line
- #
- proc sqlitecon::End w {
- $w mark set insert {insert lineend}
- }
- # Add a line to the history
- #
- proc sqlitecon::addHistory {w line} {
- upvar #0 $w v
- if {$v(historycnt)>0} {
- set last [lindex $v(history) [expr $v(historycnt)-1]]
- if {[string compare $last $line]} {
- lappend v(history) $line
- incr v(historycnt)
- }
- } else {
- set v(history) [list $line]
- set v(historycnt) 1
- }
- set v(current) $v(historycnt)
- }
- # Called when "Enter" is pressed. Do something with the line
- # of text that was entered.
- #
- proc sqlitecon::Enter w {
- upvar #0 $w v
- scan [$w index insert] %d.%d row col
- set start $row.$v(plength)
- set line [$w get $start "$start lineend"]
- $w insert end \n
- $w mark set out end
- if {$v(prior)==""} {
- set cmd $line
- } else {
- set cmd $v(prior)\n$line
- }
- if {[string index $cmd 0]=="." || [$v(db) complete $cmd]} {
- regsub -all {\n} [string trim $cmd] { } cmd2
- addHistory $w $cmd2
- set rc [catch {DoCommand $w $cmd} res]
- if {![winfo exists $w]} return
- if {$rc} {
- $w insert end $res\n err
- } elseif {[string length $res]>0} {
- $w insert end $res\n ok
- }
- set v(prior) {}
- $w insert end $v(prompt)
- } else {
- set v(prior) $cmd
- regsub -all {[^ ]} $v(prompt) . x
- $w insert end $x
- }
- $w mark set insert end
- $w mark set out {insert linestart}
- $w yview insert
- }
- # Execute a single SQL command. Pay special attention to control
- # directives that begin with "."
- #
- # The return value is the text output from the command, properly
- # formatted.
- #
- proc sqlitecon::DoCommand {w cmd} {
- upvar #0 $w v
- set mode $v(mode)
- set header $v(header)
- if {[regexp {^(\.[a-z]+)} $cmd all word]} {
- if {$word==".mode"} {
- regexp {^.[a-z]+ +([a-z]+)} $cmd all v(mode)
- return {}
- } elseif {$word==".exit"} {
- destroy [winfo toplevel $w]
- return {}
- } elseif {$word==".header"} {
- regexp {^.[a-z]+ +([a-z]+)} $cmd all v(header)
- return {}
- } elseif {$word==".tables"} {
- set mode multicolumn
- set cmd {SELECT name FROM sqlite_master WHERE type='table'
- UNION ALL
- SELECT name FROM sqlite_temp_master WHERE type='table'}
- $v(db) eval {PRAGMA database_list} {
- if {$name!="temp" && $name!="main"} {
- append cmd "UNION ALL SELECT name FROM $name.sqlite_master\
- WHERE type='table'"
- }
- }
- append cmd { ORDER BY 1}
- } elseif {$word==".fullschema"} {
- set pattern %
- regexp {^.[a-z]+ +([^ ]+)} $cmd all pattern
- set mode list
- set header 0
- set cmd "SELECT sql FROM sqlite_master WHERE tbl_name LIKE '$pattern'
- AND sql NOT NULL UNION ALL SELECT sql FROM sqlite_temp_master
- WHERE tbl_name LIKE '$pattern' AND sql NOT NULL"
- $v(db) eval {PRAGMA database_list} {
- if {$name!="temp" && $name!="main"} {
- append cmd " UNION ALL SELECT sql FROM $name.sqlite_master\
- WHERE tbl_name LIKE '$pattern' AND sql NOT NULL"
- }
- }
- } elseif {$word==".schema"} {
- set pattern %
- regexp {^.[a-z]+ +([^ ]+)} $cmd all pattern
- set mode list
- set header 0
- set cmd "SELECT sql FROM sqlite_master WHERE name LIKE '$pattern'
- AND sql NOT NULL UNION ALL SELECT sql FROM sqlite_temp_master
- WHERE name LIKE '$pattern' AND sql NOT NULL"
- $v(db) eval {PRAGMA database_list} {
- if {$name!="temp" && $name!="main"} {
- append cmd " UNION ALL SELECT sql FROM $name.sqlite_master\
- WHERE name LIKE '$pattern' AND sql NOT NULL"
- }
- }
- } else {
- return \
- ".exit\n.mode line|list|column\n.schema ?TABLENAME?\n.tables"
- }
- }
- set res {}
- if {$mode=="list"} {
- $v(db) eval $cmd x {
- set sep {}
- foreach col $x(*) {
- append res $sep$x($col)
- set sep |
- }
- append res \n
- }
- if {[info exists x(*)] && $header} {
- set sep {}
- set hdr {}
- foreach col $x(*) {
- append hdr $sep$col
- set sep |
- }
- set res $hdr\n$res
- }
- } elseif {[string range $mode 0 2]=="col"} {
- set y {}
- $v(db) eval $cmd x {
- foreach col $x(*) {
- if {![info exists cw($col)] || $cw($col)<[string length $x($col)]} {
- set cw($col) [string length $x($col)]
- }
- lappend y $x($col)
- }
- }
- if {[info exists x(*)] && $header} {
- set hdr {}
- set ln {}
- set dash ---------------------------------------------------------------
- append dash ------------------------------------------------------------
- foreach col $x(*) {
- if {![info exists cw($col)] || $cw($col)<[string length $col]} {
- set cw($col) [string length $col]
- }
- lappend hdr $col
- lappend ln [string range $dash 1 $cw($col)]
- }
- set y [concat $hdr $ln $y]
- }
- if {[info exists x(*)]} {
- set format {}
- set arglist {}
- set arglist2 {}
- set i 0
- foreach col $x(*) {
- lappend arglist x$i
- append arglist2 " \$x$i"
- incr i
- append format " %-$cw($col)s"
- }
- set format [string trimleft $format]\n
- if {[llength $arglist]>0} {
- foreach $arglist $y "append res \[format [list $format] $arglist2\]"
- }
- }
- } elseif {$mode=="multicolumn"} {
- set y [$v(db) eval $cmd]
- set max 0
- foreach e $y {
- if {$max<[string length $e]} {set max [string length $e]}
- }
- set ncol [expr {int(80/($max+2))}]
- if {$ncol<1} {set ncol 1}
- set nelem [llength $y]
- set nrow [expr {($nelem+$ncol-1)/$ncol}]
- set format "%-${max}s"
- for {set i 0} {$i<$nrow} {incr i} {
- set j $i
- while 1 {
- append res [format $format [lindex $y $j]]
- incr j $nrow
- if {$j>=$nelem} break
- append res { }
- }
- append res \n
- }
- } else {
- $v(db) eval $cmd x {
- foreach col $x(*) {append res "$col = $x($col)\n"}
- append res \n
- }
- }
- return [string trimright $res]
- }
- # Change the line to the previous line
- #
- proc sqlitecon::Prior w {
- upvar #0 $w v
- if {$v(current)<=0} return
- incr v(current) -1
- set line [lindex $v(history) $v(current)]
- sqlitecon::SetLine $w $line
- }
- # Change the line to the next line
- #
- proc sqlitecon::Next w {
- upvar #0 $w v
- if {$v(current)>=$v(historycnt)} return
- incr v(current) 1
- set line [lindex $v(history) $v(current)]
- sqlitecon::SetLine $w $line
- }
- # Change the contents of the entry line
- #
- proc sqlitecon::SetLine {w line} {
- upvar #0 $w v
- scan [$w index insert] %d.%d row col
- set start $row.$v(plength)
- $w delete $start end
- $w insert end $line
- $w mark set insert end
- $w yview insert
- }
- # Called when the mouse button is pressed at position $x,$y on
- # the console widget.
- #
- proc sqlitecon::Button1 {w x y} {
- global tkPriv
- upvar #0 $w v
- set v(mouseMoved) 0
- set v(pressX) $x
- set p [sqlitecon::nearestBoundry $w $x $y]
- scan [$w index insert] %d.%d ix iy
- scan $p %d.%d px py
- if {$px==$ix} {
- $w mark set insert $p
- }
- $w mark set anchor $p
- focus $w
- }
- # Find the boundry between characters that is nearest
- # to $x,$y
- #
- proc sqlitecon::nearestBoundry {w x y} {
- set p [$w index @$x,$y]
- set bb [$w bbox $p]
- if {![string compare $bb ""]} {return $p}
- if {($x-[lindex $bb 0])<([lindex $bb 2]/2)} {return $p}
- $w index "$p + 1 char"
- }
- # This routine extends the selection to the point specified by $x,$y
- #
- proc sqlitecon::SelectTo {w x y} {
- upvar #0 $w v
- set cur [sqlitecon::nearestBoundry $w $x $y]
- if {[catch {$w index anchor}]} {
- $w mark set anchor $cur
- }
- set anchor [$w index anchor]
- if {[$w compare $cur != $anchor] || (abs($v(pressX) - $x) >= 3)} {
- if {$v(mouseMoved)==0} {
- $w tag remove sel 0.0 end
- }
- set v(mouseMoved) 1
- }
- if {[$w compare $cur < anchor]} {
- set first $cur
- set last anchor
- } else {
- set first anchor
- set last $cur
- }
- if {$v(mouseMoved)} {
- $w tag remove sel 0.0 $first
- $w tag add sel $first $last
- $w tag remove sel $last end
- update idletasks
- }
- }
- # Called whenever the mouse moves while button-1 is held down.
- #
- proc sqlitecon::B1Motion {w x y} {
- upvar #0 $w v
- set v(y) $y
- set v(x) $x
- sqlitecon::SelectTo $w $x $y
- }
- # Called whenever the mouse leaves the boundries of the widget
- # while button 1 is held down.
- #
- proc sqlitecon::B1Leave {w x y} {
- upvar #0 $w v
- set v(y) $y
- set v(x) $x
- sqlitecon::motor $w
- }
- # This routine is called to automatically scroll the window when
- # the mouse drags offscreen.
- #
- proc sqlitecon::motor w {
- upvar #0 $w v
- if {![winfo exists $w]} return
- if {$v(y)>=[winfo height $w]} {
- $w yview scroll 1 units
- } elseif {$v(y)<0} {
- $w yview scroll -1 units
- } else {
- return
- }
- sqlitecon::SelectTo $w $v(x) $v(y)
- set v(timer) [after 50 sqlitecon::motor $w]
- }
- # This routine cancels the scrolling motor if it is active
- #
- proc sqlitecon::cancelMotor w {
- upvar #0 $w v
- catch {after cancel $v(timer)}
- catch {unset v(timer)}
- }
- # Do a Copy operation on the stuff currently selected.
- #
- proc sqlitecon::Copy w {
- if {![catch {set text [$w get sel.first sel.last]}]} {
- clipboard clear -displayof $w
- clipboard append -displayof $w $text
- }
- }
- # Return 1 if the selection exists and is contained
- # entirely on the input line. Return 2 if the selection
- # exists but is not entirely on the input line. Return 0
- # if the selection does not exist.
- #
- proc sqlitecon::canCut w {
- set r [catch {
- scan [$w index sel.first] %d.%d s1x s1y
- scan [$w index sel.last] %d.%d s2x s2y
- scan [$w index insert] %d.%d ix iy
- }]
- if {$r==1} {return 0}
- if {$s1x==$ix && $s2x==$ix} {return 1}
- return 2
- }
- # Do a Cut operation if possible. Cuts are only allowed
- # if the current selection is entirely contained on the
- # current input line.
- #
- proc sqlitecon::Cut w {
- if {[sqlitecon::canCut $w]==1} {
- sqlitecon::Copy $w
- $w delete sel.first sel.last
- }
- }
- # Do a paste opeation.
- #
- proc sqlitecon::Paste w {
- if {[sqlitecon::canCut $w]==1} {
- $w delete sel.first sel.last
- }
- if {[catch {selection get -displayof $w -selection CLIPBOARD} topaste]
- && [catch {selection get -displayof $w -selection PRIMARY} topaste]} {
- return
- }
- if {[info exists ::$w]} {
- set prior 0
- foreach line [split $topaste \n] {
- if {$prior} {
- sqlitecon::Enter $w
- update
- }
- set prior 1
- $w insert insert $line
- }
- } else {
- $w insert insert $topaste
- }
- }
- # Enable or disable entries in the Edit menu
- #
- proc sqlitecon::EnableEditMenu w {
- upvar #0 $w.t v
- set m $v(editmenu)
- if {$m=="" || ![winfo exists $m]} return
- switch [sqlitecon::canCut $w.t] {
- 0 {
- $m entryconf Copy -state disabled
- $m entryconf Cut -state disabled
- }
- 1 {
- $m entryconf Copy -state normal
- $m entryconf Cut -state normal
- }
- 2 {
- $m entryconf Copy -state normal
- $m entryconf Cut -state disabled
- }
- }
- }
- # Prompt the user for the name of a writable file. Then write the
- # entire contents of the console screen to that file.
- #
- proc sqlitecon::SaveFile w {
- set types {
- {{Text Files} {.txt}}
- {{All Files} *}
- }
- set f [tk_getSaveFile -filetypes $types -title "Write Screen To..."]
- if {$f!=""} {
- if {[catch {open $f w} fd]} {
- tk_messageBox -type ok -icon error -message $fd
- } else {
- puts $fd [string trimright [$w get 1.0 end] \n]
- close $fd
- }
- }
- }
- # Erase everything from the console above the insertion line.
- #
- proc sqlitecon::Clear w {
- $w delete 1.0 {insert linestart}
- }
- # An in-line editor for SQL
- #
- proc sqlitecon::_edit {origtxt {title {}}} {
- for {set i 0} {[winfo exists .ed$i]} {incr i} continue
- set w .ed$i
- toplevel $w
- wm protocol $w WM_DELETE_WINDOW "$w.b.can invoke"
- wm title $w {Inline SQL Editor}
- frame $w.b
- pack $w.b -side bottom -fill x
- button $w.b.can -text Cancel -width 6 -command [list set ::$w 0]
- button $w.b.ok -text OK -width 6 -command [list set ::$w 1]
- button $w.b.cut -text Cut -width 6 -command [list ::sqlitecon::Cut $w.t]
- button $w.b.copy -text Copy -width 6 -command [list ::sqlitecon::Copy $w.t]
- button $w.b.paste -text Paste -width 6 -command [list ::sqlitecon::Paste $w.t]
- set ::$w {}
- pack $w.b.cut $w.b.copy $w.b.paste $w.b.can $w.b.ok\
- -side left -padx 5 -pady 5 -expand 1
- if {$title!=""} {
- label $w.title -text $title
- pack $w.title -side top -padx 5 -pady 5
- }
- text $w.t -bg white -fg black -yscrollcommand [list $w.sb set]
- pack $w.t -side left -fill both -expand 1
- scrollbar $w.sb -orient vertical -command [list $w.t yview]
- pack $w.sb -side left -fill y
- $w.t insert end $origtxt
- vwait ::$w
- if {[set ::$w]} {
- set txt [string trimright [$w.t get 1.0 end]]
- } else {
- set txt $origtxt
- }
- destroy $w
- return $txt
- }
|