/*
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.
*/
/**
* GeoGebra Application
*
* @author Markus Hohenwarter
*/
package org.geogebra.desktop.geogebra3D;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.FlowLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import org.geogebra.common.awt.GBufferedImage;
import org.geogebra.common.euclidian.EuclidianController;
import org.geogebra.common.euclidian.EuclidianCursor;
import org.geogebra.common.euclidian.EuclidianView;
import org.geogebra.common.euclidian.event.AbstractEvent;
import org.geogebra.common.euclidian3D.Input3DConstants;
import org.geogebra.common.geogebra3D.euclidian3D.EuclidianController3D;
import org.geogebra.common.geogebra3D.euclidian3D.EuclidianView3D;
import org.geogebra.common.geogebra3D.euclidian3D.openGL.GLFactory;
import org.geogebra.common.geogebra3D.input3D.Input3D;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoPlane3D;
import org.geogebra.common.geogebra3D.main.App3DCompanion;
import org.geogebra.common.gui.layout.DockManager;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.geos.AnimationExportSlider;
import org.geogebra.common.main.App;
import org.geogebra.common.main.AppCompanion;
import org.geogebra.common.main.settings.EuclidianSettings3D;
import org.geogebra.common.util.debug.Log;
import org.geogebra.desktop.CommandLineArguments;
import org.geogebra.desktop.euclidian.event.MouseEventD;
import org.geogebra.desktop.geogebra3D.euclidian3D.EuclidianController3DD;
import org.geogebra.desktop.geogebra3D.euclidian3D.EuclidianView3DD;
import org.geogebra.desktop.geogebra3D.euclidian3D.opengl.GLFactoryD;
import org.geogebra.desktop.geogebra3D.euclidianFor3D.EuclidianControllerFor3DD;
import org.geogebra.desktop.geogebra3D.euclidianFor3D.EuclidianViewFor3DD;
import org.geogebra.desktop.geogebra3D.euclidianInput3D.EuclidianControllerHand3D;
import org.geogebra.desktop.geogebra3D.euclidianInput3D.EuclidianControllerInput3D;
import org.geogebra.desktop.geogebra3D.euclidianInput3D.EuclidianViewInput3D;
import org.geogebra.desktop.geogebra3D.gui.GuiManager3D;
import org.geogebra.desktop.geogebra3D.gui.layout.panels.EuclidianDockPanel3DD;
import org.geogebra.desktop.geogebra3D.input3D.Input3DFactory;
import org.geogebra.desktop.geogebra3D.input3D.Input3DFactory.Input3DException;
import org.geogebra.desktop.geogebra3D.input3D.Input3DFactory.Input3DExceptionType;
import org.geogebra.desktop.geogebra3D.util.ImageManager3D;
import org.geogebra.desktop.gui.GuiManagerD;
import org.geogebra.desktop.gui.app.GeoGebraFrame3D;
import org.geogebra.desktop.gui.layout.DockManagerD;
import org.geogebra.desktop.main.AppD;
import org.geogebra.desktop.main.AppletImplementation;
import org.geogebra.desktop.main.GeoGebraPreferencesD;
import org.geogebra.desktop.main.LocalizationD;
import org.geogebra.desktop.util.FrameCollector;
public class App3D extends AppD {
private EuclidianView3D euclidianView3D;
private EuclidianController3D euclidianController3D;
public App3D(CommandLineArguments args, JFrame frame, boolean undoActive) {
this(args, frame, null, undoActive);
}
public App3D(CommandLineArguments args, AppletImplementation applet,
boolean undoActive) {
this(args, null, applet, undoActive);
}
private App3D(CommandLineArguments args, JFrame frame,
AppletImplementation applet, boolean undoActive) {
super(args, frame, applet, null, undoActive, new LocalizationD(3));
runThreadForCheckInput3D();
}
public App3D(CommandLineArguments args, Container comp,
boolean undoActive) {
super(args, null, null, comp, undoActive, new LocalizationD(3));
runThreadForCheckInput3D();
}
private class ThreadForCheckInput3D extends Thread {
private App app;
public ThreadForCheckInput3D(App app) {
this.app = app;
}
@Override
public void run() {
if (app.isApplet()) {
return;
}
boolean realsenseInited = initRealsense();
if (!realsenseInited) {
initZspace();
}
}
private boolean initRealsense() {
try {
// try to init realsense
Input3DFactory.initRealsense();
Log.debug("RealSense: Session successfully created");
// save in prefs
setInput3DType(Input3DConstants.PREFS_REALSENSE);
// show message
showRealSenseCongratulations();
return true;
} catch (Input3DException e) {
Log.debug(e.getMessage());
if (e.getType() == Input3DExceptionType.NOT_UP_TO_DATE) {
showRealSenseNotUpToDate(e.getMessage());
}
}
return false;
}
private boolean initZspace() {
try {
// try to init zSpace
Log.debug("zSpace: try to init");
Input3DFactory.initZSpace();
Log.debug("zSpace: successfully detected");
// save in prefs
setInput3DType(Input3DConstants.PREFS_ZSPACE);
// show message
showZSpaceCongratulations();
return true;
} catch (Input3DException e) {
Log.debug(e.getMessage());
}
return false;
}
}
private void runThreadForCheckInput3D() {
if (!tubeLoginIsShowing && AppD.WINDOWS && !isApplet()
&& getInput3DType().equals(Input3DConstants.PREFS_NONE)) {
Log.debug("============ runThreadToCheckInput3D ");
Thread t = new ThreadForCheckInput3D(this);
t.start();
}
}
/**
* shows congratulations message for using realsense
*/
void showRealSenseCongratulations() {
showInput3DCongratulations(
getLocalization().getMenu("RealSense.DetectedMessage"),
REALSENSE_TUTORIAL);
}
/**
* recommend to update version
*
* @param version
* version currently installed
*/
void showRealSenseNotUpToDate(String version) {
showInput3DMessage(
getLocalization().getPlain("RealSense.NotUpToDate", version),
getLocalization().getPlain("RealSense.DownloadUpdate"),
"https://software.intel.com/intel-realsense-sdk/download");
}
/**
* shows congratulations message for using zspace
*/
void showZSpaceCongratulations() {
showInput3DCongratulations(
getLocalization().getMenu("ZSpace.DetectedMessage"),
"http://www.geogebra.org/tutorial/zspace");
}
private void showInput3DCongratulations(final String message,
final String tutorialURL) {
showInput3DMessage(message, getLocalization().getMenu("OpenTutorial"),
tutorialURL);
}
private void showInput3DMessage(final String message,
final String messageForURL, final String URL) {
// popup help dialog
input3DPopupShowing = true;
final JFrame frame = new JFrame();
Container c = frame.getContentPane();
JPanel panel = new JPanel();
c.add(panel);
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.setBackground(Color.WHITE);
panel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
JLabel label = new JLabel(message);
JPanel labelPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
labelPanel.setBackground(Color.WHITE);
labelPanel.add(label);
panel.add(labelPanel);
JLabel website = new JLabel();
// String tutorialText = "Click here to get a tutorial";
website.setText("<html><a href=\"\">" + messageForURL + "</a></html>");
website.setCursor(new Cursor(Cursor.HAND_CURSOR));
website.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
try {
try {
frame.setAlwaysOnTop(false);
} catch (SecurityException se) {
// failed to unset on top
}
Desktop.getDesktop().browse(new URI(URL));
} catch (IOException e1) {
// not working
} catch (URISyntaxException e1) {
// not working
}
}
});
JPanel websitePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
websitePanel.setBackground(Color.WHITE);
websitePanel.add(website);
panel.add(websitePanel);
JLabel closeLabel = new JLabel("OK");
closeLabel.setCursor(new Cursor(Cursor.HAND_CURSOR));
closeLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
frame.setVisible(false);
if (tubeLoginHasToBeShown) {
perspectivePopupHasToBeShown = perspectivePopupHasToBeShown
&& superShowTubeLogin();
}
if (perspectivePopupHasToBeShown) {
superShowPerspectivePopup();
}
}
});
JPanel closePanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
closePanel.setBackground(Color.WHITE);
closePanel.add(closeLabel);
panel.add(closePanel);
frame.setUndecorated(true);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
try {
frame.setAlwaysOnTop(true);
} catch (SecurityException e) {
// failed to set on top
}
}
/**
* download and update realsense
*/
void updateRealSense() {
Log.debug("\n========== updating RealSense");
}
boolean input3DPopupShowing = false;
boolean tubeLoginHasToBeShown = false;
private boolean tubeLoginIsShowing = false;
boolean perspectivePopupHasToBeShown = false;
@Override
protected boolean showTubeLogin() {
if (input3DPopupShowing) {
tubeLoginHasToBeShown = true;
return true;
}
return superShowTubeLogin();
}
boolean superShowTubeLogin() {
tubeLoginHasToBeShown = false;
boolean ret = super.showTubeLogin();
tubeLoginIsShowing = false;
return ret;
}
@Override
public void isShowingLogInDialog() {
tubeLoginIsShowing = true;
runThreadForCheckInput3D();
}
@Override
protected void showPerspectivePopup() {
if (input3DPopupShowing) {
perspectivePopupHasToBeShown = true;
} else {
superShowPerspectivePopup();
}
}
void superShowPerspectivePopup() {
perspectivePopupHasToBeShown = false;
super.showPerspectivePopup();
}
/**
* set 3D input
*
* @param type
* type
*/
public static void setInput3DType(String type) {
GeoGebraPreferencesD.getPref().setInput3DType(type);
}
@Override
public String getInput3DType() {
return GeoGebraPreferencesD.getPref().getInput3DType();
}
@Override
protected void initImageManager(Component component) {
imageManager = new ImageManager3D(component, this);
}
private void initEuclidianController3D() {
Input3D input3D;
if (AppD.WINDOWS && !isApplet()) {
// init the 3D euclidian view (with perhaps a specific 3D input)
try {
input3D = Input3DFactory.createInput3D(this, getInput3DType());
} catch (Input3DException e) {
if (e.getType() == Input3DExceptionType.INSTALL) {
// reset 3D input type, guessing 3d input has been
// uninstalled
setInput3DType(Input3DConstants.PREFS_NONE);
} else if (e.getType() == Input3DExceptionType.NOT_UP_TO_DATE) {
showRealSenseNotUpToDate(e.getMessage());
}
input3D = null;
Log.debug("Problem initializing 3D Input:" + e.getMessage());
} catch (Throwable e) {
input3D = null;
Log.debug("Problem initializing 3D Input:" + e.getClass() + " "
+ e.getMessage());
}
} else {
input3D = null;
}
// input3D = null;
if (input3D != null) {
switch (input3D.getDeviceType()) {
case HAND:
euclidianController3D = new EuclidianControllerHand3D(kernel,
input3D);
break;
case PEN:
default:
euclidianController3D = new EuclidianControllerInput3D(kernel,
input3D);
break;
}
// set specific settings
input3D.setSpecificSettings(
(EuclidianSettings3D) getSettings().getEuclidian(3));
} else {
euclidianController3D = new EuclidianController3DD(kernel);
}
}
@Override
protected void exitFrame() {
super.exitFrame();
if (euclidianController3D != null) {
euclidianController3D.exitInput3D();
}
}
@Override
public boolean isRightClick(AbstractEvent e) {
if (e instanceof MouseEventD) {
return isRightClick(MouseEventD.getEvent(e));
}
return e.isRightClick();
}
@Override
public EuclidianController newEuclidianController(Kernel kernel) {
return new EuclidianControllerFor3DD(kernel);
}
@Override
protected EuclidianView newEuclidianView(boolean[] showAxes1,
boolean showGrid1) {
return new EuclidianViewFor3DD(getEuclidianController(), showAxes1,
showGrid1, 1, getSettings().getEuclidian(1));
}
@Override
public void setMode(int mode) {
super.setMode(mode);
if (isEuclidianView3Dinited()) {
euclidianView3D.setMode(mode);
}
}
@Override
public String getCompleteUserInterfaceXML(boolean asPreference) {
StringBuilder sb = new StringBuilder();
// save super settings
sb.append(super.getCompleteUserInterfaceXML(asPreference));
// save euclidianView3D settings
if (isEuclidianView3Dinited()) {
euclidianView3D.getXML(sb, asPreference);
}
// save euclidian views for plane settings
((App3DCompanion) companion).addCompleteUserInterfaceXMLForPlane(sb,
asPreference);
return sb.toString();
}
/**
* return the 3D euclidian view
*
* @return the 3D euclidian view
*/
@Override
public EuclidianView3D getEuclidianView3D() {
if (this.euclidianView3D == null) {
initEuclidianController3D();
if (euclidianController3D.hasInput3D()) {
euclidianView3D = new EuclidianViewInput3D(
euclidianController3D, getSettings().getEuclidian(3));
} else {
euclidianView3D = new EuclidianView3DD(euclidianController3D,
getSettings().getEuclidian(3));
}
}
return euclidianView3D;
}
@Override
public boolean hasEuclidianView3D() {
return true;
}
@Override
public boolean isEuclidianView3Dinited() {
return this.euclidianView3D != null;
}
@Override
public void needThumbnailFor3D() {
if (euclidianView3D != null) {
getEuclidianView3D().getRenderer().needExportImage();
}
}
// ///////////////////////////////
// GUI
// ///////////////////////////////
@Override
public void refreshViews() {
if (isEuclidianView3Dinited()) {
getEuclidianView3D().reset();
DockManagerD dockManager = (DockManagerD) getGuiManager()
.getLayout().getDockManager();
((EuclidianDockPanel3DD) dockManager.getPanel(VIEW_EUCLIDIAN3D))
.refresh(dockManager);
}
super.refreshViews();
}
@Override
public void resume3DRenderer() {
if (isEuclidianView3Dinited()) {
DockManager dockManager = getGuiManager().getLayout()
.getDockManager();
((EuclidianDockPanel3DD) dockManager.getPanel(VIEW_EUCLIDIAN3D))
.resumeRenderer();
}
}
public void toggleAxis3D() {
// toggle axis
getEuclidianView3D().toggleAxis();
}
public void togglePlane() {
// toggle xOy plane
getEuclidianView3D().getSettings().togglePlane();
}
public void toggleGrid3D() {
// toggle xOy grid
getEuclidianView3D().toggleGrid();
}
public void setShowAxesSelected3D(JCheckBoxMenuItem cb) {
cb.setSelected(getEuclidianView3D().axesAreAllVisible());
}
/**
* set the show plane combo box selected if the plane is visible
*
* @param cb
*/
public void setShowPlaneSelected(JCheckBoxMenuItem cb) {
GeoPlane3D p = (GeoPlane3D) getKernel().getXOYPlane();
cb.setSelected(p.isPlateVisible());
}
/**
* set the show grid combo box selected if the plane is visible
*
* @param cb
*/
public void setShowGridSelected3D(JCheckBoxMenuItem cb) {
GeoPlane3D p = (GeoPlane3D) getKernel().getXOYPlane();
cb.setSelected(p.isGridVisible());
}
@Override
protected GuiManagerD newGuiManager() {
return new GuiManager3D(this);
}
// /////////////////////////////////////
// COMMANDS
// /////////////////////////////////////
/*
* (non-Javadoc)
*
* @see geogebra.main.Application#getCommandSyntax(java.lang.String) check
* if there's a Command.Syntax3D key. If not, return Command.Syntax key
*/
@Override
public void updateStyleBars() {
super.updateStyleBars();
if (showView(App.VIEW_EUCLIDIAN3D)) {
getEuclidianView3D().getStyleBar().updateStyleBar();
}
}
// ///////////////////////////////
// FOR TESTING : TODO remove all
@Override
public boolean is3D() {
return true;
}
private EuclidianCursor oldCursorMode;
@Override
protected void handleShiftEvent(boolean isShiftDown) {
if (!this.isEuclidianView3Dinited()) {
return;
}
if (isShiftDown) {
EuclidianCursor cursor = getEuclidianView3D()
.updateCursorIfNotTranslateViewCursor();
if (cursor != null) {
oldCursorMode = cursor;
}
// oldCursorMode = getEuclidianView3D().getCursor();
// getEuclidianView3D().setCursor(EuclidianCursor.MOVE);
// Log.debug(oldCursorMode);
} else {
if (oldCursorMode != null) {
getEuclidianView3D().setCursor(oldCursorMode);
}
}
}
@Override
public void exportAnimatedGIF(EuclidianView ev, FrameCollector gifEncoder,
AnimationExportSlider num, int n, double val, double min,
double max, double step) {
if (!(ev instanceof EuclidianView3D)) {
// regular 2D export
super.exportAnimatedGIF(ev, gifEncoder, num, n, val, min, max,
step);
return;
}
getEuclidianView3D().getRenderer().startAnimatedGIFExport(gifEncoder,
num, n, val, min, max, step);
}
@Override
public void copyGraphicsViewToClipboard() {
if (!(getActiveEuclidianView() instanceof EuclidianView3D)) {
// regular 2D export
super.copyGraphicsViewToClipboard();
return;
}
getEuclidianView3D().getRenderer().exportToClipboard();
}
@Override
public void fileNew() {
super.fileNew();
((App3DCompanion) companion).removeAllEuclidianViewForPlane();
}
@Override
public boolean loadFile(File file, boolean isMacroFile) {
if (!checkFileExistsAndShowFileNotFound(file)) {
return false;
}
((App3DCompanion) companion).removeAllEuclidianViewForPlane();
return loadExistingFile(file, isMacroFile);
}
@Override
public void createNewWindow() {
GeoGebraFrame3D.createNewWindow3D(cmdArgs.getGlobalArguments());
}
@Override
public GBufferedImage getActiveEuclidianViewExportImage(double maxX,
double maxY) {
EuclidianView ev = getActiveEuclidianView();
// force 3D view if showing
if (this.euclidianView3D != null) {
EuclidianView3D ev3D = getEuclidianView3D();
if (ev3D.isShowing()) {
ev = ev3D;
}
}
return getEuclidianViewExportImage(ev, maxX, maxY);
}
/**
* only for 3D really. Overridden in App3D
*/
@Override
public void uploadToGeoGebraTubeOnCallback() {
if (!isEuclidianView3Dinited()) {
uploadToGeoGebraTube();
return;
}
EuclidianView3D ev3D = getEuclidianView3D();
if (ev3D.isShowing()) {
ev3D.getRenderer().uploadToGeoGebraTube();
} else {
uploadToGeoGebraTube();
}
}
@Override
protected void initFactories() {
super.initFactories();
if (GLFactory.getPrototype() == null) {
GLFactory.setPrototypeIfNull(new GLFactoryD());
}
}
@Override
protected AppCompanion newAppCompanion() {
return new App3DCompanionD(this);
}
@Override
protected void handleOptionArgsEarly(CommandLineArguments args) {
super.handleOptionArgsEarly(args);
isStereo3D = false;
useShaders = false;
if (args == null) {
return;
}
if (args.containsArg("testShaders")) {
useShaders = true;
}
if (args.containsArg("stereo")) {
isStereo3D = true;
}
}
private boolean useShaders;
private boolean isStereo3D;
public boolean isStereo3D() {
return isStereo3D && !isApplet();
}
@Override
public boolean useShaders() {
return useShaders && !isApplet();
}
@Override
protected AppD newAppForTemplateOrInsertFile() {
return new App3D(new CommandLineArguments(null), new JPanel(), true);
}
@Override
public boolean handleSpaceKey() {
if (isEuclidianView3Dinited()) {
if (euclidianView3D.getCompanion().useHandGrabbing()) {
euclidianView3D.handleSpaceKey();
}
}
return super.handleSpaceKey();
}
@Override
public boolean useHugeGuiForInput3D() {
return euclidianController3D != null
&& euclidianController3D.isZSpace();
}
@Override
public int getToolbarPosition() {
if (useHugeGuiForInput3D()) {
return SwingConstants.WEST;
}
return super.getToolbarPosition();
}
@Override
public int getGUIFontSize() {
int size = super.getGUIFontSize();
if (size < 24 && useHugeGuiForInput3D()) {
return 24;
}
return size;
}
@Override
public int getScaledIconSize() {
int size = super.getScaledIconSize();
if (size < 36 && useHugeGuiForInput3D()) {
return 36;
}
return size;
}
}