/* This file is part of the source code for 3D-XplorMath-J, Version 1.0 (January 2008).
* Copyright (c) 2008 The 3D-XplorMath Consortium (http://3d-xplormath.org).
* This source code is released under a BSD License, which allows redistribution
* in source and binary form, with or without modification, provided copyright
* and license information are included, and with no warranty or guarantee of
* any kind. For details, see http://3d-xplormath.org/j/source/BSDLicense.txt
*/
package vmm3d.core;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import org.freehep.graphics2d.VectorGraphics;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import javax.swing.Action;
import javax.swing.JColorChooser;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.geom.*;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import vmm3d.actions.AbstractActionVMM;
import vmm3d.actions.ActionItem;
import vmm3d.actions.ActionList;
import vmm3d.actions.ActionRadioGroup;
/**
* A View represents one of possibly several views of an {@link vmm3d.core.Exhibit}.
* A View displays its Exhbit in a {@link vmm3d.core.Display}. These three classes
* are the central classes in VMM. Drawing takes place when the View's {@link #render(Graphics2D, int, int)}
* method is called. Note that this
* might potentially be called to draw the Exhibit to other destinations besides a Display, such as
* to the graphics device associated with a printer.
* This top-level View class might be used directly for simple two-dimensional Exhibits, but
* it is also likely to be used as a base class for customized Views for particular
* Exhibits or types of Exhibits. In particular, a specialized View class will be used for 3D views.
*
The View class contains several methods that can be used for drawing, such as
* {@link #drawCurve(Point2D[])}, {@link #setColor(Color)}, and {@link #drawPixel(double, double)}.
*
Note that to work correctly with the XML save/restore facility defined in {@link vmm3d.core.SaveAndRestore},
* a subclass of View must have a parameterless constructor, and it must be an independent class or
* a public nested class.
*/
public class View implements Parameterizable, Decorateable, ChangeListener {
private Display display;
private Exhibit exhibit;
private String name;
@VMMSave
private boolean applyGraphics2DTransform = true;
@VMMSave
private boolean preserveAspect = true;
@VMMSave
private boolean antialiased;
@VMMSave
private boolean showAxes;
@VMMSave
private Color background = Color.white;
@VMMSave
private Color foreground = Color.black;
private Point2D tempPoint = new Point2D.Double(); // For use in drawing ops -- not thread safe !!
private int previousWidth, previousHeight;
private Transform transform; // The transform that is used for this View. The view is automatically redrawn when this transform changes.
protected Animation buildAnimation;
/**
* Used to determine when the off-screen image needs to be redrawn. This is set to true
* when {@link #forceRedraw()} is called, and it is checked and reset to false in
* {@link #render(Graphics2D, int, int)}.
*/
protected boolean needsRedraw;
/**
* An offscreen image that is used for drawing.
*/
protected BufferedImage fullOSI;
protected int linewidth=2;
protected boolean recalculate=true;
/**
* When an exhibit is being drawn, this variable is set to the graphics context where the exhibit
* is being drawn. When the exhibit drawing is finished, the value is set to null. This variable
* is used in some of the rendering methods that are provided by this class and its 3D subclass.
* @see #render(Graphics2D, int, int)
*/
protected VectorGraphics currentGraphics;
/**
* Parameters that have been added to this View.
*/
protected ArrayList parameters;
/**
* Decorations that have been added to this view.
*/
protected ArrayList decorations;
/**
* This variable was introduced to handle a specific special case: when an image is
* created asynchronously over a period of time, as in ray-traced views of implicit
* surfaces. This variable is used only during morphing of implicit surfaces (or
* similar cases) to prevent the {@link Display} from grabbing an incomplete image and
* saving it to the {@link Filmstrip} of the morph that is being created. The value
* of this variable is set to true while the image is only partially complete, and
* is reset to false when the image is complete.
*/
protected volatile boolean buildingImageForFilmstrip;
/**
* The action for setting the showAxisProperty, which is one one of the commands returned
* by {@link #getViewCommands()}. (This is created along with the View object and is non-null.)
*/
protected AbstractActionVMM showAxesAction = new AbstractActionVMM(I18n.tr("vmm3d.core.commands.ShowAxes")) {
public void actionPerformed(ActionEvent evt) {
setShowAxes( ! getShowAxes() );
}
};
protected void setLineWidth(int width){
linewidth=width;
}
protected int getLineWidth(){
return linewidth;
}
/**
* Used for setting the background color of this View, this contains three commands
* for black background, white background, and custom background. It is one
* of the objects returned by {@link #getViewCommands()}. (This is created along with
* the View object and is non-null.)
*/
protected ActionRadioGroup backgroundCommands = new ActionRadioGroup( new String[] {
I18n.tr("vmm3d.core.commands.BlackBackground"),
I18n.tr("vmm3d.core.commands.WhiteBackground"),
I18n.tr("vmm3d.core.commands.CustomBackground")
}, 1) {
public void optionSelected(int selectedIndex) {
if (selectedIndex == 0) {
setBackground(Color.black);
setSelectedIndex(0);
}
else if (selectedIndex == 1) {
setBackground(Color.white);
setSelectedIndex(1);
}
else if (getDisplay() != null) {
Color c = JColorChooser.showDialog(getDisplay(),I18n.tr("vmm3d.core.dialogtitle.ChooseBackground"), getBackground());
if (c != null) {
setBackground(c);
setSelectedIndex(2);
}
else {
if (getBackground().equals(Color.black))
setSelectedIndex(0);
else if (getBackground().equals(Color.white))
setSelectedIndex(1);
else
setSelectedIndex(2);
return;
}
}
forceRedraw();
}
};
/**
* This is set to a true by {@link #beginDrawToOffscreenImage()} when a direct off-screen drawing operation is
* initiated. It is reset to false when {@link #endDrawToOffscreenImage()} is called (or when a normal drawing
* operation begins in {@link #render(Graphics2D, int, int)}). A subclass could test its value to tell whether
* direct offscreen drawing is in progress.
*/
protected boolean directOffscreenDrawing;
/**
* The type parameter used for creating athe offscreen image -- this is reset in View3DLit when
* black and white image is selected. It is not used at all in anaglyph stereo mode.
*/
protected int offscreenImageType = BufferedImage.TYPE_INT_RGB;
/**
* Returns a name for this View. By default, the name that is returned
* will be the same as the full class name of the class to which the View object belongs.
* However, a different name can be set using the setName method. The name
* is intended to be something that can be used internally to distinguish one
* view from other views of the same Exhibit. For a human-readable name,
* the getTitle method should be used instead of getName.
* (In practice, names are rarely used for views.)
* @see #getTitle()
*/
public String getName() {
if (name == null)
return this.getClass().getName();
else
return name;
}
/**
* Sets it to true if we need recalculate patches
*/
public void setNeedRecalculate(boolean recalculate) {
this.recalculate = recalculate;
}
/**
* Sets it to true if we need recalculate patches
*/
public boolean getNeedRecalculate() {
return this.recalculate;
}
/**
* Sets the name for this view.
* @see #getName()
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns a human-readable title for this View that could, for example, be used in
* a menu that is presented to the user. The title is obtained by using the name
* returned by getName as a key in the method {@link I18n#tr(String)}. This allows
* for easy internationalization. Note that if the I18n.tr method does not
* find a translation for the name, then the title will be the same as the name.
* (In practice, titles are rarely used for views.)
* @see #getName()
* @see I18n#tr(String)
*/
public String getTitle() {
return I18n.tr(getName());
}
/**
* Returns the Display where this View is installed. If it is not installed
* in a Display, the return value is null.
* @see Display#install(View, Exhibit)
*/
public Display getDisplay() {
return display;
}
/**
* Sets the Display where this View is installed. This method will ordinarily be
* called only by the install method in the Display class, which is used to install a
* View into a Display. It is not meant to be called directly (since that would bypass a lot of
* the set-up performed automatically by the install method).
* @see Display#install(View, Exhibit)
*/
public void setDisplay(Display display) {
this.display = display;
}
/**
* Get the Exhibit that is shown in this View. If no Exibit is currently associated with
* the view, then the return value is null.
*/
public Exhibit getExhibit() {
return exhibit;
}
/**
* Set the Exhibit that is shown in this View. If the View is already showing an
* Exhibit, it is first removed from the View. When an Exhibit is installed, the
* transform associated with the View is set to the default transform of the Exhibit.
* If there is a Display associated with the View, its repaint method is called.
* This setExhibit method will be called automatically when an Exhibit is installed in a Display;
* although it might be called directly, that would be much less usual since it would
* by-pass all the set-up that is done automatically by the Display's install method.
* If the exhibit is non-null, then the foreground and background colors of the view are set
* to the default foreground and background colors of the exhibit. If the exhibit is null,
* then the view's foreground and background are set to black and white.
*
Note when overriding this method: You should almost certainly start by saying
* "if (exhibit == getExhibit()) return". That is, in general, this method should do nothing
* when the exhibit that is being installed is already installed.
* @see Display#install(View, Exhibit)
* @param exhibit The exhibit to be installed in this View. The value can be null.
* In that case, no Exhibit will be associated with the View after the method executes.
* If exhibit is already installed in this View, then no changes are made.
*/
public void setExhibit(Exhibit exhibit) {
if (exhibit == this.exhibit)
return;
if (this.exhibit != null) {
this.exhibit.removeChangeListener(this);
this.exhibit.removeView(this);
}
this.exhibit = exhibit;
if (exhibit == null) {
setTransform(null);
setBackground(null);
setForeground(null);
}
else {
exhibit.addChangeListener(this);
exhibit.addView(this);
setTransform(exhibit.getDefaultTransform(this));
setBackground(exhibit.getDefaultBackground());
setForeground(exhibit.getDefaultForeground());
}
forceRedraw();
}
/**
* This method is called by the {@link Display} that contains this View, just before the
* view is removed from the display. It gives the view a chance to clean up. The method
* in this top-level view class does nothing. It is not likely that this method will be called
* directly.
*/
public void finish() {
}
/**
* Sets the exhibit of this View equal to the same Exhibit that is displayed by another specified View.
* This has the same effect as {@link #setExhibit(Exhibit)}, except that instead of taking the
* colors and transform from the default properties of the Exhibit, they are copied from the
* specified View.
* @param view The view whose Exhibit is to be copied. After the method is called, both views will
* be drawing the same exhibit. If null, then the exhibit of this view will also be set to null.
* @param shareTransform If true, the two views will share the same Transform, so that changes made to the transform in
* one will also result in a change in the other.
*/
public void takeExhibit(View view, boolean shareTransform) {
Exhibit exhibit = (view == null)? null : view.getExhibit();
if (exhibit == null)
setExhibit(null);
else {
setExhibit(exhibit);
setBackground(exhibit.getDefaultBackground());
setForeground(exhibit.getDefaultForeground());
if (shareTransform)
setTransform(view.getTransform());
else
setTransform((Transform)view.getTransform().clone());
}
}
/**
* This method is called by the display to determine what message to show in the status
* bar when this view is active. This message is used when there is no animation or one-shot
* mouse task active and the default mouse task's getStatusText returns null.
* If the return value is null, then the title of the exhibit is used
* as the status string (or, if there is no exhibit, then the string "No Exhibit" is used (in the
* English version)).
* @return returns null to use the default string.
*/
public String getStatusText() {
return null;
}
/**
* Returns the value of the showAxes property.
* @see #setShowAxes(boolean)
*/
public boolean getShowAxes() {
return showAxes;
}
/**
* If set to true, an Axes decoration is added to the View. When set to false,
* the decoration is removed. The decoration is actually whatever is returned
* by the {@link #createAxes()}.
*/
public void setShowAxes(boolean show) {
if (show != showAxes) {
if (show) {
Axes2D axes = createAxes();
addDecoration(axes);
}
else {
for (int i = decorations.size() - 1; i >= 0; i--)
if (decorations.get(i) instanceof Axes2D)
decorations.remove(i);
forceRedraw();
}
if (show)
showAxesAction.putValue(Action.NAME, I18n.tr("vmm3d.core.commands.HideAxes"));
else
showAxesAction.putValue(Action.NAME, I18n.tr("vmm3d.core.commands.ShowAxes"));
showAxes = show;
}
}
/**
* Create an "Axes" decoration that can be added to this view. A subclass should override
* this to return an object of the appropriate class for the view. Note that this assumes
* that Axes3D will be a subclass of Axes2D.
*/
protected Axes2D createAxes() {
return new Axes2D();
}
/**
* Returns a MouseTask that can be installed in the Display attached to this View. This method
* is called automatically by the Display class when a View is intalled, and it should not be
* necessary to call it directly. A sub-class of View can override this to provide an appropriate
* MouseTask that will let the user interact with the View; the method can return null if there
* should be no default mouse interaction.
* @return The value returned in the top-level View class is a {@link BasicMouseTask2D}, which allows
* dragging and zooming of the 2D window.
* @see MouseTask
*/
public MouseTask getDefaultMouseTask() {
return new BasicMouseTask2D();
}
/**
* Returns the value of the applyGraphics2DTransform property.
* @see #setApplyGraphics2DTransform(boolean)
*/
public boolean getApplyGraphics2DTransform() {
return applyGraphics2DTransform;
}
/**
* If the applyGraphics2DTransform property is set to true, then a transformation will be
* applied to the org.freehep.graphics2d.VectorGraphics objects that are used to draw this View's Exhibit. After the
* transformation is applied, real-valued xy-coordinates can be used for drawing the Exhibit
* instead of pixel coordinates. The default value of the property is true. Subclasses that
* want to use direct pixel coordinates should set it to false. (For cases where direct pixel
* coordinates are used only part of the time, see {@link Transform#getUntransformedGraphics()}.)
*
Note: When this property is true, a stroke size is also set in the graphics context used
* to draw the exhibit. If the preserveAspect property is also true, which is the default, then the
* stroke size is one pixel width. However, if preserveAspect is set to false, the stroke size
* is set to the smaller of the pixel width and pixel height.
*/
public void setApplyGraphics2DTransform(boolean applyGraphics2DTransform) {
if (applyGraphics2DTransform != this.applyGraphics2DTransform) {
forceRedraw();
this.applyGraphics2DTransform = applyGraphics2DTransform;
}
}
/**
* Returns the value of the preserveAspect property.
* @see #setPreserveAspect(boolean)
*/
public boolean getPreserveAspect() {
return preserveAspect;
}
/**
* If the preservAspect property is set to true, then the 2D window requested for
* the View is adjusted so that the shape of the rectanglar window in the xy-plane
* has the same aspect ratio as the rectangle of pixels to which the view is being drawn.
* The adjusted 2D window is the smallest window that will show the entire ranges of
* x and y values that were requested. The default value of the preserveAspect property is true.
* @see #setWindow(double, double, double, double)
* @see #setApplyGraphics2DTransform(boolean)
* @see Transform#setUpDrawInfo(Graphics2D, int, int, int, int, boolean, boolean)
*/
public void setPreserveAspect(boolean preserveAspect) {
if (preserveAspect != this.preserveAspect) {
forceRedraw();
this.preserveAspect = preserveAspect;
}
}
/**
* Returns the value of the antialiased property.
* @see #setAntialiased(boolean)
*/
public boolean getAntialiased() {
return antialiased;
}
/**
* If the antialiased property is true, then antialiasing is enabled in the Graphics2D
* objects that are used to draw this View's exhibit. The default value is false.
* @param antialiased
*/
public void setAntialiased(boolean antialiased) {
if (antialiased != this.antialiased) {
forceRedraw();
this.antialiased = antialiased;
}
}
/**
* Sets the rectangular "window" in the xy-plane for this View. This is ignored unless an
* Exhibit has been installed in the View. When an Exhibit is installed, the
* window is set to that Exhibit's default window. Note that it is not
* enforced that xmax must be greater than xmin and that ymax must be greater
* than ymin. The actual range of values shown in the drawing area might be
* adjusted if the preserveAspect property is true, but the window specified here is guaranteed
* to be visible in the drawing area.
* @see Exhibit#getDefaultWindow()
* @see #setPreserveAspect(boolean)
* @param xmin The lower limit of x-values in the xy-plane.
* @param xmax The upper limit of x-values in the xy-plane.
* @param ymin The lower limit of y-values in the xy-plane.
* @param ymax The upper limit of y-values in the xy-plane.
*/
public void setWindow(double xmin, double xmax, double ymin, double ymax) {
if (transform != null)
transform.setLimits(xmin,xmax,ymin,ymax);
}
/**
* Sets the window in the xy-plane for this View. The limits are set to
* (window[0], window[1], window[2], window[3]).
* @see #setWindow(double, double, double, double)
* @param window If non-null, must be an array containing at least four doubles. If
* the value is null, this method does nothing. A non-null value with fewer than four
* elements will cause an array index out of bounds exception.
*/
public void setWindow(double[] window) {
if (window != null)
setWindow(window[0],window[1],window[2],window[3]);
}
/**
* Returns a four-element array containing the limits of the window for this View in the
* xy-plane, assuming that an Exhibit has been installed in the View. If no Exhibit
* has been installed, the return value is null. The four elements of the array are
* the xmin, xmax, ymin, and ymax of the rectangle, in that order. The values here
* might be different from those specified in setWindow, if the preserveAspect property is true;
* the actual value adjustment is made only when the Exhibit is drawn.
* @see #setWindow(double, double, double, double)
* @see #getRequestedWindow()
* @see #setPreserveAspect(boolean)
*/
public double[] getWindow() {
if (transform == null)
return null;
else
return new double[] { transform.getXmin(), transform.getXmax(), transform.getYmin(), transform.getYmax() };
}
/**
* Returns the transform that is used when drawing this View.
* This is the transform that is applied to xy-points (or, in 3D views, to xyz-points) to convert them to
* pixel coordinates for drawing on the screen. This transform is initially obtained by calling
* an exhibit's {@link Exhibit#getDefaultTransform(View)} method when that exhibit is installed
* in this View. Note that the transform is not necessarily completely valid unless a drawing
* operation is in progress during a call to {@link #render(Graphics2D, int, int)}.
*
This is not guaranteed to be
* valid in all respects unless a drawing operation is in progress. However, the window in the xy-plane
* and the 3D projection, if any, are valid.
*/
public Transform getTransform() {
return transform;
}
/**
* Sets the transform for this view to a specified transform, with no error checking.
* This is meant mainly for use in the {@link SaveAndRestore} class.
*/
public void setTransform(Transform transform) {
if (transform != null)
transform.removeChangeListener(this);
this.transform = transform;
if (transform != null)
transform.addChangeListener(this);
}
/**
* Get the window that was originally requested in setWindow. This might differ from the values returned
* by getWindow if the preserveAspect property is true.
* @see #setWindow(double, double, double, double)
* @see #getWindow()
*/
public double[] getRequestedWindow() {
if (transform == null)
return null;
else
return new double[] { transform.getXminRequested(), transform.getXmaxRequested(), transform.getYminRequested(), transform.getYmaxRequested() };
}
/**
* Gets the background color used when rendering this view.
* @see #setBackground(Color)
*/
public Color getBackground() {
return background;
}
/**
* Set the background color that will be used for rendering this view. If the specified color is null,
* then the default color (white) is used. Note that the background color is automatically reset when
* an exhibit is installed into this View. If the exhibit is non-null, then the background color is
* set using {@link Exhibit#getDefaultBackground()}; if a null exhibit is installed, the background color
* is set to white. So, this method is only for adjusting the background color while an exhibit is displayed.
* Changing the background color will cause a redraw.
*
In addition to setting the background color, this method will set the foreground color to either
* black or white, depending on whehter the background color is light or dark. It is possible to change
* the foreground color to something else after setting the background.
*/
public void setBackground(Color c) {
if (c == null) {
if (exhibit != null)
c = exhibit.getDefaultBackground();
else
c = Color.white;
}
if (c.equals(background))
return;
background = c;
if (c.getRed() < 150 && c.getGreen() < 120 && c.getBlue() < 150)
setForeground(Color.white);
else
setForeground(Color.black);
forceRedraw();
if (Color.black.equals(getBackground()))
backgroundCommands.setSelectedIndex(0);
else if (Color.white.equals(getBackground()))
backgroundCommands.setSelectedIndex(1);
else
backgroundCommands.setSelectedIndex(2);
}
/**
* Get the foreground color used for rendering this view.
* @see #setForeground(Color)
*/
public Color getForeground() {
return foreground;
}
/**
* Set the foreground color to be used for rendering this view. If the specified color is null, then
* the default (black) is used.
* The foreground color is set automatically when a new exhibit is installed
* or when the background color is changed by calling {@link #setBackground(Color)}. As a result, this
* method will rarely be used.
*/
public void setForeground(Color c) {
if (c == null) {
if (exhibit != null)
c = exhibit.getDefaultBackground();
else
c = Color.white;
}
if (c.equals(foreground))
return;
foreground = c;
forceRedraw();
}
/**
* Sets the fastDrawing property, which tells whether the Exhibt in this View should be drawn as quickly as possible.
* Most programmers will not need to set this property, but should test it with {@link #getFastDrawing()} when the
* usual rendering method takes a long time. (Note that the "fastDrawing" property is shared by any "synchronized"
* views, and the actual variable that represents this property is stored in the Transform object that is shared
* by the synchronized views.)
*
When the value of fastDrawing is changed by this method, the view will be redrawn. Any synchronized views will also be redrawn,
* unless the redrawSynchronizedViews parameter is false. (This method was
* introduced to be used during resizing of a DisplayXM in the 3DXM application, where only one view should be redrawn.)
* @see #setFastDrawing(boolean)
*/
public void setFastDrawing(boolean fast, boolean redrawSynchronizedViews) {
if (transform != null) {
transform.setFastDrawing(fast,redrawSynchronizedViews);
if (!redrawSynchronizedViews)
forceRedraw(); // redraw this view in any case.
}
}
/**
* Sets the fastDrawing property, which tells whether the Exhibit should be drawn as quickly as possible.
* The property actually applies to any views that are "synchronized" with this view (because they share its transform).
* Most programmers will not need to set this property, but should test it with {@link #getFastDrawing()} when the
* usual rendering method takes a long time.
*
When the value of fastDrawing is changed by this method, this view and any synchronized views will be redrawn.
*/
public void setFastDrawing(boolean fast) {
setFastDrawing(fast,true);
}
/**
* Tells whether the Exhibit should be drawn as quickly as possible. Exhibits and Decorations
* can check the setting of this fastDrawing property if they ordinarily take a long time to draw.
* If their ordinary drawing is fast, they don't have to worry about it. Fast drawing might be used, for example,
* during a mouse drag, zoom, or rotate operation. The fastDrawing property is set, for example
* in {@link BasicMouseTask2D} and {@link vmm3d.core3D.BasicMouseTask3D} during mouse manipulation of the exhibit.
*/
public boolean getFastDrawing() {
return (transform == null)? false : transform.getFastDrawing();
}
//----------------- XML IO -------------------------------------------------------
/**
* This method is called when an XML representation of this view is being constructed by the
* {@link SaveAndRestore} class to give the View a chance to add any extra infomation that
* is not saved by default. Any Parameters associated with the View are
* saved automatically, as are the transform and the applyGraphics2DTransform,
* antialiased, and preserveAspect properties. Decorations associated with the View are
* not saved automatically UNLESS they are marked with the {@link vmm3d.core.VMMSave}
* annotation. Property variables will also be saved automatically IF they are marked
* with {@link vmm3d.core.VMMSave} annotations.
*
The method in this top-level View class does nothing.
*
When a subclass overrides this method, it should start by calling
* super.addExtraXML(containingDocument,viewElement) to make sure that information from the superclass
* is saved.
* @param containingDocument The overall XML document that contains the view Element that is being created.
* This parameter is necessary because it is needed to create any nested subelements that are to be added
* to the view element.
* @param viewElement The XML element that is being constructed. This element already exists; the
* purpose of this method to add any extra information that would be needed to reconstruct this view
* object from the XML represenation.
* @see #readExtraXML(Element)
*/
public void addExtraXML(Document containingDocument, Element viewElement) {
}
/**
* This method is called when this View is being reconstructed from an XML representation by the
* {@link SaveAndRestore} class. The View object has already been created, and default information
* (parameters, colors, and transform) have been retrieved.
* This method is responsible
* for retrieving any data that was written by {@link #addExtraXML(Document, Element)},
* except that properties written with {@link SaveAndRestore#addProperty(Object, String, Document, Element)}
* are retrieved automatically, and decorations written with {@link SaveAndRestore#addDecorationElement(Document, Element, Decoration)}
* are retrieved automatically.
* The method in this top-level View class does nothing.
*
In general, when a subclass overrides this method, it should be sure to call
* super.readExtraXML(viewInfo).
* @param viewInfo The <view> element from the XML file that contains the information about this
* view. Some methods from the {@link SaveAndRestore} class might be useful for getting the data.
* @throws IOException If an error is found, an exception of type IOException should be thrown.
* This will abort the whole processing of the XML file.
*/
public void readExtraXML(Element viewInfo) throws IOException {
}
//------------------------------- Parameter Handling --------------------------
/**
* Returns an array containing all the parameters associated with this View.
* No parameters are defined by the View class itself. Subclasses can add
* parameters using the addParameter method. Note that in the array returned
* by this method, parameters are listed in the REVERSE of the order in which
* they were added to the View.
* @return An array containing the parameters that have been added to this view; these are
* the actual parameters, not copies. Changing a parameter will ordinarily cause
* the View to be redrawn to reflect the change. The return value is non-null. If
* there are no parameters, a zero-length array is returned.
*/
public Parameter[] getParameters() {
if (parameters == null)
return new Parameter[] {};
else {
Parameter[] params = new Parameter[parameters.size()];
for (int i = params.length-1; i >= 0; i--)
params[params.length-i-1] = parameters.get(i);
return params;
}
}
/**
* Returns an array of Parameters containing those that have been added to
* the Exhibit (if any) that is displayed in this view, followed by those that
* have been added to this view.
* In addition, if the Exhibit is a {@link UserExhibit}, then the View parameters
* from the user exhibit are also added, between the Exhibit and the Exhibtit parameters.
* @see Exhibit#getParameters()
* @see UserExhibit.Support#getFunctionParameters()
*/
public Parameter[] getViewAndExhibitParameters() {
Parameter[] exhibitParams = null;
Parameter[] functionParams = null;
if (exhibit != null) {
exhibitParams = exhibit.getParameters();
if (exhibit instanceof UserExhibit)
functionParams = ((UserExhibit)exhibit).getUserExhibitSupport().getFunctionParameters();
}
int ct = (parameters == null)? 0 : parameters.size();
ct += (exhibitParams == null)? 0 : exhibitParams.length;
ct += (functionParams == null)? 0 : functionParams.length;
Parameter[] params = new Parameter[ct];
int pos = 0;
if (functionParams != null)
for (int i = 0; i < functionParams.length; i++)
params[pos++] = functionParams[i];
if (exhibitParams != null)
for (int i = 0; i < exhibitParams.length; i++)
params[pos++] = exhibitParams[i];
if (parameters != null)
for (int i = 0; i < parameters.size(); i++)
params[pos++] = parameters.get(i);
return params;
}
/**
* Returns a paramter that has been added to this View and that has the specified name.
* If name is null, or if there is no parameter with the specified name, then the
* return value is null.
*/
public Parameter getParameterByName(String name) {
if (parameters != null && name != null) {
for (int i = 0; i < parameters.size(); i++)
if (name.equals(parameters.get(i).getName()))
return parameters.get(i);
}
return null;
}
/**
* Associate a parameter with this view. This method is protected, so it can only be
* called by a subclass. This View is set to be the owner of the Parameter. (In practice,
* parameters are rarely associated with views; they are much more likely to be associated
* with exhibits.)
* @param param The parameter to be added. If the parameter is null, it is ignored.
* If the parameter has already been added to the View, it is not added for a second time.
*/
public void addParameter(Parameter param) {
if (param == null)
return;
if (parameters == null)
parameters = new ArrayList();
if (parameters.contains(param))
return;
parameters.add(param);
param.setOwner(this);
}
/**
* Removes a parameter from this View. The method is protected, so it can only be used by
* subclasses (and, in fact, it will only rarely be used).
* @param param The parameter to be removed. If the value is null or if the parameter is
* not associated with this View, then nothing is done.
*/
public void removeParameter(Parameter param) {
if (param == null || parameters == null || (!parameters.contains(param)))
return;
parameters.remove(param);
param.setOwner(null);
}
/**
* This method will be called automatically when a parameter that has been added to this View
* is changed. It is not meant to be called directly. Note that in fact, this method
* simply calls forceRedraw.
* @see #forceRedraw()
*/
public void parameterChanged(Parameter param, Object oldValue, Object newValue) {
forceRedraw();
}
//--------------------------------------- Decorations ------------------------------------
/**
* Add a decoration that will appear in this View only. Of course, the
* decoration must be one that is compatible with the View's Exhibit. Note
* that adding or removing a Decoration from a View causes a call to forceRedraw.
* @param d The decoration to be added. If this is null or if it has already been added to the View,
* then nothing is done. The View is added
* as a ChangeListener to the decoration. This will cause the View to be automatically
* redrawn when the Decoration is changed.
*/
public void addDecoration(Decoration d) {
if (d == null)
return;
if (decorations == null)
decorations = new ArrayList();
if (decorations.contains(d))
return;
decorations.add(d);
d.addChangeListener(this);
forceRedraw();
}
/**
* Remove a specified decoration from this View, if present.
*/
public void removeDecoration(Decoration d) {
if (decorations != null && d != null && decorations.contains(d)) {
decorations.remove(d);
if (decorations.size() == 0)
decorations = null;
d.removeChangeListener(this);
forceRedraw();
}
}
/**
* Remove all decorations from this View.
*/
public void clearDecorations() {
if (decorations != null) {
for (int i = 0; i < decorations.size(); i++)
decorations.get(i).removeChangeListener(this);
decorations = null;
forceRedraw();
}
}
/**
* Returns a list of decorations that have been added to this View
* @return a non-null array, possibly of length zero, containing the decorations.
*/
public Decoration[] getDecorations() {
if (decorations == null)
return new Decoration[0];
else {
Decoration[] decs = new Decoration[decorations.size()];
decorations.toArray(decs);
return decs;
}
}
//------------------------------------ View and Settings Commands --------------------------------------
/**
* Returns a list of "view commands" that can be applied to this View.
* The commands must implement the {@link vmm3d.actions.ActionItem} interface and will generally
* belong to one of the classes {@link vmm3d.actions.AbstractActionVMM},
* {@link vmm3d.actions.ToggleAction}, {@link vmm3d.actions.ActionRadioGroup}, or
* {@link vmm3d.actions.ActionList}. Null items represent separators.
* The idea is that the list items will be added to a menu or otherwise presented to the user
* so that the user can invoke the commands (with ActionList items representing submenus).
* Null values can occur in the list; they are meant to become separaters in the menu.
* In this top-level View class, the list contains a "Show/Hide Axes" Action, a null
* value, and an ActionRadioGroup with three items that can be used to set the background color.
*
In general, when overriding this method, a subclass should call super.getViewCommands()
* to obtain a list of commands from the superclass. It can then add additional
* commands or remove or disable commands that are in the list from the superclass.
*/
public ActionList getViewCommands() {
ActionList commands = new ActionList();
commands.add(showAxesAction);
commands.add(null);
commands.add(backgroundCommands);
return commands;
}
/**
* Generates a list of "Settings commands" that can be applied to this View.
* The commands must implement the {@link vmm3d.actions.ActionItem} interface and will generally
* belong to one of the classes {@link vmm3d.actions.AbstractActionVMM},
* {@link vmm3d.actions.ToggleAction}, {@link vmm3d.actions.ActionRadioGroup}, or
* {@link vmm3d.actions.ActionList}. Null items represent separators.
* The idea is that the list items will be added to a menu or otherwise presented to the user
* so that the user can invoke the commands (with ActionList items representing submenus).
* Null values can occur in the list; they are meant to become separaters in the menu.
*
Subclasses that override this method should generally call super.getSettingsCommands() and add their
* commands to the list retured by that method. This top-level View class does not define any Settings
* commands; the return value is an empty non-null ArrayList.
*/
public ActionList getSettingsCommands() {
return new ActionList();
}
/**
* Returns a list containing Settings commands for this View and for the Exhibit that is contained in this View.
* The Exhibit commands, if any, occur first in the list. This is a convenience method
* that simply combines the results of {@link #getSettingsCommands()} and
* {@link Exhibit#getSettingsCommandsForView(View)}. In addtion, if the exhibit is a {@link UserExhibit}, and
* if the exhbit allows the user data to be changed, then a "ChangeUserData" command is also included,
* between the other Exhibit commands and the View commands. If there is currently no Exhibit in the View,
* only the view commands are returned.
* There is presumably no need to override this method.
*
Note that of the three types of commands defined by Views and Exhibits ("Settings", "Action", and
* "View" commands), "View" commands are constructed entirely by Views, while "Action" commands and
* "Settings" commands are constructed by both.
*/
public ActionList getSettingsCommandsForViewAndExhibit() {
ActionList exhibitCommands = null;
AbstractActionVMM changeUserData = null;
if (exhibit != null) {
exhibitCommands = exhibit.getSettingsCommandsForView(this);
if (exhibit instanceof UserExhibit) {
UserExhibit.Support s = ((UserExhibit)exhibit).getUserExhibitSupport();
if (s.getAllowChangeUserDataCommand())
changeUserData = s.makeChangeUserDataAction(this);
}
}
ActionList viewCommands = getSettingsCommands();
if (exhibitCommands == null && changeUserData == null && viewCommands == null)
return null;
ActionList combinedCommands = new ActionList();
if (exhibitCommands != null)
combinedCommands.addAll(exhibitCommands);
if (changeUserData != null) {
if (combinedCommands.getItemCount() > 0)
combinedCommands.add(null);
combinedCommands.add(changeUserData);
}
if (viewCommands != null && viewCommands.getItemCount() > 0) {
if (combinedCommands.getItemCount() > 0)
combinedCommands.add(null);
combinedCommands.addAll(viewCommands);
}
return combinedCommands;
}
/**
* Generates a list of "Action commands" that can be applied to this View.
* The commands must implement the {@link vmm3d.actions.ActionItem} interface and will generally
* belong to one of the classes {@link vmm3d.actions.AbstractActionVMM},
* {@link vmm3d.actions.ToggleAction}, {@link vmm3d.actions.ActionRadioGroup}, or
* {@link vmm3d.actions.ActionList}. Null items represent separators.
* The idea is that the list items will be added to a menu or otherwise presented to the user
* so that the user can invoke the commands (with ActionList items representing submenus).
* Null values can occur in the list; they are meant to become separaters in the menu.
*
Subclasses that override this method should generally call super.getActionss() and add their
* commands to the list retured by that method. This top-level View class does not define any Action
* commands; the return value is an empty non-null ArrayList.
*/
public ActionList getActions() {
return new ActionList();
}
/**
* Returns a list containing Action commands for this View and for the Exhibit that is contined in this View.
* The Exhibit commands, if any, occur first in the list. This is a convenience method
* that simply combines the results of {@link #getActions()} and
* {@link Exhibit#getActionsForView(View)}.
* If there is currently no Exhibit in the View,
* only the view commands are returned.
* There is presumably no need to override this method.
*
Note that of the three types of commands defined by Views and Exhibits ("Settings", "Action", and
* "View" commands), "View" commands are constructed entirely by Views, while "Action" commands and
* "Settings" commands are constructed by both.
*/
public ActionList getActionsForViewAndExhibit() {
ActionList exhibitCommands = null;
if (exhibit != null) {
exhibitCommands = exhibit.getActionsForView(this);
}
ActionList viewCommands = getActions();
if (exhibitCommands == null && viewCommands == null)
return null;
ActionList combinedCommands = new ActionList();
if (exhibitCommands != null)
combinedCommands.addAll(exhibitCommands);
if (viewCommands != null && viewCommands.getItemCount() > 0) {
if (combinedCommands.getItemCount() > 0)
combinedCommands.add(null);
combinedCommands.addAll(viewCommands);
}
return combinedCommands;
}
/**
* This method returns a list of {@link ActionItem} that will be added to
* the Animation menu of the 3dxm applicaiton. The values in the list should
* ordinarily be commands (i.e. {@link vmm3d.actions.AbstractActionVMM})
* that run animations (although there is no way to enforce
* this restriction). These animations commands are in addition to the standard "Morph"
* and "Cyclic Morph" commands. Note that these commands are added to those returned
* by {@link View#getAdditionalAnimations()}.
*
In this top-level View class, the return value is a an empty list (but not null).
* In general, when overriding this method, subclasses should call "super.getAdditionalAnimations()"
* to obtain a list of actions from the superclass. It can then add additional
* actions or remove or disable actions that are in the list from the superclass.
*/
public ActionList getAdditionalAnimations() {
return new ActionList();
}
/**
* Returns the combined contents of the lists obtained from {@link #getAdditionalAnimations()}
* and {@link Exhibit#getAdditionalAnimationsForView(View)}. There should not be any
* need to override this method.
*/
public ActionList getAdditionalAnimationsForViewAndExhibit() {
ActionList exhibitCommands = null;
if (exhibit != null) {
exhibitCommands = exhibit.getAdditionalAnimationsForView(this);
}
ActionList viewCommands = getAdditionalAnimations();
if (exhibitCommands == null && viewCommands == null)
return null;
ActionList combinedCommands = new ActionList();
if (exhibitCommands != null)
combinedCommands.addAll(exhibitCommands);
if (viewCommands != null && viewCommands.getItemCount() > 0) {
if (combinedCommands.getItemCount() > 0)
combinedCommands.add(null);
combinedCommands.addAll(viewCommands);
}
return combinedCommands;
}
//------------------------------------ drawing, etc. --------------------------------------
/**
* This method is called by the render method when the Exhibit in this View
* needs to be redrawn. The default as defined here is simply to call the
* exhibit's render method. Subclasses can override this method if some
* other action is needed.
*
Drawing is likely to need information about the transform (including the rectangular area in the
* xy-plane that is being drawn and the rectangle of pixels in the graphics
* context where it is drawn) and the decorations that have been added to the view. This
* information is available from {@link #getTransform()} and {@link #getDecorations()}.
* @param g The graphics context where the exhibit is to be drawn. The drawing
* area has already been cleared to the background color.
*/
protected void doDraw(VectorGraphics g) {
needsRedraw = true;
if (exhibit != null)
exhibit.render(g,this,transform,decorations);
}
/**
* Force the Exhibit to be completely redrawn. This is stronger than just repainting
* because it forces the off-screen image to be redrawn as well. Note
* that simply calling repaint on the Display where this View is rendered will NOT
* cause the off-screen image to be redrawn and so will not necessarily cause what is
* displayed on the screen to change. The system is designed so that this method
* or its equivalent will be called automatically in most cases, such as
* when Parameters of the View or Exhibit are changed or when a Decoration is added or removed or changed.
* It is also called when other properties defined in this top-level View
* classe are changed. Subclasses of View might need to call this method when changes are made
* that affect the appearance of the Exhibit.
*/
public void forceRedraw() {
needsRedraw = true;
if (buildAnimation != null)
buildAnimation.cancel();
if (display != null)
display.repaint();
}
// set need redraw
public void setNeedRedraw(boolean needsRedraw){
this.needsRedraw= needsRedraw;
}
// get need redrwa
public boolean getNeedRedraw(){
return this.needsRedraw;
}
/**
* A View listens for state change events that are generated when the Exhibit or Decoration that it is
* displaying is changed, or by the transform (that converts real coords to pixel coords).
* This method is automatically called by the Exhibit, Decoration, or Transform when it
* is changed. It will not ordinarly be called directly. Note that in fact,
* this method simply calls forceRedraw(), which causes the View to be completely resdrawn.
* @see #forceRedraw()
*/
public void stateChanged(ChangeEvent evt) {
forceRedraw();
}
/**
* Used by the Exhibit class to install a build animation for an exhibit. The current
* build animation, if any, is canceled.
*
This method has package scope and so is not visible outside the vmm3d.core package.
* @param builder The build animation, or null if the intent is just to cancel the current build animation.
*/
void installBuildAnimation(final Animation builder) {
if (buildAnimation != null)
buildAnimation.cancel();
buildAnimation = builder;
if (builder != null)
display.installAnimation(builder);
}
/**
* Return the current build animation running in this View, or null if there is none.
*/
Animation getBuildAnimation() {
return buildAnimation;
}
/**
* This method is called to draw this View's Exhibit. This will most often
* be called from the paintComponent method of the Display class. The process of
* rendering a View is rather complicated because of the need to manage off-screen images and
* various properties of the View. Subclasses of
* View should probably not override this method. They should change the doDraw
* method, which is called by this method, instead.
*
While the exhibit is being rendered, this method sets the value of the protected
* variable {@link #currentGraphics} to the graphics context in which the exhibit is being
* drawn. This variable is used implicitely for drawing in the rendering methods that are provided
* by the View class and its 3D subclass. This graphics context is set only for the duration of the call
* to the {@link #doDraw(Graphics2D)} method in this class, and hence during rendering
* of an exhibit (in its {@link Exhibit#doDraw(Graphics2D, View, Transform)} method.
* It is also set during direct drawing to the off-screen image initiated by
* {@link #beginDrawToOffscreenImage()}. At other
* times currentGraphics is null, and calling the rendering methods that use this graphics context at those
* times will result in an error.
*
Note that this render method always completely paints the rectangle in which it draws.
*
(In the rare case that creation of an offscreen image fails because of low memory,
* an error message is placed in the drawing area.)
* @param g The graphics context where the Exhibit is to be drawn. It is assumed
* that the upper left corner of the drawing area has coordinates (0,0). The off-screen
* image is simply drawn in g, except that when no exhibit exists, it is filled with
* the background color.
* @param width The width, in pixels, of the drawing area where the exhibit is to be drawn.
* @param height The height, in pixels, of the drawing area where the exhibit is to be drawn.
*/
public void render(VectorGraphics g, int width, int height) {
currentGraphics=g;
if (directOffscreenDrawing)
endDrawToOffscreenImage();
if (width == 0 || height == 0)
return;
if (exhibit == null) { // ordinarily, this won't happen since the Display class won't call this method if exhibit is null
Color saveColor = g.getColor();
g.setColor(background);
g.fillRect(0,0,width,height);
g.setColor(saveColor);
return;
}
if (previousWidth != width || previousHeight != height) { // force full redraw if size has changed
needsRedraw = true;
previousWidth = width;
previousHeight = height;
}
/*
if (needsNewOSI(width,height)) { // force redraw if size of offscreen canvas is incorrect
needsRedraw = true;
try {
createOSI(width,height);
}
catch (OutOfMemoryError e) {
previousWidth = 0;
Color saveColor = g.getColor();
g.setColor(Color.WHITE);
g.fillRect(0,0,width,height);
g.setColor(Color.RED);
g.drawString(I18n.tr("vmm3d.core.OutOfMemoryError"),20,35);
g.setColor(saveColor);
return;
}
}
*/
if (needsRedraw) {
//System.out.println("DEBUG: Start drawing!");
//clearOSI();
currentGraphics.setStroke(new BasicStroke(linewidth));
prepareOSIForDrawing();
doDraw(currentGraphics);
finishOSIDraw();
transform.finishDrawing();
currentGraphics.dispose();
//currentGraphics.fillRect(0,0,100,100);
setFastDrawing(true);
currentGraphics.dispose();
needsRedraw = true;
}
putOSI(currentGraphics,width,height);
}
// The following six methods were introduced mainly to make it possible to write a render method
// that would also work for the View3D class. They are meant to be overridden in subclasses.
/**
* This is called by render to check whether a new offscreen image needs to be
* created, given the specified width and height of the drawing area.
*/
protected boolean needsNewOSI(int width, int height) {
return fullOSI == null || fullOSI.getWidth() != width || fullOSI.getHeight() != height;
}
/**
* This is called by render to copy the offscreen image to the screen.
* The width and height of the drawing area are given as parameters, although they
* is not used in the version of this method in class View.
*/
protected void putOSI(VectorGraphics g, int width, int height) {
//g.drawImage(fullOSI,0,0,null);
}
/**
* This method is called whenever
* an off-screen image needs to be created. This method sets the value of the
* protected variable {@link #fullOSI} to the newly created image. (Subclasses should
* respect this.) The default is to create a
* new BufferedImage whose type is specified by the protected variable
* {@link #offscreenImageType}. This is most likely what is desired.
* However, sub-classes can override this method if a different type of image
* is needed.
*/
protected void createOSI(int width, int height) {
//fullOSI = null;
//fullOSI = new BufferedImage(width,height,offscreenImageType);
}
public void setCurrentGraphics(VectorGraphics vectorg){
currentGraphics=vectorg;
}
public VectorGraphics getCurrentGraphics(){
return currentGraphics;
}
/**
* Fills the offscreen image with the background color of this View.
*/
protected void clearOSI() {
/*
Graphics gg = fullOSI.getGraphics();
VectorGraphics g = VectorGraphics.create(gg);
g.setColor(background);
g.fillRect(0,0,fullOSI.getWidth(),fullOSI.getHeight());
g.dispose();
*/
}
/**
* Prepare the offscreen image for drawing and return a graphics context for drawing to it.
* This method is called by {@link #render(Graphics2D, int, int)} before it begins drawing
* to the offscreen image. It is also called by {@link #beginDrawToOffscreenImage()}.
* The offscreen image exists before this is called. In the
* top-level View class, this applies the transform and (if approprieate) antialiasing to
* the OSI drawing context and sets the foregraund and background colors.
*
Note: It is important that the protected variable {@link #fullOSI} point to a
* BufferedImage that contains the image that is shown on the screen. Subclasses that
* override this method should respect this. This is true in View3D, for example,
* even when the actual drawing is being done elsewhere during RED_GREEN_STEREO rendering.
* @return A graphics context for drawing to the off-screen image.
*/
protected VectorGraphics prepareOSIForDrawing() {
/*
Graphics gg = fullOSI.getGraphics();
VectorGraphics g = VectorGraphics.create(gg);
g.setColor(foreground);
g.setBackground(background);
if (antialiased)
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
transform.setUpDrawInfo(g,0,0,fullOSI.getWidth(),fullOSI.getHeight(),preserveAspect,applyGraphics2DTransform);
currentGraphics = g;
*/
// System.out.println(background.toString());
int width=display.getWidth();
int height=display.getHeight();
if (currentGraphics == null) return null;
currentGraphics.setColor(foreground);
currentGraphics.setBackground(background);
if (antialiased)
currentGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
transform.setUpDrawInfo(currentGraphics,0,0,width,height,preserveAspect,applyGraphics2DTransform);
return currentGraphics;
}
/**
* This is called when drawing to the off-screen image is finished, to give it a chance for clean-up.
* The method in the top-level view does nothing.
*/
protected void finishOSIDraw() {
}
//----------------------- Rendering methods for use during drawing of an Exhibit -----------------------
/**
* This method will set the color in the current graphics context. This method should only
* be used while drawing an exhibit or after {@link #beginDrawToOffscreenImage()}. If called
* at other times, there is no effect.
* @param c the color to be used for subsequent drawing; if c is null, then the default foreground
* color of the View is restored.
*/
public void setColor(Color c) {
if (currentGraphics != null)
currentGraphics.setColor( c == null? getForeground() : c );
}
/**
* Returns the drawing color of the current graphics context. This method should only be
* used while drawing an exhibit or after {@link #beginDrawToOffscreenImage()}. If called
* at other times, the return value is null.
*/
public Color getColor() {
return (currentGraphics == null)? null : currentGraphics.getColor();
}
/**
* Sets the width of the stroke used for drawing lines and curves in the current graphics context
* to have a width equal to a specified number of pixels. This method should only be
* used while drawing an exhibit or after {@link #beginDrawToOffscreenImage()}. If called
* at other times, the call has no effect. Note that this just sets the stroke width
* to be a multiple of the default stroke size, as defined by the Transform. Calling this
* method with a parameter value of 1 will restore the default stoke width.
* @see Transform#getDefaultStrokeSize()
*/
public void setStrokeSizeMultiplier(int pixels) {
if (currentGraphics != null)
currentGraphics.setStroke(new BasicStroke(pixels * transform.getDefaultStrokeSize()));
}
/**
* Draws a point by turning on a single pixel. This is done by transforming (x,y) to pixel coordinates,
* then using drawPixelDirect to set the color of the single pixel.
* @see #drawPixelDirect(Color, int, int)
* @param x The x-coordinate of the point, in window (real xy) coodinates.
* @param y The y-coordinate of the point, in window (real xy) coodinates.
*/
public void drawPixel(double x, double y) {
tempPoint.setLocation(x,y);
transform.windowToViewport(tempPoint);
int xInt = (int)(tempPoint.getX() + 0.4999);
int yInt = (int)(tempPoint.getY() + 0.4999);
drawPixelDirect(null,xInt,yInt);
}
/**
* Draws a point by turning on a single pixel. This just calls
* drawPixel(pt.getX(),pt.getY()).
* @param pt The non-null point, in window (real xy) coodinates.
*/
public void drawPixel(Point2D pt) {
drawPixel(pt.getX(), pt.getY());
}
/**
* Draws a dot of specified diameter centered at a specified point.
* This is done by calling the org.freehep.graphics2d.VectorGraphics fill() command for an
* appropriate Ellipse2D. The diameter is specified in pixels.
* Note that if the preserveAspectRatio is off for this View, then
* the dot will can be an oval rather than a circle.
*/
public void drawDot(Point2D pt, double diameter) {
tempPoint.setLocation(pt);
transform.windowToDrawingCoords(tempPoint);
double h = diameter*transform.getPixelWidth();
double w = diameter*transform.getPixelHeight();
currentGraphics.fill(new Ellipse2D.Double(pt.getX()-h/2,pt.getY()-w/2,h,w));
}
/**
* Draws a list of pixels in the current drawing context, where the pixels are specified in object coordinates.
* This should only be called while drawing is in progress.*/
public void drawPixels(Point2D[] points, int pointIndexStart, int pointIndexEnd) { // points in window coordinates
if (points == null)
return;
if (pointIndexStart >= points.length)
pointIndexStart = points.length - 1;
if (pointIndexStart < 0)
pointIndexStart = 0;
if (pointIndexEnd >= points.length)
pointIndexEnd = points.length - 1;
if (pointIndexEnd < 0)
pointIndexEnd = 0;
if (pointIndexEnd <= pointIndexStart)
return;
Color color = currentGraphics.getColor();
for (int i = pointIndexStart; i <= pointIndexEnd; i++) {
if (points[i] != null) {
tempPoint.setLocation(points[i]);
transform.windowToViewport(tempPoint);
drawPixelDirect(color,(int)(tempPoint.getX()+0.499), (int)(tempPoint.getY()+0.499));
}
}
}
/**
* This can be called during drawing to draw a string at a specified point, given in window (real x,y) coordinates.
* The font is NOT transformed, as it would be if you simply used the drawString
* method of a drawing context to which a transform has been applied.
* The point (x,y) is properly transformed from xy-coordinates to pixel coordinates, whether a
* transform has been applied ot the drawing context or not. After conversion, if necessary, the
* graphics context returned by {@link Transform#getUntransformedGraphics()} is used to draw the string.
* @see #drawString(String, Point2D)
*/
public void drawString(String s, double x, double y) {
Point2D pt = new Point2D.Double(x,y);
transform.windowToViewport(pt);
transform.getUntransformedGraphics().drawString(s,(float)pt.getX(),(float)pt.getY());
}
/**
* This can be called during drawing to draw a string at a specified point, given in window (real x,y) coordinates.
* The font is NOT transformed, as it would be if you simply used the drawString
* method of a drawing context to which a transform has been applied.
* @see #drawString(String, double, double)
* @param xyCoords The non-null point containting the coordinates of the basepoint of the string in real xy-coordinates.
* This point is not modified.
*/
public void drawString(String s, Point2D xyCoords) {
drawString(s, xyCoords.getX(), xyCoords.getY());
}
/**
* Draws a line in the current drawing context. This should only be called while drawing is
* in progress. The line has endpoints (x1,y1) and (x2,y2), where the coordinates are
* in window (real xy) coordinates. This should only be called when a drawing operation is in
* progress.
*/
public void drawLine(double x1, double y1, double x2, double y2) {
double tx1, ty1;
tempPoint.setLocation(x1,y1);
transform.windowToDrawingCoords(tempPoint);
tx1 = tempPoint.getX();
ty1 = tempPoint.getY();
tempPoint.setLocation(x2,y2);
transform.windowToDrawingCoords(tempPoint);
currentGraphics.draw(new Line2D.Double(tx1, ty1, tempPoint.getX(), tempPoint.getY()));
}
/**
* Draws a line in the current drawing context. This should only be called while drawing is
* in progress.
* @param pt1 The first endpoint of the line, with coordinates given in the window (real xy) coordinates system.
* @param pt2 The second endpoint of the point, with coordinates given in the window (real xy) coordinates system.
*/
public void drawLine(Point2D pt1, Point2D pt2) {
drawLine(pt1.getX(), pt1.getY(), pt2.getX(), pt2.getY());
}
/**
* Draws a curve in the current drawing context. The points on the curve are given in window (real xy) coordinates.
* This should only be called while drawing is in progress.
* @param points The curve is drawn through some or all of the points in this array. If the array is null, nothing is done.
* The curve is acutually just made up of lines from one point to the next. An element in the array can be null.
* In that case, one or two segments are missing from the curve -- the segments on either side of the missing point.
* Consecutive points are also not joined by a segment if the jump from one point to the next is too large.
* @param pointIndexStart The number of points in the array that should be used for the curve. A curve is drawn
* though points[pointIndexStart], point[pointIndexStart+1], ..., points[pointIndexEnd]. The value of pointIndexStart
* is clamped to lie in the range 0 to points.length-1.
* @param pointIndexEnd The number of points in the array that should be used for the curve. A curve is drawn
* though points[pointIndexStart], point[pointIndexStart+1], ..., points[pointIndexEnd]. The value of pointIndexEnd
* is clamped to lie in the range 0 to points.length-1. If pointIndexEnd is less than or equal to pointIndexStart,
* nothing is drawn.
*/
public void drawCurve(Point2D[] points, int pointIndexStart, int pointIndexEnd) { // points in window coordinates
if (points == null)
return;
if (pointIndexStart >= points.length)
pointIndexStart = points.length - 1;
if (pointIndexStart < 0)
pointIndexStart = 0;
if (pointIndexEnd >= points.length)
pointIndexEnd = points.length - 1;
if (pointIndexEnd < 0)
pointIndexEnd = 0;
if (pointIndexEnd <= pointIndexStart)
return;
GeneralPath curve = new GeneralPath();
double maxJumpX = Math.abs(transform.getXmax() - transform.getXmin())/4;
double maxJumpY = Math.abs(transform.getYmax() - transform.getYmin())/4;
if (applyGraphics2DTransform)
maxJumpX = maxJumpY = Math.max(maxJumpX,maxJumpY);
boolean moved = false;
for (int i = pointIndexStart; i < pointIndexEnd; i++) {
if (points[i] != null) {
tempPoint.setLocation(points[i]);
transform.windowToDrawingCoords(tempPoint);
if (i == pointIndexStart)
curve.moveTo((float)tempPoint.getX(), (float)tempPoint.getY());
else if (i > pointIndexStart && points[i-1] != null && Math.abs(points[i].getX() - points[i-1].getX()) <= maxJumpX
&& Math.abs(points[i].getY() - points[i-1].getY()) <= maxJumpY)
curve.lineTo((float)tempPoint.getX(), (float)tempPoint.getY());
else {
curve.moveTo((float)tempPoint.getX(), (float)tempPoint.getY());
moved = true;
}
}
}
if (points[pointIndexEnd] != null) {
if ( (pointIndexStart == 0) && (pointIndexEnd == points.length-1)
&& (points[0] != null) && (Math.abs(points[0].getX() - points[pointIndexEnd].getX()) <= transform.getPixelWidth()/100 )
&& (Math.abs(points[0].getY() - points[pointIndexEnd].getY()) <= transform.getPixelHeight()/100 )
&& (!moved) ) {
curve.closePath(); // System.out.println("Path was closed"); // Do NOT close if moved = true - leads to errors.
}
else {
// replaced from here
tempPoint.setLocation(points[pointIndexEnd]);
transform.windowToDrawingCoords(tempPoint);
if (points[pointIndexEnd-1] != null && Math.abs(points[pointIndexEnd].getX() - points[pointIndexEnd-1].getX()) <= maxJumpX
&& Math.abs(points[pointIndexEnd].getY() - points[pointIndexEnd-1].getY()) <= maxJumpY)
curve.lineTo((float)tempPoint.getX(), (float)tempPoint.getY());
else
curve.moveTo((float)tempPoint.getX(), (float)tempPoint.getY());
}
}
currentGraphics.draw(curve);
}
/**
* Draws a curve through all the points in an array by calling drawCurve(points,points.length)
* @param points The array of points on the curve. If this is null, nothing is done.
*/
public void drawCurve(Point2D[] points) {
if (points != null)
drawCurve(points, points.length);
}
/**
* Draws a curve in the current drawing context. The points on the curve are given in window (real xy) coordinates.
* This should only be called while drawing is in progress.
* @param points The curve is drawn through some of all of the points in this array. If the array is null, nothing is done.
* The curve is acutually just made up of lines from one point to the next. An element in the array can be null.
* In that case, one or two segments are missing from the curve -- the segments on either side of the missing point.
* Consecutive points are also not joined by a segment if the jump from one point to the next is too large.
* @param pointCount The number of points in the array that should be used for the curve. A curve is drawn
* though points[0], point[1], ..., points[pointCount-1]. If pointCount is greater than points.length, then
* its value is changed to points.length.
*/
public void drawCurve(Point2D[] points, int pointCount) { // points in window coordinates
drawCurve(points,0,pointCount-1);
}
//--------------------------- Some pixel-oriented drawing methods -----------------------
/**
* Sets the pixel with pixel coordinates (x,y) to be a specified color.
* The pixel color is changed in the off-screen image, not on the screen immmediately.
* The current transformation is not applied to the coordinates.
* @param color the color for the pixel; if null, the current drawing color is used.
* @param x the horizontal pixel coordinate.
* @param y the vertical pixel coordinate.
*/
public void drawPixelDirect(Color color, int x, int y) {
if (x < 0 || y < 0 || x >= fullOSI.getWidth() || y >= fullOSI.getHeight())
return;
if (color == null)
color = currentGraphics.getColor();
int rgb;
rgb = (color.getRed() << 16) | (color.getGreen() << 8) | (color.getBlue());
// fullOSI.setRGB(x,y,rgb);
// currentGraphics.setRGB(x,y,rgb);
int size=2;
currentGraphics.setColor(color);
currentGraphics.fill(new Ellipse2D.Double(x,y,size,size));
}
/**
* Draws a line in the current color between points that are specified using
* pixel coordinates.
*/
public void drawLineDirect(int x1, int y1, int x2, int y2) {
currentGraphics = getTransform().getUntransformedGraphics();
currentGraphics.drawLine(x1,y1,x2,y2);
}
/**
* Draws a filled-in rectangle in the current color, where the rectangle is specified in pixel coordinates.
*/
public void fillRectDirect(int x, int y, int width, int height) {
currentGraphics = getTransform().getUntransformedGraphics();
//VectorGraphics vg = VectorGraphics.create(g);
//vg.fillRect(x,y,width,height);
currentGraphics.fillRect(x,y,width,height);
}
// ---------------------------- Some public methods generally for system use -------------------------------
/**
* This method, along with {@link #endDrawToOffscreenImage()}, allow you to draw directly to this view's off-screen
* image, if there is one. This operation is allowed only if (1) An offscreen image exists, (2) the View has
* already been drawn to the screen at least once since its current exhibit was installed, and (3) no drawing
* operation is in progress (including a regular draw by the render method or a draw operation
* initiated by a previous call to this method that was not matched by a call to endDrawToOffscreenImage).
* If all these conditions hold, this method initiates a drawing operation and returns true. If any of them
* fail, it returns false and no drawing operation is initiated. To terminate the drawing operation, you must
* call endDrawToOffscreenImage. A drawing operation should not remain open for any length of time.
* It should complete before the method in which beginDrawToOffscreenImage is called.
*
During a drawing operation initiated by this method, drawing done using the draw methods in this
* class, such as {@link #drawCurve(Point2D[])}, will draw to the off-screen image. This also applies to
* drawing operations in the View3D subclass and presumably in other subclasses. The point of this is
* to allow you to draw extra stuff incrementally on the off-screen canvas, without redrawing the whole
* thing from scratch each time.
*
After calling endDrawToOffscreenImage, you will probably want to call the repaint() method of
* this View's display to cause the changes to appear on the screen. Do NOT call {@link #forceRedraw()},
* since that will generate a call to render, which will erase the changes you just made!
* @return Returns true if the drawing operation was actually begun. If the return value is false,
* attemps to draw will not draw to the off-screen image and will probably cause errors.
*/
public boolean beginDrawToOffscreenImage() {
if (transform == null || transform.getWidth() == 0 || currentGraphics != null || fullOSI == null)
return false;
prepareOSIForDrawing();
directOffscreenDrawing = true;
return true;
}
/**
* This method, along with {@link #beginDrawToOffscreenImage()}, allow you to draw directly to this view's off-screen
* image, if there is one. See that method for more information. No error occurs if this is called
* when no draw-to-image operation is in progress.
*/
public void endDrawToOffscreenImage() {
if (!directOffscreenDrawing)
return;
transform.finishDrawing();
finishOSIDraw();
directOffscreenDrawing = false;
//currentGraphics = null;
}
/**
* Get a buffered image that contains the picture shown on this View's display.
* If the parameter alwaysCopy is false, then the View's off-screen image is returned (provided one
* has already been created).
* Otherwise, a new image is created and the
* View is rendered to this image by calling its render method.
* The return value can be null if this method is called when there is
* no Display associated with this view, or if there is no Exhibit associated with this View,
* or if this method is called before a picture has been
* drawn to the screen. It is possible that an OutOfMemoryError might occur when
* an attempt is made to create an image.
*
Note that if you need to draw to the offscreen image, you should use the facility provided
* by {@link #beginDrawToOffscreenImage()}. You should not generally simply draw to the image returned by
* this method (even though that will work in some cases). This method was introduced mainly to
* make it possible to save the screen image to a file or to a "filmstrip" animation.
* @param alwaysCopy If this is false, it is possible that the image that is returned is the
* actual image that the View uses to store its content; this image will be modified whenever
* the view is redrawn. (alwaysCopy = true is used to get the frame for a filmstrip;
* alwaysCopy = false is used to get an image to save to a file)
*/
public BufferedImage getImage(boolean alwaysCopy) {
if (display == null || exhibit == null || previousWidth == 0)
return null;
if (fullOSI != null && !alwaysCopy)
return fullOSI;
BufferedImage image;
image = new BufferedImage(previousWidth,previousHeight,offscreenImageType);
VectorGraphics g = (VectorGraphics)image.getGraphics();
if (fullOSI == null)
render(g,previousWidth,previousHeight);
else
g.drawImage(fullOSI,0,0,null);
g.dispose();
return image;
}
}
Ads help maintain this website.