package org.geogebra.desktop.gui.view.algebra;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import org.geogebra.common.awt.GPoint;
import org.geogebra.common.euclidian.EuclidianConstants;
import org.geogebra.common.euclidian.EuclidianViewInterfaceCommon;
import org.geogebra.common.gui.view.algebra.AlgebraController;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.geos.GProperty;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.desktop.gui.GuiManagerD;
import org.geogebra.desktop.main.AppD;
/**
* Controller for tree of geos
*
* @author mathieu
*
*/
public class AlgebraTreeController extends AlgebraController
implements MouseListener, MouseMotionListener {
/** tree */
private AlgebraTree tree;
private boolean skipSelection;
private GeoElement lastSelectedGeo = null;
/**
* Creator
*
* @param kernel
* kernel
*/
public AlgebraTreeController(Kernel kernel) {
super(kernel);
}
/**
* set the tree controlled
*
* @param tree
* tree
*/
public void setTree(AlgebraTree tree) {
this.tree = tree;
}
/**
* check double click
*
* @param geo
* geo clicked
* @param e
* mouse event
* @return true if double click
*/
protected boolean checkDoubleClick(GeoElement geo, MouseEvent e) {
return false;
}
/**
*
* @param mode
* euclidian controller mode
* @return true if the mode is a mode for selection
*/
protected boolean isSelectionModeForClick(int mode) {
return true;
}
/*
* MouseListener implementation for popup menus
*/
@Override
public void mouseClicked(MouseEvent e) {
// use mouse released instead
}
@Override
public void mouseReleased(MouseEvent e) {
// process click if no dragging
if (draggingOccured) {
return;
}
// right click is consumed in mousePressed, but in GeoGebra 3D,
// where heavyweight popup menus are enabled this doesn't work
// so make sure that this is no right click as well (ticket #302)
if (e.isConsumed() || AppD.isRightClick(e)) {
return;
}
// get GeoElement at mouse location
TreePath tp = tree.getPathForLocation(e.getX(), e.getY());
GeoElement geo = AlgebraTree.getGeoElementForPath(tp);
ArrayList<GeoElement> groupedGeos = null;
// check if we clicked on the scaled show/hide icon
if (geo != null) {
int h = tree.getIconShownHeight();
Rectangle rect = tree.getPathBounds(tp);
boolean iconClicked = rect != null && e.getX() - rect.x < h; // distance
// from
// left
// border
if (iconClicked) {
// icon clicked: toggle show/hide
geo.setEuclidianVisible(!geo.isSetEuclidianVisible());
geo.updateVisualStyle(GProperty.VISIBLE);
app.storeUndoInfo();
kernel.notifyRepaint();
return;
}
} else { // try group action
groupedGeos = groupAction(e, tp, false);
}
// check double click
if (checkDoubleClick(geo, e)) {
return;
}
EuclidianViewInterfaceCommon ev = app.getActiveEuclidianView();
int mode = ev.getMode();
if (!skipSelection && isSelectionModeForClick(mode)) {
// update selection
if (geo == null) {
if (!AppD.isControlDown(e) && !e.isShiftDown()) {
selection.clearSelectedGeos();
}
if (groupedGeos != null) {
selection.addSelectedGeos(groupedGeos, true);
}
} else {
// handle selecting geo
if (AppD.isControlDown(e)) {
selection.toggleSelectedGeo(geo);
if (selection.getSelectedGeos().contains(geo)) {
lastSelectedGeo = geo;
}
} else if (e.isShiftDown() && lastSelectedGeo != null) {
ArrayList<GeoElement> geos = tree
.getGeosBetween(lastSelectedGeo, geo);
if (geos != null) {
selection.clearSelectedGeos(false); // repaint will be
// done next step
selection.addSelectedGeos(geos, true);
}
} else {
selection.clearSelectedGeos(false); // repaint will be done
// next step
selection.addSelectedGeo(geo);
lastSelectedGeo = geo;
}
}
} else if (mode != EuclidianConstants.MODE_SELECTION_LISTENER) {
euclidianViewClick(ev, geo, e);
} else {
// tell selection listener about click
app.geoElementSelected(geo, false);
}
// Alt click: copy definition to input field
if (geo != null && e.isAltDown() && app.showAlgebraInput()) {
// F3 key: copy definition to input bar
app.getGlobalKeyDispatcher().handleFunctionKeyForAlgebraInput(3,
geo);
}
ev.mouseMovedOver(null);
}
/**
* let euclidianView know about the click
*
* @param ev
* euclidian view
* @param geo
* geo clicked
* @param e
* mouse event
*/
protected void euclidianViewClick(EuclidianViewInterfaceCommon ev,
GeoElement geo, MouseEvent e) {
setSelectedGeo(geo);
}
@Override
public void mousePressed(MouseEvent e) {
leftPress(e);
setMousePressed();
}
private long lastMousePressedTime;
/**
* set values (dragging, mouse pressed time)
*/
protected void setMousePressed() {
draggingOccured = false;
lastMousePressedTime = System.currentTimeMillis();
}
/**
* right mouse pressed
*
* @param e
* event
* @param mouseCoords
* mouse coords
*/
final protected void rightPress(MouseEvent e, GPoint mouseCoords) {
e.consume();
// get GeoElement at mouse location
TreePath tp = tree.getPathForLocation(e.getX(), e.getY());
GeoElement geo = AlgebraTree.getGeoElementForPath(tp);
// single selection: popup menu
// if (app.selectedGeosSize() < 2) {
if (geo == null) {
ArrayList<GeoElement> childs = AlgebraTree.getGeoChildsForPath(tp);
if (childs == null || childs.size() == 0) {// if click on e.g.
// object type (like
// "Point"), then select
// all and popup menu
selection.clearSelectedGeos();
AlgebraContextMenuD contextMenu = new AlgebraContextMenuD(
(AppD) app);
contextMenu.show(tree, e.getPoint().x, e.getPoint().y);
} else {// popup algebra menu
selection.clearSelectedGeos(false);
selection.addSelectedGeos(childs, true);
((GuiManagerD) app.getGuiManager()).showPopupMenu(childs, tree,
mouseCoords);
}
} else {
if (selection.containsSelectedGeo(geo)) {// popup menu for current
// selection (including
// selected object)
((GuiManagerD) app.getGuiManager()).showPopupMenu(
selection.getSelectedGeos(), tree, mouseCoords);
} else {// select only this objet and popup menu
selection.clearSelectedGeos(false);
selection.addSelectedGeo(geo, true, true);
ArrayList<GeoElement> temp = new ArrayList<GeoElement>();
temp.add(geo);
((GuiManagerD) app.getGuiManager()).showPopupMenu(temp, tree,
mouseCoords);
}
}
/*
* } // multiple selection: popup menu (several geos) else { if(geo !=
* null) {
* ((GuiManagerD)app.getGuiManager()).showPopupMenu(app.getSelectedGeos(
* ), tree, mouseCoords); } }
*/
}
/**
* left press
*
* @param e
* event
*/
final protected void leftPress(MouseEvent e) {
// When a single, new selection is made with no key modifiers
// we need to handle selection in mousePressed, not mouseClicked.
// By doing this selection early, a DnD drag will come afterwards
// and grab the new selection.
// All other selection types must be handled later in mouseClicked.
// In this case a DnD drag starts first and grabs the previously
// selected
// geos (e.g. cntrl-selected or EV selected) as the user expects.
skipSelection = false; // flag to prevent duplicate selection in
// MouseClicked
TreePath tp = tree.getPathForLocation(e.getX(), e.getY());
GeoElement geo = AlgebraTree.getGeoElementForPath(tp);
if (leftPressCanSelectGeo(e, geo)) {
ArrayList<GeoElement> groupedGeos = groupAction(e, tp, true);
if (groupedGeos != null
&& !selection.containsSelectedGeos(groupedGeos)) {
selection.clearSelectedGeos(false); // repaint will be done next
// step
selection.addSelectedGeos(groupedGeos, true);
skipSelection = true;
}
}
}
/**
*
* @param e
* mouse event
* @param geo
* geo
* @return true if left press can select the geo
*/
protected boolean leftPressCanSelectGeo(MouseEvent e, GeoElement geo) {
if (!AppD.isControlDown(e) && !e.isShiftDown()) {
if (!setSelectedGeo(geo)) {
return true;
}
}
return false;
}
/**
* set the geo selected
*
* @param geo
* geo
* @return true if geo is not null and wasn't yet selected
*/
protected boolean setSelectedGeo(GeoElement geo) {
if (geo != null && !selection.containsSelectedGeo(geo)) {
selection.clearSelectedGeos(false); // repaint will be done next
// step
selection.addSelectedGeo(geo);
lastSelectedGeo = geo;
skipSelection = true;
return true;
}
return false;
}
private ArrayList<GeoElement> groupAction(MouseEvent e, TreePath tp,
boolean mousePressed) {
Rectangle rect = tree.getPathBounds(tp);
if (rect != null) { // group action
if (e.getX() - rect.x < tree.getOpenIconHeight()) { // collapse/expand
// icon
if (mousePressed) {
if (tree.isCollapsed(tp)) {
tree.expandPath(tp);
} else {
tree.collapsePath(tp);
}
}
} else { // collect geos of the group
DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp
.getLastPathComponent();
ArrayList<GeoElement> groupedGeos = new ArrayList<GeoElement>();
for (int i = 0; i < node.getChildCount(); i++) {
groupedGeos.add((GeoElement) ((DefaultMutableTreeNode) node
.getChildAt(i)).getUserObject());
}
return groupedGeos;
}
}
return null;
}
@Override
public void mouseEntered(MouseEvent p1) {
//
}
@Override
public void mouseExited(MouseEvent p1) {
highlight(app.getActiveEuclidianView(), (GeoElement) null);
}
private boolean draggingOccured = false;
// MOUSE MOTION LISTENER
@Override
public void mouseDragged(MouseEvent arg0) {
// used for interactive boards
if (System.currentTimeMillis() > EuclidianConstants.DRAGGING_DELAY
+ lastMousePressedTime) {
draggingOccured = true;
}
}
/**
*
* @return true if view is editing
*/
protected boolean viewIsEditing() {
return false;
}
// tell EuclidianView
@Override
public void mouseMoved(MouseEvent e) {
if (viewIsEditing()) {
return;
}
int x = e.getX();
int y = e.getY();
GeoElement geo = AlgebraTree.getGeoElementForLocation(tree, x, y);
// tell EuclidianView to handle mouse over
// EuclidianView ev = app.getEuclidianView();
EuclidianViewInterfaceCommon ev = app.getActiveEuclidianView();
// ev.mouseMovedOver(geo,true);
highlight(ev, geo);
if (geo != null) {
app.getLocalization().setTooltipFlag();
tree.setToolTipText(geo.getLongDescriptionHTML(true, true));
app.getLocalization().clearTooltipFlag();
} else {
tree.setToolTipText(null);
TreePath tp = tree.getPathForLocation(e.getX(), e.getY());
if (!tree.isCollapsed(tp)) {
Rectangle rect = tree.getPathBounds(tp);
if (rect != null) { // mouse over group
if (e.getX() - rect.x > 16) { // collect geos of the group
DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp
.getLastPathComponent();
ArrayList<GeoElement> groupedGeos = new ArrayList<GeoElement>();
for (int i = 0; i < node.getChildCount(); i++) {
groupedGeos
.add((GeoElement) ((DefaultMutableTreeNode) node
.getChildAt(i)).getUserObject());
}
highlight(ev, groupedGeos);
}
}
}
}
}
/**
* highlight this geo using euclidian view
*
* @param ev
* euclidian view
* @param geo
* geo
*/
protected void highlight(EuclidianViewInterfaceCommon ev, GeoElement geo) {
ev.highlight(geo);
}
/**
* highlight these geos using euclidian view
*
* @param ev
* euclidian view
* @param geos
* geos
*/
protected void highlight(EuclidianViewInterfaceCommon ev,
ArrayList<GeoElement> geos) {
ev.highlight(geos);
}
}