/*
GeoGebra - Dynamic Mathematics for Everyone
http://www.geogebra.org
This file is part of GeoGebra.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation.
*/
package org.geogebra.desktop.euclidian;
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.io.File;
import java.io.IOException;
import javax.swing.JPanel;
import javax.swing.border.Border;
import org.geogebra.common.awt.GBufferedImage;
import org.geogebra.common.awt.GColor;
import org.geogebra.common.awt.GDimension;
import org.geogebra.common.awt.GFont;
import org.geogebra.common.awt.GGraphics2D;
import org.geogebra.common.euclidian.EuclidianConstants;
import org.geogebra.common.euclidian.EuclidianController;
import org.geogebra.common.euclidian.EuclidianCursor;
import org.geogebra.common.euclidian.EuclidianStyleBar;
import org.geogebra.common.euclidian.EuclidianView;
import org.geogebra.common.javax.swing.GBox;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.EVProperty;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.main.App.ExportType;
import org.geogebra.common.main.Localization;
import org.geogebra.common.main.settings.EuclidianSettings;
import org.geogebra.common.plugin.EuclidianStyleConstants;
import org.geogebra.common.util.debug.Log;
import org.geogebra.desktop.awt.GBufferedImageD;
import org.geogebra.desktop.awt.GColorD;
import org.geogebra.desktop.awt.GDimensionD;
import org.geogebra.desktop.awt.GFontD;
import org.geogebra.desktop.awt.GGraphics2DD;
import org.geogebra.desktop.euclidianND.EuclidianViewInterfaceD;
import org.geogebra.desktop.export.GraphicExportDialog;
import org.geogebra.desktop.io.MyImageIO;
import org.geogebra.desktop.javax.swing.GBoxD;
import org.geogebra.desktop.main.AppD;
import org.geogebra.desktop.util.GuiResourcesD;
import org.geogebra.desktop.util.ImageResourceD;
/**
*
* @author Markus Hohenwarter
*/
public class EuclidianViewD extends EuclidianView
implements EuclidianViewInterfaceD, Printable {
// temp
// public static final int DRAW_MODE_DIRECT_DRAW = 0;
// public static final int DRAW_MODE_BACKGROUND_IMAGE = 1;
/** reset image in applets */
protected Image resetImage;
/** play image for animations */
protected Image playImage;
/** pause image for animations */
protected Image pauseImage;
/** play image for animations */
protected Image playImageHL;
/** pause image for animations */
protected Image pauseImageHL;
// public Graphics2D lastGraphics2D;
/** default mouse cursor */
protected Cursor defaultCursor;
/** Java component for this view */
protected EuclidianViewJPanelD evjpanel;
// set EuclidianView no - 2 for 2nd EulidianView, 1 for 1st EuclidianView
// and Applet
// EVNO_GENERAL for others
/**
* @param ec
* controller
* @param showAxes
* whether to show x-axis and y-axis
* @param showGrid
* whether to show grid
* @param settings
* settings
*/
public EuclidianViewD(EuclidianController ec, boolean[] showAxes,
boolean showGrid, EuclidianSettings settings) {
this(ec, showAxes, showGrid, 1, settings);
}
/**
* Creates EuclidianView
*
* @param ec
* controller
* @param showAxes
* whether x-axis and y-axis should be shown
* @param showGrid
* whether grid should be shown
* @param evno
* number of this view
* @param settings
* euclidian settings
*/
public EuclidianViewD(EuclidianController ec, boolean[] showAxes,
boolean showGrid, int evno, EuclidianSettings settings) {
super(ec, evno, settings);
viewTextField = new ViewTextFieldD(this);
evjpanel = new EuclidianViewJPanelD(this);
setApplication(ec.getApplication());
setShowAxis(0, showAxes[0], false);
setShowAxis(1, showAxes[1], false);
this.showGrid = showGrid;
// algebra controller will take care of our key events
euclidianController.setView(this);
attachView();
initView(false);
// updateRightAngleStyle(app.getLocale());
EuclidianSettings es = null;
if (settings != null) {
es = settings;
// settings from XML for EV1, EV2
// not for eg probability calculator
} else if ((evNo == 1) || (evNo == 2)) {
es = getApplication().getSettings().getEuclidian(evNo);
}
if (es != null) {
settingsChanged(es);
es.addListener(this);
}
}
@Override
protected void initView(boolean repaint) {
initPanel(repaint);
super.initView(repaint);
}
/**
* @return whether preferred size is defined and greater than minimum
*/
public boolean hasPreferredSize() {
Dimension prefSize = getPreferredSize();
return (prefSize != null) && (prefSize.width > MIN_WIDTH)
&& (prefSize.height > MIN_HEIGHT);
}
/**
* Switch to drag cursor
*/
public void setDragCursor() {
if (getMode() == EuclidianConstants.MODE_TRANSLATEVIEW) {
setGrabbingCursor();
}
else if (getApplication().useTransparentCursorWhenDragging()) {
setCursor(getApplication().getTransparentCursor());
} else {
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
}
private void setTransparentCursor() {
setCursor(getApplication().getTransparentCursor());
}
private void setMoveCursor() {
setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
}
private void setResizeXAxisCursor() {
setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
}
private void setResizeYAxisCursor() {
setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
}
/**
* Set the cursor to grabbing hand
*/
public void setGrabbingCursor() {
// TODO gui/image/cursor..
setCursor(getCursorForImage(GuiResourcesD.CURSOR_GRABBING));
}
/**
* Switch to hit cursor
*/
public void setHitCursor() {
if (defaultCursor == null) {
setCursor(Cursor.getDefaultCursor());
} else {
setCursor(defaultCursor);
}
}
/**
* Switch to default cursor
*/
public void setDefaultCursor() {
if (defaultCursor == null) {
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
} else {
setCursor(defaultCursor);
}
}
@Override
protected void initCursor() {
defaultCursor = null;
switch (getMode()) {
default:
// do nothing
break;
case EuclidianConstants.MODE_ZOOM_IN:
defaultCursor = getCursorForImage(GuiResourcesD.CURSOR_ZOOMIN);
break;
case EuclidianConstants.MODE_ZOOM_OUT:
defaultCursor = getCursorForImage(GuiResourcesD.CURSOR_ZOOMOUT);
break;
case EuclidianConstants.MODE_TRANSLATEVIEW:
defaultCursor = getCursorForImage(GuiResourcesD.CURSOR_GRAB);
break;
}
setDefaultCursor();
}
/**
* @param name
* cursor resource
* @return cursor
*/
protected Cursor getCursorForImage(ImageResourceD name) {
return getCursorForImage(getApplication().getInternalImage(name));
}
/**
* @param image
* image file
* @return cursor created from image
*/
private static Cursor getCursorForImage(Image image) {
if (image == null) {
return null;
}
// Query for custom cursor support
Toolkit tk = Toolkit.getDefaultToolkit();
Dimension d = tk.getBestCursorSize(32, 32);
int colors = tk.getMaximumCursorColors();
if (!d.equals(new Dimension(0, 0)) && (colors != 0)) {
// load cursor image
try {
// Create custom cursor from the image
Cursor cursor = tk.createCustomCursor(image, new Point(16, 16),
"custom cursor");
return cursor;
} catch (Exception exc) {
// Catch exceptions so that we don't try to set a null
// cursor
Log.debug("Unable to create custom cursor.");
}
}
return null;
}
/**
* @param g2d
* graphics
* @param scaleString
* title
* @param pageFormat
* format
* @param app
* application
* @return height
*/
public static int printTitle(Graphics2D g2d, String scaleString,
PageFormat pageFormat, AppD app) {
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
// construction title
int y = 0;
Construction cons = app.getKernel().getConstruction();
String title = cons.getTitle();
if (!"".equals(title)) {
GFont titleFont = app.getBoldFontCommon().deriveFont(GFont.BOLD,
app.getBoldFont().getSize() + 2);
g2d.setFont(GFontD.getAwtFont(titleFont));
g2d.setColor(Color.black);
// Font fn = g2d.getFont();
FontMetrics fm = g2d.getFontMetrics();
y += fm.getAscent();
g2d.drawString(title, 0, y);
}
// construction author and date
String author = cons.getAuthor();
String date = cons.getDate();
String line = null;
if (!"".equals(author)) {
line = author;
}
if (!"".equals(date)) {
if (line == null) {
line = date;
} else {
line = line + " - " + date;
}
}
if (scaleString != null) {
if (line == null) {
line = scaleString;
} else {
line = line + " - " + scaleString;
}
}
if (line != null) {
g2d.setFont(app.getPlainFont());
g2d.setColor(Color.black);
// Font fn = g2d.getFont();
FontMetrics fm = g2d.getFontMetrics();
y += fm.getHeight();
g2d.drawString(line, 0, y);
}
return y;
}
@Override
public int print(Graphics g, PageFormat pageFormat, int pageIndex0) {
int pageIndex = ((AppD)kernel.getApplication()).getPrintPreview().adjustIndex(pageIndex0);
if (pageIndex > 0) {
return (NO_SUCH_PAGE);
}
Graphics2D g2d = (Graphics2D) g;
AffineTransform oldTransform = g2d.getTransform();
int h = printTitle(g2d, getScaleString(), pageFormat, getApplication());
if (h > 0) {
g2d.translate(0, h + 20);
}
double scale = (PRINTER_PIXEL_PER_CM / getXscale()) * printingScale;
exportPaint(g2d, scale, ExportType.PRINTING);
// clear page margins at bottom and right
double pagewidth = pageFormat.getWidth();
double pageheight = pageFormat.getHeight();
double xmargin = pageFormat.getImageableX();
double ymargin = pageFormat.getImageableY();
g2d.setTransform(oldTransform);
g2d.setClip(null);
g2d.setPaint(Color.white);
Rectangle2D.Double rect = new Rectangle2D.Double();
rect.setFrame(0, pageheight - ymargin, pagewidth, ymargin);
g2d.fill(rect);
rect.setFrame(pagewidth - xmargin, 0, xmargin, pageheight);
g2d.fill(rect);
return (PAGE_EXISTS);
}
/**
* @param g2d
* graphics
* @param scale
* ratio of desired size and current size of the graphics
* @param exportType
* export type
*/
public void exportPaint(Graphics2D g2d, double scale,
ExportType exportType) {
exportPaint(new GGraphics2DD(g2d), scale, false, exportType);
}
@Override
public void exportImagePNG(double scale, boolean transparency, int dpi,
File file, boolean exportToClipboard, ExportType exportType) {
try {
GBufferedImage img = getExportImage(scale, transparency,
exportType);
MyImageIO.write(GBufferedImageD.getAwtBufferedImage(img), "png",
dpi, file);
if (exportToClipboard) {
GraphicExportDialog.sendToClipboard(file);
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
protected void drawResetIcon(GGraphics2D g) {
// need to use getApplet().width rather than width so that
// it works with applet rescaling
int w = getApplication().onlyGraphicsViewShowing()
? getApplication().getApplet().width : getWidth() + 2;
GGraphics2DD.getAwtGraphics(g).drawImage(getResetImage(), w - 18, 2,
null);
}
private Image getResetImage() {
if (resetImage == null) {
resetImage = getApplication().getRefreshViewImage();
}
return resetImage;
}
private Image getPlayImage(boolean highlight) {
if (playImage == null) {
playImage = getApplication().getPlayImageCircle();
playImageHL = getApplication().getPlayImageCircleHover();
}
return highlight ? playImageHL : playImage;
}
private Image getPauseImage(boolean highlight) {
if (pauseImage == null) {
pauseImage = getApplication().getPauseImageCircle();
pauseImageHL = getApplication().getPauseImageCircleHover();
}
return highlight ? pauseImageHL : pauseImage;
}
@Override
final protected void drawAnimationButtons(GGraphics2D g2) {
// draw button in focused EV only
if (!drawPlayButtonInThisView()) {
return;
}
int x = 3;
int y = getHeight() - 27;
/*
* if (highlightAnimationButtons) { // draw filled circle to highlight
* button g2.setColor(GColor.DARK_GRAY); } else {
* g2.setColor(GColor.LIGHT_GRAY); }
*
* g2.setStroke(EuclidianStatic .getDefaultStroke());
*
* // draw pause or play button g2.drawRect(x - 2, y - 2, 18, 18);
*/
Image img = kernel.isAnimationRunning()
? getPauseImage(highlightAnimationButtons)
: getPlayImage(highlightAnimationButtons);
GGraphics2DD.getAwtGraphics(g2).drawImage(img, x, y, null);
}
@Override
public final boolean hitAnimationButton(int x, int y) {
// draw button in focused EV only
if (!drawPlayButtonInThisView()) {
return false;
}
return kernel.needToShowAnimationButton() && (x <= 27)
&& (y >= (getHeight() - 27));
}
@Override
public EuclidianController getEuclidianController() {
return euclidianController;
}
/**
* @return graphics of the underlying component
*/
@Override
public GGraphics2D getGraphicsForPen() {
return new GGraphics2DD((Graphics2D) evjpanel.getGraphics());
}
@Override
public void setBoldAxes(boolean bold) {
// TODO Auto-generated method stub
}
@Override
public AppD getApplication() {
return (AppD) super.getApplication();
}
// ////////////////////////////
// EVJPANEL
// ////////////////////////////
/**
* @param cursor
* new cursor
*/
@Override
public void setCursor(Cursor cursor) {
evjpanel.setCursor(cursor);
}
@Override
public boolean hasFocus() {
return evjpanel.hasFocus();
}
@Override
public void repaint() {
this.updateBackgroundIfNecessary();
evjpanel.repaint();
}
@Override
public void paintBackground(GGraphics2D g2) {
g2.drawImage(bgImage, 0, 0);
}
@Override
public void add(GBox box) {
evjpanel.add(((GBoxD) box).getImpl());
}
@Override
public void remove(GBox box) {
evjpanel.remove(((GBoxD) box).getImpl());
}
/**
* @return underlying component
*/
@Override
public JPanel getJPanel() {
return evjpanel;
}
/**
* This view should be focused
*/
@Override
public void requestFocus() {
evjpanel.requestFocus();
}
@Override
public GFont getFont() {
// TODO Auto-generated method stub
return new GFontD(evjpanel.getFont());
}
/**
* @return mouse position
*/
@Override
public Point getMousePosition() {
return evjpanel.getMousePosition();
}
/**
* @see JPanel#getFontMetrics(java.awt.Font)
* @param font
* font
* @return font metrics
*/
public FontMetrics getFontMetrics(Font font) {
return evjpanel.getFontMetrics(font);
}
/**
* @return whethe this view is visible
*/
@Override
public boolean isShowing() {
return evjpanel.isShowing();
}
@Override
public boolean requestFocusInWindow() {
return evjpanel.requestFocusInWindow();
}
/**
* @see JPanel#setPreferredSize(Dimension)
* @param preferredSize
* prefered size
*/
public void setPreferredSize(Dimension preferredSize) {
evjpanel.setPreferredSize(preferredSize);
}
@Override
public void setPreferredSize(GDimension preferredSize) {
evjpanel.setPreferredSize(GDimensionD.getAWTDimension(preferredSize));
}
/**
* @see JPanel#revalidate()
*/
public void revalidate() {
evjpanel.revalidate();
}
/**
* @see JPanel#addMouseListener(MouseListener)
* @param ml
* mouse listener
*/
public void addMouseListener(MouseListener ml) {
evjpanel.addMouseListener(ml);
}
/**
* @see JPanel#removeComponentListener(ComponentListener)
* @param ml
* mouse listener
*/
public void removeMouseListener(MouseListener ml) {
evjpanel.removeMouseListener(ml);
}
/**
* @see JPanel#addMouseMotionListener(MouseMotionListener)
* @param mml
* mouse motion listener
*/
public void addMouseMotionListener(MouseMotionListener mml) {
evjpanel.addMouseMotionListener(mml);
}
/**
* @see JPanel#removeMouseMotionListener(MouseMotionListener)
* @param mml
* mouse motion listener
*/
public void removeMouseMotionListener(MouseMotionListener mml) {
evjpanel.removeMouseMotionListener(mml);
}
/**
* @see JPanel#addMouseWheelListener(MouseWheelListener)
* @param mwl
* mouse wheel listener
*/
public void addMouseWheelListener(MouseWheelListener mwl) {
evjpanel.addMouseWheelListener(mwl);
}
/**
* @see JPanel#removeMouseWheelListener(MouseWheelListener)
* @param mwl
* mouse wheel listener
*/
public void removeMouseWheelListener(MouseWheelListener mwl) {
evjpanel.removeMouseWheelListener(mwl);
}
/**
* @see JPanel#dispatchEvent(AWTEvent)
* @param componentEvent
* component event
*/
public void dispatchEvent(ComponentEvent componentEvent) {
evjpanel.dispatchEvent(componentEvent);
}
/**
* @see JPanel#setBorder(Border)
* @param border
* new border
*/
@Override
public void setBorder(Border border) {
evjpanel.setBorder(border);
}
/**
* @see JPanel#addComponentListener(ComponentListener)
* @param componentListener
* component listener
*/
public void addComponentListener(ComponentListener componentListener) {
evjpanel.addComponentListener(componentListener);
}
/**
* @param dimension
* new size
*/
public void setSize(Dimension dimension) {
evjpanel.setSize(dimension);
}
/**
* @return prefered size
*/
public Dimension getPreferredSize() {
return evjpanel.getPreferredSize();
}
/**
* @see EuclidianViewJPanelD#processMouseEventImpl(MouseEvent)
* @param e
* mouse event
*/
protected void processMouseEvent(MouseEvent e) {
evjpanel.processMouseEventImpl(e);
}
/**
* Initializes this panel
*
* @param repaint
* ignored parameter
*/
protected void initPanel(boolean repaint) {
// preferred size
evjpanel.setPreferredSize(null);
}
// @Override
@Override
public void setToolTipText(String plain) {
if ((tooltipsInThisView == EuclidianStyleConstants.TOOLTIPS_ON)
|| (tooltipsInThisView == EuclidianStyleConstants.TOOLTIPS_AUTOMATIC)) {
evjpanel.setToolTipText(plain);
}
}
@Override
public int getWidth() {
return evjpanel.getWidth();
}
@Override
public int getHeight() {
return evjpanel.getHeight();
}
@Override
protected void updateSizeKeepDrawables() {
// record the old coord system
setWidth(getWidth());
setHeight(getHeight());
if ((getWidth() <= 0) || (getHeight() <= 0)) {
return;
}
// real world values
companion.setXYMinMaxForUpdateSize();
if (app.getKernel().getConstruction() != null) {
app.getKernel().getConstruction()
.notifyEuclidianViewCE(EVProperty.SIZE);
}
setRealWorldBounds();
// ================================================
// G.Sturr 8/27/10: test: rescale on window resize
//
// reset the coord system so that our view dimensions are restored
// using the new scaling factors.
// setRealWorldCoordSystem(xminTemp, xmaxTemp, yminTemp, ymaxTemp);
GraphicsConfiguration gconf = evjpanel.getGraphicsConfiguration();
try {
createImage(gconf);
} catch (OutOfMemoryError e) {
bgImage = null;
bgGraphics = null;
}
updateBackgroundImage();
}
private void createImage(GraphicsConfiguration gc) {
if (gc != null) {
bgImage = new GBufferedImageD(
gc.createCompatibleImage(getWidth(), getHeight()));
bgGraphics = bgImage.createGraphics();
bgGraphics.setAntialiasing();
}
}
@Override
public void clearView() {
evjpanel.removeAll(); // remove hotEqns
resetLists();
removeTextField();
updateBackgroundImage(); // clear traces and images
// We call this on file loading, so we don't want to mess up the
// settings we have just loaded using initView
}
@Override
public GColor getBackgroundCommon() {
return GColorD.newColor(evjpanel.getBackground());
}
@Override
public void setBackground(GColor bgColor) {
evjpanel.setBackground(GColorD.getAwtColor(bgColor));
}
// temp image
private final Graphics2D g2Dtemp = new BufferedImage(5, 5,
BufferedImage.TYPE_INT_RGB).createGraphics();
private boolean printScaleString;
/**
* @return temporary graphics that is stored in this view
*/
final public Graphics2D getTempGraphics2D() {
g2Dtemp.setFont(getApplication().getPlainFont());
return g2Dtemp;
}
@Override
final public GGraphics2D getTempGraphics2D(GFont font) {
g2Dtemp.setFont(GFontD.getAwtFont(font));
return new GGraphics2DD(g2Dtemp);
}
@Override
final protected void setHeight(int height) {
//
}
@Override
final protected void setWidth(int width) {
//
}
@Override
final protected void setStyleBarMode(int mode) {
if (hasStyleBar()) {
getStyleBar().setMode(mode);
}
}
/**
*
* @return new euclidian style bar
*/
@Override
protected EuclidianStyleBarD newEuclidianStyleBar() {
return new EuclidianStyleBarD(this);
}
@Override
protected MyZoomerD newZoomer() {
return new MyZoomerD(this);
}
/**
* @return whether to print scalestring
*/
public boolean isPrintScaleString() {
return printScaleString;
}
/**
*
* @param printScaleString
* whether to print scalestring
*/
public void setPrintScaleString(boolean printScaleString) {
this.printScaleString = printScaleString;
}
private String getScaleString() {
if (isPrintScaleString()) {
Localization loc = getApplication().getLocalization();
StringBuilder sb = new StringBuilder(
loc.getMenu("ScaleInCentimeter"));
if (printingScale <= 1) {
sb.append(": 1:");
sb.append(printScaleNF.format(1 / printingScale));
} else {
sb.append(": ");
sb.append(printScaleNF.format(printingScale));
sb.append(":1");
}
// add yAxis scale too?
if (!Kernel.isEqual(getScaleRatio(), 1.0)) {
sb.append(" (x), ");
double yPrintScale = (printingScale * getYscale())
/ getXscale();
if (yPrintScale < 1) {
sb.append("1:");
sb.append(printScaleNF.format(1 / yPrintScale));
} else {
sb.append(printScaleNF.format(yPrintScale));
sb.append(":1");
}
sb.append(" (y)");
}
return sb.toString();
}
return null;
}
@Override
public boolean suggestRepaint() {
return false;
// only used in web for now
}
@Override
public void closeDropdowns() {
closeAllDropDowns();
}
@Override
public void setCursor(EuclidianCursor cursor) {
switch (cursor) {
case HIT:
setHitCursor();
return;
case DRAG:
setDragCursor();
return;
case MOVE:
setMoveCursor();
return;
case DEFAULT:
setDefaultCursor();
return;
case RESIZE_X:
setResizeXAxisCursor();
return;
case RESIZE_Y:
setResizeYAxisCursor();
return;
case TRANSPARENT:
setTransparentCursor();
return;
}
}
@Override
public void readText(String text) {
// TODO Auto-generated method stub
}
@Override
protected EuclidianStyleBar newDynamicStyleBar() {
// TODO Auto-generated method stub
return null;
}
@Override
protected void addDynamicStylebarToEV(EuclidianStyleBar dynamicStylebar) {
// TODO Auto-generated method stub
}
}