Viewing contents of file '../idllib/contrib/mallozzi/track_mouse.pro'
;+
; NAME:
;      TRACK_MOUSE
;
; VERSION:
;      0.1
;
; PURPOSE:
;      Tracks the mouse cursor in a DRAW_WIDGET, and returns coordinates
;      of a selected region
;
; TYPE:
;      Function
;
; CATEGORY:
;      Widgets
;
; CALLING SEQUENCE:
;      HAVE_SELECTION = TRACK_MOUSE(drawWidgetID, REGION, COORDINATES)
;
; REQUIRED INPUTS:
;      drawWidgetID: Widget ID of a valid WIDGET_DRAW()
;
; OPTIONAL INPUTS:
;      NONE
;
; OUTPUTS:
;      HAVE_SELECTION: INTEGER specifying if selection was completed: 0 if
;          selection was aborted, 1 if it was completed
;
;      REGION: INTEGER specifying the plot region in which the mouse was 
;          clicked to exit TRACK_MOUSE()
;
;                      4               
;               ----------------
;               |              |
;               |              | 
;             1 |      0       | 2
;               |              |
;               ----------------
;                      3
;
;      COORDINATES: FLTARR(2, 2) = coordinates of the selection 
;          COORDINATES[0, *] = [X0, X1]
;          COORDINATES[1, *] = [Y0, Y1]
;          
;          If a selection is aborted midway through (i.e., only one mouse 
;          click), COORDINATES is returned as scalar zero.
;
; KEYWORD PARAMETERS:
;
;      GET_BOX:     Select a box (rubberbanding).  This is the default.
;      GET_XRANGE:  Select an interval from the x-axis
;      GET_YRANGE:  Select an interval from the y-axis
;
;                   Only one GET keyword can be specified, else an error 
;                   is generated. 
;
;      LEFT_TEXT:   Text displayed when mouse enters left margin (D = 'EXIT')
;      RIGHT_TEXT:  Right margin text (D = 'RESET')
;      TOP_TEXT:    Top margin text (D = '')
;      BOTTOM_TEXT: Bottom margin text (D = 'Numerical Entry')
;
;                   NOTE: The default text values expect you to perform
;                   certain actions when TRACK_MOUSE() exits in order to 
;                   make sense.  For example, when clicking in the right margin
;                   (RESET), you might reset the plot axes, and replot.  For
;                   the left margin (EXIT), you might replot the zoomed region.
;
;      NOWAIT:      By default, TRACK_MOUSE() waits for a margin click before
;                   exiting.  Set NOWAIT to return immediately after a selection
;                   is completed (i.e., after the second mouse click).  REGION
;                   is returned as 0 in this case.  Use this to make multiple
;                   selections by calling TRACK_MOUSE() again.
;
;      XTOLERANCE:  Allow selections slightly outside the left and right 
;                   axes, in normalized units (D = 0.0)   
;      YTOLERANCE:  Allow selections slightly outside the top and bottom 
;                   axes, in normalized units (D = 0.0)
;
;                   WARNING: Large tolerance values (i.e., more than about
;                   0.05) may prevent you from accessing the plot margins.     
;
;      COLOR:       Color index of the plotted selections.
;
;      _EXTRA:      Any valid OPLOT keywords.
;
;
; COMMON BLOCKS:
;      NONE
;
; SIDE EFFECTS:
;      NONE
;
; RESTRICTIONS:
;      IDL v5.0 or later
;      You must trap WIDGET_DRAW events in your event handler, since
;          TRACK_MOUSE() enables BUTTON and MOTION events in the DRAW widget.
;
; EXAMPLE:
;      See TRACK_MOUSE_EXAMPLE.PRO
;
; MODIFICATION HISTORY:
;       Written, Robert.Mallozzi@msfc.nasa.gov, March, 1998.  
;
;                Idea based on TRACK() for direct graphics windows 
;                written by Rob.Preece@msfc.nasa.gov 
;
;-
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

FUNCTION TRACK_MOUSE, drawWidgetID, $                                     ; in
                      REGION, $                                           ; out 
                      COORDINATES, $                                      ; out
                      
                      GET_BOX    = GET_BOX, $
                      GET_XRANGE = GET_XRANGE, $
                      GET_YRANGE = GET_YRANGE, $
                 
                      LEFT_TEXT   = LEFT_TEXT, $
                      RIGHT_TEXT  = RIGHT_TEXT, $
                      TOP_TEXT    = TOP_TEXT, $
                      BOTTOM_TEXT = BOTTOM_TEXT, $
                      
                      NOWAIT = NOWAIT, $
                      
                      XTOLERANCE = XTOLERANCE, $
                      YTOLERANCE = YTOLERANCE, $
                     
                      COLOR = COLOR, $
                      		      
		      _EXTRA = EXTRA
                 


; ----- ERROR CHECKING -----

; Is drawWidgetID a current, valid DRAW_WIDGET?
;
theWidgetValid = WIDGET_INFO(drawWidgetID, /VALID_ID)
IF (NOT theWidgetValid) THEN BEGIN
   MESSAGE, /INFO, 'Input widget ID is not valid.'
   RETURN, 0
ENDIF

theWidgetName = WIDGET_INFO(drawWidgetID, /NAME)
IF (theWidgetName NE 'DRAW') THEN BEGIN
   MESSAGE, /INFO, 'Input widget ID is not a WIDGET_DRAW.'
   RETURN, 0
ENDIF

; ----- KEYWORDS -----

doGetBox    = KEYWORD_SET (GET_BOX)
doGetXrange = KEYWORD_SET (GET_XRANGE)
doGetYrange = KEYWORD_SET (GET_YRANGE)

CASE (TOTAL([doGetBox, doGetXrange, doGetYrange])) OF
     0: doGetBox = 1
     1:
     ELSE: BEGIN
         MESSAGE, /INFO, 'Multiple GET keywords not allowed.'
         RETURN, 0
         END
ENDCASE
   
IF (N_ELEMENTS(LEFT_TEXT) EQ 0) THEN $
   LEFT_TEXT = 'EXIT' 
   
IF (N_ELEMENTS(RIGHT_TEXT) EQ 0) THEN $
   RIGHT_TEXT = 'RESET' 

IF (N_ELEMENTS(TOP_TEXT) EQ 0) THEN $
   TOP_TEXT = '' 

IF (N_ELEMENTS(BOTTOM_TEXT) EQ 0) THEN $
   BOTTOM_TEXT = 'Numerical Entry' 

doWait = 1
IF (N_ELEMENTS(NOWAIT) NE 0) THEN $
   doWait = 0 

xTol = 0.0
IF (N_ELEMENTS(XTOLERANCE) NE 0) THEN $
   xTol = FLOAT(XTOLERANCE) 

yTol = 0.0
IF (N_ELEMENTS(YTOLERANCE) NE 0) THEN $
   yTol = FLOAT(YTOLERANCE) 

IF (N_ELEMENTS(color) EQ 0) THEN $
   color = !P.COLOR
   
; Ensure that the DRAW widget accepts BUTTON and MOTION events
;
haveButtonEvents = WIDGET_INFO (drawWidgetID, /DRAW_BUTTON_EVENTS)
IF (NOT haveButtonEvents) THEN $
   WIDGET_CONTROL, drawWidgetID, DRAW_BUTTON_EVENTS = 1
   
haveMotionEvents = WIDGET_INFO (drawWidgetID, /DRAW_MOTION_EVENTS)
IF (NOT haveMotionEvents) THEN $
   WIDGET_CONTROL, drawWidgetID, DRAW_MOTION_EVENTS = 1


; ----- TRACKING CODE -----

PRESS   = 0
RELEASE = 1
MOTION  = 2

; Allow selections slightly outside the plot box
;
TOL_X = (!X.CRANGE[1] - !X.CRANGE[0]) * xTol
TOL_Y = (!Y.CRANGE[1] - !Y.CRANGE[0]) * yTol

; Handle LOG scales
;
X_LO = !X.CRANGE[0] - TOL_X
X_HI = !X.CRANGE[1] + TOL_X
IF (!X.TYPE EQ 1) THEN BEGIN
   X_LO = 10.0^(X_LO - TOL_X)
   X_HI = 10.0^(X_HI + TOL_X)
ENDIF

Y_LO = !Y.CRANGE[0] - TOL_Y
Y_HI = !Y.CRANGE[1] + TOL_Y
IF (!Y.TYPE EQ 1) THEN BEGIN
   Y_LO = 10.0^(Y_LO - TOL_Y)
   Y_HI = 10.0^(Y_HI + TOL_Y) 
ENDIF

CLIPRECT = [X_LO, Y_LO, X_HI, Y_HI]

; Initialize
;
HAVE_ONE_CLICK = 0
HAVE_TWO_CLICK = 0
DONE_SELECTING = 0
COORDINATES    = 0

PREV_STRING = '(X: ' + ', Y: ' + ')'   
XYOUTS, 10, 10, PREV_STRING, /DEVICE, COLOR = !P.COLOR


WHILE (1) DO BEGIN 

CONTINUE:

      ; Get mouse coodinates
      ;
      EVENT = WIDGET_EVENT (drawWidgetID)         

      ; No double clicks
      ;
      IF (EVENT.CLICKS EQ 2) THEN $
         GOTO, CONTINUE
      
      XY = CONVERT_COORD (EVENT.X, EVENT.Y, /DEVICE, /TO_DATA)
      XY = XY[0:1]
           
      CASE (EVENT.TYPE) OF

           MOTION: BEGIN

               CASE (1) OF
                    XY[0] LT X_LO: NEW_STRING = STRING (LEFT_TEXT)
                    XY[0] GT X_HI: NEW_STRING = STRING (RIGHT_TEXT)
                    XY[1] LT Y_LO: NEW_STRING = STRING (BOTTOM_TEXT)
                    XY[1] GT Y_HI: NEW_STRING = STRING (TOP_TEXT)
                    
                    ELSE: NEW_STRING = '(X: ' + STRTRIM(XY[0], 2) + $
                                      ', Y: ' + STRTRIM(XY[1], 2) + ')'
               ENDCASE

               XYOUTS, 10, 10, PREV_STRING, /DEVICE, COLOR = !P.BACKGROUND
               XYOUTS, 10, 10, NEW_STRING, /DEVICE, COLOR = !P.COLOR
               PREV_STRING = NEW_STRING

	       IF ((doGetBox) AND (HAVE_ONE_CLICK) AND $
                  (NOT DONE_SELECTING)) THEN BEGIN

                  ; Starting coordinates
                  ;
                  START_XY = FIRST_CLICK_COORDS       
                  
                  ; Current coordinates
                  ;
                  CURRENT_XY = XY                       
                  
                  ; Initialize previous coordinates
                  ;
                  IF (N_ELEMENTS(PREV_XY) EQ 0) THEN $
                     PREV_XY = START_XY
                                       
                  ; Erase previous box
                  ; 
                  PLOTS, [START_XY[0], START_XY[0], PREV_XY[0],  $
                          PREV_XY[0], START_XY[0]],  $                  
                         [START_XY[1], PREV_XY[1], PREV_XY[1], $
                          START_XY[1], START_XY[1]], $
                         COLOR = !P.BACKGROUND, LINESTYLE = 1, $
                         CLIP = CLIPRECT, NOCLIP = 0
                  
                  ; Draw current box
                  ; 
                  PLOTS, [START_XY[0], START_XY[0], CURRENT_XY[0],  $
                          CURRENT_XY[0], START_XY[0]],  $                  
                         [START_XY[1], CURRENT_XY[1], CURRENT_XY[1], $
                          START_XY[1], START_XY[1]], $
                         COLOR = color, LINESTYLE = 1, $
                         CLIP = CLIPRECT, NOCLIP = 0
                         
                  ; Save current coordinates
                  ;
                  PREV_XY = CURRENT_XY 
                  
               ENDIF

               END ; MOTION

           PRESS: BEGIN

               RETURN_FLAG = 0
               REGION = -1

               ; Left margin
               ; 
	       IF (XY[0] LT X_LO) THEN BEGIN
                  RETURN_FLAG = 1
                  REGION = 1
               ENDIF

               ; Right margin
               ;
	       IF (XY[0] GT X_HI) THEN BEGIN
                  RETURN_FLAG = 1
                  REGION = 2
               ENDIF

               ; Top margin
               ;
               IF (XY[1] GT Y_HI) THEN BEGIN
                  RETURN_FLAG = 1
                  REGION = 4
               ENDIF

               ; Bottom margin
               ;
               IF (XY[1] LT Y_LO) THEN BEGIN
                  RETURN_FLAG = 1
                  REGION = 3
               ENDIF

               IF (RETURN_FLAG) THEN BEGIN

                  XYOUTS, 10, 10, PREV_STRING, /DEVICE, COLOR = !P.BACKGROUND

		  IF (doGetXrange) THEN BEGIN
	             IF ((HAVE_ONE_CLICK) OR (DONE_SELECTING)) THEN $
	                OPLOT, [1, 1] * FIRST_CLICK_COORDS[0], [Y_LO, Y_HI], $
                               COLOR = !P.BACKGROUND, _EXTRA = EXTRA	  
	             IF ((HAVE_TWO_CLICK)  OR (DONE_SELECTING)) THEN $
	                OPLOT, [1, 1] * SECOND_CLICK_COORDS[0], [Y_LO, Y_HI], $
                               COLOR = !P.BACKGROUND, _EXTRA = EXTRA	  
                  ENDIF

		  IF (doGetYrange) THEN BEGIN
	             IF ((HAVE_ONE_CLICK) OR (DONE_SELECTING)) THEN $
	                OPLOT, [X_LO, X_HI], [1, 1] * FIRST_CLICK_COORDS[1], $
                               COLOR = !P.BACKGROUND, _EXTRA = EXTRA
	             IF ((HAVE_TWO_CLICK) OR (DONE_SELECTING)) THEN $
	                OPLOT, [X_LO, X_HI], [1, 1] * SECOND_CLICK_COORDS[1], $
                               COLOR = !P.BACKGROUND, _EXTRA = EXTRA
                  ENDIF

		  IF (doGetBox) THEN BEGIN
                     IF ((HAVE_ONE_CLICK) OR (DONE_SELECTING)) THEN $
                	PLOTS, [START_XY[0], START_XY[0], PREV_XY[0],  $
                        	PREV_XY[0], START_XY[0]],  $                  
                               [START_XY[1], PREV_XY[1], PREV_XY[1], $
                        	START_XY[1], START_XY[1]], $
                               COLOR = !P.BACKGROUND, LINESTYLE = 1, $
                               CLIP = CLIPRECT, NOCLIP = 0
                  ENDIF

                  ; Restore DRAW widget event handler state
                  ;
                  IF (NOT haveButtonEvents) THEN $
                     WIDGET_CONTROL, drawWidgetID, DRAW_BUTTON_EVENTS = 0
                  IF (NOT haveMotionEvents) THEN $
                     WIDGET_CONTROL, drawWidgetID, DRAW_MOTION_EVENTS = 0
                  
                  IF (DONE_SELECTING) THEN $
                     RETURN, 1
                  
                  RETURN, 0    
                  
	       ENDIF ; RETURN_FLAG
	       

               IF (DONE_SELECTING) THEN BEGIN

		  IF (doGetXrange) THEN BEGIN
		     OPLOT, [1, 1] * FIRST_CLICK_COORDS[0], [Y_LO, Y_HI], $
                            COLOR = !P.BACKGROUND, _EXTRA = EXTRA 
		     OPLOT, [1, 1] * SECOND_CLICK_COORDS[0], [Y_LO, Y_HI], $
                            COLOR = !P.BACKGROUND, _EXTRA = EXTRA 
                  ENDIF
		  IF (doGetYrange) THEN BEGIN
		     OPLOT, [X_LO, X_HI], [1, 1] * FIRST_CLICK_COORDS[1], $
                            COLOR = !P.BACKGROUND, _EXTRA = EXTRA
		     OPLOT, [X_LO, X_HI], [1, 1] * SECOND_CLICK_COORDS[1], $
                            COLOR = !P.BACKGROUND, _EXTRA = EXTRA
                  ENDIF                  
		  IF (doGetBox) THEN BEGIN
                     PLOTS, [START_XY[0], START_XY[0], PREV_XY[0],  $
                             PREV_XY[0], START_XY[0]],  $                  
                            [START_XY[1], PREV_XY[1], PREV_XY[1], $
                             START_XY[1], START_XY[1]], $
                            COLOR = !P.BACKGROUND, LINESTYLE = 1, $
                            CLIP = CLIPRECT, NOCLIP = 0
                  ENDIF                  

               ENDIF

               ; Did not return, so this must be the third click -> reset
               ;
               IF ((HAVE_ONE_CLICK) AND (HAVE_TWO_CLICK)) THEN BEGIN
                  HAVE_ONE_CLICK = 0
                  HAVE_TWO_CLICK = 0
                  DONE_SELECTING = 0
                  COORDINATES    = 0
               ENDIF   
	       	  
               IF (NOT HAVE_ONE_CLICK) THEN BEGIN
		                  
		  HAVE_ONE_CLICK = 1
                  FIRST_CLICK_COORDS = XY
                  
		  IF (doGetXrange) THEN $
		     OPLOT, [1, 1] * FIRST_CLICK_COORDS[0], [Y_LO, Y_HI], $
		            LINESTYLE = 2, COLOR = color, _EXTRA = EXTRA 

		  IF (doGetYrange) THEN $
		     OPLOT, [X_LO, X_HI], [1, 1] * FIRST_CLICK_COORDS[1], $
                            LINESTYLE = 2, COLOR = color, _EXTRA = EXTRA
               
               ENDIF ELSE $
               IF ((HAVE_ONE_CLICK) AND (NOT HAVE_TWO_CLICK)) THEN BEGIN

                  HAVE_TWO_CLICK = 1                     
                  SECOND_CLICK_COORDS = XY

		  IF (doGetXrange) THEN BEGIN
	             OPLOT, [1, 1] * FIRST_CLICK_COORDS[0], [Y_LO, Y_HI], $
		         COLOR = color, _EXTRA = EXTRA	  
	             OPLOT, [1, 1] * SECOND_CLICK_COORDS[0], [Y_LO, Y_HI], $
		         COLOR = color, _EXTRA = EXTRA	  
                  ENDIF

		  IF (doGetYrange) THEN BEGIN
	             OPLOT, [X_LO, X_HI], [1, 1] * FIRST_CLICK_COORDS[1], $
		         COLOR = color, _EXTRA = EXTRA
	             OPLOT, [X_LO, X_HI], [1, 1] * SECOND_CLICK_COORDS[1], $
		         COLOR = color, _EXTRA = EXTRA
                  ENDIF

               ENDIF
               
               IF ((HAVE_ONE_CLICK) AND (HAVE_TWO_CLICK)) THEN BEGIN
                                 
                  DONE_SELECTING = 1
                  
                  COORDINATES = FLTARR(2, 2)
                  
                  COORDINATES(0, *) = $
                     [FIRST_CLICK_COORDS[0], SECOND_CLICK_COORDS[0]]
                  COORDINATES(0, *) = COORDINATES(0, SORT(COORDINATES(0, *)))
                  
                  COORDINATES(1, *) = $
                     [FIRST_CLICK_COORDS[1], SECOND_CLICK_COORDS[1]]
                  COORDINATES(1, *) = COORDINATES(1, SORT(COORDINATES(1, *)))
                 
                  IF (NOT doWait) THEN BEGIN

                     REGION = 0
   
                     XYOUTS, 10, 10, PREV_STRING, /DEVICE, COLOR = !P.BACKGROUND

		     IF (doGetXrange) THEN BEGIN
	        	IF ((HAVE_ONE_CLICK) OR (DONE_SELECTING)) THEN $
	                   OPLOT, [1, 1] * FIRST_CLICK_COORDS[0], $ 
	                          [Y_LO, Y_HI], COLOR = !P.BACKGROUND, $
				  _EXTRA = EXTRA	  
	        	IF ((HAVE_TWO_CLICK)  OR (DONE_SELECTING)) THEN $
	                   OPLOT, [1, 1] * SECOND_CLICK_COORDS[0], $
                                  [Y_LO, Y_HI], COLOR = !P.BACKGROUND, $
				  _EXTRA = EXTRA	  
                     ENDIF

		     IF (doGetYrange) THEN BEGIN
	        	IF ((HAVE_ONE_CLICK) OR (DONE_SELECTING)) THEN $
	                   OPLOT, [X_LO, X_HI], $
                                  [1, 1] * FIRST_CLICK_COORDS[1], $
                        	  COLOR = !P.BACKGROUND, _EXTRA = EXTRA
	        	IF ((HAVE_TWO_CLICK) OR (DONE_SELECTING)) THEN $
	                   OPLOT, [X_LO, X_HI], $
                                  [1, 1] * SECOND_CLICK_COORDS[1], $
                        	  COLOR = !P.BACKGROUND, _EXTRA = EXTRA
                     ENDIF

		     IF (doGetBox) THEN BEGIN
                	IF ((HAVE_ONE_CLICK) OR (DONE_SELECTING)) THEN $
                	   PLOTS, [START_XY[0], START_XY[0], PREV_XY[0],  $
                        	   PREV_XY[0], START_XY[0]],  $                  
                        	  [START_XY[1], PREV_XY[1], PREV_XY[1], $
                        	   START_XY[1], START_XY[1]], $
                        	  COLOR = !P.BACKGROUND, LINESTYLE = 1, $
                        	  CLIP = CLIPRECT, NOCLIP = 0
                     ENDIF

                     ; Restore DRAW widget event handler state
                     ;
                     IF (NOT haveButtonEvents) THEN $
                        WIDGET_CONTROL, drawWidgetID, DRAW_BUTTON_EVENTS = 0
                     IF (NOT haveMotionEvents) THEN $
                        WIDGET_CONTROL, drawWidgetID, DRAW_MOTION_EVENTS = 0

                     RETURN, 1

                  ENDIF ; NOT doWait

               ENDIF ; HAVE_CLICKS

               END ; PRESS

           ELSE: ; Ignore all other events

      ENDCASE

ENDWHILE



END