;+
; NAME:
;	DISPLAY
;
; VERSION:
;	1.1 (multiple modifications added to the original 1.0)
;
; PURPOSE:
;	The purpose of this routine is to provide transparent replacement for PLOT
;	and OPLOT routines. Operates in 2D space.
;
; AUTHOR:
;	Pavel A. Romashkin, Ph. D.
;
; CATEGORY:
;	Graphics.
;
; CALLING SEQUENCE:
;	DISPLAY, [X], [Y], [/OVER], [KEYWORDS_TO_PLOT_COMMAND], [KEYWORDS_TO_IDLgrPlot]
;
; INPUTS:
;	Optional X and/or Y, vectors to be displayed.
;
; KEYWORD PARAMETERS:
;	Accepts all IDLgrPlot keywords at the time of creation of the plot or with NAME and OVER keywords set.
;	Accepts all IDLgrText and IDLgrLegend keywords.  Use with /OVER and /LEGEND, or when setting TEXT.
;	Accepts standard PLOT keywords but is not guaranteed to honor them all.
;	BACKGROUND: set to three-point vector, RGB, in the form [255, 255, 255]. Defaults to white.
;	COLOR: set to three-point vector, RGB, in the form [0, 255, 0]. Defaults to black. Will act on the item currently
;		being added, or on the named item specified along with COLOR.
;	FONT: set to font name, in the form 'Helvetica' (default).
;	GROUP_LEADER: set ta valid widget ID to use it as a geroup leader for Display.
;	HIDE: use to hide or display a named plot or other named item.
;	KEEP_SCALE: set when adding a plot to prevent autoscaling. To rescale to full range later, use double-click.
;	LEGEND: will produce a self-updating legend.  Use with /OVER.
;	LEGEND_TITLE: set to a valid string containing a title for the legend. Use with /OVER.
;	LINESTYLE: 0 through 5 as for PLOT, additionally Linestyle=6 for No line.
;	LINK: set to a value >0 but <255. This value is used to identify linked windows. If two or more DISPLAY
;		windows have the same LINK value and contain plots with the same NAMEs, a ROI drawn to one window
;		will cause ROI selection in other linked windows. ROI is drawn using the middle mouse button.
;		ROI plots can be removed from DISPLAYs by right-double-clicking on them.
;	NAME: optional name for the plot being added. Later, a modifying command with OVER keyword set
;		can be used to modify that plot's appearance, or hide it.
;	OREF: returns the object reference to the Display window. Use OREF when accessing  a Display window
;		created as a child in a widget hierarchy.
;	OVER: set to apply changes to an existing topmost Display window.
;	PARENT: set to valid Widget_base ID. Will cause Display to appear as a child of that base. Use with OREF keyword.
;	PRINTOUT: set to print the newly created Display window. Use with OVER to print existing topmost window.
;	RESTORE_FILE: set to a string containing the name of a file where a DISPLAY was saved, to restore that DISPLAY.
;	SAVE: set this keyword to save contents of DISPLAY to a file. Use with optional FILENAME keyword.
;	SET_ROI_COLOR: set to three-point vector, RGB, to specify a ROI color. Defaults to red.
;	TEXT: set to a valid string or string array. Most formatting keywords for the text (object) will be also honored.
;		DATA_SCALE: use with TEXT to specify position in data coordinates. Dragging data scaled text will produce
;		unexpected results.
;		T_NAME: set to a string to identify a particular text block.
;		KILL: use with T_NAME to delete a named text block programmatically. Right_double_click will delete, too.
;	VERT_COLOR: color matrix bytarr(3, n_elements(x)) describing RGB colors of each vertex of the plot.
;		Symbols will be of the color set by COLOR keyword.
;	WTITLE: set to a string containing (short) text for window title.
;
; OBJECT METHODS:
;	Unless listed below, transparent to the end user. See individual routines for functionality description.
;	GET_DATA: invoke this function method on an instance of PARgrDisplay returned by OREF keyword. The result
;	is a structure which fields 1 through X are FLTARR(3, *) of plots data from the object.
;
; COMMON BLOCKS:
;	PARgrDisplay_top_window, Temp. One common variable, type - object reference.
;	Used to keep track of the topmost Display window so that a user can create and
;	modify several of them.
;
; SIDE EFFECTS:
;	None.
;
; RESTRICTIONS:
;	Designed under IDL 5.3. Version-specific features include:
;	COMPILE_OPT calls. In earlier versions of IDL, comment these lines out.
;	Some keyword parameters are passed undefined. IDL 5.3 allows that.
;	Earlier versions of IDL may require checking for validity of variables.
;
; EXAMPLE:
;	Creating a new Display window (plots can be appended):
;	DISPLAY
;
;	Creating a new Display with existing data:
;	DISPLAY, sin(findgen(100)*0.1)+1.0, psym=4, linestyle=6, color=[255,0,0], $
;		xtitle='My X axis', ytitle='My Y axis', title='My nice plots', $
;		name='plot_1', wtitle='Top window'
;
;	Appending a plot to the existing topmost Display window and making the window linked using the ID of 1:
;	DISPLAY, sin(findgen(100)*0.15)+0.9, psym=5, color=[0,0,200], LINK=1, /OVER
;
;	Changing properties of the existing topmost Display window:
;	DISPLAY, background=[200,200,0], title='New plot title', name='plot_1', $
;		color=[200,0,0], /OVER
;	
; PROCEDURE:
;	Display will create and maintain automatic ranges for the window. When a plot
;	is added that expands the range, or a plot is removed that had the largest range,
;	the window can re-scale to accomodate the largest present plot. Use /KEEP_SCALE
;	to prevent autoscaling.
;	Legend and / or Text items can be freely moved using dragging with the right mouse button, and
;	can be deleted using right-double-click.
;	Zooming is performed by Left-mouse-drag in the window. Full zoom-out is performed
;	using Left-double-click in the window.
;	Any item, including plots and axes, can be removed from Display by Right-double-clicking on that plot.
;	No "undo" functionality is provided.
;	Multiple windows are allowed to be active at the same time. Another plot can be
;	added to any window by bringing it to the foreground (clicking on it with the mouse)
;	and executing Display, . . . , /OVER command.
;	Co-existing windows can interact with each other through the use of LINK keyword parameter.
;	Windows with the same, non-zero LINK value and containing the same plot NAMEs, will produce
;	a region of interest (ROI) highlight if a ROI is drawn in one of such linked windows. Use middle
;	mouse button to draw a ROI.
;	LINK can be set to another value at any time using /OVER keyword.
;	Formatting commands can be issued to an existing Display window without adding
;	new plots by using Display, . . . , /OVER command.
;	Programmatically, can be used as a child to any base widget in any widget program
;	(refer to PARENT, OREF and GROUP_LEADER keywords).
;
; EXPANDABILITY:
;	Right, middle and left mouse buttons can be used while Single, Double clicking or
;	dragging. Different color ROI selection rectangles are provided to indicate action.
;	Supplying user procedures allows to perform various actions on the data in ROI.
;
; MODIFICATION HISTORY:
;	Written: P.A.Romashkin, 08-2000
;	Documented, P.A.Romashkin, 8-2000
;	GET_DATA, SAVE and RESTORE added, PAR, 12-2000
;	Documented additions, P.A.Romashkin, 12-2000
;	Separated mouse events into object methods and added DATA_SCALE to TEXT, PAR, 8-2001
;-

; *************************************************************************
; Provides event handling for the top level widget and draw window.
pro Display_event, event
compile_opt IDL2, OBSOLETE
Common PARgrDisplay_top_window, got_focus

; Provide error handling.
Catch, Err_code
if (Err_code ne 0) then begin
	print,'PARgrDisplay_event error caught:'
	help, /last_message
	catch, /cancel
	return
endif

; Get object reference out, as it is used virually everwhere.
widget_control, event.id, get_uval=Object

; ---------------------------- THESE ARE TOP BASE EVENTS ----------------------------
; Now, handle the possibility of focusing event. User will be able to Oplot to existing window.
; Common block is used to store the information about topmost window as it gains focus.
if tag_names(event, /STRUCTURE_NAME) EQ 'WIDGET_KBRD_FOCUS' then begin
	if  (event.enter eq 1) then begin ; This means Display window was brought to the top.
	
		if n_elements(got_focus) eq 0 then begin ; If this is the first object ever, reset GOT_FOCUS.
			got_focus = object
			return
		endif
		
		if not obj_valid(got_focus[0]) then begin ; If the first object is invalid, replace it.
			got_focus[0] = object
			got_focus = got_focus[where(obj_valid(got_focus))]
			return
		endif
		
		if got_focus[0] eq object then return ; Don't waste time if it was on top already.
		
		index = where(got_focus eq object, count) ; Locate the object that was brought up in the array...
		if count gt 0 then got_focus[index] = obj_new() ; and invalidate the existing reference to it.
		got_focus = got_focus[where(obj_valid(got_focus))]
		
		; Now, got_focus does NOT contain the Object reference. Add it:
		if got_focus[0] -> is_linked() eq 0 then got_focus[0] = object else got_focus = [object, got_focus]
		
		; Now, common block has top Display at 0 position, and Linked Displays in other positions of got_focus.
	endif
	return
endif

; Lastly, it may be resize event. Change the size of daughter Draw widget.
if tag_names(event, /STRUCTURE_NAME) EQ 'WIDGET_BASE' then begin
	widget_control, widget_info(event.id, /child), draw_xsize=event.x, draw_ysize=event.y
	Object -> setProperty, /draw
	return
endif
; ---------------------------- END OF TOP BASE EVENTS ----------------------------

; Process mouse events. Use all options of Single or Double click, drag and release with three mouse buttons.
case event.type of
	0: object -> mouse_press, event ; Mouse press
	1: object -> mouse_release, event ; Mouse release
	2: object -> mouse_drag, event ; Mouse drag
endcase

end ; EVENT HANDLING PROCEDURE

;*************************************************************************
; Method called on mouse press in the DISPLAY window. Helps structure event handling procedure.
pro PARgrDisplay::mouse_press, event
compile_opt IDL2, OBSOLETE
if event.clicks eq 2 then begin ; Process double-click events.
	case event.press of
		1: begin ; Left button double-click zooms fully out.
			self -> getProperty, xrange=xrange, yrange=yrange, obj_win=obj_win, Plot_list = temp, $
				view = view
			temp = temp[0]
			if obj_valid(temp) then temp -> zoom, obj_win, xrange=xrange, yrange=yrange, $ ; Zoom out.
				norm_x=norm_x, norm_y=norm_y, /draw
			self -> setProperty, norm_x=norm_x, norm_y=norm_y ; Remember new normalization.
		end
		2: begin ; Middle-double-click is for user added functionality.
			self -> getProperty, obj_win=obj_win, plot_list=plot_list, view=view
			; The following prints the coordinates of the double click.
			;temp = obj_win -> pickData(view, plot_list[0], [event.x, event.y], coords)
			;print, 'X coord.: ', coords[0], ' Y coord.: ', coords[1]
			; The following is optional. It prints out plot name, if double click hit a plot.
			temp = obj_win -> select(view, [event.x, event.y])
			if obj_valid(temp[0]) then begin
				temp[0] -> getProperty, name=name
				print, name
			endif
		end
		4: begin ; Right-double-click kills selected plot.
			self -> killplot, mpress=[event.x, event.y], /draw
		end
		else:
	endcase
endif else begin ; Single click establishes the starting point for ROI or zooming rectangle.
	mpress = [event.x, event.y]
	self -> setproperty, Mpress = mpress, mouse_button=event.press
	self -> getproperty, norm_x=n_xr, norm_y=n_yr, second_layer=second_layer ; Get coordinate conversions.
	case event.press of
		1: begin ; This is for zooming, using left mouse button.
			widget_control, event.id, /draw_motion ; Turn on motion events.
			temp = obj_new('IDLgrPolygon', color=[0, 200, 0], linestyle=0, style=1, $
				xcoord_conv=n_xr, ycoord_conv=n_yr) ; Use data coordinate system.
			self -> setProperty, roi=temp ; Store the polygon in ROI field and add it to the model.
		end
		2: begin ; Use this event to create ROI object.
			widget_control, event.id, /draw_motion ; Turn on motion events.
			geom = widget_info(event.id, /geometry)
			x0 = (Mpress[0]*1.12/geom.draw_xsize-0.1 - n_xr[0])/n_xr[1]
			y0 = (Mpress[1]*1.16/geom.draw_ysize-0.1 - n_yr[0])/n_yr[1]
			; These lines are here because of the bug in IDLgrROI.
			;In IDL 5.3, only normalized coords were supported.
			;**********
;			x0 = (Mpress[0]*1.12/geom.draw_xsize-0.1)
;			y0 = (Mpress[1]*1.16/geom.draw_ysize-0.1)
			;**********
			; If self is not linked, allow drawing of ROI but make it dotted.
			if self -> is_linked() eq 0 then begin
				linestyle=1
				temp = obj_new('IDLgrText', 'This window is not linked with others.', location=[0.3, 0.5], $
					color=[255,0,0])
				second_layer -> add, temp ; Add the text to the window.
				self -> setProperty, trash=temp
			endif else linestyle=0
			roi = obj_new('IDLgrRoi', x0, y0, xcoord_conv=n_xr, ycoord_conv=n_yr, linestyle=linestyle)
			self -> setProperty, roi=roi, /draw ; Store the ROI in roi field.
		end
		4: begin ; Right mouse click and drag is used to move objects on Display.
			; Drag events are not needed for moving objects, just as nothing else is needed.
			; All action is performed on mouse release.
		end
		else:
	endcase
endelse
end

;*************************************************************************
; Method called on mouse release in the DISPLAY window. Helps structure event handling procedure.
pro PARgrDisplay::mouse_release, event
compile_opt IDL2, OBSOLETE
widget_control, event.id, draw_motion=0 ; Turn motion events off.
self -> getProperty, Plot_list=Plot_list, Mpress=Mpress, obj_win=obj_win, model=model, view=view, $
	norm_x=n_xr, norm_y=n_yr
case event.release of
	1: begin ; This release event is left mouse button and will zoom in.
		Plot_list = Plot_list[0]
		if obj_valid(Plot_list) then Plot_list -> zoom, obj_win, mpress, [event.x, event.y], $
			norm_x=norm_x, norm_y=norm_y, /draw ; Apply Zoom.
		self -> setProperty, norm_x=norm_x, norm_y=norm_y, roi=0, /draw ; Kill ROI object.
	end
	2: begin ; This event is middle mouse button and will select ROI.
		self -> getproperty, roi=roi
			; If active window is not linked, clean up and leave.
		if self -> is_linked() eq 0 then begin
			self -> setProperty, roi=0, trash=0, /draw
			return
		endif
		if not obj_valid(roi) then return
	; These lines are here because of the bug in IDLgrROI. ROI in 5.3 is drawn in the right place but
	; uses totally wrong data space. Replace data with correct set before aquiring data from plots.
	;**********
;		x = (data[0, *]-n_xr[0])/n_xr[1] ; because IDLgrROI uses normalized coordinates because of the bug,
;		y = (data[1, *]-n_yr[0])/n_yr[1] ; replace ROI with the one in data coordinates.
;		roi -> setProperty, /hide ; ROI will be off-scale. Hide it.
;		roi -> replacedata, x, y
	;**********
		self -> link_roi, roi
		self -> setProperty, roi=0, /draw ; Kill ROI object.
	end
	4: begin
		selected_id = (obj_win -> select(view, mpress))[0]
		if not obj_valid(selected_id) then return
			; All text is located on Second_layer. That model does not use data coordinates.
		geom = widget_info(event.id, /geometry)
		x0 = (Mpress[0]*1.12/geom.draw_xsize-0.1)
		y0 = (Mpress[1]*1.16/geom.draw_ysize-0.1)
		x1 = (event.x*1.12/geom.draw_xsize-0.1)
		y1 = (event.y*1.16/geom.draw_ysize-0.1)
			; If a legend was selected, use its Translate method in normalized coordinates.
		if obj_class(selected_id) eq 'IDLGRLEGEND' then begin
			delta_x = x1 - x0
			delta_y = y1 - y0
			selected_id -> translate, delta_x, delta_y, 0
		endif else $ ; If it was a text object, use its Location property.
			if obj_class(selected_id) eq 'IDLGRTEXT' then begin
				selected_id -> getProperty, location=loc, strings=strings
				n_lines = n_elements(strings)
					; Keep row spacing for multiple rows.
				if n_lines eq 1 then loc = [x1, y1] else begin
					offset = (loc[1, 0:1]-shift(loc[1, 0:1],1))[1] ; Obtain row spacing...
					loc[0, *] = x1
					loc[1, *] = indgen(n_lines)*offset + y1 ; ...and apply this same spacing at new location.
				endelse
				selected_id -> setProperty, location=loc
			endif
		self -> draw
	end
	else:
endcase
end

;*************************************************************************
; Method called on mouse drag in the DISPLAY window. Helps structure event handling procedure.
pro PARgrDisplay::mouse_drag, event
compile_opt IDL2, OBSOLETE
self -> getProperty, Mpress=Mpress, view=view, obj_win=obj_win, roi=roi, $
	xrange=xrange, yrange=yrange, mouse_button=mouse_button, norm_x=n_xr, norm_y=n_yr
	; This is needed to convert manually mouse screen coordinates to data coordinates for ROI,
	; and for Zoom rectangle.
geom = widget_info(event.id, /geometry)

; Since the viewplane_rect is from -0.1, -0.1 and is 1.12 by 1.16, correct for these. Convert the
; window coordinates to data coordinates.
x0 = (Mpress[0]*1.12/geom.draw_xsize-0.1 - n_xr[0])/n_xr[1] ; This is for mouse press.
x1 = (event.x*1.12/geom.draw_xsize-0.1 - n_xr[0])/n_xr[1] ; This is for drag coordinate.
y0 = (Mpress[1]*1.16/geom.draw_ysize-0.1 - n_yr[0])/n_yr[1] ; This is for mouse press.
y1 = (event.y*1.16/geom.draw_ysize-0.1 - n_yr[0])/n_yr[1] ; This is for drag coordinate.

case mouse_button of
	1: begin ; Update the zooming rectangle.
		if not obj_valid(roi) then return ; On some fast (poorly controlled) drags, a ROI does not get established.
		roi -> setProperty, data=[[x0, y0], [x0, y1], [x1, y1], [x1, y0], [x0, y0]]
		self -> draw
		end
	2: begin ; Update ROI object.
		x1 = (event.x*1.12/geom.draw_xsize-0.1 - n_xr[0])/n_xr[1]
		y1 = (event.y*1.16/geom.draw_ysize-0.1 - n_yr[0])/n_yr[1]
		; These lines are here because of the bug in IDLgrROI in IDL 5.3.
		;**********
	;		x1 = (event.x*1.12/geom.draw_xsize-0.1)
	;		y1 = (event.y*1.16/geom.draw_ysize-0.1)
		;**********
		roi -> appendData, x1, y1
		self -> draw
		end
	4: begin
		end
	else:
endcase
end

;*************************************************************************
; Standard object property retrieval method.
pro PARgrDisplay::getProperty, view=view, Obj_win=Obj_win, mouse_button=mouse_button, Mpress=Mpress, $
	Plot_list=Plot_list, Axes_list=Axes_list, xrange=xrange, yrange=yrange, symbol_list=symbol_list, $
	title=title, xtitle=xtitle, ytitle=ytitle, top_ID=top_ID, model=model, roi=roi, uvalue=uvalue, $
	second_layer=second_layer, link=link, norm_x=norm_x, norm_y=norm_y, get_data=get_data

compile_opt IDL2, OBSOLETE

	top_ID = self.top_ID
	model = self.container -> get(position=0)
	second_layer = self.container -> get(position=1)
	view = self.view
	link = self.link
	Obj_win = self.Obj_win
	roi = self.roi
	mouse_button = self.mouse_button
	Mpress = self.Mpress
	Plot_list = self.Plot_list -> get(/all)
	Axes_list = self.Axes_list -> get(/all)
	xrange = self.xrange
	yrange = self.yrange
	symbol_list = self.symbol_list -> get(/all)
	title = self.title -> get(position=0)
	xtitle = self.title -> get(position=1)
	ytitle = self.title -> get(position=2)
	norm_x = self.norm_x
	norm_y = self.norm_y
	if n_elements(*self.uvalue) ne 0 then uvalue = *self.uvalue else uvalue = 0b

end

;*************************************************************************
; Standard object property definition method.
pro PARgrDisplay::setProperty, title=title, xtitle=xtitle, ytitle=ytitle, mpress=mpress, link=link, $
	mouse_button=mouse_button, draw=draw, background=background, name=name, uvalue=uvalue, $
	wtitle=wtitle, roi=roi, norm_x=norm_x, norm_y=norm_y, legend=legend, text=text, trash=trash, $
	set_roi_color=set_roi_color, psym=psym, rename=rename, kill=kill, polygon=polygon, _extra=_extra

compile_opt IDL2, OBSOLETE

if n_elements(title) gt 0 then (self.title -> get(position=0)) -> setProperty, string = title
if n_elements(link) ne 0 then self.link = link
if n_elements(xtitle) gt 0 then (self.title -> get(position=1)) -> setProperty, string = xtitle
if n_elements(ytitle) gt 0 then (self.title -> get(position=2)) -> setProperty, string = ytitle
if n_elements(mpress) gt 0 then self.mpress = mpress
if n_elements(norm_x) gt 0 then self.norm_x = norm_x
if n_elements(norm_y) gt 0 then self.norm_y = norm_y
if n_elements(mouse_button) gt 0 then self.mouse_button = mouse_button
if n_elements(uvalue) gt 0 then *self.uvalue = uvalue
if n_elements(background) eq 3 then (self.container -> get(position=1)) -> setProperty, color=background
if n_elements(set_roi_color) ne 0 then self.roi_color = set_roi_color
if keyword_set(legend) then self -> legend, /update, _extra=_extra
if n_elements(text) ne 0 then self -> text, text, kill=kill, _extra=_extra
if n_elements(polygon) ne 0 then self -> polygon, polygon, _extra=_extra

if n_elements(wtitle) ne 0 then begin
	widget_control, self.top_id, tlb_set_title=wtitle
	self.wtitle = wtitle
endif

if n_elements(roi) ne 0 then begin ; Use this to set or kill the ROI object. If set not to obj. ref., destroy the ROI.
	obj_destroy, self.roi
	if obj_valid(roi) then begin ; Add the ROI object to the model for displaying.
		self.roi=roi
		if obj_class(roi) eq 'IDLGRROI' then roi -> setProperty, color=self.roi_color
		;roi -> setProperty, xcoord_conv=self.norm_x, ycoord_conv=self.norm_y
		model = self.container -> get(position=0)
		model -> add, roi
	endif
endif

if n_elements(trash) ne 0 then begin ; Use this to add or empty the trash object. If set not to obj. ref., empty trash.
	trashed_items = self.trash -> get(/all, count=count)
	if count gt 0 then obj_destroy, trashed_items
	if obj_valid(trash) then self.trash -> add, trash
endif

if n_elements(name) ne 0 then begin
	model = self.container -> get(position=0)
	target_plot = model -> getByName(name)
		; If PSYM was specified, modify symbols. Else, modify plot itself.
	if obj_valid(target_plot) then begin ; If plot valid
		if size(rename, /type) eq 7 then target_plot -> setProperty, name=rename ; Allow to rename the plot
		if n_elements(psym) ne 0 then begin
			target_plot -> getProperty, symbol=symbol_used
			if obj_valid(symbol_used) then symbol_used -> setProperty, data=psym, _extra=_extra $
			else begin
				symbol_used = obj_new('IDLgrSymbol', psym, _extra=_extra)
				self.container -> add, symbol_used
				target_plot -> setProperty, symbol=symbol_used
			endelse
		endif $
		else target_plot -> setProperty, _extra=_extra ; If PSYM was specified
		self -> rescale, /keep_scale ; Rescale in case plot data were changed
		if keyword_set(kill) then obj_destroy, target_plot
	endif else print, 'No plot with such name.'
endif

if keyword_set(draw) then self.obj_win -> draw, self.view

end

;*************************************************************************
; Plotting method for the PARgrDisplay. Used for both initial plotting and overplotting.
; Some part of this procedure are redundant with RESCALE method, but it is more efficient here
; not to call RESCALE, because there is no need to query all Plots, as all ranges are available already.
pro PARgrDisplay::oplot, x_in, y_in, psym=psym, xrange=xrange, yrange=yrange, draw=draw, $
	keep_scale=keep_scale, _extra=_extra

compile_opt IDL2, OBSOLETE

if n_elements(y_in) eq 0 then begin
	x = indgen(n_elements(x_in)) ; Generate abscissa if it is missing.
	y = x_in
endif else begin ; Isolate the parameters from internal data used to make a plot.
	x = x_in
	y = y_in
endelse

	; Always store full ranges for zooming out to right full range.
xrange_calc = get_range(x)
yrange_calc = get_range(y)
	; If this is the first plot, replace ranges altogether.
if self.plot_list -> count() eq 0 then begin
	self.xrange = xrange_calc
	self.yrange = yrange_calc
endif else begin ; Otherwise, choose the wider ranges.
	self.xrange = [self.xrange[0] < xrange_calc[0], self.xrange[1] > xrange_calc[1]]
	self.yrange = [self.yrange[0] < yrange_calc[0], self.yrange[1] > yrange_calc[1]]
endelse

 	; Check if we want to keep a Zoom-in.
if keyword_set(keep_scale) then begin
		; Get axes to obtain current display ranges from...
	x_axis = self.axes_list -> get(position=0)
	y_axis = self.axes_list -> get(position=1)
		; ...and get current ranges from axes to use for overplotting.
	x_axis -> getProperty, xrange=xrange
	y_axis -> getProperty, yrange=yrange
endif else begin ; If not retaining Zoom, use either provided or automatic ranges..
	if not keyword_set(xrange) then xrange = self.xrange
	if not keyword_set(yrange) then yrange = self.yrange
endelse

if n_elements(psym) ne 0 then symbol = obj_new('IDLgrSymbol', psym, _extra=_extra)
add_plot = obj_new('IDLgrPlot', x, y, symbol=symbol, _extra=_extra)

model = self.container -> get(position=0)
model -> add, add_plot, position=0

	; Bring Display to scale and retrieve new normalization vectors.
add_plot -> zoom, self.obj_win, xrange=xrange, yrange=yrange, norm_x=norm_x, norm_y=norm_y, /draw
	; Norm_X and Norm_Y are defined now. Store them in Self.
self.norm_x = norm_x
self.norm_y = norm_y

	; Need to change *norm_coord for text objects in the model.
objects_list = model -> get(isa='IDLGRTEXT', /all, count=count)
if count gt 1 then for i=1, count-1 do objects_list[i] -> setProperty, xcoord_conv=norm_x, ycoord_conv=norm_y

	; Update the display.
self.plot_list -> add, add_plot
if obj_valid(symbol) then self.symbol_list -> add, symbol else self.symbol_list -> add, obj_new()
self -> legend, /update

if keyword_set(draw) then self.obj_win -> draw, self.view

end

;*************************************************************************
; Adds a polygon to the Display.
pro PARgrDisplay::polygon, coords, kill=kill, _extra=_extra
compile_opt IDL2, OBSOLETE
self -> getProperty, model=model
keywords = tag_names(_extra)
loc = (where(keywords eq 'P_NAME', count))[0] ; Check if polygon was addressed by name.
if count ne 0 then begin
	polygon = model -> GetByName(_extra.(loc))
	if not obj_valid(polygon) then begin
	polygon = obj_new('IDLgrPolygon', coords, _extra=_extra, xcoord_conv=self.norm_x, ycoord_conv=self.norm_y)
	model -> add, polygon
	endif
	polygon -> setProperty, _extra=_extra, name=_extra.p_name
endif else $
	if not obj_valid(polygon) then begin
	polygon = obj_new('IDLgrPolygon', coords, _extra=_extra, xcoord_conv=self.norm_x, ycoord_conv=self.norm_y)
	model -> add, polygon
	endif
if keyword_set(kill) then obj_destroy, polygon
self -> draw
end

;*************************************************************************
; Adding a CONTOUR to the plot object.
pro PARgrDisplay::contour, data=data
compile_opt IDL2, OBSOLETE

if n_elements(data) ne 0 then begin
	temp = obj_new('IDLgrContour', /planar)
	self -> getProperty, model=model
	temp -> setProperty, n_levels=20, data_values=data, $
		xcoord_conv=self.norm_x, ycoord_conv=self.norm_y
	model -> add, temp
	self -> setProperty, trash=temp, /draw
endif

end

;*************************************************************************
; Updates scaling of the plot if the data on it has changed through IDLgrPlot keywords.
pro PARgrDisplay::rescale, keep_scale=keep_scale, draw=draw
compile_opt IDL2, OBSOLETE

plot_list = self.plot_list -> get(/all, count=count)
if count ne 0 then begin
	plot_list[0] -> getProperty, data=data
	self.xrange = get_range(data[0, *])
	self.yrange = get_range(data[1, *])
endif else return

if count gt 1 then $
for i = 1, n_elements(plot_list)-1 do begin
	plot_list[i] -> getProperty, data=data
	xrange = get_range(data[0, *])
	yrange = get_range(data[1, *])
	; Find new ranges. They may expand the existing ranges, or may not.
	xrange = [self.xrange[0] < xrange[0], self.xrange[1] > xrange[1]]
	yrange = [self.yrange[0] < yrange[0], self.yrange[1] > yrange[1]]
	self.xrange = xrange
	self.yrange = yrange
endfor

if not keyword_set(keep_scale) then begin
	plot_list[0] -> zoom, self.obj_win, xrange=self.xrange, yrange=self.yrange, $
		norm_x=norm_x, norm_y=norm_y, draw=keyword_set(draw)
		; Store new coord_conv in the object.
	self.norm_x = norm_x
	self.norm_y = norm_y
endif

end

;*************************************************************************
; Removes selected plot from the graph and kills empty references in Containers.
pro PARgrDisplay::killplot, plot_id, mpress=mpress, draw=draw
compile_opt IDL2, OBSOLETE

if keyword_set(mpress) and size(plot_id, /type) ne 11 then begin
	view = self.view
	plot_id = (self.obj_win -> select(view, mpress))[0]
end
; If something other than a plot was selected, do nothing.
if not obj_valid(plot_id) then return
if obj_class(plot_id) ne 'IDLGRPLOT' then begin
	obj_destroy, plot_id
	if keyword_set(draw) then self.obj_win -> draw, self.view
	return
endif

; Kill the plot and remove it from the container.
plot_id -> getProperty, symbol=symbol
self -> getProperty, plot_list=plot_list, symbol_list=symbol_list
loc = (where(plot_id eq plot_list))[0]

; Remove plot and symbol from their containers.
self.plot_list -> remove, plot_id
self.symbol_list -> remove, position = loc

; Destroy these objects.
if obj_valid(symbol) then obj_destroy, symbol
obj_destroy, plot_id

; Need to update ranges, in case the plot that's removed was affecting the limits of the axes.
self -> rescale, /keep_scale ; Leave zoom at its current level.
self -> legend, /update

if keyword_set(draw) then self.obj_win -> draw, self.view

end

;*************************************************************************
; Will return link value for the object.
function PARgrDisplay::is_linked
compile_opt IDL2, OBSOLETE
return, self.link
end

;*************************************************************************
; This method links several DISPLAYs that have the same LINK flag value, using a plot that has a name
; 'LINK' and supplied region of interest index. I can't come up with how exactly to link DISPLAYs if they
; are used inside widget programs, so I leave LINK only to independent windows.
; Some loops are present here. They are quite short and result from operations on object arrays.
pro PARgrDisplay::link_roi, roi
compile_opt IDL2, OBSOLETE
Common PARgrDisplay_top_window, obj_list

	; Get list of plots from the window that initiated the ROI link.
self -> getProperty, plot_list=plot_list, link=link_id
	; If plot is not linked, exit quietly.
if link_id eq 0 then return
	; Get information about plots in the window that initiated ROI.
n_plots = n_elements(plot_list)
names = strarr(n_plots)
for i=0, n_plots-1 do begin ; Get list of plot names from Display containing ROI.
	plot_list[i] -> getProperty, name=temp
	names[i] = temp
endfor

for outer_loop=0, n_elements(obj_list)-1 do begin
		; We will link over all plots having same, non-empty names.
	obj_list[outer_loop] -> getProperty, plot_list=curr_plot_list, model=model, link=curr_link_id
		; Do not look into Display windows that have different link ID.
	if curr_link_id ne link_id then goto, leave_loop
	
	for i=0, n_elements(curr_plot_list)-1 do begin
		curr_plot_list[i] -> getProperty, name=temp ; Do not get DATA here to reduce memory use.
			if temp eq '' then goto, leave_loop ; Ignore plots whos name was not explicitly specified.
			if strpos(temp, 'Linked') ne -1 then goto, leave_loop ; Ignore links, or it may get confusing.
			
		loc = where(names eq temp, count)
			if count eq 0 then goto, leave_loop ; If the name is not common with ROI-initiator, leave.
			if count gt 1 then message, 'More than one plot named '+temp+' is present.', /informational
			
				; If a plot is already linked, add a number to name's end to help identify different subsets.
			added_name = where(strpos(names, 'Linked '+temp) ne -1, count)
			if count gt 0 then begin
				added_name = names[added_name[count -1]]
				last_number = strpos(added_name, ' ', /reverse_offset, /reverse_search) > 0
				last_number = strmid(added_name, last_number+1)
				if (byte(last_number))[0] gt 57b then added_name = 'Linked '+temp+' 1' $
				else added_name = 'Linked '+temp+' '+strcompress(fix(last_number)+1, /rem)
			endif else added_name = 'Linked '+temp ; Done adding number to "linked xxxx ".
		
		plot_list[loc[0]] -> getProperty, data=data ; Get data from the parent DISPLAY's plot.
		roi_index = roi -> ContainsPoints(data) ; This is index of points in "LINK" enclosed by ROI.
		inside_index = where(roi_index gt 0, count) ; Include points inside, on borders and on verteces of ROI.
			if count eq 0 then goto, leave_loop ; If there are no points inside ROI for this NAME, leave.
		
		curr_plot_list[i] -> getProperty, data=data ; Use the same variable to reduce memory use.
		data = data[*, inside_index] ; Restrict data to ROI index.
		
			; Use DISPLAY to add a plot to the linked window.
		display, data[0, *], data[1, *], psym=4, color=self.roi_color, linestyle=6, oref=obj_list[outer_loop], /over, $
			name=added_name
		leave_loop:
	endfor
endfor ; outer_loop

end

;*************************************************************************
; Procedure for printing the selected Display window.
pro PARgrDisplay::printout
compile_opt IDL2, OBSOLETE

	; Get main PARgrDisplay properties.
	self -> getProperty, view=view, Obj_win=Obj_win, model=model
	Printer = obj_new('IDLgrPrinter') ; Create a printer object.
	printer -> getProperty, dim=prt_dim
	
	; Get dimensions of Obj_win, Screen and Printer to preserve the XY ratio of the object plot.
	Obj_win -> getproperty, dim=win_dim
	scr_dim = get_screen_size()
	
	; Take care to preserve PARgrDisplay's X to screen width ratio and aspect ratio.
	x_ratio = win_dim[0] / scr_dim[0]
	aspect_ratio = win_dim[1] / win_dim[0]
	; Now, scale the plot so that X takes X_RATIO of the printed width and Y is factor of
	; ASPECT_RATIO with respect to the new X size.
	model -> scale, x_ratio, aspect_ratio*x_ratio, 1
	
	; Ready to print. Allow choosing printing options.
	ok = dialog_printjob(Printer)
	if ok then Printer -> draw, view, vector=0
	view -> setproperty, location=[0.0, 0.0], dimensions=[0, 0]
	; Return the model to the original scale.
	model -> scale, 1/x_ratio, 1/(aspect_ratio*x_ratio), 1
	; Printer object no longer needed.
	obj_destroy, Printer

end

;*************************************************************************
; Allows to copy the object to the clipboard
pro PARgrDisplay::copy, _extra=_extra
compile_opt IDL2, OBSOLETE
	temp = obj_new('IDLgrClipboard', _extra=_extra)
	temp -> draw, self.view, _extra=_extra
	obj_destroy, temp
end

;*************************************************************************
; Allows to save the current display window, with all the data, for future restoring.
pro PARgrDisplay::save, _extra=_extra
compile_opt IDL2, OBSOLETE
save, self, _extra=_extra
end

;*************************************************************************
; This procedure allows to create a window for a restored contents of a previously saved DISPLAY.
; The only two properties of SELF that pertain to GUI are SELF.TOP_ID and SELF.OBJ_WIN, so they will
; need to be replaced with valid references.
pro PARgrDisplay::gui
compile_opt IDL2, OBSOLETE
on_error, 1
	if obj_class(self) ne 'PARGRDISPLAY' then return

	self.top_ID = widget_base(/kbrd_focus_ev, /tlb_size_ev, uvalue=self)

	if self.wtitle ne '' then widget_control, self.top_ID, tlb_set_title=self.wtitle $
	else widget_control, self.top_ID, tlb_set_title='Display window '+strtrim(self.top_ID, 2)

	; Draw area is a child of self.top_ID. Restoring is for interactive use only, so Parent etc. is not needed.
	draw_ID = widget_draw(self.top_ID, graphics=2, /button_event, xsize=self.window_xsize, $
		ysize=self.window_ysize, event_pro='Display_event', retain=2, uvalue=self)
		
	; Need to realize it all now, because we can only get value of realized widget_draw in object mode.
	widget_control, self.top_ID, /realize
	xmanager, 'display', self.top_ID, /No_block, cleanup='display_cleanup'

	widget_control, draw_ID, get_value=Obj_Win
	obj_destroy, self.obj_win ; Delete this invalid reference...
	self.obj_win = obj_win ; ...replace it with the newly created one...
	self -> draw ; ... and update the contents of DISPLAY.

end

;*************************************************************************
; There also is a DRAW keyword to SetProperty procedure. This one is short and fast and is used
; when drawing is the only thing done at that particular time.
pro PARgrDisplay::draw
compile_opt IDL2, OBSOLETE
self.obj_win -> draw, self.view
end

;*************************************************************************
; The following procedure will analyze the plot_list container, get all needed info and create a legend
; in the form of a separate model (IDLgrLegend) that will be moveable etc.
pro PARgrDisplay::legend, legend_title=legend_title, kill=kill, update=update, _extra=_extra
compile_opt IDL2, OBSOLETE

self -> getproperty, second_layer=model
	; Check if there is a legend already.
legend = model -> get(isa='IDLGRLEGEND', /all) ; Legend is unique for its own set of plots.
if obj_valid(legend) and keyword_set(kill) then begin
	obj_destroy, legend ; Kill the legend if KILL is set.
	self -> draw
	return
endif

if not obj_valid(legend) then begin ; Only add a legend if it is new.
	if keyword_set(update) then return ; If there was no legend, do not create one. Only update if one existed.
	legend = obj_new('IDLgrLegend')
	legend -> setProperty, gap=0.5, glyph_width=3.
	model -> add, legend
endif

Plot_list = self.Plot_list -> get(/all, count=count)
if count eq 0 then begin
	; If the last plot was removed from DISPLAY, remove all legend information but keep the legend there.
	; This way, if a user adds a plot to DISPLAY, it will automatically show up in the legend.
	legend -> setProperty, item_name='', item_object=obj_new()
	return ; If no plots are present, quit.
endif

item_color = fltarr(3, count)
item_name = strarr(count)
item_linestyle = bytarr(count)
item_object = objarr(count)

for i=0, count-1 do begin ; Get information from all plot objects in DISPLAY.
	plot_list[i] -> getProperty, color=color, symbol=symbol, linestyle=linestyle, name=name
	item_color[*, i] = color
	if name ne '' then item_name[i] = name else item_name[i] = 'Plot '+strcompress(i, /rem)
	item_linestyle[i] = linestyle
	if obj_valid(symbol) then item_object[i] = symbol
endfor

if n_elements(legend_title) ne 0 then begin ; If legend_title was provided, create title object and store it in Container.
	title_obj = obj_new('IDLgrText', legend_title, _extra=_extra, name='legend_title', recompute=2)
	self.container -> add, title_obj
endif

	; Set all properties of the legend according to the currend window contents.
legend -> setProperty, item_name=item_name, item_color=item_color, item_linestyle=item_linestyle, $
	item_object=item_object, /select_target, title=title_obj, _extra=_extra

self -> draw

end

;*************************************************************************
; This method will add text to Display object. Text will be moveable using the right mouse button.
; The text is updatable given it was named at the time of its creation. To update the text, pass
; its new value as an argument and set T_NAME keyword to the name of the text block.
; Named text block can also be killed programmatically using /KILL ketword.
; Data_scale keyword will place text into the data coordinates that it is set to.
pro PARgrDisplay::text, value, t_name=t_name, kill=kill, data_scale=data_scale, _extra=_extra
compile_opt IDL2, OBSOLETE

; Determine the location for the new text right in the middle of Display.
n_lines = n_elements(value)
if n_lines eq 0 then return ; If TEXT was not provided, do nothing.

loc = fltarr(2, n_lines)

if n_elements(data_scale) ne 0 then begin
	self -> getProperty, model=model
	norm_x = self.norm_x
	norm_y = self.norm_y
endif else begin
	self -> getProperty, second_layer=model ; Get Model to add Text to.
	loc[*] = 0.5 ; Set up the location for the new text in the middle of the window.
endelse
temp = 'void' ; Initialize TEMP with something invalid.

if size(t_name, /type) eq 7 then temp = model -> getbyname(t_name)
if keyword_set(kill) then begin ; Kill text if requested.
	obj_destroy, temp
	return
endif

; Creating new text in DISPLAY window.
if obj_class(temp) ne 'IDLGRTEXT' then begin ; Create new text object.
	temp = obj_new('IDLgrText', value, alignment=0.5, recompute=2, $
		xcoord_conv = norm_x, ycoord_conv = norm_y, _extra=_extra) ; Create new object...
	if size(t_name, /type) eq 7 then temp -> setProperty, name=t_name
	model -> add, temp ; ...add it to second_layer...
	self.container -> add, temp ; ...add it to Container.
	if n_elements(data_scale) eq 2 then begin
		loc[0, *] = data_scale[0] ; Set up the location for the new text.
		loc[1, *] = data_scale[1] ; Set up the location for the new text.
	endif
	if n_lines gt 1 then begin
		char_dim = self.obj_win -> GetTextDimensions(temp)
		spacing = char_dim[1]*1.5
		loc[1, *] = loc[1, *] - transpose(indgen(n_lines)*spacing)
		temp -> setProperty, location=reform(loc)
	endif
endif else begin
	temp -> getProperty, location=former_position, strings=old_contents
	n_old_strings= n_elements(old_contents)
	if n_old_strings gt 1 then begin
		char_dim = self.obj_win -> GetTextDimensions(temp)
		spacing = char_dim[1]/n_old_strings
		loc[1, *] = loc[1, *] - transpose(indgen(n_lines)*spacing)
	endif
	if n_elements(data_scale) eq 2 then begin
		loc[0, *] = loc[0, *]+data_scale[0] ; Set up the location for the new text.
		loc[1, *] = loc[1, *]+data_scale[1] ; Set up the location for the new text.
	endif else begin
		loc[0, *] = former_position[0]+loc[0, *] ; Set up the location for the new text.
		loc[1, *] = former_position[1]+loc[1, *] ; Set up the location for the new text.
	endelse
	temp -> setProperty, strings=value, location=reform(loc), _extra=_extra
endelse

self.obj_win -> draw, self.view

end

;*************************************************************************
; This is a narrow scope method for extracting the data from displayed plots. It can be utilized (at least
; was designed) for extracting data produced by legacy code for plotting, after PLOT command was substituted
; with DISPLAY. Now, this enables us to retain and reuse the data produced by that legacy code.
; Data are returned as a structure whos fields have same names as the plots the data were pulled out from,
; and field values are FLTARR(3, n) where (0, n) is X, (1, n) is Y and (2, n) is Z dimensions of the displayed data.
; Notice that the first field of the returned structure is VOID.
function PARgrDisplay::get_data
compile_opt IDL2, OBSOLETE

self -> getProperty, plot_list=plot_list
n_plots = n_elements(plot_list)
if n_plots eq 0 then return, 0.0
output = create_struct('Void', 'Void')

for i = 0, n_plots-1 do begin
	plot_list[i] -> getProperty, data=data, name=name
	if strlen(name) eq 0 then name = 'Plot '+strcompress(i, /rem)
	name = str_cleanup(name)
	output = create_struct(output, name, data)
endfor

return, output

end

;*************************************************************************
; Initializes the object itself and creates widget system for displaying object data.
function PARgrDisplay::init, parent=parent, group_leader=group_leader, ylog=ylog, xlog=xlog, $
	font=font, charsize=charsize, window_xsize=window_xsize, window_ysize=window_ysize
	
compile_opt IDL2, OBSOLETE

; Provide error handling.
Catch, Err_code
if (Err_code ne 0) then begin
	print,'PARgrDisplay::init error caught:'
	help, /last_message
	catch, /cancel
	return, 0
endif

; If Log keywords are not provided, init them as No.
if not keyword_set(xlog) then xlog = 0b
if not keyword_set(ylog) then ylog = 0b
if n_elements(window_xsize) eq 0 then window_xsize = 600
if n_elements(window_ysize) eq 0 then window_ysize = 350
self.window_xsize = window_xsize
self.window_ysize = window_ysize

; Make it easy to assign values to pointer fields.
temp = n_tags({PARgrDisplay})
for i=0, temp-1 do if size(self.(i), /type) eq 10 then self.(i) = ptr_new(/allocate)

a_child = 0b ; Assume we are creating independent window.
; Check if Parent was provided. If no, create top base. If yes and Parent is a base, use it.
if n_elements(parent) ne 0 then $ ; Check if Parent is even provided...
if widget_info(parent, /valid_id) then $ ; Parent is a valid widget...
if widget_info(parent, /type) eq 0 then a_child = 1b ; and it is a base. Set a flag variable.

if a_child then begin
	self.top_ID = widget_base(Parent, uvalue=self)
endif else begin
	self.top_ID = widget_base(/kbrd_focus_ev, /tlb_size_ev, uvalue=self)
	widget_control, self.top_ID, tlb_set_title='Display window '+strtrim(self.top_ID, 2)
endelse

if n_elements(group_leader) ne 0 then $ ; Check if group_leader is even provided...
if widget_info(group_leader, /valid_id) then $ ; group_leader is a valid widget...
	widget_control, self.top_ID, group_leader=group_leader ; Use that ID as group leader.

; Draw area is a child of self.top_ID regardless of whether or not parent was provided.
draw_ID = widget_draw(self.top_ID, graphics=2, /button_event, xsize=window_xsize, $
	ysize=window_ysize, event_pro='Display_event', retain=2, uvalue=self)
	
; Need to realize it all now, because we can only get value of realized widget_draw in object mode.
widget_control, self.top_ID, /realize
xmanager, 'display', self.top_ID, /No_block, cleanup='display_cleanup'

;======================== OBJECT INITIALIZATION START ============================
widget_control, draw_ID, get_value=Obj_Win
self.obj_win = obj_win
self.container = obj_new('IDL_Container')
self.axes_list = obj_new('IDL_Container')
self.title = obj_new('IDL_Container')
self.roi_color = [255,0,0]

	; Make X and Y ranges.
yrange = (xrange = [0.0, 1.0])

	; Normalize data.
n_xr = normalize(xrange)
n_yr = normalize(yrange)

	; Set ranges in the object. They would not be used normally, only if a user is clicking randomly.
self.xrange = xrange
self.yrange = yrange

	; Need to create new Model, View etc. objects.
model = obj_new('IDLgrModel')
view = obj_new('IDLgrView', viewplane_rect=[-0.1, -0.1, 1.12, 1.16], location=[0.0, 0.0])

	; Create window-wide font, if requested. If not requested, default of Helvetica 12 pt will be used.
if size(font, /type) eq 7 then $
	if n_elements(charsize) ne 0 then begin
		charsize = float(charsize)
		default_font = obj_new('IDLgrFont', font, size=12.0*charsize)
	endif else default_font = obj_new('IDLgrFont', font, size=12.0)

	; Create and initialize axes.
x_axis = obj_new('IDLgrAxis', 0, ticklen=0.03, name='X_AXIS', location=[1000, 0, 0], /exact, log=xlog, _EXTRA=extra)
y_axis = obj_new('IDLgrAxis', 1, ticklen=0.03, name='Y_AXIS', location=[0, 1000, 0], /exact, log=ylog, _EXTRA=extra)
	; Create upper and right axes.
x_axis_top = obj_new('IDLgrAxis', 0, ticklen=0.03, tickdir=1, loc=[1000, 1, 0], /notext, /exact, log=xlog, _EXTRA=extra)
y_axis_top = obj_new('IDLgrAxis', 1, ticklen=0.03, tickdir=1, loc=[1, 1000, 0], /notext, /exact, log=ylog, _EXTRA=extra)
	; Only place labels on the left and bottom axes.
	x_axis -> getProperty, ticktext=x_tick_labels
	y_axis -> getProperty, ticktext=y_tick_labels
x_tick_labels -> setProperty, recompute=2, font=default_font
y_tick_labels -> setProperty, recompute=2, font=default_font
	; Create default axis titles.
x_title = obj_new('IDLgrText', 'X title', font=default_font, recompute=2)
y_title = obj_new('IDLgrText', 'Y title', font=default_font, recompute=2)
x_axis -> setProperty, title=x_title, tickformat='(G0)'
y_axis -> setProperty, title=y_title, tickformat='(G0)'
	; Create plot title.
title = obj_new('IDLgrText', font=default_font, recompute=2, location=[0.5, 1.02], align=0.5)
	; Set axes ranges and remove extra zeros from tick labels if they are present.
X_axis -> setProperty, range=xrange, xcoord_conv=n_xr
Y_axis -> setProperty, range=yrange, ycoord_conv=n_yr
X_axis_top -> setProperty, range=xrange, xcoord_conv=n_xr
Y_axis_top -> setProperty, range=yrange, ycoord_conv=n_yr
	; Add atomic objects to the model.
model -> add, x_axis
model -> add, y_axis
model -> add, x_axis_top
model -> add, y_axis_top
model -> add, title
	; Add model to its view and add view to the view.
view -> add, model
	; Store View in PARgrDisplay.
self.view = view
	; Need to store all newly created objects in CONTAINER so that they can be easily found or killed.
second_layer = obj_new('IDLgrModel') ; This model will hold text objects and legend.
view -> add, second_layer
self.container -> add, model ; To locate with ease. Pos 0
self.container -> add, second_layer ; To locate with ease. Pos 0
if obj_valid(default_font) then self.container -> add, default_font ; To kill with ease.
	; Create separate container for Axes.
self.axes_list -> add, x_axis
self.axes_list -> add, y_axis
self.axes_list -> add, x_axis_top
self.axes_list -> add, y_axis_top
	; Create container for titles.
self.title -> add, title ; To locate or kill with ease. Pos 0
self.title -> add, x_title ; To locate or kill with ease. Pos 1
self.title -> add, y_title ; To locate or kill with ease. Pos 2
	; Update the plot.
Obj_win -> draw, self.view
;======================== OBJECT INITIALIZATION END ==============================

; Initialize container for plots.
self.plot_list = obj_new('IDL_Container')
self.symbol_list = obj_new('IDL_Container')
self.trash = obj_new('IDL_Container')

return, 1
end

;*************************************************************************
; Universal cleanup routine. Kills everything it finds, but will miss nested pointers or ORefs.
pro PARgrDisplay::cleanup
compile_opt IDL2, OBSOLETE
;Release all pointers and object references that are present in PARgrDisplay object.
tags = n_tags({PARgrDisplay})
for i=0, tags-1 do begin
	if size(self.(i), /type) eq 10 then ptr_free, self.(i)
	if size(self.(i), /type) eq 11 then obj_destroy, self.(i)
endfor
end

;*************************************************************************
; PARgrDisplay definition procedure.
pro PARgrDisplay__define
compile_opt IDL2, OBSOLETE
temp = fltarr(2)
result = {PARgrDisplay, $			; Name of class
	top_ID: 0L, $				; Top base widget ID
	wtitle: '', $				; Title of the window
	link: 0b, $				; Flag indicating that the object is to be used for linking of ROIs.
	obj_win: obj_new(), $			; Draw window reference
	view: obj_new(), $,			; View - for easy drawing.
	title: obj_new(), $			; Container with titles for plot and axes
	container: obj_new(), $		; Container for easy clean-up
	trash: obj_new(), $			; Container object for disposable items.
	plot_list: obj_new(), $			; List of all plots for easy access
	symbol_list: obj_new(), $		; List of symbols for each plot for easy access
	axes_list: obj_new(), $		; List of all axes for easy access
	ROI: obj_new(), $			; Temporary storage for ROI or zooming rectangle.
	roi_color: bytarr(3), $			; Color of the ROI.
	xrange: temp, $				; X range vector
	yrange: temp, $				; Y range vector
	norm_x: temp, $			; XCOORD_CONV setting
	norm_y: temp, $			; YCOORD_CONV setting
	mpress: intarr(2), $			; Location of mouse press
	mouse_button: 0b, $			; Mouse button type - 1 right, 2 middle, 4 left.
	window_xsize: intarr(2), $		; Horizontal size of the DISPLAY window
	window_ysize: intarr(2), $		; Vertical size of the DISPLAY window
	uvalue: ptr_new()}			; User value
end

;*************************************************************************
; Procedure to allow calling Cleanup method on the PARgrDisplay by Xmanager. This is needed
; because an object method can is not allowed in a call to Xmanager.
pro display_cleanup, top_ID
Common PARgrDisplay_top_window, got_focus

widget_control, top_ID, get_uval=Object ; Get object reference and destroy it.
obj_destroy, Object
got_focus = got_focus[where(obj_valid(got_focus)) > 0] ; Clean up the common block.

end

;*************************************************************************
; Dummy procedure to isolate the user from the object syntax of PARgrDisplay.
; Tracking of independent windows is provided through common block. Update of common block
; occurs in the event procedure, when focus changes. Therefore, daughter windows provide no updates
; to common block, because they can not generate focus events. This is the desired behavior.
pro display, x, y, ylog=ylog, xlog=xlog, over=over, psym=psym, printout=printout, charsize=charsize, $
	parent=parent, group_leader=group_leader, oref=oref, $ ; These are for programmatic access to a Display window.
	copy=copy, save=save, restore_file=restore_file, _extra=_extra
	
compile_opt IDL2, OBSOLETE
Common PARgrDisplay_top_window, temp

filename = size(restore_file, /type)
if filename ne 0 then begin ; Restore a saved DISPLAY.
	call_procedure, 'IDLgrLegend__define' ; Create the right definition of IDLgrLegend
	if filename eq 7 then filename = restore_file else filename = dialog_pickfile(filter='*.dis')
	restore, filename
	self -> gui
	return ; No other action is performed, because you need to see what you have first.
endif

if n_elements(temp) ne 0 then $
if obj_valid(temp[0]) and (not obj_valid(oref)) then oref = temp[0]
; We can afford to drop the object reference here, because it is still stored in the widget's Uvalue.
; Object will be deleted when the widget is closed.

; Provide error handling.
Catch, Err_code
if (Err_code ne 0) then begin
	print,'DISPLAY error caught:'
	help, /last_message
	catch, /cancel
	return
endif

; Create new instance if required, or used existing one to add items. Generate error on a mistake.
if not keyword_set(over) then oref = obj_new('PARgrDisplay', ylog=ylog, xlog=xlog, charsize=charsize, $
	parent=parent, group_leader=group_leader)
if not obj_valid(oref) then message, 'No window exists to overplot.'

; Set all necessary properties, update the plot.
if n_params() ne 0 then oref -> oplot, x, y, psym=psym, _extra=_extra
; The following line serves in several ways. First, it allows to bypass listing keywords for setProperty.
; Second, it will pass any extra keywords to the named plot object, if name is provided.
oref -> setProperty, psym=psym, _extra=_extra, /draw

if keyword_set(copy) then oref -> copy, _extra=_extra
; Lastly, print it out if requested.
if keyword_set(printout) then oref -> printout
if keyword_set(save) then oref -> save, _extra=_extra

end