drh | 74217cc | 2005-09-24 11:01:11 +0000 | [diff] [blame] | 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 | } |