Adding Complex Input Bindings to Gnocl Text Widgets

WJG (25/11/08) Tk has a single command, bind, which allows the scripter to assign events and event handlers to proceedures. This is a really useful command, Gtk has no direct equivalent and events and signal need to be handled individually. But, there are exceptions. The following script creates a bind-like procedure for gnocl text widgets, to allow operations such as 'Ctrl-Key-a' to be implemented easily.


 #
 # gnocl::bind.tcl
 #
 # This file adds keysequence bindings to a gnocl widget
 #
 # Author: William J Giddings, 13-Sept-2007

 # basic Tcl/Gnocl Script
 #!/bin/sh/
 #\
 exec tclsh "$0" "$@"

 # Modifier Bitmask Values
 # 0   | no modifiers
 # 1   | Shift
 # 2   | Caps_Lock on
 # 4   | Control_L/R
 # 8   | Alt_L/R
 # 16  | Num_Lock (on)
 # 32  | ?
 # 64  | Super_L/R
 # 128 | alt-gr
 # 256 | Button-1
 # 512 | Button-2

 # Modifications to the the gnocl text.c code
 # If there version of Gnocl used is pre 0.9.92, then insert the folling options
 # into the static GnoclOption textOptions[] array, recompile and re-install.
 #
 #   { "-onKeyPress", GNOCL_OBJ, "P", gnoclOptOnKeyPress },
 #   { "-onKeyRelease", GNOCL_OBJ, "R", gnoclOptOnKeyRelease },
 #   { "-onButtonPress", GNOCL_OBJ, "P", gnoclOptOnButton },
 #   { "-onButtonRelease", GNOCL_OBJ, "R", gnoclOptOnButton },

 # remove comments to execute the demo
 set DEMO 1
 if {$DEMO} { package require Gnocl }

 #---------------
 # As I like real-words rather than deniary
 #---------------
 proc kb_modifiers {v} {

   set state 0
   set flags {
      Shift       1
      Caps_Lock   2
      Ctrl        4
      Alt         8
      Num_Lock    16
      Super       64
      Alt-Gr      128
      Button_1    256
      Button_2    512
      }
   foreach {a b} $flags {
      if {$v & $b } {lappend state $a}
   }
   return $state
 }

 #---------------
 # create binding handler
 #---------------
 proc gnocl::keyBindingHandler {w s K} {

   # remove Num_Lock On event bitmask
   set event [lindex $s 0]
   if {16 & $event} { set event [expr 16 ^ $event ] }
   set s [lreplace $s 0 0 $event]

   # check for Shift, if a single letter, restore to lowercase
   if {1 & $event && [string length $K] == "1"} { set K [string tolower $K] }
   set events [array names ::keyBindings]

   # sorry, not the best practice to error trap with catch, but its the easiest!
   catch { eval $::keyBindings($s,$K) }

 }

 #---------------
 # create binding handler
 #---------------
 proc gnocl::buttonBindingHandler { w s b x y} {

   # remove Num_Lock On event bitmask
   set event [lindex $s 0]
   if {16 & $event} { set event [expr 16 ^ $event ] }
   set s [lreplace $s 0 0 $event]

   # execute binding
   catch {
      # save current pointer coordinate of last click
      set ::gnocl::x $x
      set ::gnocl::y $y
      eval [ set ${w}.buttonBindings($s,Button$b) ]
      }

 }

 #---------------
 # assign bindings to (text) widget
 # concatenate these bindings with others which may have been assigned to events
 #---------------
 proc gnocl::bind {widget event script} {

        # what are the existing bindings?

        # cget not yet implemented
        # puts "keyPress [$widget cget -onKeyPress]"
        # puts "buttonPress [$widget cget -onButtonPress]"

        set event [string trimleft $event "<"]
        set event [string trimright $event ">" ]
        set tmp "-"
        regsub -all -- - $event " " event

        # parse event and create BITMASK
        set bitMask 0
        foreach {eventType bitVal} {
                Shift   1
                Ctrl        4
                Alt        8
                } {
                        if { [string first $eventType $event] != -1 } {
                                set bitMask [expr $bitMask + $bitVal]
                                }
                        }

   if { [string first Key $event] != -1 } {

      # add to the list of Key events
      set ::keyBindings($bitMask,[lindex $event end]) $script

   } elseif { [string first Button $event] !=-1 } {

      # add to the list of Button Events
      set ${widget}.buttonBindings($bitMask,[lindex $event end]) $script

   }

   # attach bindings
   $widget configure -onKeyPress { gnocl::keyBindingHandler %w %s %K }
   $widget configure -onButtonPress { gnocl::buttonBindingHandler %w %s %b %x %y }

 }

 #----- DEMO CODE -----
 if {$DEMO} {

 proc bind:demo {} {

   set txt [gnocl::text]

   gnocl::window \
          -child $txt \
          -title "GNOCL Text Bindings" \
          -visible 1 \
          -width 250 \
          -height 120 \
          -onDestroy {exit}

   $txt insert end TEST

   # Add some bindings, some of these will conflict with GTK defaults
   # These bindings do not replace the defaults as in TK

   gnocl::bind $txt <Shift-Key-a> {puts "Say 'Shift-a'"}
   gnocl::bind $txt <Alt-Key-A> {puts "Say 'Alt-a'"}
   gnocl::bind $txt <Alt-Key-a> {puts "Say 'Alt-a'"}
   gnocl::bind $txt <Ctrl-Key-a> {puts "Say 'Ctrl-a'"}
   gnocl::bind $txt <Shift-Alt-Key-a> {puts "Say 'Shift-Alt-a'"}
   gnocl::bind $txt <Shift-Ctrl-Key-a> {puts "Say 'Shift-Ctrl-a'"}
   gnocl::bind $txt <Shift-Alt-Ctrl-Key-a> {puts "Say 'Shift-Alt-Ctrl-a'"}

   gnocl::bind $txt <Ctrl-Key-F1> {puts "Ctrl F1"}
   gnocl::bind $txt <Shift-Key-F1> {puts "Shift F1"}
   gnocl::bind $txt <Key-F2> {puts "F2"}

   gnocl::bind $txt <Alt-Button1> {puts "Alt Button1!"}
   gnocl::bind $txt <Ctrl-Button1> {puts "Ctrl Button1!"}
   gnocl::bind $txt <Shift-Button1> {puts "Shift Button1! $::gnocl::x $::gnocl::y"}

   gnocl::bind $txt <Alt-Button2> {puts "Alt Button2!"}
   gnocl::bind $txt <Ctrl-Button2> {puts "Ctrl Button2!"}
   gnocl::bind $txt <Shift-Button2> {puts "Shift Button2!"}

   gnocl::bind $txt <Ctrl-Key-z> {puts "UNDO!"}
   gnocl::bind $txt <Shift-Ctrl-Key-z> {puts "REDO!"}

 }

 # the ubiquitous demo
 bind:demo
 }