/** * Copyright (C) 2001-2017 by RapidMiner and the contributors * * Complete list of developers available at our web site: * * http://rapidminer.com * * This program is free software: you can redistribute it and/or modify it under the terms of the * GNU Affero General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.gui; import java.awt.Component; import java.awt.Container; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import com.vlsolutions.swing.docking.Dockable; import com.vlsolutions.swing.docking.DockableState; import com.vlsolutions.swing.docking.DockingUtilities; import com.vlsolutions.swing.docking.TabbedDockableContainer; /** * * This class saves and restores certain properties associated with a specific {@link Perspective}. * Currently, this class is used to restore the selected tabs and the scroll positions of * {@link JScrollPane}s for a specific {@link Perspective} after a perspective switch occurred. * * @author Dominik Halfkann */ public class PerspectiveProperties { /** * Try to reposition scroll bars at most the specified number of times (repositioning might fail * due to asynchronous code in the docking framework). */ private static final int POSITION_SCROLL_BARS_MAX_RETRIES = 5; /** Time to wait in between scroll bar repositioning attempts. */ private static final int POSITION_SCROLL_BARS_WAIT_PERIOD = 50; private List<Dockable> focusedDockables = new ArrayList<>(); private Map<String, ScrollBarsPosition> scrollBarsPositions = new HashMap<>(); private class ScrollBarsPosition { private final int vertical; private final int horizontal; public ScrollBarsPosition(int vertical, int horizontal) { this.vertical = vertical; this.horizontal = horizontal; } public int getVertical() { return vertical; } public int getHorizontal() { return horizontal; } } /** * This method stores certain properties about the current {@link Perspective}. */ public void store() { storeFocusedDockables(); storeScrollBarPositions(); } /** * This method applies properties to the current {@link Perspective} which were previously * stored by {@link #store()}. */ public void apply() { applyFocusedDockables(); applyScrollBarPositions(); } /** * This method saves all tabs which are currently selected/visible. */ private void storeFocusedDockables() { MainFrame mainFrame = RapidMinerGUI.getMainFrame(); if (mainFrame != null) { focusedDockables = new ArrayList<>(); DockableState[] states = mainFrame.getDockingDesktop().getContext().getDockables(); List<TabbedDockableContainer> memorizedContainer = new ArrayList<>(); for (DockableState state : states) { TabbedDockableContainer container = DockingUtilities.findTabbedDockableContainer(state.getDockable()); if (container != null) { if (!memorizedContainer.contains(container)) { focusedDockables.add(container.getSelectedDockable()); memorizedContainer.add(container); } } } } } /** * This method will make all tabs visible which were previously saved by * {@link #storeFocusedDockables()}. */ private void applyFocusedDockables() { for (Dockable dockable : focusedDockables) { TabbedDockableContainer tabbedContainer = DockingUtilities.findTabbedDockableContainer(dockable); if (tabbedContainer != null) { tabbedContainer.setSelectedDockable(dockable); } } } /** * This method saves scroll positions of all {@link JScrollPane}s in the current Perspective. */ private void storeScrollBarPositions() { MainFrame mainFrame = RapidMinerGUI.getMainFrame(); if (mainFrame != null) { DockableState[] states = mainFrame.getDockingDesktop().getContext().getDockables(); for (DockableState state : states) { Dockable dockable = state.getDockable(); if (dockable.getComponent() instanceof Container) { JScrollPane scrollPane = findScrollPane((Container) dockable.getComponent()); if (scrollPane != null) { ScrollBarsPosition scrollBarsPosition = new ScrollBarsPosition( scrollPane.getVerticalScrollBar().getValue(), scrollPane.getHorizontalScrollBar().getValue()); scrollBarsPositions.put(dockable.getDockKey().getKey(), scrollBarsPosition); } } } } } /** * This method will apply scroll positions to all {@link JScrollPane}s which were previously * saved by {@link #storeScrollBarPositions()} */ private void applyScrollBarPositions() { for (final Entry<String, ScrollBarsPosition> scrollBarsPosition : scrollBarsPositions.entrySet()) { final Dockable dockable = RapidMinerGUI.getMainFrame().getDockingDesktop().getContext() .getDockableByKey(scrollBarsPosition.getKey()); if (dockable.getComponent() instanceof Container) { final JScrollPane scrollPane = findScrollPane((Container) dockable.getComponent()); if (scrollPane != null) { // I'm testing after 50ms if the scrollbars have been set correctly. // This is due to the vldocking which seems to resets all windows asynchronously // and can reset the scrollbars after they have been set by this method. // A simple SwingUtilities.invokeLater() won't do in this case. new Thread(new Runnable() { @Override public void run() { final JScrollBar vScrollBar = scrollPane.getVerticalScrollBar(); final JScrollBar hScrollBar = scrollPane.getHorizontalScrollBar(); /** Tries reposition scroll bars to stored positions. */ Runnable scrollBarUpdater = new Runnable() { @Override public void run() { vScrollBar.setValue(scrollBarsPosition.getValue().getVertical()); hScrollBar.setValue(scrollBarsPosition.getValue().getHorizontal()); } }; int maxIterations = POSITION_SCROLL_BARS_MAX_RETRIES; int i = 0; do { try { SwingUtilities.invokeAndWait(scrollBarUpdater); Thread.sleep(POSITION_SCROLL_BARS_WAIT_PERIOD); } catch (InterruptedException | InvocationTargetException e) { // ignore } i++; } while (i < maxIterations && (vScrollBar.getValue() != scrollBarsPosition.getValue().getVertical() || hScrollBar.getValue() != scrollBarsPosition.getValue().getHorizontal())); } }).start(); } } } } /** * Utility method that searches for a {@link JScrollPane} starting from the provided Container * and returning the first occurrence. */ private JScrollPane findScrollPane(Container parent) { if (parent != null) { for (Component child : parent.getComponents()) { if (child instanceof JScrollPane) { return (JScrollPane) child; } else if (child instanceof Container) { JScrollPane scrollPane = findScrollPane((Container) child); if (scrollPane != null) { return scrollPane; } } } } return null; } }