Home | History | Annotate | Download | only in contrib
      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 }
    680