; $Id: cw_orient.pro,v 1.19 2004/02/27 23:06:40 douge Exp $
;
; Copyright (c) 1992-2004, Research Systems, Inc.  All rights reserved.
;   Unauthorized reproduction prohibited.

PRO drawbox, state, FINAL = FINAL

    ; This routine is responsible for drawing the cube when the
    ; cw_orient compound widget is in the trackball mode.  When
    ; the FINAL keyword is set, the view is first erased and then
    ; the origin indicator is added.

  COMPILE_OPT hidden
  COMMON CW_ORIENT_BOX, box, faces, norms

  wind = !D.WINDOW              ; set to the view window and
  wset, state.trackball             ; make sure to save the old

  IF (KEYWORD_SET(final)) THEN ERASE

  IF ( NOT(KEYWORD_SET(box))) THEN BEGIN    ; build the face vertices
                        ; topology and normals only
                        ; once
    verts = [[.15, .15, .15],[.15, .15, .85],[.15, .85, .85],[.15, .85, .15], $
         [.85, .15, .15],[.85, .15, .85],[.85, .85, .85],[.85, .85, .15]]

    faces = [[0,1,2,3,0], $
             [1,2,6,5,1], $
             [4,5,6,7,4], $
             [4,7,3,0,4], $
             [2,6,7,3,2], $
             [1,5,4,0,1]]

    norms = [[-1, 0, 1, 0, 0, 0, 0], $      ; The last entry in the norms
         [0, 0, 0, 0, 1, -1, 0], $      ; is the origin.  Each normal
         [0, 1, 0, -1, 0, 0, 0], $      ; is a vector from the origin
         [1, 1, 1, 1, 1, 1, 1 ]]        ; to the endpoint in +/- XYZ.
  ENDIF
  ns = norms # !P.T             ; Translate the norms for the
                                ; current !P.T

  FOR i = 0, 5 DO BEGIN
    IF (ns[6,2] - ns[i,2] LT 0) THEN $  ; Compute the Z component of
    PLOTS, verts[*, faces[*,i]], $      ; each normal and if it is less
        /T3D, /NORMAL, $                ; than zero, the cooresponding
        COLOR = state.color             ; face can be seen so draw it.
  ENDFOR

  IF (KEYWORD_SET(FINAL)) THEN BEGIN        ; If a final drawing, then
    plots, [.15, .15, .15], $           ; draw the little origin
       [.25, .15, .15], $           ; indicator.
       [.15, .15, .25], /T3D, /NORMAL
    plots, [.25, .15], $
       [.15,.15], $
       [.15,.15], /T3D, /NORMAL
  ENDIF

  WSET, wind
END ; end of drawbox routine

;---------------------------------------------------------------------------

function CW_ORIENT_EVENT, ev

  COMPILE_OPT hidden
  COMMON CW_OR_PRIVATE, scaler
  COMMON CW_TMP, xlast, zlast, xrot, zrot

  base = ev.handler
  stash = WIDGET_INFO(base, /CHILD)
  WIDGET_CONTROL, stash, GET_UVALUE=state, /NO_COPY


  CASE ev.id OF

    state.modemenu: BEGIN

      CASE ev.value OF

    1: BEGIN; If slider mode was selected, map the sliders and set current.
            IF (state.currentbase NE state.slidebase) THEN BEGIN
          WIDGET_CONTROL, state.slidebase, MAP = 1
          WIDGET_CONTROL, state.currentbase, MAP = 0
          state.currentbase = state.slidebase
        ENDIF
        GOTO, SWALLOW_EV_RET
      END

    2: BEGIN; If trackball mode was selected, make sure window id is known
        IF (state.trackballid NE state.currentbase) THEN BEGIN
          WIDGET_CONTROL, state.trackbase, MAP = 1
          WIDGET_CONTROL, state.currentbase, MAP = 0
          state.currentbase = state.trackballid
          IF state.trackball EQ 0 THEN BEGIN
        WIDGET_CONTROL, state.trackballid, GET_VALUE = temp
        state.trackball = temp[0]
          ENDIF
          drawbox, state, /FINAL
        ENDIF
        GOTO, SWALLOW_EV_RET
      END

    3: BEGIN; If reset menu selection was made.
        WIDGET_CONTROL, state.xslide, GET_UVALUE = xrot
        WIDGET_CONTROL, state.zslide, GET_UVALUE = zrot
        t3d, /RESET, TRANSLATE = [-.5, -.5, -.5], SCALE = scaler
        t3d, ROTATE = [-90, xrot, 0]
        t3d, ROTATE = [zrot, 0, 0]
        t3d, TRANSLATE = [.5, .5, .5]
        WIDGET_CONTROL, state.xslide, SET_VALUE = xrot
        WIDGET_CONTROL, state.zslide, SET_VALUE = zrot
        IF (state.trackballid EQ state.currentbase) THEN $
          drawbox, state, /FINAL
      END
    ENDCASE ; End of ev.value CASE
      END ; End of case state.modemenu.

    state.trackballid: BEGIN
    IF ev.press THEN BEGIN
        ; Begin tracking the trackball as the mouse button is down.
        ; Set the drawing mode to XOR
      WIDGET_CONTROL, state.xslide, GET_VALUE = xrot
      WIDGET_CONTROL, state.zslide, GET_VALUE = zrot
      xlast = ev.x
      zlast = ev.y
      state.trackmode = 1
      DEVICE, SET_GRAPHICS_FUNCTION = 6
      GOTO, SWALLOW_EV_RET
    ENDIF

    IF ev.release THEN BEGIN
        ; Once finished tracking, do range checking and then restore
        ; drawing mode and redraw the cube.
      IF (xrot LT 0) THEN xrot = xrot + 360
      IF (zrot LT 0) THEN zrot = zrot + 360
      IF (xrot GT 360) THEN xrot = xrot - 360
      IF (zrot GT 360) THEN zrot = zrot - 360
      WIDGET_CONTROL, state.xslide, SET_VALUE = xrot
      WIDGET_CONTROL, state.zslide, SET_VALUE = zrot
      state.trackmode = 0
      DEVICE, SET_GRAPHICS_FUNCTION = 3
      drawbox, state, /FINAL
    ENDIF

    IF state.trackmode THEN BEGIN
      drawbox, state
        ; For each direction, figure out the magnitude of movement
        ; first and then the direction.
      IF (xlast NE ev.x) THEN BEGIN
        xangle = ASIN((100.0 - ABS(xlast-ev.x)) / 100.0) * 1.7
        IF ((xlast - ev.x) LT 0) THEN xangle = -xangle
        xlast = ev.x
      ENDIF ELSE xangle = 0

      IF (zlast NE ev.y) THEN BEGIN
        zangle = -ASIN((100.0 - ABS(zlast-ev.y)) / 100.0) * 1.7
        IF ((zlast - ev.y) LT 0) THEN zangle = -zangle
        zlast = ev.y
      ENDIF ELSE zangle = 0

      xrot = xrot - xangle          ; Apply the changes to the
      zrot = zrot - zangle          ; rotation values.

      t3d, /RESET, TRANSLATE = [-.5, -.5, -.5], SCALE = scaler
      t3d, ROTATE = [-90, xrot, 0]
      t3d, ROTATE = [zrot, 0, 0]
      t3d, TRANSLATE = [.5, .5, .5]

      drawbox, state
      GOTO, SWALLOW_EV_RET          ; Don't return an event until
    ENDIF                   ; the user lets go.

    IF (ev.release EQ 0) THEN GOTO, SWALLOW_EV_RET
      END; case state.trackballid


    ELSE: BEGIN ; Handle the sliders which are the only events not covered
        ; above.
    WIDGET_CONTROL, state.xslide, GET_VALUE = xrot
    WIDGET_CONTROL, state.zslide, GET_VALUE = zrot
    t3d, /RESET, TRANSLATE = [-.5, -.5, -.5], SCALE = scaler
    t3d, ROTATE = [-90, xrot, 0]
    t3d, ROTATE = [zrot, 0, 0]
    t3d, TRANSLATE = [.5, .5, .5]
      END

  ENDCASE ; End of ev.id CASE

  ev.id = base
  ev.handler = 0

  WIDGET_CONTROL, stash, SET_UVALUE=state, /NO_COPY
  RETURN, ev

swallow_ev_ret:
  WIDGET_CONTROL, stash, SET_UVALUE=state, /NO_COPY
  RETURN, 0

END ; End of cw_orient_event routine.

;---------------------------------------------------------------------------

FUNCTION CW_ORIENT, parent, UVALUE = UVALUE, XSIZE = XSIZE, YSIZE = YSIZE, $
            FRAME = FRAME, AX = AX, AZ = AZ, TITLE = TITLE, $
            UNAME = uname, TAB_MODE = tab_mode

;+
; NAME:
;   CW_ORIENT
;
; PURPOSE:
;   This compound widget provides a means to interactively adjust the
;   three dimensional drawing transformation (!P.T). The compound
;   widget only returns events when !P.T has been altered.
;
; CATEGORY:
;   Compound widgets.
;
; CALLING SEQUENCE:
;   widget = CW_ORIENT(Parent)
;
; INPUTS:
;       Parent: The ID of the parent widget.
;
; KEYWORD PARAMETERS:
;   AX: The initial rotation in the X direction. If not set, the
;       default is 30 degrees.
;   AZ: The initial rotation in the Z direction. If not set, the
;       default is 30 degrees.
;   FRAME:  If set, a frame will be drawn around the widget. The
;       default is FRAME=0.
;   TITLE:  The title of the widget. (Default is no title.)
;   UVALUE: The user value of the widget.
;       UNAME:  The user name of the widget.
;   XSIZE:  Determines the width of the widget. The default is 100.
;   YSIZE:  Determines the height of the widget. The default is 100.
;
; OUTPUTS:
;       The ID of the created widget is returned.
;
; COMMON BLOCKS:
;   CW_OR_PRIVATE: Private to this module.
;
; SIDE EFFECTS:
;   This widget generates event structures each time a modification
;   to the orientation is made.  This widget also resets !P.T to
;   reflect the changed orientation.
;
; MODIFICATION HISTORY:
;   August 7, 1992, SMR
;       7 April 1993, AB, Removed state caching.
;-

  COMMON CW_OR_PRIVATE, scaler

  ON_ERROR, 2               ; Return to caller.

    ; Define the contents of the state structure.

  state = {trackball:0L, trackballid:0L, $
        slidebase:0L, trackbase:0L, currentbase:0L, $
        xslide:0L, zslide:0L, $
        trackmode:0, modemenu:0L, color:0L}

    ; Set default values for the keywords.

  IF N_ELEMENTS(FRAME) NE 0 THEN frm = 1 ELSE frm = 0
  IF N_ELEMENTS(UVALUE) NE 0    THEN uval = UVALUE ELSE uval = 0
  if (NOT KEYWORD_SET(uname))   THEN uname = 'CW_ORIENT_UNAME'
  IF N_ELEMENTS(XSIZE) NE 0 THEN xsi = XSIZE ELSE xsi = 120
  IF N_ELEMENTS(YSIZE) NE 0 THEN ysi = YSIZE ELSE ysi = 120
  IF N_ELEMENTS(AX) NE 0    THEN xrot = AX ELSE xrot = 30
  IF N_ELEMENTS(AZ) NE 0    THEN zrot = AZ ELSE zrot = 30
  IF N_ELEMENTS(TITLE) NE 0 THEN tit = TITLE ELSE tit = ''

  orientbase = WIDGET_BASE(parent, $
            EVENT_FUNC = "CW_ORIENT_EVENT", $
            UVALUE = uval, $
            /COLUMN, $
            FRAME = frm, $
            UNAME = uname)

  if ( N_ELEMENTS(tab_mode) ne 0 ) then $
      WIDGET_CONTROL, orientbase, TAB_MODE = tab_mode

  IF (TIT NE '') THEN title = WIDGET_LABEL(orientbase, $
            VALUE = TIT)

  panelbase = WIDGET_BASE(orientbase)
  state.slidebase = WIDGET_BASE(panelbase, /COLUMN, MAP = 1)
  state.currentbase = state.slidebase

  state.modemenu = cw_pdmenu(orientbase, [ $
        {CW_PDMENU_S, flags:1, name:'Orientation'}, $
        {CW_PDMENU_S, flags:0, name:'Sliders'}, $
        {CW_PDMENU_S, flags:0, name:'Trackball'},   $
        {CW_PDMENU_S, flags:0, name:'Reset Transformation'}], $
                ids = i, $
                UNAME=uname+'_MODE')

  state.xslide = WIDGET_SLIDER(state.slidebase, $
            MIN = 0, MAX = 359, VALUE = xrot, $
            TITLE = "X angle", $
            XSIZE = 120, $
            UVALUE = xrot, $
                        UNAME=uname+'_XSLIDE')
  state.zslide = WIDGET_SLIDER(state.slidebase, $
            MIN = 0, MAX = 359, VALUE = zrot, $
            TITLE = "Z angle", $
            XSIZE = 120, $
            UVALUE = zrot, $
                        UNAME=uname+'_ZSLIDE')

  state.trackbase = WIDGET_BASE(panelbase, MAP = 0)
  state.trackballid = WIDGET_DRAW(state.trackbase, $
            XSIZE = xsi, YSIZE = ysi, $
            /MOTION, /BUTTON, $
                        UNAME=uname+'_TRACKBALL')

  ; Pick a "middle" color to use for the trackball cube
  DEVICE, GET_DECOMPOSED = decomp
  state.color = decomp ? '808080'XUL : !D.TABLE_SIZE/2

  WIDGET_CONTROL, WIDGET_INFO(orientbase, /CHILD), SET_UVALUE=state, /NO_COPY

  scaler = REPLICATE(1./SQRT(3),3)

  !X.S = [0, 1]
  !Y.S = [0, 1]
  !Z.S = [0, 1]
  t3d, /RESET, TRANSLATE = [-.5, -.5, -.5], SCALE = scaler
  t3d, ROTATE = [-90, xrot, 0]
  t3d, ROTATE = [zrot, 0, 0]
  t3d, TRANSLATE = [.5, .5, .5]

  RETURN, orientbase
END
