Chapter 46. A User Interface to Bindings

This chapter presents a user interface to view and edit bindings.

A good way to learn about how a widget works is to examine the bindings that are defined for it. This chapter presents a user interface that lets you browse and change bindings for a widget or a class of widgets.

The interface uses a pair of listboxes to display the events and their associated commands. An entry widget is used to enter the name of a widget or a class. There are a few command buttons that let the user add a new binding, edit an existing binding, save the bindings to a file, and dismiss the dialog. Here is what the display looks like:

A User Interface to Bindings

Example 46-1. A user interface to widget bindings

proc Bind_Interface { w } {
   # Our state
   global bind
   set bind(class) $w

   # Set a class used for resource specifications
   set frame [toplevel .bindui -class Bindui]
   # Default relief
   option add *Bindui*Entry.relief sunken startup
   option add *Bindui*Listbox.relief raised startup
   # Default Listbox sizes
   option add *Bindui*key.width 18 startup
   option add *Bindui*cmd.width 25 startup
   option add *Bindui*Listbox.height 5 startup

   # A labeled entry at the top to hold the current
   # widget name or class.
   set t [frame $frame.top -bd 2]
   label $t.l -text "Bindings for" -width 11
   entry $t.e -textvariable bind(class)
   pack $t.l -side left
   pack $t.e -side left -fill x -expand true
   pack $t -side top -fill x
   bind $t.e <Return> [list Bind_Display $frame]

   # Command buttons
   button $t.quit -text Dismiss 
      -command [list destroy $frame]
   button $t.save -text Save 
      -command [list Bind_Save $frame]
   button $t.edit -text Edit 
      -command [list Bind_Edit $frame]
   button $t.new -text New 
      -command [list Bind_New $frame]
   pack $t.quit $t.save $t.edit $t.new -side right

   # A pair of listboxes and a scrollbar
   scrollbar $frame.s -orient vertical 
      -command [list BindYview 
         [list $frame.key $frame.cmd]]
   listbox $frame.key 
      -yscrollcommand [list $frame.s set] 
      -exportselection false
   listbox $frame.cmd 
      -yscrollcommand [list $frame.s set]
   pack $frame.s -side left -fill y
   pack $frame.key $frame.cmd -side left 
      -fill both -expand true

   foreach l [list $frame.key $frame.cmd] {
      bind $l <B2-Motion>
         [list BindDragto %x %y $frame.key $frame.cmd]
      bind $l <Button-2> 
         [list BindMark %x %y $frame.key $frame.cmd]
      bind $l <Button-1> 
         [list BindSelect %y $frame.key $frame.cmd]
      bind $l <B1-Motion> 
         [list BindSelect %y $frame.key $frame.cmd]
      bind $l <Shift-B1-Motion> {}
      bind $l <Shift-Button-1> {}
   }
   # Initialize the display
   Bind_Display $frame
}

The Bind_Interface command takes a widget name or class as a parameter. It creates a toplevel and gives it the Bindui class so that resources can be set to control widget attributes. The option add command is used to set up the default listbox sizes. The lowest priority, startup, is given to these resources so that clients of the package can override the size with their own resource specifications.

At the top of the interface is a labeled entry widget. The entry holds the name of the class or widget for which the bindings are displayed. The textvariable option of the entry widget is used so that the entry's contents are available in a variable, bind(class). Pressing <Return> in the entry invokes Bind_Display that fills in the display.

Example 46-2. Bind_Display presents the bindings for a widget or class

proc Bind_Display { frame } {
   global bind
   $frame.key delete 0 end
   $frame.cmd delete 0 end
   foreach seq [bind $bind(class)] {
      $frame.key insert end $seq
      $frame.cmd insert end [bind $bind(class) $seq]
   }
}

The Bind_Display procedure fills in the display with the binding information. The bind command returns the events that have bindings, and what the command associated with each event is. Bind_Display loops through this information and fills in the listboxes.

A Pair of Listboxes Working Together

The two listboxes in the interface, $frame.key and $frame.cmd, are set up to work as a unit. A selection in one causes a parallel selection in the other. Only one listbox exports its selection as the PRIMARY selection. Otherwise, the last listbox to assert the selection steals the selection rights from the other widget. The following example shows the bind commands from Bind_Interface and the BindSelect routine that selects an item in both listboxes:

Example 46-3. Related listboxes are configured to select items together

foreach l [list $frame.key $frame.cmd] {
   bind $l <Button-1> 
      [list BindSelect %y $frame.key $frame.cmd]
   bind $l <B1-Motion> 
      [list BindSelect %y $frame.key $frame.cmd]
}
proc BindSelect { y args } {
   foreach w $args {
      $w select clear 0 end
      $w select anchor [$w nearest $y]
      $w select set anchor [$w nearest $y]
   }
}

Note

Related listboxes are configured to select items together

A scrollbar for two listboxes.

A single scrollbar scrolls both listboxes. The next example shows the scrollbar command from Bind_Interface and the BindYview procedure that scrolls the listboxes:

Example 46-4. Controlling a pair of listboxes with one scrollbar

scrollbar $frame.s -orient vertical 
   -command [list BindYview [list $frame.key $frame.cmd]]

proc BindYview { lists args } {
   foreach l $lists {
      eval {$l yview} $args
   }
}

The BindYview command is used to change the display of the listboxes associated with the scrollbar. The first argument to BindYview is a list of widgets to scroll, and the remaining arguments are added by the scrollbar to specify how to position the display. The details are essentially private between the scrollbar and the listbox. See page 501 for the details. The args keyword is used to represent these extra arguments, and eval is used to pass them through BindYview. The reasoning for using eval like this is explained in Chapter 10 on page 136.

The Listbox class bindings for <Button-2> and <B2-Motion> cause the listbox to scroll as the user drags the widget with the middle mouse button. These bindings are adjusted so that both listboxes move together. The following example shows the bind commands from the Bind_Interface procedure and the BindMark and BindDrag procedures that scroll the listboxes:

Example 46-5. Drag-scrolling a pair of listboxes together

bind $l <B2-Motion>
   [list BindDragto %x %y $frame.key $frame.cmd]
bind $l <Button-2> 
   [list BindMark %x %y $frame.key $frame.cmd]

proc BindDragto { x y args } {
   foreach w $args {
      $w scan dragto $x $y
   }
}
proc BindMark { x y args } {
   foreach w $args {
      $w scan mark $x $y
   }
}

The BindMark procedure does a scan mark that defines an origin, and BindDragto does a scan dragto that scrolls the widget based on the distance from that origin. All Tk widgets that scroll support yview, scan mark, and scan dragto. Thus the BindYview, BindMark, and BindDragto procedures are general enough to be used with any set of widgets that scroll together.

The Editing Interface

Editing and defining a new binding are done in a pair of entry widgets. These widgets are created and packed into the display dynamically when the user presses the New or Edit button:

Example 46-6. An interface to define bindings

An interface to define bindings
proc Bind_New { frame } {
   if [catch {frame $frame.edit} f] {
      # Frame already created
      set f $frame.edit
   } else {
      foreach x {key cmd} {
         set f2 [frame $f.$x]
         pack $f2 -fill x -padx 2
         label $f2.l -width 11 -anchor e
         pack $f2.l -side left
         entry $f2.e
         pack $f2.e -side left -fill x -expand true
         bind $f2.e <Return> [list BindDefine $f]
      }
      $f.key.l config -text Event:
      $f.cmd.l config -text Command:
   }
   pack $frame.edit -after $frame.top -fill x
}
proc Bind_Edit { frame } {
   Bind_New $frame
   set line [$frame.key curselection]
   if {$line == {}} {
      return
   }
   $frame.edit.key.e delete 0 end
   $frame.edit.key.e insert 0 [$frame.key get $line]
   $frame.edit.cmd.e delete 0 end
   $frame.edit.cmd.e insert 0 [$frame.cmd get $line]
}

The -width 11 and -anchor e attributes for the label widgets are specified so that the Event: and Command: labels will line up with the Bindings for label at the top.

Saving and Loading Bindings

All that remains is the actual change or definition of a binding and some way to remember the bindings the next time the application is run. The BindDefine procedure attempts a bind command that uses the contents of the entries. If it succeeds, then the edit window is removed by unpacking it.

The bindings are saved by Bind_Save as a series of Tcl commands that define the bindings. It is crucial that the list command be used to construct the commands properly.

Bind_Read uses the source command to read the saved commands. The application must call Bind_Read as part of its initialization to get the customized bindings for the widget or class. It also must provide a way to invoke Bind_Interface, such as a button, menu entry, or key binding.

Example 46-7. Defining and saving bindings

proc BindDefine { f } {
   if [catch {
      bind [$f.top.e get] [$f.edit.key.e get] 
         [$f.edit.cmd.e get]
   } err] {
      Status $err
   } else {
      # Remove the edit window
      pack forget $f.edit
   }
}
proc Bind_Save { dotfile args } {
   set out [open $dotfile.new w]
   foreach w $args {
      foreach seq [bind $w] {
         # Output a Tcl command
         puts $out [list bind $w $seq [bind $w $seq]]
      }
   }
   close $out
   file rename -force $dotfile.new $dotfile
}
proc Bind_Read { dotfile } {
   if [catch {
      if [file exists $dotfile] {
         # Read the saved Tcl commands
         source $dotfile
      }
   } err] {
      Status "Bind_Read $dotfile failed: $err"
   }
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.35.81