rdt and, of course _everybody_ knows just where to get ttd.tcl, its only the stupid ones like me that don't know where it is or who Bryan Oakley happens to be! :). APN See the ttd page for a copy.Bryan Oakley wow, I had no idea anybody used that ttd stuff. I did that years ago. Glad it's useful for something...WJG ttd? yep. Use it every day. "Wouldn't leave home without it!"
#--------------- # postit.tcl #--------------- # Created by William J Giddings, 2006 # # Purpose: # ------- # Provide a simple package to enable the creation # of embedded postits within a Tk text widget. # Included within the package is load/save via # Brian Oakley's ttd package. A data file is created # as a list two entries. The first is the ttd dump of # the text including tags, the second a list of postits # including individual configurations and text. # # Notes: # ----- # The array ::postits::postits contains the postit # window parameters. # # Usage: # ----- # * Ctrl-B1 will cause postit window to open. # * Hovering mouse pointer over tag for more than 1sec # will cause rollover display of postit content. # # Acknowledgments: # --------------- # Bryan Oakley: ttd package. http://wiki.tcl.tk/5790 #--------------- set DEMO(postit) yes set DEBUG(postit) yes #--------------- # initislise namespace to hold postit variables #--------------- namespace eval postit { # always use ttd source ttd.tcl # binding to activate postit edit window set bindings <Control-Button-1> # set available colours set magenta \#fd0ae51effff set yellow \#f3bfffff90a3 set blue \#d851d8b7ffff set cyan \#deb7feffffff set green \#d851ffffd8b7 set orange \#ffffd5b568f5 # set rollover colour set rollover #ffffdd # set available fonts -the old way set small -Adobe-Helvetica-Medium-R-Normal-*-*-100-*-*-*-*-*-* set medium -Adobe-Helvetica-Medium-R-Normal-*-*-120-*-*-*-*-*-* set large -Adobe-Helvetica-Medium-R-Normal-*-*-140-*-*-*-*-*-* # posit window size set width 250 set height 150 # set transparency for Windows only if {$tcl_platform(platform)=="windows"} { set trans 0.97 } # set default postit settings set default "$::postit::medium $::postit::magenta" # index of active postit variable active } #---------------------------------------------------------------------- # posit popup menus #---------------------------------------------------------------------- menu .pipopup -tearoff 0 # # Lock/Unlock * # ----------- * # Undo * # Redo * # ----------- # Delete # ----------- # Textsize > S M L # Colors > Magenta Yellow Blue Cyan Green Orange # .pipopup add command -label Lock \ -command { if { [.pipopup entrycget 0 -label] == "Unlock" } { .postit.txt configure -state normal .pipopup entryconfigure 0 -label "Lock" } else { .postit.txt configure -state disabled .pipopup entryconfigure 0 -label "Unlock" } } #--- .pipopup add separator .pipopup add command -label "Undo" \ -command {.postit.txt edit undo} ;# -accelerator "Ctrl-z" .pipopup add command -label "Redo" \ -command { .postit.txt edit redo} ;# -accelerator "Ctrl-Z" .pipopup add separator .pipopup add command -label "Delete" -command { postit:delete } #--- .pipopup add separator .pipopup add cascade -label "Text" -menu .pipopup.cas1 menu .pipopup.cas1 -tearoff 0 .pipopup.cas1 add radiobutton -label "Small" -variable ::postit::font -value $::postit::small -command { .postit.txt config -font $::postit::font } .pipopup.cas1 add radiobutton -label "Medium" -variable ::postit::font -value $::postit::medium -command { .postit.txt config -font $::postit::font } .pipopup.cas1 add radiobutton -label "Large" -variable ::postit::font -value $::postit::large -command { .postit.txt config -font $::postit::font } #--- .pipopup add cascade -label "Colors" -menu .pipopup.cas2 menu .pipopup.cas2 -tearoff 0 foreach i { Magenta Yellow Blue Cyan Green Orange } { switch $i { Magenta { set tmp $::postit::magenta } Yellow { set tmp $::postit::yellow } Blue { set tmp $::postit::blue } Cyan { set tmp $::postit::cyan } Green { set tmp $::postit::green } Orange { set tmp $::postit::orange } } .pipopup.cas2 add command -label $i \ -background $tmp \ -command "catch { .postit.txt config -background $tmp }" } #--------------- # display popup menu #--------------- proc postit:showPopup {w x y m} { # popup-menu bindings # get global X/Y position of app set gx [winfo rootx $w] set gy [winfo rooty $w] # add to local mouse position set mx [expr $gx + $x] set my [expr $gy + $y] # display popup menu tk_popup $m $mx $my } #--------------- # add new postit tag to selected text #--------------- proc postit:add {} { # some entries may become delete, # NEED TO MODIFY THIS SECTION set a [array size ::postit::postit] set ::postit::postit([incr a]) {} # tag the text [focus] tag add postit_$a sel.first sel.last [focus] tag configure postit_$a -foreground red # set some default values for the new array entry set ::postit::postit_$a "normal $::postit::medium $::postit::magenta \{<emtpy>\}" # add binding postit:setBindings [focus] $a # check the text for any deletions bind [focus] <Key> {puts %K} } #--------------- # accessed from postit popup menu # delete tags and associated data #--------------- proc postit:delete {} { puts $::postit::active .txt tag delete postit_$::postit::active array unset ::postit::postit $::postit::active if {[winfo exists .postit]} { destroy .postit } } #---------------- # list tags within text object #---------------- proc postit:listall { {pref postit_}} { global fruit set taglist {} foreach {a tag c} [.txt dump -tag 1.0 end] { # trim out tag prefix set tag [string trimleft $tag $pref ] # add new tags to a list if {[lsearch $taglist $tag]=="-1"} { # sort the list set taglist [lsort [lappend taglist $tag]] #puts $taglist } } return [lsort $taglist] } #--------------- # dump text tags to console #--------------- proc postit:dump {} { puts [[focus] dump -tag 1.0 end-1c] } #--------------- # show postit window with text #--------------- proc postit:show {w a} { global tcl_platform # keep track of active postit set ::postit::active $a # determine window position scan [ postit:tagScreenPos $w postit_$a ] "%s %s" x y puts "Tag No: $a $x $y" # destroy any rollover window if {[winfo exists .rollover]} {destroy .rollover} # create new postit window if {[winfo exists .postit]} {destroy .postit} toplevel .postit wm withdraw .postit # set transparency for Windows only if {$tcl_platform(platform)=="windows"} { wm attributes .postit -alpha $::postit::trans } wm geometry .postit "${::postit::width}x${::postit::height}+$x+$y" #wm transient .postit [winfo toplevel $w] wm overrideredirect .postit 1 ;# borderless wm deiconify .postit # add widgets text .postit.txt \ -background $::postit::magenta \ -font $::postit::medium \ -borderwidth 2 -relief groove -wrap word -width 24 -height 40 -undo 1 pack .postit.txt -anchor center -expand 1 -fill both -side right # show window and set focus focus .postit.txt # modify bindings -this works!!! bind .postit.txt <FocusOut> "postit:save $a ; postit:check $w ::postit::postit postit_" # initialize popumenus bind .postit.txt <Button-3> { postit:showPopup %W %x %y .pipopup } # change note display settings foreach {state font bgclr txt} $::postit::postit($a) {} .postit.txt configure -state $state -font $font -background $bgclr .postit.txt insert end $txt } #--------------- # display postit contents as a rollover effect # this is just to provide a quick indicator #--------------- proc postit:rollover {w a} { # don't obscure any postit window if {[winfo exists .postit]} {return} # create new rollover every time if {[winfo exists .rollover]} {destroy .rollover} toplevel .rollover scan [ postit:tagScreenPos $w postit_$a ] "%s %s" x y wm geometry .rollover "200x50+$x+$y" #wm transient .rollover [winfo toplevel $w] wm overrideredirect .rollover 1 ;# borderless # add widgets text .rollover.txt \ -background $::postit::rollover\ -font $::postit::medium \ -borderwidth 1 -relief solid -wrap word -width 24 -height 40 pack .rollover.txt -anchor center -expand 1 -fill both -side right # insert text # change note display settings set txt <Empty> foreach {state font bgclr txt} $::postit::postit($a) {} .rollover.txt insert end $txt .rollover.txt configure -state disabled } #--------------- # copy postit to internal buffer #--------------- proc postit:save {a} { # keep settings set ::postit::postit($a) \ "[.postit.txt cget -state] \{[.postit.txt cget -font]\} [.postit.txt cget -background] \{[.postit.txt get 1.0 end-1c]\}" # remove the postit window puts $::postit::postit($a) destroy .postit } #--------------- # determine screen position of tag #--------------- proc postit:tagScreenPos {widget tag {side SW}} { # this will only work with tags that occur only once in a text widget. scan [$widget tag ranges $tag] "%s %s" start end ;# get the range in line.col coordinates scan [$widget bbox $start] "%s %s %s %s" xpos1 ypos1 width1 height1 ;# get bounds of **start** index scan [$widget bbox $end] "%s %s %s %s" xpos2 ypos2 width2 height2 ;# get bounds of the **end** index # # # NWx,NWy = (xpos1,ypos1) **NW**-N----NE # | | # | | # W C E # | | # | | # SW---S--**SE** SEx,SEy = (xpos2 + $width2),(ypos2+height2) # # # NWx,NWy = (xpos1,ypos1) Nx,Ny = ((NWx+SEx)/2,NWy) NEx,NEy = (SEx,NWy) # Wx,Wy = (NWx,(NWy+SEy)/2) Cx,Cy = (Nx.Wy) Ex,Ey = (SEx,Wy) # SWx,SWy = (NWx,SEy) Sx,Sy = (Nx,SEy) SEx,SEy = ((xpos2 + $width2),(ypos2+height2)) # get position of the widget on the root screen set rx [winfo rootx $widget] set ry [winfo rooty $widget] # create variables, could these be simplified? set NWx [expr $rx + $xpos1] ; set NWy [expr $ry + $ypos1] set SEx [expr $rx + $xpos2 + $width2] ; set SEy [expr $ry + $ypos2 + $height2] #-- set Nx [expr $NWx + ($SEx / 2)] ; set Ny $NWy set NEx $SEx ; set NEy $NWy #-- set Wx $NWx ; set Wy [expr ($NWy + $SEy) / 2] set Cx $Nx ; set Cy $Wy set Ex $SEx ; set Ey $Wy #-- set SWx $NWx ; set SWy $SEy set Sx $Nx ; set Sy $SEy # decide how to handle the data switch $side { N { return "$Nx $Ny"} NE { return "$NEx $NEy"} E { return "$Ex $Ey"} SE { return "$SEx $SEy"} S { return "$Sx $Sy"} SW { return "$SWx $SWy"} W { return "$Wx $Wy"} NW { return "$NWx $NWy"} } } #--------------- # read in dump of postits #--------------- proc postit:write { {fname tmp.txt} } { set fp [open $fname w] foreach i [array names ::postit::postit] { puts $fp "\{$i\} \{$::postit::postit($i)\}" } close $fp } #--------------- # read in dump of postits #--------------- proc postit:read { {fname tmp.txt} } { set fp [open $fname r] catch {unset ::postit::postit} array set ::postit::postit [read $fp] close $fp } #--------------- # the ubiquitous demo # the load/save functions merely load/save the file demo.txt # # To add a postit, select a block of text and click 'Add' button. # The selectio will be tagged. To edit, Ctrl-B1 over the tag. An # edit window will appear, which will have input focus. When the # focus is lost, the window will be destroyed after its content # had been store in the array ::postit::postit. #--------------- proc postit:demo {} { console show wm title . "Postits V0.1a:" pack [frame .fr1] -side top -anchor nw button .fr1.but0 -text New -command {.txt delete 1.0 end ; array unset ::postit::postit} -width 10 button .fr1.but1 -text Add -command {postit:add} -width 10 button .fr1.but2 -text Dump -command {puts [postit:dump] } -width 10 button .fr1.but3 -text Load -command {load} -width 10 button .fr1.but4 -text Save -command {save} -width 10 pack .fr1.but0 .fr1.but1 .fr1.but2 .fr1.but3 .fr1.but4 -fill both -anchor nw -side left text .txt -width 50 pack .txt -fill both -expand 1 postit:init .txt } #--------------- # save text file with embedded postits #--------------- proc save {{w .txt} {fname demo.txt}} { set ttdData [ttd::get $w] set fp [open $fname w] # create file which is a list with 2 blocks # 1) text, in ttd format # 2) postits # write text block puts -nonewline $fp "\{$ttdData\}" # write postits block puts -nonewline $fp " \{" foreach i [array names ::postit::postit] { puts $fp "\{$i\} \{$::postit::postit($i)\}" } puts -nonewline $fp "\}" close $fp } #--------------- # load text file with embedded posits #--------------- proc load {{w .txt} {fname demo.txt}} { set fp [open $fname r] # read data, a list with two entries, text and postits set data [read $fp] $w delete 1.0 end ttd::insert $w [lindex $data 0] array unset ::postit::postit foreach {a b} [lindex $data 1] { set ::postit::postit($a) $b # add binding s postit:setBindings $w $a } close $fp } #--------------- # initialise postit package #--------------- proc postit:init {w} { # add bindings bind $w <Key> { switch %K { BackSpace - Delete { puts %K if {[info exists ::postit::postit]} { postit:check %W ::postit::postit postit_} } } } } #--------------- # set bindings for the tagged text block #--------------- proc postit:setBindings {w a} { # add binding s $w tag bind postit_$a $::postit::bindings "postit:show $w $a" $w tag bind postit_$a <Enter> " set ::postit::lastcursor [$w cget -cursor] $w config -cursor arrow after 1000 postit:rollover $w $a " $w tag bind postit_$a <Leave> { %W config -cursor $::postit::lastcursor if {[winfo exists .rollover]} {destroy .rollover} } } #---------------- # compare tags and array, delete the differences # w text widget pathname # a data array # p prefix used for tags #---------------- proc postit:check {w a p} { if {$::DEBUG(postit)} { puts $p puts $a puts Before: parray $a } # compare the two lists set i [array names $a] set j [postit:listall $w $p] set d [postit:listcomp $i $j ] # delete differences foreach i $d { $w tag delete $p$i array unset $a $i } if {$::DEBUG(postit)} { puts After: parray $a } } #---------------- # create a list of tags with specified prefix # w text widget pathname # p prefix used for tags #---------------- proc postit:listall { w p} { set taglist {} foreach {a tag c} [$w dump -tag 1.0 end] { # trim out tag prefix to obtain the prefix set tag [string trimleft $tag $p] # add index to the list if {[lsearch $taglist $tag]=="-1"} { # sort the list set taglist [lsort [lappend taglist $tag]] } } return [lsort $taglist] } #---------------- # compare list a with b, return the difference #---------------- proc postit:listcomp {a b} { set diff {} foreach i $a { if {[lsearch -exact $b $i]==-1} { lappend diff $i } } return $diff } if {$DEMO(postit)} { catch {console show} postit:demo }