Version 1 of TkClipper

Updated 2009-11-06 12:11:43 by LVwikignome

Googie - 5 Nov 2009 - Eclipse (an IDE) has problems with copying contents to clipboard, especially under Linux with KDE and Klipper application running.

The Klipper is clipboard helper application which enables our clipboard to remember more than 1 copied value and lets us to choose which value we want to paste for specific moment. It persists in system tray and shows a menu when clicked - the menu contains copied positions so we can choose which one we're interested in right now.

Problem is that Eclipse doesn't like Klipper, or vice versa, I don't know. What I know is that quitting Klipper gets Eclipse copy&paste to work again. The price is lost multi-value clipboard.

I was getting problem with copy&paste for almost 100% cases, which motivated me to write a little workaround. I just wrote my own Klipper substitute, which I call TkClipper. It's written in "just get it done" way, so you have to pick your own icon for systray (I used tkclipper.png and its name is hardcoded in code below). Also libtray extension is the one you have to get by yourself - for now. I got it from aMSN. Source code quality is bad, but I don't care. Not for this small utility.

If I have some more free time and willingness, I'll provide some starkits.

So here's the source code:

 #!/usr/bin/env tclsh

 package require Tk
 wm withdraw .
 package require img::png
 package require libtray

 set img [image create photo -file [file dirname [info script]]/tkclipper.png]
 proc dummyProc {args} return

 newti .t -pixmap $img -command "dummyProc"

 bind .t <Button-1> [list buttonClicked left %X %Y]
 bind .t <Button-3> [list buttonClicked right %X %Y]

 menu .menu -borderwidth 1 -activeborderwidth 1

 # Adding to GUI "hints"
 set ::HINT_FONT TkDefaultFont
 set ::HINT_BG #FFFFDD
 set ::HINT_FG #000000
 proc helpHint {w} {
         bind $w <Enter> "+after 100 \"hint_aux %W\""
         bind $w <Leave> "+after cancel \"hint_aux %W\"
                                          catch {destroy .hint_help}"
         bind $w <Destroy> "+after cancel \"hint_aux %W\"
                                          catch {destroy .hint_help}"
 }

 proc hint_aux {w} {
         set t .hint_help
         catch {destroy $t}
         toplevel $t -background black
         wm overrideredirect $t 1
         set labelWidget [ttk::label $t.l -font $::HINT_FONT -borderwidth 0 -background $::HINT_BG -foreground $::HINT_FG -justify left]
         set font [$t.l cget -font]

         set msg [getShortText]
         if {$msg != ""} {
                 set label " Current TkClipper contents: \n $msg "
                 set w1 [font measure $font " Current TkClipper contents: "]
                 set w2 [font measure $font " $msg "]
                 set x1 [expr {[winfo pointerx $w]-${w1}-5}]
                 set x2 [expr {[winfo pointerx $w]-${w2}-5}]
                 set x [expr {min($x1, $x2)}]
         } else {
                 set label " No TkClipper contents "
                 set width [font measure $font $label]
                 set x [expr {[winfo pointerx $w]-${width}-5}]
         }
         $labelWidget configure -text $label
         pack $labelWidget -fill both -padx 1 -pady 1 -ipadx 0 -ipady 1

         set height [font metrics $font -ascent]

         if {$msg != ""} {
                 set heightRatio 2
         } else {
                 set heightRatio 1
         }

         set y [expr {[winfo pointery $w]-${height}*${heightRatio}-10}]
         wm geometry $t +$x\+$y
         bind $t <Enter> {after cancel {catch {destroy .hint_help}}}
         bind $t <Leave> "catch {destroy .hint_help}"
         bind $t <Destroy> "catch {destroy .hint_help}"
 }
 #------------------------------------------------

 set ::delay 100
 set ::buffer [list]
 set ::ptr -1
 set ::bufferLimit 20

 proc getShortText {{idx ""}} {
         if {$idx != ""} {
                 set val [lindex $::buffer $idx]
         } else {
                 set val [lindex $::buffer $::ptr]
         }
         if {[string length $val] > 100} {
                 set val "[string range $val 0 96]..."
         }
         set val [string trim [string map [list \n " "] $val]]
         return $val
 }

 proc getFullText {{idx ""}} {
         if {$idx != ""} {
                 return [lindex $::buffer $idx]
         } else {
                 return [lindex $::buffer $::ptr]
         }
 }

 proc buttonClicked {button x y} {
         if {$button == "right"} {
                 .menu delete 0 end
                 .menu add command -label "Exit" -command exit

         } else {
                 if {$::ptr < 0} return
                 .menu delete 0 end
                 for {set i 0} {$i < [llength $::buffer]} {incr i} {
                         .menu add radiobutton -label [getShortText $i] -value $i -variable ::ptr -command updateClipboard
                 }
                 .menu add separator
                 .menu add command -label "Erase clipboard" -command eraseClipboard
         }
         update
         set width [winfo reqwidth .menu]
         set height [winfo reqheight .menu]
         .menu post [expr {$x - $width}] [expr {$y - $height}]
 }

 proc eraseClipboard {} {
         set ::ptr -1
         set ::buffer [list]
 }

 proc updateClipboard {} {
         if {$::ptr < 0} return
         clipboard clear
         clipboard append [getFullText]
 }

 proc checkClipboard {} {
         if {![catch {clipboard get -type STRING} val]} {
                 if {$val ni $::buffer} {
                         set ::buffer [linsert $::buffer 0 $val]
                         set ::ptr 0

                         if {[llength $::buffer] > $::bufferLimit} {
                                 set ::buffer [lrange $::buffer 0 [expr {$::bufferLimit - 1}]]
                         }
                 } else {
                         set ::ptr [lsearch $::buffer $val]
                 }
         }
         after $::delay "checkClipboard"
 }
 helpHint .t
 checkClipboard