Version 3 of label selection

Updated 2003-02-21 04:54:05

MGS Sometimes I want to be able to select the text in a label to paste into another application. So I wrote this little package to do just that. It wasn't as straightforward as I thought it might be, but at least I learned a few things about selection and the clipboard :-)

  # label.tcl --

  # Label selection.

  # You can double-click (Button 1) any label to set the selection to the
  # label's text. If a label can take focus (with the -takefocus option),
  # a single click (Button 1) will focus the label and then you can Cut,
  # Copy and Paste with <Control-x>, <Control-c> and <Control-v>.

  # Version   : 0.0.1
  # Author    : Mark G. Saye
  # Email     : [email protected]
  # Copyright : Copyright (C) 2003
  # Date      : February 20, 2003

  # ======================================================================

    namespace eval label {}

    package require Tk
    package provide label 0.0.1

  # ======================================================================

  proc label::button1 {W} {

    if { [string equal [$W cget -state] disabled] } { return }

    if { [$W cget -takefocus] } { focus $W }


  }

  # ======================================================================

  proc label::copy {W} {

    clipboard clear
    clipboard append [$W cget -text]

  }

  # ======================================================================

  proc label::cut {W} {

    copy $W

    if { ![string equal [$W cget -state] disabled] } {
      $W configure -text ""
    }

  }

  # ======================================================================

  proc label::paste {W} {

    if { [catch {clipboard get -displayof $W} clip] } {
      puts stderr "\[$::errorCode\] $clip"
      return
    }

    if { ![string equal [$W cget -state] disabled] } {
      $W configure -text $clip
    }

  }

  # ======================================================================

  proc label::focus:in {W} {

  }

  # ======================================================================

  proc label::focus:out {W} {

  }

  # ======================================================================

  proc label:handle {W offset maxChars} {

    return \
      [string range [$W cget -text] $offset [expr {$offset+$maxChars-1}]]

  }

  # ======================================================================

  proc label::normal {W} {

    # label has lost the selection - reset state to normal
    if { [string equal [$W cget -state] active] } {
      $W configure -state normal
    }

  }

  # ======================================================================

  proc label::select {W} {

    if { [string equal [$W cget -state] disabled] } { return }

    $W configure -state active

    selection own -command [list label::normal $W] $W

    selection handle -type UTF8_STRING $W [list label:handle $W]
    selection handle                   $W [list label:handle $W]

  }

  # ======================================================================

    bind Label <FocusIn>   [list label::focus:in  %W]
    bind Label <FocusOut>  [list label::focus:out %W]

    bind Label <<Copy>>    [list label::copy  %W]
    bind Label <<Cut>>     [list label::cut   %W]
    bind Label <<Paste>>   [list label::paste %W]

    bind Label <Double-1>  [list label::select  %W]
    bind Label <Button-1>  [list label::button1 %W]

    bind Label <Control-x> [list event generate %W <<Cut>>]
    bind Label <Control-c> [list event generate %W <<Copy>>]
    bind Label <Control-v> [list event generate %W <<Paste>>]

  # ======================================================================

  # demo code

  proc main {{argc 0} {argv {}}} {

    entry  .e
    .e insert end "Entry - Hello World"

    label .l1 -text "Label Without Focus"

    label .l2 -text "Label With Focus" \
      -takefocus 1 \
      -highlightthickness 1 \
      -relief sunken -bd 1

    global state
    set state [.l2 cget -state]

    foreach _state {normal active disabled} {
      set r .$_state
      radiobutton $r \
        -text $_state \
        -variable state \
        -value $_state \
        -command [list .l2 configure -state $_state]
    }

    button .b -text Close -default active -command exit

    pack .b        -side bottom -anchor se
    pack .e        -side top    -expand 0 -fill x -padx 20 -pady 20
    pack .l1       -side top    -expand 0 -fill x -padx 20 -pady 20
    pack .l2       -side top    -expand 0 -fill x -padx 20 -pady 20
    pack .normal   -side top    -expand 0 -fill x
    pack .active   -side top    -expand 0 -fill x
    pack .disabled -side top    -expand 0 -fill x

  }

  # ======================================================================

    if { [info exists argv0] && [string equal [info script] $argv0] } {
      main $argc $argv
    }


  # ======================================================================

KBK A very pretty discussion of bindings and clipboard management. Nevertheless, most of us prefer an entry or text with -state disabled for implementing a UI component that supports selection but not modification.

MGS Agreed. But this way, you can load this on top of any existing code, without modification, to get the desired functionality. And you can use features of a label that an entry doesn't have (e.g. compound images). And labels do use (slightly) less memory then entry widgets. I also prefer labels if you want text wrapping. Try this for auto-wrapping:

  label .l -text "A label with a long text string to test auto-wrapping"
  pack  .l -expand 1
  bind  .l <Configure> [list %W configure -wraplength %w]

and then resize the toplevel window. You have to make sure that the label is packed/gridded to fill available space.


Mark G. Saye