package org.geogebra.desktop.gui.layout; import java.awt.AWTEvent; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.AWTEventListener; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.util.ArrayList; import javax.swing.JPanel; import javax.swing.JSplitPane; import javax.swing.SwingUtilities; import org.geogebra.common.gui.layout.DockComponent; /** * Glass pane is used to draw the drag-preview area on the panels if the user * enters the drag'n'drop mode. * * @author Florian Sonner */ public class DockGlassPane extends JPanel implements AWTEventListener { private static final long serialVersionUID = 1L; private boolean dragInProgress = false; private BasicStroke stroke; private DockManagerD dockManager; private DockPanelD[] dockPanels; private Rectangle[] dockPanelsBounds; private DnDState dndState; private Rectangle previewRect = new Rectangle(); private Color color; private static final Color COLOR_DEFAULT = Color.gray; private static final Color COLOR_NOT_ENOUGH_SPACE = COLOR_DEFAULT;// Color.red; private static final Color COLOR_SAME_PLACE = Color.white; public DockGlassPane(DockManagerD dockManager) { this.dockManager = dockManager; stroke = new BasicStroke(4); setOpaque(false); } public boolean isDragInProgress() { return dragInProgress; } /** * Start the dragging process by adding the mouse listeners. * * @param state */ public void startDrag(DnDState state) { if (dragInProgress) { return; } dragInProgress = true; Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK); setVisible(true); dndState = state; dndState.setTarget(state.getSource()); // cache the absolute bounds of all DockPanels dockPanels = dockManager.getPanels(); ArrayList<DockPanelD> dockPanelsList = new ArrayList<DockPanelD>(); ArrayList<Rectangle> bounds = new ArrayList<Rectangle>(); Rectangle tmpRect; for (int i = 0; i < dockPanels.length; ++i) { // we don't need to care about invisible or views in a different // window for the drag'n'drop if (!dockPanels[i].isVisible() || dockPanels[i].isOpenInFrame()) { continue; } tmpRect = dockPanels[i].getBounds(); tmpRect.setLocation(SwingUtilities.convertPoint(dockPanels[i], dockPanels[i].getLocation(), this)); tmpRect.x -= dockPanels[i].getX(); tmpRect.y -= dockPanels[i].getY(); dockPanelsList.add(dockPanels[i]); bounds.add(tmpRect); } dockPanelsBounds = new Rectangle[bounds.size()]; dockPanelsBounds = (bounds.toArray(dockPanelsBounds)); dockPanels = new DockPanelD[dockPanelsList.size()]; dockPanels = (dockPanelsList.toArray(dockPanels)); previewRect = new Rectangle(); } /** * The mouse was released, quit the drag'n'drop mode. */ public void stopDrag() { if (!dragInProgress) { return; } dragInProgress = false; Toolkit.getDefaultToolkit().removeAWTEventListener(this); setVisible(false); dockManager.drop(dndState); dndState = null; } /** * Paint the outline of the region on the window where the selected panel * would reside if the user drops the panel. */ @Override public void paintComponent(Graphics g) { if (dndState == null || dndState.getTarget() == null) { return; } Graphics2D g2d = (Graphics2D) g; g2d.setStroke(stroke); g2d.setColor(color); // draw the preview rectangle g2d.drawRect(previewRect.x, previewRect.y, previewRect.width, previewRect.height); } private void setColorEnoughHeight(DockPanelD target) { if (target.getHeight() < DockComponent.MIN_SIZE * 2) { color = COLOR_NOT_ENOUGH_SPACE; } } private void setColorEnoughWidth(DockPanelD target) { if (target.getWidth() < DockComponent.MIN_SIZE * 2) { color = COLOR_NOT_ENOUGH_SPACE; } } /** * Calculate where the panel would be placed if the mouse is released. * * @param event */ public void mouseDragged(MouseEvent event) { Point mousePosition = SwingUtilities.convertPoint(event.getComponent(), event.getPoint(), this); boolean update = false; // Check if the mouse intersects with any DockPanel for (int i = 0; i < dockPanelsBounds.length; ++i) { if (mousePosition.x >= dockPanelsBounds[i].x && mousePosition.x <= dockPanelsBounds[i].x + dockPanelsBounds[i].getWidth() && mousePosition.y >= dockPanelsBounds[i].y && mousePosition.y <= dockPanelsBounds[i].y + dockPanelsBounds[i].getHeight()) { update = true; dndState.setTarget(dockPanels[i]); break; } } if (update) { DockPanelD target = dndState.getTarget(); Point targetAbsPosition = SwingUtilities.convertPoint( target.getParent(), target.getLocation(), this); Point mouseRelPosition = mousePosition; mouseRelPosition.translate(-targetAbsPosition.x, -targetAbsPosition.y); int orientation = ((DockSplitPane) target.getParent()) .getOrientation(); float leftPercent = mouseRelPosition.x * 1.0f / target.getWidth(); float topPercent = mouseRelPosition.y * 1.0f / target.getHeight(); float maxDist = 0.35f; previewRect.setLocation(targetAbsPosition); previewRect.setSize(target.getSize()); color = COLOR_DEFAULT; // calculate the preview rectangle if (orientation == JSplitPane.VERTICAL_SPLIT) { if (leftPercent < maxDist) { if (leftPercent < maxDist / 2) { dndState.setRegion(DnDState.LEFT_OUT); setColorEnoughWidth(target); DockSplitPane splitPane = (DockSplitPane) target .getParent(); previewRect.setLocation(SwingUtilities.convertPoint( splitPane.getParent(), splitPane.getLocation(), this)); previewRect.width *= maxDist / 2; previewRect.height = splitPane.getHeight(); } else { dndState.setRegion(DnDState.LEFT); setColorEnoughWidth(target); previewRect.width *= maxDist; } } else if (leftPercent > 1 - maxDist) { if (leftPercent > 1 - maxDist / 2) { dndState.setRegion(DnDState.RIGHT_OUT); setColorEnoughWidth(target); DockSplitPane splitPane = (DockSplitPane) target .getParent(); previewRect.setLocation(SwingUtilities.convertPoint( splitPane.getParent(), splitPane.getLocation(), this)); previewRect.x += previewRect.width * (1 - maxDist / 2); previewRect.width *= maxDist / 2; previewRect.height = splitPane.getHeight(); } else { dndState.setRegion(DnDState.RIGHT); setColorEnoughWidth(target); previewRect.x += previewRect.width * (1 - maxDist); previewRect.width *= maxDist; } } else { if (topPercent < 0.5) { dndState.setRegion(DnDState.TOP); setColorEnoughHeight(target); previewRect.height *= 0.5f; } else { dndState.setRegion(DnDState.BOTTOM); setColorEnoughHeight(target); previewRect.y += previewRect.height * 0.5f; previewRect.height *= 0.5f; } } } else { if (topPercent < maxDist) { if (topPercent < maxDist / 2) { dndState.setRegion(DnDState.TOP_OUT); setColorEnoughHeight(target); DockSplitPane splitPane = (DockSplitPane) target .getParent(); previewRect.setLocation(SwingUtilities.convertPoint( splitPane.getParent(), splitPane.getLocation(), this)); previewRect.height *= maxDist / 2; previewRect.width = splitPane.getWidth(); } else { dndState.setRegion(DnDState.TOP); setColorEnoughHeight(target); previewRect.height *= maxDist; } } else if (topPercent > 1 - maxDist) { if (topPercent > 1 - maxDist / 2) { dndState.setRegion(DnDState.BOTTOM_OUT); setColorEnoughHeight(target); DockSplitPane splitPane = (DockSplitPane) target .getParent(); previewRect.setLocation(SwingUtilities.convertPoint( splitPane.getParent(), splitPane.getLocation(), this)); previewRect.y += previewRect.height * (1 - maxDist / 2); previewRect.height *= maxDist / 2; previewRect.width = splitPane.getWidth(); } else { dndState.setRegion(DnDState.BOTTOM); setColorEnoughHeight(target); previewRect.y += previewRect.height * (1 - maxDist); previewRect.height *= maxDist; } } else { if (leftPercent < 0.5) { dndState.setRegion(DnDState.LEFT); setColorEnoughWidth(target); previewRect.width *= 0.5f; } else { dndState.setRegion(DnDState.RIGHT); setColorEnoughWidth(target); previewRect.x += previewRect.width * 0.5f; previewRect.width *= 0.5f; } } } // nothing changed if (target == dndState.getSource() && !dndState.isRegionOut()) { previewRect.setSize(target.getSize()); previewRect.setLocation(targetAbsPosition); color = COLOR_SAME_PLACE; } previewRect.x += (int) (Math.ceil(stroke.getLineWidth() / 2)); previewRect.y += (int) (Math.ceil(stroke.getLineWidth() / 2)); previewRect.width -= stroke.getLineWidth(); previewRect.height -= stroke.getLineWidth(); } else { dndState.setTarget(null); } repaint(); } /** * Redirect every mouse action. */ @Override public void eventDispatched(AWTEvent event) { if (event instanceof MouseEvent) { if (event.getID() == MouseEvent.MOUSE_DRAGGED && ((MouseEvent) event) .getModifiers() == InputEvent.BUTTON1_MASK) { mouseDragged((MouseEvent) event); } else if (event.getID() == MouseEvent.MOUSE_RELEASED) { stopDrag(); } } } }