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:
5 # sqlitecon::create .sqlcon {sql:- } {SQL Console} db
7 # A toplevel window is created that allows you to type in SQL commands to
8 # be processed on the spot.
10 # A limited set of dot-commands are supported:
14 # .mode list|column|multicolumn|line
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:
25 # UPDATE table1 SET dscr = edit(dscr) WHERE rowid=15;
29 # Create a namespace to work in
31 namespace eval ::sqlitecon {
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
39 proc sqlitecon
::create {w prompt title db
} {
41 if {[winfo exists
$w]} {destroy $w}
42 if {[info exists v
]} {unset v
}
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
55 $db function edit
::sqlitecon::_edit
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.
62 proc sqlitecon
::create_child {w prompt 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"
71 $m add command
-label {Save As...
} -command "sqlitecon::SaveFile $w.t"
72 catch {$editmenu config
-postcommand "sqlitecon::EnableEditMenu $w"}
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
85 set v
(plength
) [string length
$v(prompt
)]
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
)
95 after idle
"focus $w.t"
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
}
128 # Insert a single character at the insertion cursor
130 proc sqlitecon
::Insert {w a
} {
135 # Move the cursor one character to the left
137 proc sqlitecon
::Left {w
} {
139 scan [$w index insert
] %d.
%d row col
140 if {$col>$v(plength
)} {
141 $w mark
set insert
"insert -1c"
145 # Erase the character to the left of the cursor
147 proc sqlitecon
::Backspace {w
} {
149 scan [$w index insert
] %d.
%d row col
150 if {$col>$v(plength
)} {
151 $w delete
{insert
-1c}
155 # Erase to the end of the line
157 proc sqlitecon
::EraseEOL {w
} {
159 scan [$w index insert
] %d.
%d row col
160 if {$col>=$v(plength
)} {
161 $w delete insert
{insert lineend
}
165 # Move the cursor one character to the right
167 proc sqlitecon
::Right {w
} {
168 $w mark
set insert
"insert +1c"
171 # Erase the character to the right of the cursor
173 proc sqlitecon
::Delete w
{
177 # Move the cursor to the beginning of the current line
179 proc sqlitecon
::Home w
{
181 scan [$w index insert
] %d.
%d row col
182 $w mark
set insert
$row.
$v(plength
)
185 # Move the cursor to the end of the current line
187 proc sqlitecon
::End w
{
188 $w mark
set insert
{insert lineend
}
191 # Add a line to the history
193 proc sqlitecon
::addHistory {w line
} {
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
202 set v
(history) [list $line]
205 set v
(current
) $v(historycnt
)
208 # Called when "Enter" is pressed. Do something with the line
209 # of text that was entered.
211 proc sqlitecon
::Enter w
{
213 scan [$w index insert
] %d.
%d row col
214 set start
$row.
$v(plength
)
215 set line
[$w get
$start "$start lineend"]
221 set cmd
$v(prior
)\n$line
223 if {[string index
$cmd 0]=="." ||
[$v(db
) complete
$cmd]} {
224 regsub -all {\n} [string trim
$cmd] { } cmd2
226 set rc
[catch {DoCommand
$w $cmd} res
]
227 if {![winfo exists
$w]} return
229 $w insert end
$res\n err
230 } elseif
{[string length
$res]>0} {
231 $w insert end
$res\n ok
234 $w insert end
$v(prompt
)
237 regsub -all {[^
]} $v(prompt
) . x
240 $w mark
set insert end
241 $w mark
set out
{insert linestart
}
245 # Execute a single SQL command. Pay special attention to control
246 # directives that begin with "."
248 # The return value is the text output from the command, properly
251 proc sqlitecon
::DoCommand {w cmd
} {
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
)
259 } elseif
{$word==".exit"} {
260 destroy [winfo toplevel $w]
262 } elseif
{$word==".header"} {
263 regexp {^.
[a-z
]+ +([a-z
]+)} $cmd all v
(header
)
265 } elseif
{$word==".tables"} {
267 set cmd
{SELECT name FROM sqlite_master WHERE type
='table'
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\
276 append cmd
{ ORDER BY
1}
277 } elseif
{$word==".fullschema"} {
279 regexp {^.
[a-z
]+ +([^
]+)} $cmd all pattern
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"
291 } elseif
{$word==".schema"} {
293 regexp {^.
[a-z
]+ +([^
]+)} $cmd all pattern
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"
307 ".exit\n.mode line|list|column\n.schema ?TABLENAME?\n.tables"
315 append res
$sep$x($col)
320 if {[info exists x
(*)] && $header} {
329 } elseif
{[string range
$mode 0 2]=="col"} {
333 if {![info exists cw
($col)] ||
$cw($col)<[string length
$x($col)]} {
334 set cw
($col) [string length
$x($col)]
339 if {[info exists x
(*)] && $header} {
342 set dash
---------------------------------------------------------------
343 append dash
------------------------------------------------------------
345 if {![info exists cw
($col)] ||
$cw($col)<[string length
$col]} {
346 set cw
($col) [string length
$col]
349 lappend ln
[string range
$dash 1 $cw($col)]
351 set y
[concat $hdr $ln $y]
353 if {[info exists x
(*)]} {
360 append arglist2
" \$x$i"
362 append format " %-$cw($col)s"
364 set format [string trimleft
$format]\n
365 if {[llength $arglist]>0} {
366 foreach $arglist $y "append res \[format [list $format] $arglist2\]"
369 } elseif
{$mode=="multicolumn"} {
370 set y
[$v(db
) eval $cmd]
373 if {$max<[string length
$e]} {set max
[string length
$e]}
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
} {
383 append res
[format $format [lindex $y $j]]
385 if {$j>=$nelem} break
392 foreach col
$x(*) {append res
"$col = $x($col)\n"}
396 return [string trimright
$res]
399 # Change the line to the previous line
401 proc sqlitecon
::Prior w
{
403 if {$v(current
)<=0} return
405 set line
[lindex $v(history) $v(current
)]
406 sqlitecon
::SetLine $w $line
409 # Change the line to the next line
411 proc sqlitecon
::Next w
{
413 if {$v(current
)>=$v(historycnt
)} return
415 set line
[lindex $v(history) $v(current
)]
416 sqlitecon
::SetLine $w $line
419 # Change the contents of the entry line
421 proc sqlitecon
::SetLine {w line
} {
423 scan [$w index insert
] %d.
%d row col
424 set start
$row.
$v(plength
)
427 $w mark
set insert end
431 # Called when the mouse button is pressed at position $x,$y on
432 # the console widget.
434 proc sqlitecon
::Button1 {w x y
} {
439 set p
[sqlitecon
::nearestBoundry $w $x $y]
440 scan [$w index insert
] %d.
%d ix iy
443 $w mark
set insert
$p
445 $w mark
set anchor
$p
449 # Find the boundry between characters that is nearest
452 proc sqlitecon
::nearestBoundry {w x y
} {
453 set p
[$w index
@$x,$y]
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"
460 # This routine extends the selection to the point specified by $x,$y
462 proc sqlitecon
::SelectTo {w x y
} {
464 set cur
[sqlitecon
::nearestBoundry $w $x $y]
465 if {[catch {$w index anchor
}]} {
466 $w mark
set anchor
$cur
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
475 if {[$w compare
$cur < anchor
]} {
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
490 # Called whenever the mouse moves while button-1 is held down.
492 proc sqlitecon
::B1Motion {w x y
} {
496 sqlitecon
::SelectTo $w $x $y
499 # Called whenever the mouse leaves the boundries of the widget
500 # while button 1 is held down.
502 proc sqlitecon
::B1Leave {w x y
} {
509 # This routine is called to automatically scroll the window when
510 # the mouse drags offscreen.
512 proc sqlitecon
::motor w
{
514 if {![winfo exists
$w]} return
515 if {$v(y
)>=[winfo height
$w]} {
516 $w yview scroll
1 units
518 $w yview scroll
-1 units
522 sqlitecon
::SelectTo $w $v(x
) $v(y
)
523 set v
(timer
) [after 50 sqlitecon
::motor $w]
526 # This routine cancels the scrolling motor if it is active
528 proc sqlitecon
::cancelMotor w
{
530 catch {after cancel
$v(timer
)}
531 catch {unset v
(timer
)}
534 # Do a Copy operation on the stuff currently selected.
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
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.
548 proc sqlitecon
::canCut w
{
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
554 if {$r==1} {return 0}
555 if {$s1x==$ix && $s2x==$ix} {return 1}
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.
563 proc sqlitecon
::Cut w
{
564 if {[sqlitecon
::canCut $w]==1} {
566 $w delete sel.first sel.last
570 # Do a paste opeation.
572 proc sqlitecon
::Paste w
{
573 if {[sqlitecon
::canCut $w]==1} {
574 $w delete sel.first sel.last
576 if {[catch {selection get
-displayof $w -selection CLIPBOARD
} topaste
]
577 && [catch {selection get
-displayof $w -selection PRIMARY
} topaste
]} {
580 if {[info exists
::$w]} {
582 foreach line
[split $topaste \n] {
588 $w insert insert
$line
591 $w insert insert
$topaste
595 # Enable or disable entries in the Edit menu
597 proc sqlitecon
::EnableEditMenu w
{
600 if {$m=="" ||
![winfo exists
$m]} return
601 switch [sqlitecon
::canCut $w.t
] {
603 $m entryconf Copy
-state disabled
604 $m entryconf Cut
-state disabled
607 $m entryconf Copy
-state normal
608 $m entryconf Cut
-state normal
611 $m entryconf Copy
-state normal
612 $m entryconf Cut
-state disabled
617 # Prompt the user for the name of a writable file. Then write the
618 # entire contents of the console screen to that file.
620 proc sqlitecon
::SaveFile w
{
622 {{Text Files
} {.txt
}}
625 set f
[tk_getSaveFile -filetypes $types -title "Write Screen To..."]
627 if {[catch {open $f w
} fd
]} {
628 tk_messageBox -type ok
-icon error -message $fd
630 puts $fd [string trimright
[$w get
1.0 end
] \n]
636 # Erase everything from the console above the insertion line.
638 proc sqlitecon
::Clear w
{
639 $w delete
1.0 {insert linestart
}
642 # An in-line editor for SQL
644 proc sqlitecon
::_edit {origtxt
{title
{}}} {
645 for {set i
0} {[winfo exists .ed
$i]} {incr i
} continue
648 wm protocol
$w WM_DELETE_WINDOW
"$w.b.can invoke"
649 wm title
$w {Inline SQL Editor
}
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
]
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
661 label $w.title
-text $title
662 pack $w.title
-side top
-padx 5 -pady 5
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
673 set txt
[string trimright
[$w.t get
1.0 end
]]