package org.geogebra.desktop.gui.layout; import java.awt.AWTEvent; import java.awt.Component; import java.awt.Toolkit; import java.awt.event.AWTEventListener; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.TreeSet; import javax.swing.JSplitPane; import javax.swing.SwingUtilities; import org.geogebra.common.euclidian.EuclidianStyleBar; import org.geogebra.common.gui.layout.DockComponent; import org.geogebra.common.gui.layout.DockManager; import org.geogebra.common.gui.layout.DockPanel; import org.geogebra.common.io.layout.DockPanelData; import org.geogebra.common.io.layout.DockSplitPaneData; import org.geogebra.common.io.layout.Perspective; import org.geogebra.common.io.layout.ShowDockPanelListener; import org.geogebra.common.main.App; import org.geogebra.common.util.debug.Log; import org.geogebra.desktop.awt.GRectangleD; import org.geogebra.desktop.euclidian.EuclidianViewJPanelD; import org.geogebra.desktop.gui.GuiManagerD; import org.geogebra.desktop.gui.layout.panels.EuclidianDockPanelAbstract; import org.geogebra.desktop.gui.toolbar.ToolbarContainer; import org.geogebra.desktop.main.AppD; /** * Class responsible to manage the whole docking area of the window. * * @author Florian Sonner */ public class DockManagerD extends DockManager implements AWTEventListener { private AppD app; private LayoutD layout; /** * False if the application is running in unsigned mode. We can only listen * to euclidian view focus changes in this case. */ private boolean hasFullFocusSystem; /** * The glass panel used for drag'n'drop. */ private DockGlassPane glassPane; /** * The root split pane. */ private DockSplitPane rootPane; /** * The dock panel which has the focus at the moment. */ private DockPanelD focusedDockPanel; /** * The euclidian dock panel which had the focus the last. */ private EuclidianDockPanelAbstract focusedEuclidianDockPanel; /** * A list with all registered dock panels. */ private ArrayList<DockPanelD> dockPanels; /** * List of DockPanelListeners, informed when some dockpanel is shown. */ private List<ShowDockPanelListener> showDockPanelListener; /** * @param layout */ public DockManagerD(LayoutD layout) { this.layout = layout; this.app = layout.getApplication(); dockPanels = new ArrayList<DockPanelD>(); showDockPanelListener = new ArrayList<ShowDockPanelListener>(); glassPane = new DockGlassPane(this); if (!app.isApplet()) { app.setGlassPane(glassPane); } // register focus changes try { Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_EVENT_MASK); hasFullFocusSystem = true; } catch (Exception e) { hasFullFocusSystem = false; } } /** * Register a new dock panel. Use Layout::registerPanel() as public * interface. * * @param dockPanel */ public void registerPanel(DockPanelD dockPanel) { dockPanels.add(dockPanel); dockPanel.register(this); } /** * remove panel for the dock panels list * * @param dockPanel * panel */ @Override public void unRegisterPanel(DockPanel dockPanel) { dockPanels.remove(dockPanel); } private ToolbarContainer getToolbarPanel() { return ((GuiManagerD) app.getGuiManager()).getToolbarPanel(); } /** * Apply a certain perspective by arranging the dock panels in the requested * order. * * @param spData * @param dpData * * @see LayoutD#applyPerspective(geogebra.io.layout.Perspective) */ public void applyPerspective(DockSplitPaneData[] spData, DockPanelData[] dpData) { if (dockPanels != null) { // hide existing external windows for (DockPanelD panel : dockPanels) { if (panel.isOpenInFrame() && panel.isVisible()) { hide(panel); } panel.setAlone(false); } TreeSet<Integer> updated = new TreeSet<Integer>(); // copy dock panel info settings for (int i = 0; i < dpData.length; ++i) { DockPanelD panel = getPanel(dpData[i]); updated.add(dpData[i].getViewId()); if (panel == null) { Log.error("Adding null panel"); // TODO insert error panel } else { panel.setToolbarString(dpData[i].getToolbarString()); panel.setFrameBounds(GRectangleD .getAWTRectangle(dpData[i].getFrameBounds())); panel.setEmbeddedDef(dpData[i].getEmbeddedDef()); panel.setEmbeddedSize(dpData[i].getEmbeddedSize()); panel.setShowStyleBar(dpData[i].showStyleBar()); panel.setOpenInFrame(dpData[i].isOpenInFrame()); // detach views which were visible, but are not in the new // perspective if (panel.isVisible() && !dpData[i].isVisible()) { app.getGuiManager().detachView(panel.getViewId()); } panel.setVisible(dpData[i].isVisible()); } } for (DockPanelD dockPanel : dockPanels) { if (!dockPanel.hasPlane() && !updated.contains(dockPanel.getViewId())) { dockPanel.setVisible(false); } } } if (spData.length > 0) { DockSplitPane[] splitPanes = new DockSplitPane[spData.length]; // construct the split panes for (int i = 0; i < spData.length; ++i) { splitPanes[i] = new DockSplitPane(spData[i].getOrientation()); } // cascade the split panes rootPane = splitPanes[0]; // loop through every but the first split pane for (int i = 1; i < spData.length; ++i) { DockSplitPane currentParent = rootPane; // a similar system as it's used to determine the position of // the dock panels (see comment in DockManager::show()) // 0: turn left/up, 1: turn right/down String[] directions = spData[i].getLocation().split(","); // get the parent split pane, the last position is reserved for // the location // of the current split pane and therefore ignored here for (int j = 0; j < directions.length - 1; ++j) { if (directions[j].equals("0")) { currentParent = (DockSplitPane) currentParent .getLeftComponent(); } else { currentParent = (DockSplitPane) currentParent .getRightComponent(); } } // insert the split pane if (directions[directions.length - 1].equals("0")) { currentParent.setLeftComponentCheckEmpty(splitPanes[i]); } else { currentParent.setRightComponentCheckEmpty(splitPanes[i]); } } // now insert the dock panels for (int i = 0; i < dpData.length; ++i) { DockPanelD panel = getPanel(dpData[i].getViewId()); // skip panels which will not be drawn in the main window if (!dpData[i].isVisible() // eg run "no 3D" with 3D View open in saved settings || panel == null) { continue; } // attach view to kernel (being attached multiple times is // ignored) app.getGuiManager().attachView(panel.getViewId()); if (dpData[i].isOpenInFrame()) { show(panel); continue; } DockSplitPane currentParent = rootPane; String[] directions = dpData[i].getEmbeddedDef().split(","); /* * Get the parent split pane of this dock panel and ignore the * last direction as its reserved for the position of the dock * panel itself. * * In contrast to the algorithm used in the show() method we'll * not take care of invalid positions as the data should not be * corrupted. */ for (int j = 0; j < directions.length - 1; ++j) { if (directions[j].equals("0") || directions[j].equals("3")) { currentParent = (DockSplitPane) currentParent .getLeftComponent(); } else { currentParent = (DockSplitPane) currentParent .getRightComponent(); } } if (currentParent == null) { Log.error("Invalid perspective"); } else if (directions[directions.length - 1].equals("0") || directions[directions.length - 1].equals("3")) { currentParent.setLeftComponentCheckEmpty(panel); } else { currentParent.setRightComponentCheckEmpty(panel); } panel.updatePanel(); // move toolbar to main container if (panel.hasToolbar()) { ToolbarContainer mainContainer = getToolbarPanel(); mainContainer.addToolbar( getPanel(dpData[i].getViewId()).getToolbar()); } } // recursive update resize weights for giving new space to euclidian // views updateSplitPanesResizeWeight(); int windowWidth = app.getPreferredSize().width; int windowHeight = app.getPreferredSize().height; // set the dividers of the split panes for (int i = 0; i < spData.length; ++i) { if (spData[i].getOrientation() == JSplitPane.VERTICAL_SPLIT) { splitPanes[i].setDividerLocation( (int) (spData[i].getDividerLocation() * windowHeight)); } else { splitPanes[i].setDividerLocation( (int) (spData[i].getDividerLocation() * windowWidth)); } splitPanes[i].updateUI(); } markAlonePanel(); // is focused dock panel not visible anymore => choose another one if (focusedDockPanel == null || !focusedDockPanel.isVisible()) { // for(DockPanel panel : dockPanels) { boolean focusDone = false; for (int i = 0; i < dpData.length && !focusDone; ++i) { if (dpData[i].getPlane() == null) { // we can't focus on // view for plane // otherwise we will // recreate it DockPanelD panel = getPanel(dpData[i]); if (panel != null && panel.isVisible() && !panel.isInFrame()) { setFocusedPanel(panel); // don't like algebra view as focused view if (panel.getViewId() != App.VIEW_ALGEBRA && panel .getViewId() != App.VIEW_PROPERTIES) { focusDone = true; } } } } } } // update all labels at once setLabels(); } /** * update dispatching of new space with split panes */ private void updateSplitPanesResizeWeight() { rootPane.updateResizeWeight(); } /** * Start the drag'n'drop process of a DockPanel. * * @param panel */ public void drag(DockPanelD panel) { // Do not allow docking in case this is the last view if (panel.getParentSplitPane() == rootPane) { if (rootPane.getOpposite(panel) == null) { return; } } glassPane.startDrag(new DnDState(panel)); } /** * Stop the drag'n'drop procedure and drop the component to the the defined * location. * * @param dndState */ public void drop(DnDState dndState) { DockPanelD source = dndState.getSource(); DockSplitPane sourceParent = source.getParentSplitPane(); DockPanelD target = dndState.getTarget(); Component opposite = sourceParent.getOpposite(source); // No action required if (target == null || target == source && !dndState.isRegionOut()) { return; } // Hide the source first hide(source, false); source.setVisible(true); // Add the source panel at the new position DockSplitPane newSplitPane = new DockSplitPane(); int dndRegion = dndState.getRegion(); // Determine the orientation of the new split pane if (dndRegion == DnDState.LEFT || dndRegion == DnDState.LEFT_OUT || dndRegion == DnDState.RIGHT || dndRegion == DnDState.RIGHT_OUT) { newSplitPane.setOrientation(JSplitPane.HORIZONTAL_SPLIT); } else { newSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT); } if (dndState.isRegionOut() && (target.getParent() == sourceParent || target == source)) { dndRegion >>= 4; dndState.setRegion(dndRegion); } boolean updatedRootPane = false; if (dndState.isRegionOut()) { DockSplitPane targetParent = target.getParentSplitPane(); if (targetParent == rootPane) { rootPane = newSplitPane; } else { ((DockSplitPane) targetParent.getParent()) .replaceComponent(targetParent, newSplitPane); } if (dndRegion == DnDState.LEFT_OUT || dndRegion == DnDState.TOP_OUT) { newSplitPane.setRightComponent(targetParent); newSplitPane.setLeftComponent(source); } else { newSplitPane.setRightComponent(source); newSplitPane.setLeftComponent(targetParent); } } else { if (source == target) { if (opposite instanceof DockPanelD) { if (((DockPanelD) opposite).getParentSplitPane() .getOpposite(opposite) == null) { rootPane = newSplitPane; } else { ((DockPanelD) opposite).getParentSplitPane() .replaceComponent(opposite, newSplitPane); } } else { if (opposite == rootPane) { rootPane = newSplitPane; } else { ((DockSplitPane) opposite.getParent()) .replaceComponent(opposite, newSplitPane); } } if (dndRegion == DnDState.LEFT || dndRegion == DnDState.TOP) { newSplitPane.setRightComponent(opposite); newSplitPane.setLeftComponent(source); } else { newSplitPane.setRightComponent(source); newSplitPane.setLeftComponent(opposite); } } else if (target.getParentSplitPane().getOpposite(target) == null && target.getParentSplitPane() == rootPane) { rootPane.removeAll(); if (dndRegion == DnDState.LEFT || dndRegion == DnDState.TOP) { rootPane.setLeftComponent(source); rootPane.setRightComponent(target); } else { rootPane.setLeftComponent(target); rootPane.setRightComponent(source); } updatedRootPane = true; rootPane.setOrientation(newSplitPane.getOrientation()); } else { target.getParentSplitPane().replaceComponent(target, newSplitPane); if (dndRegion == DnDState.LEFT || dndRegion == DnDState.TOP) { newSplitPane.setRightComponent(target); newSplitPane.setLeftComponent(source); } else { newSplitPane.setRightComponent(source); newSplitPane.setLeftComponent(target); } } } app.updateCenterPanel(true); double dividerLocation = 0; if (dndRegion == DnDState.LEFT || dndRegion == DnDState.LEFT_OUT || dndRegion == DnDState.TOP || dndRegion == DnDState.TOP_OUT) { dividerLocation = 0.4; } else { dividerLocation = 0.6; } if (updatedRootPane) { rootPane.setDividerLocation(dividerLocation); } else { newSplitPane.setDividerLocation(dividerLocation); } updatePanels(); // update new space dispatching updateSplitPanesResizeWeight(); // add toolbar to main toolbar container if necessary if (source.hasToolbar()) { ToolbarContainer mainContainer = getToolbarPanel(); mainContainer.addToolbar(source.getToolbar()); mainContainer.updateToolbarPanel(); } // has to be called *after* the toolbar was added to the container setFocusedPanel(source); unmarkAlonePanels(); markAlonePanel(); // Manually dispatch a resize event as the size of the // euclidian view isn't updated all the time. // TODO What does the resize do which will update the component ?! app.repaintEuclidianViews(rootPane); } /** * Show a DockPanel identified by its ID. * * @param viewId */ public void show(int viewId) { show(getPanel(viewId)); } /** * Show a DockPanel where it was displayed the last time - either in the * main window or in a separate frame. * * The location of the DockPanel in the main window is given by the * definition string stored in DockPanelInfo.getEmbeddedDef(). A definition * string can be read like a list of directions, where numbers represents * the four directions we can go: * * 0: Top 1: Right 2: Bottom 3: Left * * A definition string like "0,3,2" is read by the program this way: - Go to * the top (=0) container of the root pane. - Go to the container at the * left (=3) of the current container. - Insert the DockPanel at the bottom * (=2) of the current container. * * Note that the program differs between the top & left and bottom & right * position while the DockSplitPane just differs between a left and right * component and the orientation of the split pane. * * As the layout of the panels is changed frequently and may be completely * different if the DockPanel is inserted again, the algorithm ignores all * directions which are not existing anymore in order to get the best * possible result. Using the example from above, the second direction ("3") * may be ignored if the top container of the root pane isn't divided * anymore or the orientation of the container was changed. The algorithm * will continue with "2" and will insert the DockPanel at the bottom of the * top container of the root pane. * * @param panel */ public void show(DockPanelD panel) { panel.setVisible(true); panel.setHidden(false); // undo maximized state if another dock panel is to be shown if (isMaximized) { undoMaximize(false); } // TODO causes any problems? app.getGuiManager().attachView(panel.getViewId()); if (panel.isOpenInFrame()) { panel.createFrame(); } else { // Transform the definition into an array of integers String[] def = panel.getEmbeddedDef().split(","); int[] locations = new int[def.length]; for (int i = 0; i < def.length; ++i) { if (def[i].length() == 0) { def[i] = "1"; } locations[i] = Integer.parseInt(def[i]); if (locations[i] > 3 || locations[i] < 0) { locations[i] = 3; // left as default direction } } // We insert this panel at the left by default if (locations.length == 0) { locations = new int[] { 3 }; } DockSplitPane currentPane = rootPane; int secondLastPos = -1; // Get the location of our new DockPanel (ignore last entry) for (int i = 0; i < locations.length - 1; ++i) { // The orientation of the current pane does not match the stored // orientation, skip this if (currentPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT && (locations[i] == 0 || locations[i] == 2)) { continue; } else if (currentPane .getOrientation() == JSplitPane.VERTICAL_SPLIT && (locations[i] == 1 || locations[i] == 3)) { continue; } Component component; if (locations[i] == 0 || locations[i] == 3) { component = currentPane.getLeftComponent(); } else { component = currentPane.getRightComponent(); } if (!(component instanceof DockSplitPane)) { secondLastPos = locations[i]; break; } currentPane = (DockSplitPane) component; } int size = panel.getEmbeddedSize(); int lastPos = locations[locations.length - 1]; DockSplitPane newSplitPane = new DockSplitPane(); if (lastPos == 0 || lastPos == 2) { newSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT); } else { newSplitPane.setOrientation(JSplitPane.HORIZONTAL_SPLIT); } // the size (height / width depending upon lastPos) of the parent // element, // this value is necessary to prevent panels which completely hide // their opposite element // the component opposite to the current component Component opposite; if (secondLastPos == -1) { opposite = rootPane; rootPane = newSplitPane; // in root pane, the opposite may be null if (lastPos == 0 || lastPos == 3) { if (((DockSplitPane) opposite).getLeftComponent() == null) { opposite = ((DockSplitPane) opposite) .getRightComponent(); } } else { if (((DockSplitPane) opposite) .getRightComponent() == null) { opposite = ((DockSplitPane) opposite) .getLeftComponent(); } } } else { if (secondLastPos == 0 || secondLastPos == 3) { opposite = currentPane.getLeftComponent(); } else { opposite = currentPane.getRightComponent(); } // in root pane, the opposite may be null if (opposite == null) { opposite = currentPane.getOpposite(null); rootPane = newSplitPane; } else if (opposite.getParent() == rootPane && rootPane.getOpposite(opposite) == null) { rootPane = newSplitPane; } else { currentPane.replaceComponent(opposite, newSplitPane); } } // Application.debug("\n"+((DockComponent) opposite).toString("")); // save divider locations to prevent not visible views if (opposite != null) { ((DockComponent) opposite).saveDividerLocation(); } if (lastPos == 0 || lastPos == 3) { newSplitPane.setLeftComponent(panel); newSplitPane.setRightComponent(opposite); } else { newSplitPane.setLeftComponent(opposite); newSplitPane.setRightComponent(panel); } if (!app.isIniting()) { app.updateCenterPanel(true); } // check new split pane size regarding orientation int newSplitPaneSize; if (newSplitPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) { newSplitPaneSize = newSplitPane.getWidth(); } else { newSplitPaneSize = newSplitPane.getHeight(); } // check if panel size is not too large if (size + DockComponent.MIN_SIZE > newSplitPaneSize) { size = newSplitPaneSize / 2; } // set the divider location if (lastPos == 0 || lastPos == 3) { newSplitPane.setDividerLocation(size); } else { newSplitPane.setDividerLocation(newSplitPaneSize - size); } // AbstractApplication.debug("\nnewSplitPaneSize = // "+newSplitPaneSize+"\nsize = "+size); // Application.debug("\n======\n"+((DockComponent) // opposite).toString("")); // re dispatch divider locations to prevent not visible views if (opposite != null) { ((DockComponent) opposite).updateDividerLocation( newSplitPaneSize - size, newSplitPane.getOrientation()); } } panel.updatePanel(); // update dispatching of new space updateSplitPanesResizeWeight(); // add toolbar to main toolbar container if necessary, *has* to be // called after // DockPanel::updatePanel() as the toolbar is initialized there if (!panel.isOpenInFrame() && panel.hasToolbar()) { ToolbarContainer mainContainer = getToolbarPanel(); mainContainer.addToolbar(panel.getToolbar()); mainContainer.updateToolbarPanel(); } // has to be called *after* the toolbar was added to the container setFocusedPanel(panel); unmarkAlonePanels(); markAlonePanel(); for (ShowDockPanelListener l : showDockPanelListener) { l.showDockPanel(panel); } // make sure that 3D renderer works correctly app.resume3DRenderer(); } /** * Hide a dock panel identified by the view ID. * * @param viewId * @return true if succeeded to hide the panel */ public boolean hide(int viewId, boolean isPermanent) { return hide(getPanel(viewId), isPermanent); } /** * Hide a dock panel permanently. * * @param panela * @return true if succeeded to hide the panel */ public boolean hide(DockPanelD panel) { return hide(panel, true); } /** * close the dock panel * * @param viewId * id of the dock panel * @param isPermanent * says if the close is permanent */ public void closePanel(int viewId, boolean isPermanent) { closePanel(getPanel(viewId), isPermanent); } /** * close the dock panel * * @param panel * dock panel * @param isPermanent * says if the close is permanent */ public void closePanel(DockPanelD panel, boolean isPermanent) { if (hide(panel, isPermanent)) { getLayout().getApplication().updateMenubar(); if (getFocusedPanel() == panel) { setFocusedPanel(null); } } } /** * * @return true if the layout contains less than two panels */ public boolean containsLessThanTwoPanels() { return (rootPane == null) || (rootPane.getLeftComponent() == null) || (rootPane.getRightComponent() == null); } /** * Hide a dock panel. * * @param panel * @param isPermanent * If this change is permanent. * @return true if it succeeded to hide the panel */ public boolean hide(DockPanelD panel, boolean isPermanent) { if (!panel.isVisible()) { // some views (especially CAS) will close so slowly that the user is // able // to issue another "close" call, therefore we quit quietly return false; } // if panel is open in frame, check if it's not the last one if (!panel.isOpenInFrame() && containsLessThanTwoPanels()) { return false; } panel.setHidden(!isPermanent); panel.setVisible(false); setFocusedPanel(null); if (isPermanent) { app.getGuiManager().detachView(panel.getViewId()); } if (panel.isOpenInFrame()) { panel.removeFrame(); panel.setOpenInFrame(true); // open in frame the next time } else { DockSplitPane parent = panel.getParentSplitPane(); // Save settings if (parent.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) { panel.setEmbeddedSize(panel.getWidth()); } else { panel.setEmbeddedSize(panel.getHeight()); } panel.setEmbeddedDef(panel.calculateEmbeddedDef()); panel.setOpenInFrame(false); Component opposite = parent.getOpposite(panel); // save divider location and size (if DockSplitPane) if (opposite != null) { ((DockComponent) opposite).saveDividerLocation(); } int orientation = parent.getOrientation(); int size = 0; if (orientation == JSplitPane.VERTICAL_SPLIT) { size = parent.getHeight(); } else { size = parent.getWidth(); } if (parent == rootPane) { if (opposite instanceof DockSplitPane) { rootPane = (DockSplitPane) opposite; } else { parent.replaceComponent(panel, null); } app.updateCenterPanel(true); } else { DockSplitPane grandParent = (DockSplitPane) parent.getParent(); int dividerLoc = grandParent.getDividerLocation(); grandParent.replaceComponent(parent, opposite); grandParent.setDividerLocation(dividerLoc); } // re dispatch divider location if (opposite != null) { ((DockComponent) opposite).updateDividerLocation(size, orientation); } if (isPermanent) { app.validateComponent(); } markAlonePanel(); if (panel.hasToolbar()) { ToolbarContainer mainContainer = getToolbarPanel(); mainContainer.removeToolbar(panel.getToolbar()); mainContainer.updateToolbarPanel(); } app.updateToolBar(); } // make sure that 3D renderer works correctly app.resume3DRenderer(); return true; } /** * Listen to mouse clicks and determine if the view focus changed. Just used * in case the full focus system is active. * * Euclidian views always inform the dock manager about focus changes using * their own mouse click events (see EuclidianController:mouseClicked()) * because 1) This AWT event cannot be used for unsigned applets but we need * euclidian view focus changes for new object placement 2) An own mouse * listener may be called *after* the euclidian controller mouse listener * was called, therefore new objects may be created in the wrong euclidian * view as the focus was not changed at the time of object creation. */ @Override public void eventDispatched(AWTEvent event) { // we also get notified about other mouse events, but we want to ignore // them if (event.getID() != MouseEvent.MOUSE_CLICKED) { // System.out.println(event); return; } // determine ancestor element of the event source which is of type // dock panel Component source = (Component) event.getSource(); // System.out.println(" source: " + source); DockPanelD dp = (DockPanelD) SwingUtilities .getAncestorOfClass(DockPanelD.class, source); // ignore this if we didn't hit a dock panel at all or if we hit the // euclidian // view, they are always handled by their own mouse event (see doc // comment above) if (dp != null && !(dp.getComponent() instanceof EuclidianViewJPanelD)) { // updates the properties view only if source is not the euclidian // style bar boolean updatePropertiesView = true; if (source instanceof EuclidianStyleBar) { updatePropertiesView = false; } else if (SwingUtilities.getAncestorOfClass(EuclidianStyleBar.class, source) != null) { updatePropertiesView = false; } setFocusedPanel(dp, updatePropertiesView); } } /** * Change the focused panel to "panel". * * @param panel * panel */ public void setFocusedPanel(DockPanelD panel) { setFocusedPanel(panel, true); } /** * Change the focused panel to "panel". * * @param panel * panel * @param updatePropertiesView * update the properties view */ public void setFocusedPanel(DockPanelD panel, boolean updatePropertiesView) { if (focusedDockPanel == panel) { return; } // euclidian focus // in case there is no focused panel there is also no focused euclidian // dock panel if (panel == null) { if (focusedEuclidianDockPanel != null) { focusedEuclidianDockPanel.setEuclidianFocus(false); if (focusedEuclidianDockPanel != focusedDockPanel) { focusedEuclidianDockPanel.setTitleLabelFocus(); } focusedEuclidianDockPanel = null; } } else { if (panel instanceof EuclidianDockPanelAbstract && focusedEuclidianDockPanel != panel) { // remove focus from previously focused dock panel if (focusedEuclidianDockPanel != null) { focusedEuclidianDockPanel.setEuclidianFocus(false); if (focusedEuclidianDockPanel != focusedDockPanel) { focusedEuclidianDockPanel.setTitleLabelFocus(); } } // if a panel has focus and that panel is a euclidian dock panel // change the focused euclidian dock panel to that panel focusedEuclidianDockPanel = (EuclidianDockPanelAbstract) panel; focusedEuclidianDockPanel.setEuclidianFocus(true); // (panels which are not euclidian dock panels do not change the // focused // euclidian dock panel (ie the old is kept)) } } // remove focus from previously focused dock panel if (focusedDockPanel != null) { focusedDockPanel.setFocus(false, false); } focusedDockPanel = panel; if (focusedDockPanel != null) { focusedDockPanel.setFocus(true, updatePropertiesView); } app.getGuiManager().updateMenubarSelection(); if (focusedDockPanel != null && panel.isInFrame()) { panel.getFrame().toFront(); } } /** * Changes the focused panel to the dock panel with ID viewId. Uses * {@link DockManagerD#setFocusedPanel(DockPanelD)} internally but adds some * validation checks. * * @param viewId * @return true if focus was changed, false if the requested dock panel does * not exist or is invisible at the moment */ @Override public boolean setFocusedPanel(int viewId) { DockPanelD dockPanel = getPanel(viewId); if (dockPanel != null && dockPanel.isVisible()) { setFocusedPanel(dockPanel); return true; } return false; } /** * @return The dock panel which has focus at the moment. */ public DockPanelD getFocusedPanel() { return focusedDockPanel; } /** * @return The viewId of the dock panel which has focus at the moment. */ @Override public int getFocusedViewId() { if (focusedDockPanel == null) { return -1; } return focusedDockPanel.getViewId(); } /** * @return The dock euclidian panel which had focus the last. */ @Override public EuclidianDockPanelAbstract getFocusedEuclidianPanel() { return focusedEuclidianDockPanel; } /** * Moves the focus between visible panels. Just the register order is taken * into consideration here, so the focus changing order does not depend upon * the visual order. * * @param forward * If the next or previous panel should be focused, calling this * method once with both possibilities will cancel out the effect * so to say. */ public void moveFocus(boolean forward) { if (focusedDockPanel == null) { return; } // to follow the DRY principle we'll use a single iterator for both // forward and backward iteration Iterator<DockPanelD> it = getForwardBackwardIterator(forward); // if the focused dock panel was found already boolean foundFocused = false; boolean changedFocus = false; while (it.hasNext()) { DockPanelD panel = it.next(); // all we do for now on just takes visible dock panels // into consideration if (panel.isVisible()) { // we already found the focused dock panel, so this is the // new panel to focus if (foundFocused) { setFocusedPanel(panel); changedFocus = true; break; } // is this the focused dock panel? else if (panel == focusedDockPanel) { foundFocused = true; } else { // we have not reached the focused dock panel, therefore // we do nothing } } } // if just invisible dock panels (or none) followed the focused dock // panel we have // not changed the focus so far, so we go through our list from the // beginning if (!changedFocus) { // recreate our iterator as we can't reset it it = getForwardBackwardIterator(forward); while (it.hasNext()) { DockPanelD panel = it.next(); if (panel.isVisible()) { // quit if we reached the focused panel until we found // another // visible panel if (panel == focusedDockPanel) { break; } setFocusedPanel(panel); changedFocus = true; break; } } } } /** * If just one panel is visible in the main frame, mark him as 'alone'. */ private void markAlonePanel() { // determine if such a panel exists DockPanelD singlePanel = null; if (rootPane.getRightComponent() == null) { Component leftComponent = rootPane.getLeftComponent(); if (leftComponent != null && leftComponent instanceof DockPanelD) { singlePanel = (DockPanelD) leftComponent; } } if (rootPane.getLeftComponent() == null) { Component rightComponent = rootPane.getRightComponent(); if (rightComponent != null && rightComponent instanceof DockPanelD) { singlePanel = (DockPanelD) rightComponent; } } // mark the found panel as 'alone' if (singlePanel != null) { singlePanel.setAlone(true); } } /** * Remove marks from any panel, that it might be alone. */ private void unmarkAlonePanels() { for (DockPanelD panel : dockPanels) { if (panel.isAlone()) { panel.setAlone(false); } } } /** * Helper method to create an iterator which either iterates forward or * backward through the dock panel list. * * @param forward * If the returned iterator should return forward or backward * @return The iterator */ private Iterator<DockPanelD> getForwardBackwardIterator(boolean forward) { if (forward) { return dockPanels.iterator(); } final ListIterator<DockPanelD> original = dockPanels .listIterator(dockPanels.size()); // we create our own iterator which iterates through our list in // reversed order return new Iterator<DockPanelD>() { @Override public void remove() { original.remove(); } @Override public DockPanelD next() { return original.previous(); } @Override public boolean hasNext() { return original.hasPrevious(); } }; } /** * Update the labels of all DockPanels. */ @Override public void setLabels() { for (DockPanelD panel : dockPanels) { panel.updateLabels(); } for (DockPanelD panel : dockPanels) { panel.buildToolbarGui(); } } /** * Update the glass pane */ public void updateGlassPane() { if (!app.isApplet() && glassPane.getParent() != null) { app.setGlassPane(glassPane); } } /** * Update the titles of the frames as they contain the file name of the * current document. */ public void updateTitles() { for (DockPanelD panel : dockPanels) { panel.updateTitle(); } } /** * Update all DockPanels. * * This is required if the user changed whether the title bar should be * displayed or not. * * @see #setLabels() */ public void updatePanels() { for (DockPanelD panel : dockPanels) { panel.updatePanel(); } } /** * Update the toolbars of all dock panels. */ public void updateToolbars() { for (DockPanelD panel : dockPanels) { panel.updateToolbar(); } } /** * Change the toolbar mode for all toolbars in external frames. * * @param mode */ public void setToolbarMode(int mode) { for (DockPanelD panel : dockPanels) { panel.setToolbarMode(mode); } } /** * Update the fonts in all dock panels. */ public void updateFonts() { for (DockPanelD panel : dockPanels) { panel.updateFonts(); } for (DockPanelD panel : dockPanels) { panel.buildToolbarGui(); } } /** * Scale the split panes based upon the given X and Y scale. This is used to * keep relative dimensions of the split panes if the user is switching * between applet and frame mode. * * @param scaleX * @param scaleY */ public void scale(float scaleX, float scaleY) { scale(scaleX, scaleY, rootPane); } private void scale(float scaleX, float scaleY, DockSplitPane splitPane) { splitPane.setDividerLocation((int) (splitPane.getDividerLocation() * (splitPane.getOrientation() == JSplitPane.VERTICAL_SPLIT ? scaleX : scaleY))); if (splitPane.getLeftComponent() != null && splitPane.getLeftComponent() instanceof DockSplitPane) { scale(scaleX, scaleY, (DockSplitPane) splitPane.getLeftComponent()); } if (splitPane.getRightComponent() != null && splitPane.getRightComponent() instanceof DockSplitPane) { scale(scaleX, scaleY, (DockSplitPane) splitPane.getRightComponent()); } } /** * @return GeoGebraLayout instance */ public LayoutD getLayout() { return layout; } /** * @return The glass pane which is used to draw the preview rectangle if the * user dragged a DockPanel. */ public DockGlassPane getGlassPane() { return glassPane; } /** * * @param dpData * dock panel data * @return a DockPanel */ public DockPanelD getPanel(DockPanelData dpData) { if (dpData.getPlane() == null) { return getPanel(dpData.getViewId()); } // euclidian view for plane case DockPanelD panel = (DockPanelD) app.getCompanion() .createEuclidianDockPanelForPlane(dpData.getViewId(), dpData.getPlane()); if (panel == null) { Log.error("panel==null"); return null; } // set the view id of the dock panel data for apply perspective dpData.setViewId(panel.getViewId()); return panel; } /** * Returns a specific DockPanel. * * Use the constants VIEW_EUCLIDIAN, VIEW_ALGEBRA etc. as viewId. * * @param viewId * @return The panel associated to the viewId */ @Override public DockPanelD getPanel(int viewId) { DockPanelD panel = null; for (DockPanelD dockPanel : dockPanels) { if (dockPanel.getViewId() == viewId) { panel = dockPanel; break; } } return panel; } /** * @return All dock panels */ public DockPanelD[] getPanels() { return dockPanels.toArray(new DockPanelD[0]); } /** * @return The root split pane which contains all other elements like * DockPanels or DockSplitPanes. */ public DockSplitPane getRoot() { return rootPane; } /** * @return True if all focus may change between all views, false if just the * euclidian views are affected by this. */ public boolean hasFullFocusSystem() { return hasFullFocusSystem; } // =================================================================== // // Temporary code for developing a maximize feature // G. Sturr 4/4/2012 // // =================================================================== /** * Perspective that stores the configuration just before a dock panel is * maximized. */ private Perspective restorePerspective; /** * Flag to determine if the layout has been maximized, i.e. the layout * temporarily displays a single dock panel */ private boolean isMaximized = false; /** * @return true if the dock panel layout has been maximized */ public boolean isMaximized() { return isMaximized; } /** * Undo a maximized layout. * * @param doRestore * if true, then attempt to restore the previous state */ public void undoMaximize(boolean doRestore) { if (!isMaximized) { return; } isMaximized = false; if (doRestore) { if (restorePerspective != null) { layout.applyPerspective(restorePerspective); } restorePerspective = null; } } /** * Maximizes the layout. * * @param dp * the dock panel to maximize */ public void maximize(DockPanelD dp) { restorePerspective = layout.createPerspective("tmp"); for (int i = 0; i < getPanels().length; i++) { if (getPanels()[i] != dp) { hide(getPanels()[i], false); } } isMaximized = true; // app.updateCenterPanel(true); app.updateMenubar(); } public void addShowDockPanelListener(ShowDockPanelListener l) { showDockPanelListener.add(l); } @Override public int getNumberOfOpenViews() { int num = 0; for (DockPanelD d : this.dockPanels) { if (d.isShowing()) { num++; } } return num; } public void exitAllCurrent() { Log.debug("closing panels"); for (DockPanelD d : this.dockPanels) { if (d.getFrame() != null) { d.getFrame().setVisible(false); } } } }