package org.geogebra.desktop.gui.view.data;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Point;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.event.ActionEvent;
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.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JDialog;
import javax.swing.JPopupMenu;
import org.geogebra.common.euclidian.EuclidianController;
import org.geogebra.common.gui.view.data.PlotPanelEuclidianViewCommon;
import org.geogebra.common.gui.view.data.PlotPanelEuclidianViewInterface;
import org.geogebra.common.gui.view.data.PlotSettings;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.main.Localization;
import org.geogebra.common.util.debug.Log;
import org.geogebra.common.util.lang.Unicode;
import org.geogebra.desktop.awt.GBufferedImageD;
import org.geogebra.desktop.euclidian.EuclidianControllerListeners;
import org.geogebra.desktop.euclidian.EuclidianViewD;
import org.geogebra.desktop.export.GraphicExportDialog;
import org.geogebra.desktop.gui.GuiManagerD;
import org.geogebra.desktop.main.AppD;
import org.geogebra.desktop.util.GuiResourcesD;
/**
*
* An extension of EuclidianView used for display of a set of GeoElements
* without all of the mouse and key controls of the full EuclidianView. Unlike
* EuclidianView, this view remains centered in the panel when resized.
*
* Includes a right-click context menu and DnD support for exporting either the
* set of GeoElements or an image of the view.
*
*
* @author G.Sturr 2010-6-30
*
*/
public class PlotPanelEuclidianViewD extends EuclidianViewD
implements ComponentListener, DragGestureListener, DragSourceListener,
PlotPanelEuclidianViewInterface {
private EuclidianController ec;
private final PlotPanelEuclidianViewD plotPanelEV;
public PlotPanelEuclidianViewCommon commonFields;
/** Mouse listener to trigger context menu */
private MyMouseListener myMouseListener;
/** Drag source for DnD */
private DragSource ds;
/** DnD cursors */
private Cursor grabCursor;
/** List of AbstractActions for the popup context menu */
ArrayList<AbstractAction> actionList;
/**
* Action method export of GeoElements to EuclidianView. Since the action is
* specific to the parent container, it is injected in the constructor.
*/
private AbstractAction exportToEVAction;
/** DataFlavor for plotPanel drags */
public final static DataFlavor plotPanelFlavor = new DataFlavor(
DataFlavor.javaJVMLocalObjectMimeType
+ ";class=javax.swing.AbstractAction",
"plotPanelFlavor");
/*************************************************
* Construct the panel
*/
public PlotPanelEuclidianViewD(Kernel kernel, AbstractAction exportAction) {
super(new PlotPanelEuclidianControllerD(kernel),
PlotPanelEuclidianViewCommon.showAxes,
PlotPanelEuclidianViewCommon.showGrid, EVNO_GENERAL, null);
this.exportToEVAction = exportAction;
// set fields
if (commonFields == null) {
setCommonFields();
}
plotPanelEV = this;
// create cursors for DnD
grabCursor = getCursorForImage(GuiResourcesD.CURSOR_GRAB);
// enable/disable mouseListeners
setMouseEnabled(false, true);
setMouseMotionEnabled(false);
setMouseWheelEnabled(false);
this.addMouseMotionListener(new MyMouseMotionListener());
// set some default EV features
setAllowShowMouseCoords(false);
setAxesCornerCoordsVisible(false);
updateFonts();
// set preferred size so that updateSize will work and this EV can be
// properly initialized
setPreferredSize(new Dimension(300, 200));
setSize(new Dimension(300, 200));
updateSize();
// add a component listener that will allow the view to resize in a
// centered
addComponentListener(this);
enableDnD();
}
private void setCommonFields() {
// set fields
commonFields = new PlotPanelEuclidianViewCommon(false);
commonFields.setPlotSettings(new PlotSettings());
setViewId(kernel);
this.ec = this.getEuclidianController();
}
@Override
public void setViewId(Kernel kernel) {
// get viewID from GuiManager
commonFields.setViewID(
((GuiManagerD) kernel.getApplication().getGuiManager())
.assignPlotPanelID(this));
}
/*********** End Constructor **********************/
/**
* Overrides EuclidianView setMode method so that no action is taken on a
* mode change.
*/
@Override
public void setMode(int mode) {
// .... do nothing
}
/** Returns viewID */
@Override
public int getViewID() {
if (commonFields == null) {
setCommonFields();
}
return commonFields.getViewID();
}
/**
* Override updateSize() so that our plots stay centered and scaled in a
* resized window.
*/
@Override
public void updateSize() {
commonFields.updateSize(this);
}
@Override
public void updateSizeKeepDrawables() {
super.updateSizeKeepDrawables();
}
// ==================================================
// Plot Settings
// =================================================
/**
* Returns plotSettings field for this panel.
*
* @return
*/
public PlotSettings getPlotSettings() {
return commonFields.getPlotSettings();
}
/**
* Uses the values stored in the plotSettings field to update the features
* of this EuclidianView (e.g. axes visibility)
*/
@Override
public void setEVParams() {
commonFields.setEVParams(this);
}
// ===========================================================
// Component Listener
// ===========================================================
@Override
public void componentHidden(ComponentEvent arg0) {
// ignore
}
@Override
public void componentMoved(ComponentEvent arg0) {
// ignore
}
@Override
public void componentResized(ComponentEvent arg0) {
// make sure that we force a pixel buffer under the x-axis
setEVParams();
}
@Override
public void componentShown(ComponentEvent arg0) {
// ignore
}
// ==================================================
// Mouse Listeners
// =================================================
/**
* Enables/disables the default EuclidianController mouse listener and
* myMouseListener, the listener that handles the right-click context menu.
*
* @param enableECMouseListener
* default = false
* @param enableMyMouseListener
* default = true
*/
public void setMouseEnabled(boolean enableECMouseListener,
boolean enableMyMouseListener) {
if (myMouseListener == null) {
myMouseListener = new MyMouseListener();
}
removeMouseListener(myMouseListener);
removeMouseListener((EuclidianControllerListeners) ec);
if (enableMyMouseListener) {
addMouseListener(myMouseListener);
}
if (enableECMouseListener) {
addMouseListener((EuclidianControllerListeners) ec);
}
}
/**
* Enables/disables the EuclidianController mouse motion listener
*
* @param enableMouseMotion
* default = false
*/
public void setMouseMotionEnabled(boolean enableMouseMotion) {
removeMouseMotionListener((EuclidianControllerListeners) ec);
if (enableMouseMotion) {
addMouseMotionListener((EuclidianControllerListeners) ec);
}
}
/**
* Enables/disables the EuclidianController mouse wheel listener
*
* @param enableMouseWheel
* default = false
*/
public void setMouseWheelEnabled(boolean enableMouseWheel) {
removeMouseWheelListener((EuclidianControllerListeners) ec);
if (enableMouseWheel) {
addMouseWheelListener((EuclidianControllerListeners) ec);
}
}
/**
* Mouse listener class to handle right click trigger for the context menu.
* Right click events are consumed to prevent the EuclidianController from
* handling right-clicks as well.
*/
private class MyMouseListener implements MouseListener {
@Override
public void mouseClicked(MouseEvent e) {
// right click shows context menu
if (AppD.isRightClick(e)) {
e.consume();
ContextMenu popup = new ContextMenu();
popup.show(e.getComponent(), e.getX(), e.getY());
}
}
@Override
public void mousePressed(MouseEvent e) {
if (AppD.isRightClick(e)) {
e.consume();
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (AppD.isRightClick(e)) {
e.consume();
}
}
@Override
public void mouseEntered(MouseEvent e) {
// ignore
}
@Override
public void mouseExited(MouseEvent e) {
// ignore
}
}
/**
* Mouse motion listener for handling DnD drags
*/
class MyMouseMotionListener implements MouseMotionListener {
@Override
public void mouseDragged(MouseEvent e) {
// ignore
}
/** handles mouse motion over the drag region */
@Override
public void mouseMoved(MouseEvent e) {
commonFields.setOverDragRegion(e.getPoint().y < 10);
setDefaultCursor();
}
}
/**
* Overrides EuclidianView.setDefaultCursor so that DnD grab hand cursors
* are drawn when over the drag region.
*/
@Override
public void setDefaultCursor() {
if (commonFields.isOverDragRegion()) {
setCursor(grabCursor);
} else {
setCursor(defaultCursor);
}
}
// =============================================
// Context Menu Popup
// =============================================
/**
* Popup menu with menu items for exporting either the GeoElements or an
* image of the view.
*/
private class ContextMenu extends JPopupMenu {
private static final long serialVersionUID = 1L;
public ContextMenu() {
this.setOpaque(true);
setFont(getApplication().getPlainFont());
for (AbstractAction action : getActionList()) {
add(action);
}
}
}
public JPopupMenu getContextMenu() {
return new ContextMenu();
}
/**
* Sets the list of AbstractActions to be used in the popup context menu.
*
* @param actionList
*/
public void setActionList(ArrayList<AbstractAction> actionList) {
this.actionList = actionList;
}
/**
* Returns the list of AbstractActions to be used in the popup context menu.
*
* @return
*/
public ArrayList<AbstractAction> getActionList() {
if (actionList == null) {
actionList = new ArrayList<AbstractAction>();
Localization loc = getApplication().getLocalization();
if (exportToEVAction != null) {
exportToEVAction.putValue(Action.NAME,
loc.getMenu("CopyToGraphics"));
exportToEVAction.putValue(Action.SMALL_ICON,
getApplication().getEmptyIcon());
actionList.add(exportToEVAction);
}
if (!app.isMacOS() || !((AppD) app).isJava7()) {
actionList.add(drawingPadToClipboardAction);
}
actionList.add(exportGraphicAction);
}
return actionList;
}
/**
* Adds an AbstractAction to the end of the list of AbstractActions
* displayed in the context menu.
*
* @param action
*/
public void appendActionList(AbstractAction action) {
getActionList().add(action);
}
/**
* Action to export an image of the view as a file.
*/
AbstractAction exportGraphicAction = new AbstractAction(
getApplication().getLocalization().getMenu("ExportAsPicture")
+ Unicode.ellipsis,
getApplication().getScaledIcon(GuiResourcesD.IMAGE_X_GENERIC)) {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
try {
Thread runner = new Thread() {
@Override
public void run() {
getApplication().setWaitCursor();
try {
getApplication().getSelectionManager()
.clearSelectedGeos(true, false);
getApplication().updateSelection(false);
// use reflection for
JDialog d = new GraphicExportDialog(
getApplication(), plotPanelEV);
d.setVisible(true);
} catch (Exception ex) {
Log.debug("GraphicExportDialog not available");
}
getApplication().setDefaultCursor();
}
};
runner.start();
}
catch (java.lang.NoClassDefFoundError ee) {
getApplication().showError("ExportJarMissing");
ee.printStackTrace();
}
}
};
/**
* Action to export an image of the view to the clipboard.
*/
AbstractAction drawingPadToClipboardAction = new AbstractAction(
getApplication().getLocalization().getMenu("CopyToClipboard"),
getApplication().getEmptyIcon()) {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
getApplication().getSelectionManager().clearSelectedGeos(true,
false);
getApplication().updateSelection(false);
Thread runner = new Thread() {
@Override
public void run() {
getApplication().setWaitCursor();
getApplication().copyGraphicsViewToClipboard(plotPanelEV);
getApplication().setDefaultCursor();
}
};
runner.start();
}
};
// =====================================================
// Drag and Drop
// =====================================================
protected void enableDnD() {
ds = new DragSource();
ds.createDefaultDragGestureRecognizer(this.getJPanel(),
DnDConstants.ACTION_COPY, this);
}
@Override
public void dragDropEnd(DragSourceDropEvent e) {
// clean up selection rectangle
plotPanelEV.setSelectionRectangle(null);
plotPanelEV.repaint();
}
@Override
public void dragEnter(DragSourceDragEvent e) {
// ignore
}
@Override
public void dragExit(DragSourceEvent e) {
// ignore
}
@Override
public void dragOver(DragSourceDragEvent e) {
// ignore
}
@Override
public void dropActionChanged(DragSourceDragEvent e) {
// ignore
}
@Override
public void dragGestureRecognized(DragGestureEvent dge) {
if (commonFields.isOverDragRegion()) {
plotPanelEV.setSelectionRectangle(null);
// start drag
ds.startDrag(dge, DragSource.DefaultCopyDrop, null, new Point(0, 0),
new TransferablePlotPanel(), this);
}
}
/**
* Extension of Transferable for exporting PlotPanelEV contents
*/
public class TransferablePlotPanel implements Transferable {
private final DataFlavor supportedFlavors[] = { plotPanelFlavor,
DataFlavor.imageFlavor };
private final Image image;
// private final Action act;
public TransferablePlotPanel() {
image = GBufferedImageD
.getAwtBufferedImage(plotPanelEV.getExportImage(1d));
// act = sampleAction;
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return supportedFlavors;
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
for (int i = 0; i < supportedFlavors.length; i++) {
if (flavor.equals(supportedFlavors[i])) {
return true;
}
}
return false;
}
@Override
public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException {
if (flavor.equals(plotPanelFlavor)) {
return exportToEVAction;
}
if (flavor.equals(DataFlavor.imageFlavor)) {
return image;
}
throw new UnsupportedFlavorException(flavor);
}
}
@Override
public double getPixelOffset() {
return (30 * getApplication().getSmallFont().getSize()) / 12.0;
}
@Override
public boolean isPlotPanel() {
return true;
}
}