;+
; NAME:
;   FSC_PLOTWINDOW
;
; PURPOSE:
;
;   The purpose of this compound widget is to create a resizeable
;   "plot window" inside a larger "page window". I'm not sure it
;   has any value except as a utility routine for the PostScript
;   configuration object FSC_PSCONFIG__DEFINE, but it's a neat
;   program anyway. :-)
;
; AUTHOR:
;
;   FANNING SOFTWARE CONSULTING
;   David Fanning, Ph.D.
;   1645 Sheely Drive
;   Fort Collins, CO 80526 USA
;   Phone: 970-221-0438
;   E-mail: davidf@dfanning.com
;   Coyote's Guide to IDL Programming: http://www.dfanning.com/
;
; CATEGORY:
;
;   Utility routine for FSC_PSCONFIG__DEFINE.
;
; CALLING SEQUENCE:
;
;   plotwindowObject = CW_PlotWindow(parent)
;
; REQUIRED INPUT PARAMETERS:
;
;   parent - The parent base widget of this compound widget.
;
; RETURN VALUE:
;
;   plotwindowObject - The object reference of the compound widget.
;
; KEYWORDS:
;
;   COLOR - If set, display the window in "color". This is the default on 24-bit devices.
;   DEBUG - Set this keyword to turn traceback error handling on in the error handling code.
;   EVENT_PRO - The event procedure for the widget. Required for events to be generated. Otherwise, all events are handled internally.
;   LANDSCAPE - If set, display the page in landscape mode. Otherwise the page is display in portrait mode.
;   PAGESIZE - The "pagesize" of the widget. Possible values are: "LETTER", "LEDGER", "LEGAL", "A4", and "DISPLAY".
;   UNITS - A string indicating INCHES or CENTIMETER units. DEVICE units represented by a null string, "".
;   UVALUE - A user value for the caller of this program.
;   WINDOWCOLOR - A three-element array specifying the background window color (RGB).
;   WINDOWSIZE - The size of the "window" on the page. A four-element array of normalized coordinates in the form [x0, y0, x1, y1].
;
; EVENT STRUCTURE:
;
;   The event structure that is returned from this compound widget is defined like this,
;   where the sizes and offsets locate the target "window" on the page in normalized units:
;
;      event = {ID:0L, TOP:0L, HANDLER:0L, XSize:0.0, YSize:0.0, XOffset:0.0, YOffset:0.0}
;
; MODIFICATIONS:
;
;   Written by David Fanning, 31 January 2000.
;   Fixed a small bug that prevented it working on Macintosh computers. 26 Sept 2000. DWF.
;   Added a "DISPLAY" page size, so the program can be used to position
;      plots and other graphics in a display window. The "page area" will
;      have the same aspect ratio is the current graphics window. 17 March 2001. DWF.
;   Changed some of the tolerances for "closeness" from 0.1 to 0.025 to allow smaller
;      sizing for colorbars and other small objects. 6 July 2005. DWF.
;-
;
;###########################################################################
;
; LICENSE
;
; This software is OSI Certified Open Source Software.
; OSI Certified is a certification mark of the Open Source Initiative.
;
; Copyright  2000-2001 Fanning Software Consulting
;
; This software is provided "as-is", without any express or
; implied warranty. In no event will the authors be held liable
; for any damages arising from the use of this software.
;
; Permission is granted to anyone to use this software for any
; purpose, including commercial applications, and to alter it and
; redistribute it freely, subject to the following restrictions:
;
; 1. The origin of this software must not be misrepresented; you must
;    not claim you wrote the original software. If you use this software
;    in a product, an acknowledgment in the product documentation
;    would be appreciated, but is not required.
;
; 2. Altered source versions must be plainly marked as such, and must
;    not be misrepresented as being the original software.
;
; 3. This notice may not be removed or altered from any source distribution.
;
; For more information on Open Source Software, visit the Open Source
; web site: http://www.opensource.org.
;
;###########################################################################
;


FUNCTION FSC_PLOTWINDOW_Error_Message, theMessage, Traceback=traceback, NoName=noName

On_Error, 2

   ; Check for presence and type of message.

IF N_Elements(theMessage) EQ 0 THEN theMessage = !Error_State.Msg
s = Size(theMessage)
messageType = s[s[0]+1]
IF messageType NE 7 THEN BEGIN
   Message, "The message parameter must be a string."
ENDIF

   ; Get the call stack and the calling routine's name.

Help, Calls=callStack
callingRoutine = (StrSplit(StrCompress(callStack[1])," ", /Extract))[0]

   ; Are widgets supported? Doesn't matter in IDL 5.3 and higher.

widgetsSupported = ((!D.Flags AND 65536L) NE 0) OR Float(!Version.Release) GE 5.3
IF widgetsSupported THEN BEGIN
   IF Keyword_Set(noName) THEN answer = Dialog_Message(theMessage) ELSE BEGIN
      IF StrUpCase(callingRoutine) EQ "$MAIN$" THEN answer = Dialog_Message(theMessage) ELSE $
         answer = Dialog_Message(StrUpCase(callingRoutine) + ": " + theMessage)
   ENDELSE
ENDIF ELSE BEGIN
      Message, theMessage, /Continue, /NoPrint, /NoName, /NoPrefix
      Print, '%' + callingRoutine + ': ' + theMessage
      answer = 'OK'
ENDELSE

   ; Provide traceback information if requested.

IF Keyword_Set(traceback) THEN BEGIN
   Help, /Last_Message, Output=traceback
   FOR j=0,N_Elements(traceback)-1 DO Print, traceback[j]
ENDIF

RETURN, answer
END ;----------------------------------------------------------------------------------



FUNCTION FSC_PLOTWINDOW_Normalize, range, Position=position

On_Error, 1
IF N_Params() EQ 0 THEN Message, 'Please pass range vector as argument.'

IF (N_Elements(position) EQ 0) THEN position = [0.0, 1.0] ELSE $
    position=Float(position)
range = Float(range)

scale = [((position[0]*range[1])-(position[1]*range[0])) / $
    (range[1]-range[0]), (position[1]-position[0])/(range[1]-range[0])]

RETURN, scale
END ;----------------------------------------------------------------------------------



PRO FSC_PLOTWINDOW::Refresh
self.theWindow->Draw, self.theView
END ;----------------------------------------------------------------------------------



PRO FSC_PLOTWINDOW::SetWindowColor, theColor

; Set the background color of the view.

self.theView->SetProperty, Color=theColor
self.theWindow->Draw, self.theView
END ;----------------------------------------------------------------------------------



FUNCTION FSC_PLOTWINDOW::GetUValue

; Get the user value of the compound widget.

parent = Widget_Info(self.drawID, /Parent)
Widget_Control, parent, Get_UValue=theValue
RETURN, theValue
END ;----------------------------------------------------------------------------------



PRO FSC_PLOTWINDOW::SetColor, on_off

; Sets the color of the plot window. Different colors
; depending on depth of visual class.

IF on_off THEN BEGIN
   self.theBackground->SetProperty, Color=[255,255,150]
   self.thePlot->SetProperty, Color=[255, 0, 0]
ENDIF ELSE BEGIN
   Device, Get_Visual_Depth=theDepth
   IF theDepth GT 8 THEN backColor = [210, 200, 180] ELSE backColor = [220, 220, 220]
   self.theBackground->SetProperty, Color=backColor
   self.thePlot->SetProperty, Color=[0, 0, 0]
ENDELSE

   ; Draw the view.

IF Obj_Valid(self.theWindow) THEN self.theWindow->Draw, self.theView
END ;----------------------------------------------------------------------------------



PRO FSC_PLOTWINDOW::SetUnits, units

; Sets the type of units to report in.

possibleUnits = ["INCHES", "CENTIMETERS", ""]
units = StrUpCase(units)
index = WHERE(possibleUnits EQ units, count)
IF count EQ 0 THEN BEGIN
   ok = Dialog_Message('Unknown units: ' + units + '. Returning...')
   RETURN
ENDIF
self.units = units
END ;----------------------------------------------------------------------------------



PRO FSC_PLOTWINDOW::GetWindowLocation, xsize, ysize, xoffset, yoffset

; Returns the current size and offsets of the plot window in
; units of inches or centimeters, as appropriate.

xsize = self.xlength
ysize = self.ylength
xoffset = self.x1
yoffset = self.y1

factor = Float(self.pixels_per_inch)

CASE self.units OF
   'INCHES': BEGIN
      xsize_in_pixels = (xsize * self.xsize)
      ysize_in_pixels = (ysize * self.ysize)
      xoff_in_pixels = (xoffset * self.xsize)
      yoff_in_pixels = (yoffset * self.ysize)
      xsize = (xsize_in_pixels / factor)
      ysize = (ysize_in_pixels / factor)
      xoffset = (xoff_in_pixels / factor)
      yoffset = (yoff_in_pixels / factor)
      ENDCASE
   'CENTIMETERS': BEGIN
      xsize_in_pixels = (xsize * self.xsize)
      ysize_in_pixels = (ysize * self.ysize)
      xoff_in_pixels = (xoffset * self.xsize)
      yoff_in_pixels = (yoffset * self.ysize)
      xsize = (xsize_in_pixels / factor * 2.54)
      ysize = (ysize_in_pixels / factor * 2.54)
      xoffset = (xoff_in_pixels / factor * 2.54)
      yoffset = (yoff_in_pixels / factor * 2.54)
      ENDCASE
   "" :
   ELSE: ok = Dialog_Message('Unknown units: ' + self.units + '. Returning...')
ENDCASE

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



PRO FSC_PLOTWINDOW::SetWindowLocation, xsize, ysize, xoffset, yoffset

; Sets the location of the plot window in the view.
; Converts inches or centimeters to pixels.

factor = Float(self.pixels_per_inch)

CASE self.units OF
   'INCHES': BEGIN
      xsize_in_pixels = (xsize * factor)
      ysize_in_pixels = (ysize * factor)
      xoff_in_pixels = (xoffset * factor)
      yoff_in_pixels = (yoffset * factor)
      x1 = 0.0 > (xoff_in_pixels / self.xsize) < 0.85
      y1 = 0.0 > (yoff_in_pixels / self.ysize) < 0.85
      x2 = (x1 + (xsize_in_pixels / self.xsize)) < 1.0
      y2 = (y1 + (ysize_in_pixels / self.ysize)) < 1.0
      ENDCASE
   'CENTIMETERS': BEGIN
      xsize_in_pixels = (xsize * factor / 2.54)
      ysize_in_pixels = (ysize * factor / 2.54)
      xoff_in_pixels = (xoffset * factor / 2.54)
      yoff_in_pixels = (yoffset * factor / 2.54)
      x1 = 0.0 > (xoff_in_pixels / self.xsize) < 0.85
      y1 = 0.0 > (yoff_in_pixels / self.ysize) < 0.85
      x2 = (x1 + (xsize_in_pixels / self.xsize)) < 1.0
      y2 = (y1 + (ysize_in_pixels / self.ysize)) < 1.0
      ENDCASE
   "" :
   ELSE: ok = Dialog_Message('Unknown units: ' + self.units + '. Returning...')
ENDCASE

self.xlength = x2 - x1
self.ylength = y2 - y1
self.x1 = x1
self.x2 = x2
self.y1 = y1
self.y2 = y2
self->SetWindowSize, [x1, y1, x2, y2]

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

PRO FSC_PLOTWINDOW::SetWindowSize, position

; Set the size of the plot window. Axes have to be
; rescaled appropriately.

IF N_Elements(position) EQ 0 THEN position = [0.2, 0.2, 0.8, 0.8]

x1 = position[0]
y1 = position[1]
x2 = position[2]
y2 = position[3]

new = FltArr(2,5)
new[0,*] = [x1, x1, x2, x2, x1]
new[1,*] = [y1, y2, y2, y1, y1]

   ; Modify the graphics objects.

self.theBackground->SetProperty, Data=new
xs = FSC_PlotWindow_Normalize([0,100], Position=[new[0,0]+0.05, new[0,2]-0.05])
ys = FSC_PlotWindow_Normalize([-1,1], Position=[new[1,0]+0.05, new[1,1]-0.05])

self.theXAxis->SetProperty, XCoord_Conv=xs, Location=[1000, new[1,0]+0.05, 0]
self.theYAxis->SetProperty, YCoord_Conv=ys, Location=[new[0,0]+.05, 1000, 0]
self.thePlot->SetProperty, XCoord_Conv=xs, YCoord_Conv=ys
self.x1 = new[0,0]
self.x2 = new[0,2]
self.y1 = new[1,0]
self.y2 = new[1,1]
self.xlength = self.x2 - self.x1
self.ylength = self.y2 - self.y1

   ; Draw the view after resetting the transformation matrix.

self.theModel->Reset
self.theWindow->Draw, self.theView
END ;----------------------------------------------------------------------------------



PRO FSC_PLOTWINDOW::SetPageSize, pagesize, Landscape=landscape, TLB=tlb

; Sets the page size of the window.

; 200 pixels = 11 inches.
; Letter 8.5 x 11.
; Ledger 11 x 17.
; Legal 8.5 x 14.
; A4 8.27 x 11.7.

STANDARD_SIZE = self.pixels_per_inch * 11.0

IF N_Elements(pagesize) EQ 0 THEN pagesize = "LETTER"
pagesize = StrUpCase(pagesize)
IF N_Elements(tlb) EQ 0 THEN tlb = self.base

CASE pagesize OF
   'LETTER': BEGIN
         xsize = STANDARD_SIZE * (8.5/11.0)
         ysize = STANDARD_SIZE
      ENDCASE
   'LEDGER': BEGIN
         xsize = STANDARD_SIZE
         ysize = STANDARD_SIZE * (17.0/11.0)
      ENDCASE
   'LEGAL': BEGIN
         xsize = STANDARD_SIZE * (8.5/11.0)
         ysize = STANDARD_SIZE * (14.0/11.0)
      ENDCASE
   'A4': BEGIN
         xsize = STANDARD_SIZE * (8.27/11.0)
         ysize = STANDARD_SIZE * (11.7/11.0)
      ENDCASE
   'DISPLAY': BEGIN
         xsize = 300
         ratio = Float(!D.Y_Size) / !D.X_Size
         ysize = 300 * ratio
      ENDCASE
   ELSE: BEGIN
         ok = Dialog_Message('Unknown page size: ' + pagesize + '. Returning...')
         RETURN
      ENDCASE
ENDCASE

IF Keyword_Set(landscape) THEN BEGIN
   temp = xsize
   xsize = ysize
   ysize = temp
ENDIF

self.xsize = ROUND(xsize)
self.ysize = ROUND(ysize)
self.pagesize = pagesize

   ; Bugs in X windows implementation of window resizing necessitate
   ; destroying and re-creating the draw widget window.

IF !D.Name NE 'X' THEN BEGIN

   self.theWindow->SetProperty, Dimensions=[xsize, ysize]

ENDIF ELSE BEGIN

   Widget_Control, tlb, Update=0
   Widget_Control, self.drawID, Kill_Notify=""
   Widget_Control, self.drawID, /Destroy
   self.drawID = Widget_Draw(self.base, Button_Events=1, Expose_Events=1, $
      Retain=0, Graphics_Level=2, XSize=xsize, YSize=ysize, $
      Event_Pro='FSC_PLOTWINDOW_Events', UValue=self, Renderer=1, $
      Kill_Notify='FSC_PLOTWINDOW_Kill_Notify', Motion_Events=1)
   Widget_Control, self.drawID, /Realize
   Widget_Control, self.drawID, Get_Value=theWindow
   self.theWindow = theWindow
   Widget_Control, tlb, Update=1

ENDELSE

self.thewindow->Draw, self.theView

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



FUNCTION FSC_PLOTWINDOW::GetPagePixels, pagesize, Landscape=landscape

; Given the page size or type, returns the xsize and ysize
; of the draw widget in pixels.

; 198 pixels = 11 inches.
; Letter 8.5 x 11.
; Ledger 11 x 17.
; Legal 8.5 x 14.
; A4 8.27 x 11.7.

STANDARD_SIZE = self.pixels_per_inch * 11.0

IF N_Elements(pagesize) EQ 0 THEN pagesize = "LETTER"
pagesize = StrUpCase(pagesize)

CASE pagesize OF
   'LETTER': BEGIN
         xsize = STANDARD_SIZE * (8.5/11.0)
         ysize = STANDARD_SIZE
      ENDCASE
   'LEDGER': BEGIN
         xsize = STANDARD_SIZE
         ysize = STANDARD_SIZE * (17.0/11.0)
      ENDCASE
   'LEGAL': BEGIN
         xsize = STANDARD_SIZE * (8.5/11.0)
         ysize = STANDARD_SIZE * (14.0/11.0)
      ENDCASE
   'A4': BEGIN
         xsize = STANDARD_SIZE * (8.27/11.0)
         ysize = STANDARD_SIZE * (11.7/11.0)
      ENDCASE
   'DISPLAY': BEGIN
         xsize = 300
         ratio = Float(!D.Y_Size) / !D.X_Size
         ; Check for *very* strange windows.
         IF ratio LT 0.2 THEN ratio = 1.0
         IF ratio GT 5 THEN ratio = 1.0
         ysize = 300 * ratio
      ENDCASE
ENDCASE

IF Keyword_Set(landscape) THEN BEGIN
   temp = xsize
   xsize = ysize
   ysize = temp
ENDIF

RETURN, [ROUND(xsize), ROUND(ysize)]

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



FUNCTION FSC_PLOTWINDOW::GetPosition

; Returns the current position of the plot window in
; the view. Normalized coordinates.

RETURN, [self.x1, self.y1, self.x2, self.y2]
END ;----------------------------------------------------------------------------------



PRO FSC_PLOTWINDOW::Resize, event, direction

; Performs window resizing, depending upon which of eight
; possible directions you can move the window.

IF direction EQ 'NONE' OR direction EQ 'MOVE' THEN BEGIN
   RETURN
ENDIF

   ; Calculate movement in normalized coordinates. Update start coords.

deltax = (event.x - self.currentX) / self.xsize
deltay = (event.y - self.currentY) / self.ysize
self.currentX = event.x
self.currentY = event.y

CASE direction OF
   'SE': BEGIN
         x1 = self.x1
         x2 = (self.x2 + deltax) < 1.0
         y1 = (self.y1 + deltay) > 0.0
         y2 = self.y2
         IF x2 LE x1 + 0.025 THEN x2 = x1 + 0.025
         IF y1 GE y2 - 0.025 THEN y1 = y2 - 0.025
         END
   'E' : BEGIN
         x1 = self.x1
         x2 = (self.x2 + deltax) < 1.0
         y1 = self.y1
         y2 = self.y2
         IF x2 LE x1 + 0.025 THEN x2 = x1 + 0.1
         END
   'SW': BEGIN
         x1 = (self.x1 + deltax) > 0.0
         x2 = self.x2
         y1 = (self.y1 + deltay) > 0.0
         y2 = self.y2
         IF x1 GE x2 - 0.025 THEN x1 = x2 - 0.025
         IF y1 GE y2 - 0.025 THEN y1 = y2 - 0.025
         END
   'S' : BEGIN
         x1 = self.x1
         x2 = self.x2
         y1 = (self.y1 + deltay) > 0.0
         y2 = self.y2
         IF y1 GE y2 - 0.025 THEN y1 = y2 - 0.025
         END
   'N' : BEGIN
         x1 = self.x1
         x2 = self.x2
         y1 = self.y1
         y2 = (self.y2  + deltay) < 1.0
         IF y2 LE y1 + 0.025 THEN y2 = y1 + 0.025
         END
   'NE': BEGIN
         x1 = self.x1
         x2 = (self.x2 + deltax) < 1.0
         y1 = self.y1
         y2 = (self.y2 + deltay) < 1.0
         IF x2 LE x1 + 0.025 THEN x2 = x1 + 0.025
         IF y2 LE y1 + 0.025 THEN y2 = y1 + 0.025
         END
   'W' : BEGIN
         x1 = (self.x1  + deltax) > 0.0
         x2 = self.x2
         y1 = self.y1
         y2 = self.y2
         IF x1 GE x2 - 0.025 THEN x1 = x2 - 0.025
         END
   'NW': BEGIN
         x1 = (self.x1 + deltax) > 0.0
         x2 = self.x2
         y1 = self.y1
         y2 = (self.y2 + deltay) < 1.0
         IF x1 GE x2 - 0.025 THEN x1 = x2 - 0.025
         IF y2 LE y1 + 0.025 THEN y2 = y1 + 0.025

         END
ENDCASE

new = FltArr(2,5)
new[0,*] = [x1, x1, x2, x2, x1]
new[1,*] = [y1, y2, y2, y1, y1]

   ; Modify the graphics objects.

self.theBackground->SetProperty, Data=new

xs = FSC_PlotWindow_Normalize([0,100], Position=[new[0,0]+0.05, new[0,2]-0.05]) > 10e-6
ys = FSC_PlotWindow_Normalize([-1,1], Position=[new[1,0]+0.05, new[1,1]-0.05]) > 10e-6

self.theXAxis->SetProperty, XCoord_Conv=xs, Location=[1000, new[1,0]+0.05, 0]
self.theYAxis->SetProperty, YCoord_Conv=ys, Location=[new[0,0]+.05, 1000, 0]
self.thePlot->SetProperty, XCoord_Conv=xs, YCoord_Conv=ys
self.x1 = new[0,0]
self.x2 = new[0,2]
self.y1 = new[1,0]
self.y2 = new[1,1]
self.xlength = self.x2 - self.x1
self.ylength = self.y2 - self.y1

   ; Draw the view after resetting the transformation matrix.

self.theModel->Reset
self.theWindow->Draw, self.theView

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



PRO FSC_PLOTWINDOW::CenterPlot, event

   ; Centers the plot in the window.

new = FltArr(2,5)
self.x1 = (1.0 - self.xlength) / 2.0
self.x2 = self.x1 + self.xlength
self.y1 = (1.0 - self.ylength) / 2.0
self.y2 = self.y1 + self.ylength

new[0,*] = [self.x1, self.x1, self.x2, self.x2, self.x1]
new[1,*] = [self.y1, self.y2, self.y2, self.y1, self.y1]

   ; Modify the graphics objects.

self.theBackground->SetProperty, Data=new

xs = FSC_PlotWindow_Normalize([0,100], Position=[new[0,0]+0.05, new[0,2]-0.05])
ys = FSC_PlotWindow_Normalize([-1,1], Position=[new[1,0]+0.05, new[1,1]-0.05])

self.theXAxis->SetProperty, XCoord_Conv=xs, Location=[1000, new[1,0]+0.05, 0]
self.theYAxis->SetProperty, YCoord_Conv=ys, Location=[new[0,0]+.05, 1000, 0]
self.thePlot->SetProperty, XCoord_Conv=xs, YCoord_Conv=ys
self.x1 = new[0,0]
self.x2 = new[0,2]
self.y1 = new[1,0]
self.y2 = new[1,1]

   ; Draw the view after resetting the transformation matrix.

self.theModel->Reset
self.theWindow->Draw, self.theView

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



PRO FSC_PLOTWINDOW::MovePlot, event

   ; Calculate movement in normalized coordinates. Update start coords.

deltax = (event.x - self.currentX) / self.xsize
deltay = (event.y - self.currentY) / self.ysize
self.currentX = event.x
self.currentY = event.y

   ; Translate the selected model.

self.theModel->Translate, deltax, deltay, 0

   ; Get the current transformation matrix.

matrix = self.theModel->GetCTM()

   ; Get the vertices of the polygon object.

self.theBackground->GetProperty, Data=vertices

   ; Apply the transformation matrix to the vertices.

new = FltArr(2,5)
FOR j=0,4 DO BEGIN
   v = [vertices[*, j], 0.0, 1.0]
   new[*, j] = (matrix ## v)[0:1]
ENDFOR

IF new[0,0] LT 0.0 THEN new[0,*] = [0.0, 0.0, self.xlength, self.xlength, 0.0]
IF new[0,2] GT 1.0 THEN new[0,*] = [1.0-self.xlength, 1.0-self.xlength, 1.0, 1.0, 1.0-self.xlength]
IF new[1,0] LT 0.0 THEN new[1,*] = [0.0, self.ylength, self.ylength, 0.0, 0.0]
IF new[1,1] GT 1.0 THEN new[1,*] = [1.0-self.ylength, 1.0, 1.0, 1.0-self.ylength, 1.0-self.ylength]

   ; Modify the graphics objects.

self.theBackground->SetProperty, Data=new

xs = FSC_PlotWindow_Normalize([0,100], Position=[new[0,0]+0.05, new[0,2]-0.05])
ys = FSC_PlotWindow_Normalize([-1,1], Position=[new[1,0]+0.05, new[1,1]-0.05])

self.theXAxis->SetProperty, XCoord_Conv=xs, Location=[1000, new[1,0]+0.05, 0]
self.theYAxis->SetProperty, YCoord_Conv=ys, Location=[new[0,0]+.05, 1000, 0]
self.thePlot->SetProperty, XCoord_Conv=xs, YCoord_Conv=ys
self.x1 = new[0,0]
self.x2 = new[0,2]
self.y1 = new[1,0]
self.y2 = new[1,1]

   ; Draw the view after resetting the transformation matrix.

self.theModel->Reset
self.theWindow->Draw, self.theView

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



FUNCTION FSC_PLOTWINDOW::WhichButtonReleased, event

; Determines which button was used.

buttons = ['NONE', 'LEFT', 'MIDDLE', 'NONE', 'RIGHT']
RETURN, buttons[event.release]
END ;----------------------------------------------------------------------------------



FUNCTION FSC_PLOTWINDOW::WhichButtonPressed, event

; Determines which button was used.

buttons = ['NONE', 'LEFT', 'MIDDLE', 'NONE', 'RIGHT']
RETURN, buttons[event.press]
END ;----------------------------------------------------------------------------------



FUNCTION FSC_PLOTWINDOW::InTarget, x, y, DIRECTION=direction

; Given a location of the cursor in the window returns the
; target location and the direction the window should be
; resized in. Have to be within 2.5% of window edge to be
; in moveable target.

x = x / self.xsize
y = y / self.ysize

xtest = -1
IF x GE self.x1 AND x LE self.x1 + 0.025 THEN xtest = 0
IF x GT self.x1 + 0.025 AND x LT self.x2 - 0.025 THEN xtest = 1
IF x GE self.x2 - 0.025 AND x LE self.x2 THEN xtest = 2
ytest = -1
IF y GE self.y1 AND y LE self.y1 + 0.1 THEN ytest = 0
IF y GT self.y1 + 0.025 AND y LT self.y2 - 0.025 THEN ytest = 1
IF y GE self.y2 - 0.025 AND y LE self.y2 THEN ytest = 2

IF xtest EQ -1 OR ytest EQ -1 THEN BEGIN
   retVal = 'ORIGINAL'
   direction = 'NONE'
ENDIF
IF xtest EQ 0 AND ytest EQ  0 THEN BEGIN
   retVal = 'SIZE_SW'
   direction = 'SW'
ENDIF
IF xtest EQ 0 AND ytest EQ  1 THEN BEGIN
    retVal = 'SIZE_EW'
   direction = 'W'
ENDIF
IF xtest EQ 0 AND ytest EQ  2 THEN BEGIN
    retVal = 'SIZE_NW'
   direction = 'NW'
ENDIF
IF xtest EQ 1 AND ytest EQ  0 THEN BEGIN
    retVal = 'SIZE_NS'
   direction = 'S'
ENDIF
IF xtest EQ 1 AND ytest EQ  1 THEN BEGIN
    retVal = 'MOVE'
   direction = 'MOVE'
ENDIF
IF xtest EQ 1 AND ytest EQ  2 THEN BEGIN
    retVal = 'SIZE_NS'
   direction = 'N'
ENDIF
IF xtest EQ 2 AND ytest EQ  0 THEN BEGIN
    retVal = 'SIZE_SE'
   direction = 'SE'
ENDIF
IF xtest EQ 2 AND ytest EQ  1 THEN BEGIN
    retVal = 'SIZE_EW'
   direction = 'E'
ENDIF
IF xtest EQ 2 AND ytest EQ  2 THEN BEGIN
    retVal = 'SIZE_NE'
   direction = 'NE'
ENDIF

RETURN, retVal
END ;----------------------------------------------------------------------------------



PRO FSC_PLOTWINDOW::Realize

Widget_Control, self.drawID, Get_Value=theWindow
self.theWindow = theWindow
END ;----------------------------------------------------------------------------------



PRO FSC_PLOTWINDOW_Notify_Realize, drawID
Widget_Control, drawID, Get_UValue=self
self->Realize
END ;----------------------------------------------------------------------------------



PRO FSC_PLOTWINDOW::Process_Events, event

; Handles draw widget events.

possibleEvents = ['DOWN', 'UP', 'MOTION', 'SCROLL', 'EXPOSE']
thisEvent = possibleEvents[event.type]

   ; Are we inside a target? If so, change cursor.

target = self->InTarget(event.x, event.y, Direction=direction)
self.theWindow->SetCurrentCursor, target


   ; What kind of event is this?

CASE thisEvent OF
   'EXPOSE': self.theWindow->Draw, self.theView
   'UP': BEGIN
         thisButton = self->WhichButtonReleased(event)
         Widget_Control, event.id, Clear_Events=1
         CASE self.action OF
            'MOVE': BEGIN
               self.currentX = -1
               self.currentY = -1
               self.action = ""
               self->GetWindowLocation, xsize, ysize, xoffset, yoffset
               IF self.event_pro NE "" THEN BEGIN
                  thisEvent = {ID:event.id, TOP:event.top, HANDLER:event.handler, $
                     xsize:xsize, ysize:ysize, xoffset:xoffset, yoffset:yoffset}
                  Call_Procedure, self.event_pro, thisEvent
               ENDIF
               ENDCASE
            'ORIGINAL':
            'RESIZE': BEGIN
               self.currentX = -1
               self.currentY = -1
               self.action = ""
               self.direction = "NONE"
               self->GetWindowLocation, xsize, ysize, xoffset, yoffset
               IF self.event_pro NE "" THEN BEGIN
                  thisEvent = {ID:event.id, TOP:event.top, HANDLER:event.handler, $
                     xsize:xsize, ysize:ysize, xoffset:xoffset, yoffset:yoffset}
                  Call_Procedure, self.event_pro, thisEvent
               ENDIF
               ENDCASE
            ELSE:
         ENDCASE
         ENDCASE
   'DOWN': BEGIN
         thisButton = self->WhichButtonPressed(event)
         IF thisButton EQ 'MIDDLE' THEN BEGIN
            self->CenterPlot, event
            self->GetWindowLocation, xsize, ysize, xoffset, yoffset
            IF self.event_pro NE "" THEN BEGIN
                  thisEvent = {ID:event.id, TOP:event.top, HANDLER:event.handler, $
                     xsize:xsize, ysize:ysize, xoffset:xoffset, yoffset:yoffset}
                  Call_Procedure, self.event_pro, thisEvent
            ENDIF
            RETURN
         ENDIF
         CASE target OF
            'ORIGINAL': RETURN
            'MOVE': BEGIN
               self.currentX = event.x
               self.currentY = event.y
               self.action = 'MOVE'
               ENDCASE
            ELSE: BEGIN
               self.currentX = event.x
               self.currentY = event.y
               self.action = 'RESIZE'
               self.direction = direction
               ENDCASE
         ENDCASE
         ENDCASE
   'MOTION': BEGIN
         CASE self.action OF
            'MOVE': self->MovePlot, event
            'RESIZE': self->Resize, event, self.direction
            ELSE:
         ENDCASE
         ENDCASE
ENDCASE

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



PRO FSC_PLOTWINDOW_Events, event

; Widget event handler. Calls event handler method.

Widget_Control, event.id, Get_UValue=self
self->Process_Events, event
END ;----------------------------------------------------------------------------------



PRO FSC_PLOTWINDOW_Kill_Notify, drawID

; Destroy the plot window object when the window is destroyed.

Widget_Control, drawID, Get_UValue=self
Obj_Destroy, self
END ;----------------------------------------------------------------------------------



PRO FSC_PLOTWINDOW::CLEANUP
Obj_Destroy, self.theModel
Obj_Destroy, self.theWindow
Obj_Destroy, self.theView
Obj_Destroy, self.plotModel
END ;----------------------------------------------------------------------------------



FUNCTION FSC_PLOTWINDOW::INIT, $
   parent, $                  ; The parent base widget of this compound widget. Required.
   PageSize=pagesize, $       ; The "pagesize" of the widget. Possible values are: "LETTER", "LEDGER", "LEGAL", "A4", and "DISPLAY".
   WindowSize=windowsize, $   ; The size of the "window" on the page. A four-element array of normalized coordinates in the form [x0, y0, x1, y1].
   Event_Pro=event_pro, $     ; The event procedure for the widget.
   Units=units, $             ; A string indicating INCHES or CENTIMETER units. DEVICE units represented by a null string, "".
   UValue=uvalue, $           ; A user value for the caller of this program.
   Landscape=landscape, $     ; If set, display the page in landscape mode. Otherwise the page is display in portrait mode.
   Color=color, $             ; If set, display the window in "color". This is the default on 24-bit devices.
   Debug=debug, $             ; Set this keyword to turn traceback error handling on in the error handling code.
   WindowColor=windowColor    ; A three-element array specifying the background window color (RGB).

Catch, theError
IF theError NE 0 THEN BEGIN
   Catch, /Cancel
   ok = FSC_PlotWindow_Error_Message(Traceback=Keyword_Set(debug))
   RETURN, 0
ENDIF

IF N_Elements(pagesize) EQ 0 THEN pageSize = 'LETTER'
IF N_Elements(windowsize) EQ 0 THEN windowsize = [0.2, 0.2, 0.8, 0.8]
IF N_Elements(event_pro) EQ 0 THEN event_pro = ""
IF N_Elements(units) EQ 0 THEN units=""
IF N_Elements(uvalue) EQ 0 THEN uvalue=""
IF N_Elements(landscape) EQ 0 THEN landscape = 0
Device, Get_Visual_Depth=theDepth
self.pixels_per_inch = 18
sizes = self->GetPagePixels(pageSize, Landscape=landscape)
xsize = sizes[0]
ysize = sizes[1]
IF N_Elements(windowColor) EQ 0 THEN $
   IF theDepth GT 8 THEN windowColor = [60, 140, 140] ELSE windowColor = [80, 80, 80]

IF N_Elements(color) EQ 0 THEN BEGIN
   Device, Get_Visual_Depth=theDepth
   IF theDepth GT 8 THEN color = 1 ELSE color = 0
ENDIF ELSE color = Keyword_Set(color)

x1 = windowsize[0]
x2 = windowsize[2]
y1 = windowsize[1]
y2 = windowsize[3]

data = Findgen(101)
data = Sin(data/5) / Exp(data/20)

self.thePlot = Obj_New('IDLgrPlot', data, Color=[0,0,0])
self.thePlot->SetProperty, XCoord_Conv=FSC_PlotWindow_Normalize([0,100], $
   Position=[x1+0.05, x2-0.05]) > 10e-6, $
   YCoord_Conv=FSC_PlotWindow_Normalize([-1,1], Position=[y1+0.05, y2-0.05]) > 10e-6

self.theXAxis = Obj_New('IDLgrAxis', 0, Color=[0,0,0], Ticklen=0.025, $
   Minor=4, Range=[0,100], Location=[1000, y1+0.05 , 0.0], /NoText)
self.theXAxis->SetProperty, XCoord_Conv=FSC_PlotWindow_Normalize([0,100], $
   Position=[x1+0.05, x2-0.05]) > 10e-6

self.theYAxis = Obj_New('IDLgrAxis', 1, Color=[0,0,0], Ticklen=0.025, $
   Minor=4, Range=[-1,1], Location=[x1+0.05, 1000, 0.0], /NoText)
self.theYAxis->SetProperty, YCoord_Conv=FSC_PlotWindow_Normalize([-1,1], $
   Position=[y1+0.05, y2-0.05]) > 10e-6

self.plotModel = Obj_New('IDLgrModel')

IF theDepth GT 8 THEN theColor = [210, 200, 180] ELSE theColor = [255,255,255]
self.theBackground = Obj_New('IDLgrPolygon', Color=theColor, $
   [x1, x1, x2, x2, x1], [y1, y2, y2, y1, y1])

self.theModel = Obj_New('IDLgrModel', Select_Target=1)
self.theModel->Add, self.theBackground

self.plotModel->Add, self.theXAxis
self.plotModel->Add, self.theYAxis
self.plotModel->Add, self.thePlot
self.plotModel->Translate, 0, 0, 0.1

self.theView = Obj_New('IDLgrView', Color=windowColor,  Viewplane_Rect=[0,0,1,1])
self.theView->Add, self.theModel
self.theView->Add, self.plotModel

self.base = Widget_Base(parent, UValue=uvalue)
self.drawID = Widget_Draw(self.base, Button_Events=1, Expose_Events=1, $
   Retain=0, Graphics_Level=2, XSize=xsize, YSize=ysize, $
   Event_Pro='FSC_PLOTWINDOW_Events', UValue=self, Renderer=1, $
   Kill_Notify='FSC_PLOTWINDOW_Kill_Notify', Motion_Events=1, $
   Notify_Realize='FSC_PLOTWINDOW_Notify_Realize')

self.pagesize = pagesize
self.landscape = Keyword_Set(landscape)
self.x1 = x1
self.x2 = x2
self.y1 = y1
self.y2 = y2
self.xsize = Float(xsize)
self.ysize = Float(ysize)
self.currentX = -1
self.currentY = -1
self.xlength = x2 - x1
self.ylength = y2 - y1
self.units = units
self.event_pro = event_pro
self.color = color
self->SetColor, color
RETURN, 1
END ;----------------------------------------------------------------------------------



PRO FSC_PLOTWINDOW__DEFINE

   struct = { FSC_PLOTWINDOW,          $ ; The object class name.
              action:"",               $ ; A flag to indicate whether you are moving or resizing the plot window.
              base: 0L,                $ ; The top-level base of the compound widget.
              color:0,                 $ ; A flag to indicate if the window is in "color" or not.
              currentX:0,              $ ; The starting X location for resizing.
              currentY:0,              $ ; The starting Y location for resizing.
              direction:"",            $ ; The direction of resizing movement.
              event_pro:"",            $ ; The name of an event handler to call.
              drawID:0L,               $ ; The draw widget identifier.
              pageSize:"",             $ ; A string to indicate the "type" of page: Letter, A4, etc.
              pixels_per_inch:0,       $ ; The pixel per inch scaling. 18 pixels/inch hardcoded.
              landscape: 0,            $ ; A flag to indicate the window should be in landscape mode.
              plotModel:Obj_New(),     $ ; The model for the plot and axes objects.
              theBackground:Obj_New(), $ ; The background filled polygon.
              thePlot:Obj_New(),       $ ; The plot object.
              theModel:Obj_New(),      $ ; The model for the background object.
              theView:Obj_New(),       $ ; The object view.
              theWindow:Obj_New(),     $ ; The object window.
              theXAxis:Obj_New(),      $ ; The X axis object.
              theYAxis:Obj_New(),      $ ; The Y axis object.
              units:"",                $ ; A string indicating INCHES or CENTIMETER units.
              x1:0.0,                  $ ; The x location of lower-left corner of plot window. (Normalized coordinates.)
              x2:0.0,                  $ ; The x location of upper-right corner of plot window. (Normalized coordinates.)
              xlength:0.0,             $ ; The length of the plot window in x. (Normalized coordinates.)
              xsize:0.0,               $ ; The current X size of the draw widget. (Device coordinates.)
              y1:0.0,                  $ ; The y location of lower-left corner of plot window. (Normalized coordinates.)
              y2:0.0,                  $ ; The yx location of upper-right corner of plot window. (Normalized coordinates.)
              ylength:0.0,             $ ; The length of the plot window in y. (Normalized coordinates.)
              ysize:0.0                $ ; The current X size of the draw widget. (Device coordinates.)
            }
END ;----------------------------------------------------------------------------------



FUNCTION FSC_PLOTWINDOW, parent, PageSize=pagesize, WindowSize=windowsize, $
   Event_Pro=event_pro, Units=units, UValue=uvalue, Landscape=landscape, $
   Color=color

; A wrapper function so this object can be treated as a compound
; widget function.

IF N_Elements(pagesize) EQ 0 THEN pageSize = 'LETTER'
IF N_Elements(windowsize) EQ 0 THEN windowsize = [0.15, 0.15, 0.85, 0.85]
IF N_Elements(event_pro) EQ 0 THEN event_pro = ""
IF N_Elements(units) EQ 0 THEN units=""
color = Keyword_Set(color)
landscape = Keyword_Set(landscape)
IF N_Elements(parent) EQ 0 THEN BEGIN
   ok = Dialog_Message('Parent parameter is required. Returning...')
   RETURN, Obj_New()
ENDIF

RETURN, Obj_New('FSC_PLOTWINDOW', parent, PageSize=pagesize, WindowSize=windowsize, $
   Event_Pro=event_pro, Units=units, UValue=uvalue, Landscape=landscape, Color=color)
END ;----------------------------------------------------------------------------------