Fanning Software Consulting

Processing Keyboard Events in Widget Programs

QUESTION: I would like to be able to process keyboard interrupts from within my widget programs, but I can find no way to do this in IDL. Does anyone have a suggestion?

EDITOR'S NOTE: With IDL 6.0, IDL finally has a fully capable, platform independent, keyboard event mechanism available through the KEYBOARD_EVENTS keyword to WIDGET_DRAW. This was technically available starting with IDL 5.6, but suffered damaging bugs in all Unix versions. Now you can even trap arrow and control keys, and get reliable high repeat rates (e.g., sitting on an arrow key). And if you need key events without a draw widget, you can use precisely the same trick described below, just hiding a small draw widget instead of a text widget, and giving it input focus whenever necessary.

ANSWER: This often asked for capability does not exist in IDL. But J.D. Smith of Cornell University has come up with a wonderfully clever hack that works surprisingly well. J.D. recently published this code on the IDL newsgroup. Here is his answer and his example code.

"As David points out, this functionality is not built in. But if you're willing to settle for a hack, I have come up with one. The mouse focus and keyboard focus are independent, which you can use to your advantage. It's very simple, really. All you need to do is hide a widget_text widget with all_events set underneath your draw widget, and ensure its input focus is set when appropriate in the event handler. I have simply set focus every time through, but a better technique would do so only when entering the window, or when a button on the draw window is clicked (motion events do not remove the input focus), etc. I've tested this on Linux IDL v5.0, but no guarantees are made for other platforms (it should work on any of them though).

"A few suggestions:

"If you want the hotkeys active only while the cursor is inside the draw widget, you must enable widget tracking for the draw widget. On entering the window, set the input focus to the hidden text widget. On leaving the window, set it to something else (maybe just a "dummy" text widget which also lives under the draw widget but has only EDITABLE set). Conversely, you could just turn off the text events for the hidden widget with widget_control, hidden, ALL_TEXT_EVENTS=0, turning events back on for reentry. This method will cause a beep if the user presses a key while outside the draw widget (maybe you'd want that). The method of setting focus to the "dummy" widget (with EDITABLE enabled) avoids this beep. The ALL_TEXT_EVENTS method might look like: widget_control, hidden, ALL_TEXT_EVENTS=ev.enter.

"This technique is in no way tied to a draw widget and can be used in any widget application (as long as you can hide the text widget somewhere), but it will be more difficult when there are other text entry widgets... you must ensure your input focus returns to the hidden text widget when appropriate, which may become annoying for the user -- he might have to click to enter text each time. Encapsulating the functionality (by turning on and off as suggested for draw widgets) to those parts of the application for which it is useful might help.

"The ability to turn the hotkeys on and off simply by changing the input focus can be put to good use... for instance, you could disable hotkeys when a click-and-drag operation is being done (that's actually done for you, since button presses change the input focus). But keep in mind that on most systems, the user can change the input_focus with the TAB key, so be careful, and use ALL_TEXT_EVENTS=0 when a danger exists. You could even employ KBRD_FOCUS events for this purpose.

"Hope this proves useful."

JD

Here's the code. Run the program like this.

   IDL> TestHotKey

TestHotKey Program

   PRO TestHotKey_Event, event

   ; The event handler for the program.

   Widget_Control, event.top, Get_UValue=info
   thisEvent = Tag_Names(event, /Structure_Name)

      ; Handle character input events. Al other events pass through.
      
   CASE thisEvent OF

      'WIDGET_TEXT_CH': BEGIN

         TVLCT, [0,255], [0,255], [0,0], 1
         Erase, Color=1
         
            ; Print the character in the window.
            
         text = 'Character: ('+ StrTrim(event.ch,2) + ')'
         XYOUTS, 0.5, 0.5, /Normal, Alignment=0.5, $
            Color=2, text, Charsize=2.0
            
            ; Is this a quit character? If so, destroy the widget.
            
         quitString = 'qQxX'
         index = Where(Byte(quitString) EQ event.ch, count)
         IF count NE 0 THEN Widget_Control, event.top, /Destroy

         ENDCASE

      ELSE: BEGIN

            ; Set the input focus to the text widget. (Mostly
            ; draw widget motion events handled here.)
            
         Widget_Control, info.hiddenTextID, /INPUT_FOCUS

         ENDCASE

   ENDCASE

   END ;---------------------------------------------------------------------------



   PRO TestHotKey

      ; Color decomposition off.
      
   Device, Decomposed=0

      ; Create the program's widgets. The text widget is hidden behind
      ; the draw widget in the bullitin board top-level base. Motion 
      ; events turned on for the draw widget to get into the event handler
      ; and put input focus on the text widget.
      
   tlb = Widget_Base(Column=1, Title='Select Key from Keyboard. "Q" or "X" to Quit...', $
      XOffset=100, YOffset=200)
   label = Widget_Label(tlb, Value='Hot Key Test')
   base = Widget_Base(tlb) ; Bullitin Board Base.
   drawID = Widget_Draw(base, XSize=500, YSize=200, /Motion_Events)

      ; Text widgets returns all events, but is NOT editable by the user.
      
   hiddenTextID = Widget_Text(base, Scr_XSize=1, Scr_YSize=1, /All_Events)

      ; Realise the hierarchy. Get the window ID of the draw widget.
      
   Widget_Control, tlb, /Realize
   Widget_Control, drawID, Get_Value=wid
   WSet, wid

      ; Display graphics.
      
   TVLCT, [0,255], [0,255], [0,0], 1
   XYOUTS, 0.5, 0.5, /Normal, Alignment=0.5, Charsize=2.0, $
      Color=2, 'Place cursor in window. Select a key.'

      ; Store program information in UValue of TLB.
      
   info = {hiddenTextID:hiddenTextID, wid:wid}
   Widget_Control, tlb, Set_UValue=info

   XManager, 'testhotkey', tlb, /No_Block
   END

Google
 
Web Coyote's Guide to IDL Programming