/* * PaneManager.java * * Copyright (C) 2009-12 by RStudio, Inc. * * Unless you have received this program directly from RStudio pursuant * to the terms of a commercial license agreement with RStudio, then * this program is licensed to you under the terms of version 3 of the * GNU Affero General Public License. This program is distributed WITHOUT * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. * */ package org.rstudio.studio.client.workbench.ui; import com.google.gwt.animation.client.Animation; import com.google.gwt.core.client.JsArray; import com.google.gwt.core.client.JsArrayString; import com.google.gwt.dom.client.Element; import com.google.gwt.event.logical.shared.SelectionEvent; import com.google.gwt.event.logical.shared.SelectionHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.SplitterResizedEvent; import com.google.gwt.user.client.ui.Widget; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.name.Named; import org.rstudio.core.client.Debug; import org.rstudio.core.client.JsArrayUtil; import org.rstudio.core.client.Triad; import org.rstudio.core.client.command.AppCommand; import org.rstudio.core.client.command.CommandBinder; import org.rstudio.core.client.command.Handler; import org.rstudio.core.client.dom.DomUtils; import org.rstudio.core.client.events.ManageLayoutCommandsEvent; import org.rstudio.core.client.events.WindowEnsureVisibleEvent; import org.rstudio.core.client.events.WindowStateChangeEvent; import org.rstudio.core.client.js.JsObject; import org.rstudio.core.client.layout.DualWindowLayoutPanel; import org.rstudio.core.client.layout.LogicalWindow; import org.rstudio.core.client.layout.WindowState; import org.rstudio.core.client.theme.MinimizedModuleTabLayoutPanel; import org.rstudio.core.client.theme.MinimizedWindowFrame; import org.rstudio.core.client.theme.PrimaryWindowFrame; import org.rstudio.core.client.theme.WindowFrame; import org.rstudio.core.client.theme.res.ThemeResources; import org.rstudio.core.client.widget.ToolbarButton; import org.rstudio.studio.client.application.events.EventBus; import org.rstudio.studio.client.workbench.commands.Commands; import org.rstudio.studio.client.workbench.events.ZoomPaneEvent; import org.rstudio.studio.client.workbench.model.ClientState; import org.rstudio.studio.client.workbench.model.Session; import org.rstudio.studio.client.workbench.model.WorkbenchServerOperations; import org.rstudio.studio.client.workbench.model.helper.IntStateValue; import org.rstudio.studio.client.workbench.model.helper.JSObjectStateValue; import org.rstudio.studio.client.workbench.prefs.model.UIPrefs; import org.rstudio.studio.client.workbench.prefs.views.PaneLayoutPreferencesPane; import org.rstudio.studio.client.workbench.views.console.ConsolePane; import org.rstudio.studio.client.workbench.views.output.find.FindOutputTab; import org.rstudio.studio.client.workbench.views.output.markers.MarkersOutputTab; import org.rstudio.studio.client.workbench.views.source.SourceShim; import org.rstudio.studio.client.workbench.views.source.SourceWindowManager; import org.rstudio.studio.client.workbench.views.source.model.SourceDocument; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /* * TODO: Push client state when selected tab or layout changes */ public class PaneManager { public interface Binder extends CommandBinder<Commands, PaneManager> {} public enum Tab { History, Files, Plots, Packages, Help, VCS, Build, Connections, Presentation, Environment, Viewer, Source, Console } class SelectedTabStateValue extends IntStateValue { SelectedTabStateValue(String name, WorkbenchTabPanel tabPanel) { super("workbench-pane", name, ClientState.PROJECT_PERSISTENT, session_.getSessionInfo().getClientState(), true); tabPanel_ = tabPanel; finishInit(session_.getSessionInfo().getClientState()); } @Override protected void onInit(Integer value) { if (value != null) tabPanel_.selectTab(value); } @Override protected Integer getValue() { return tabPanel_.getSelectedIndex(); } private final WorkbenchTabPanel tabPanel_; } private class ZoomedTabStateValue extends JSObjectStateValue { public ZoomedTabStateValue() { super("workbench-pane", "TabZoom", ClientState.PROJECT_PERSISTENT, session_.getSessionInfo().getClientState(), true); finishInit(session_.getSessionInfo().getClientState()); } @Override protected void onInit(final JsObject value) { if (value == null) return; if (!value.hasKey(MAXIMIZED_TAB_KEY) || !value.hasKey(WIDGET_SIZE_KEY)) return; // Time-out action just to ensure all client state is ready new Timer() { @Override public void run() { String tabString = value.getString(MAXIMIZED_TAB_KEY); double widgetSize = value.getDouble(WIDGET_SIZE_KEY); maximizedTab_ = Tab.valueOf(tabString); maximizedWindow_ = getWindowForTab(maximizedTab_); widgetSizePriorToZoom_ = widgetSize; fullyMaximizeWindow(maximizedWindow_, maximizedTab_); manageLayoutCommands(); } }.schedule(200); } @Override protected boolean hasChanged() { if (lastValue_ == null) return true; JsObject oldValue = lastValue_; JsObject newValue = getValue(); boolean oldHasKey = oldValue.hasKey(MAXIMIZED_TAB_KEY); boolean newHasKey = newValue.hasKey(MAXIMIZED_TAB_KEY); if (oldHasKey && newHasKey) return !oldValue.getString(MAXIMIZED_TAB_KEY).equals(newValue.getString(MAXIMIZED_TAB_KEY)); return oldHasKey != newHasKey; } @Override protected JsObject getValue() { final JsObject object = JsObject.createJsObject(); if (maximizedTab_ != null) object.setString(MAXIMIZED_TAB_KEY, maximizedTab_.toString()); if (widgetSizePriorToZoom_ >= 0) object.setDouble(WIDGET_SIZE_KEY, widgetSizePriorToZoom_); lastValue_ = object; return object; } private static final String MAXIMIZED_TAB_KEY = "MaximizedTab"; private static final String WIDGET_SIZE_KEY = "WidgetSize"; private JsObject lastValue_ = null; } private LogicalWindow getWindowForTab(Tab tab) { switch (tab) { case Console: return getConsoleLogicalWindow(); case Source: return getSourceLogicalWindow(); default: return getOwnerTabPanel(tab).getParentWindow(); } } @Inject public PaneManager(Provider<MainSplitPanel> pSplitPanel, WorkbenchServerOperations server, EventBus eventBus, Session session, Binder binder, Commands commands, UIPrefs uiPrefs, @Named("Console") final Widget consolePane, SourceShim source, @Named("History") final WorkbenchTab historyTab, @Named("Files") final WorkbenchTab filesTab, @Named("Plots") final WorkbenchTab plotsTab, @Named("Packages") final WorkbenchTab packagesTab, @Named("Help") final WorkbenchTab helpTab, @Named("VCS") final WorkbenchTab vcsTab, @Named("Build") final WorkbenchTab buildTab, @Named("Presentation") final WorkbenchTab presentationTab, @Named("Connections") final WorkbenchTab connectionsTab, @Named("Environment") final WorkbenchTab environmentTab, @Named("Viewer") final WorkbenchTab viewerTab, @Named("Compile PDF") final WorkbenchTab compilePdfTab, @Named("Source Cpp") final WorkbenchTab sourceCppTab, @Named("R Markdown") final WorkbenchTab renderRmdTab, @Named("Deploy") final WorkbenchTab deployContentTab, @Named("Terminal") final WorkbenchTab terminalTab, final MarkersOutputTab markersTab, final FindOutputTab findOutputTab, OptionsLoader.Shim optionsLoader) { eventBus_ = eventBus; session_ = session; commands_ = commands; uiPrefs_ = uiPrefs; consolePane_ = (ConsolePane)consolePane; source_ = source; historyTab_ = historyTab; filesTab_ = filesTab; plotsTab_ = plotsTab; packagesTab_ = packagesTab; helpTab_ = helpTab; vcsTab_ = vcsTab; buildTab_ = buildTab; presentationTab_ = presentationTab; connectionsTab_ = connectionsTab; environmentTab_ = environmentTab; viewerTab_ = viewerTab; compilePdfTab_ = compilePdfTab; findOutputTab_ = findOutputTab; sourceCppTab_ = sourceCppTab; renderRmdTab_ = renderRmdTab; deployContentTab_ = deployContentTab; markersTab_ = markersTab; terminalTab_ = terminalTab; optionsLoader_ = optionsLoader; binder.bind(commands, this); PaneConfig config = validateConfig(uiPrefs.paneConfig().getValue()); initPanes(config); panes_ = createPanes(config); left_ = createSplitWindow(panes_.get(0), panes_.get(1), "left", 0.4); right_ = createSplitWindow(panes_.get(2), panes_.get(3), "right", 0.6); panel_ = pSplitPanel.get(); panel_.initialize(left_, right_); // count the number of source docs assigned to this window JsArray<SourceDocument> docs = session_.getSessionInfo().getSourceDocuments(); String windowId = SourceWindowManager.getSourceWindowId(); int numDocs = 0; for (int i = 0; i < docs.length(); i++) { String docWindowId = docs.get(i).getSourceWindowId(); if (docWindowId == windowId) { numDocs++; } } if (numDocs == 0 && sourceLogicalWindow_.getState() != WindowState.HIDE) { sourceLogicalWindow_.onWindowStateChange( new WindowStateChangeEvent(WindowState.HIDE)); } else if (numDocs > 0 && sourceLogicalWindow_.getState() == WindowState.HIDE) { sourceLogicalWindow_.onWindowStateChange( new WindowStateChangeEvent(WindowState.NORMAL)); } uiPrefs.paneConfig().addValueChangeHandler(new ValueChangeHandler<PaneConfig>() { public void onValueChange(ValueChangeEvent<PaneConfig> evt) { ArrayList<LogicalWindow> newPanes = createPanes(validateConfig(evt.getValue())); panes_ = newPanes; left_.replaceWindows(newPanes.get(0), newPanes.get(1)); right_.replaceWindows(newPanes.get(2), newPanes.get(3)); tabSet1TabPanel_.clear(); tabSet2TabPanel_.clear(); populateTabPanel(tabNamesToTabs(evt.getValue().getTabSet1()), tabSet1TabPanel_, tabSet1MinPanel_); populateTabPanel(tabNamesToTabs(evt.getValue().getTabSet2()), tabSet2TabPanel_, tabSet2MinPanel_); manageLayoutCommands(); } }); eventBus_.addHandler(ZoomPaneEvent.TYPE, new ZoomPaneEvent.Handler() { @Override public void onZoomPane(ZoomPaneEvent event) { String pane = event.getPane(); LogicalWindow window = panesByName_.get(pane); assert window != null : "No pane with name '" + pane + "'"; toggleWindowZoom(window, tabForName(event.getTab())); } }); eventBus_.addHandler(WindowEnsureVisibleEvent.TYPE, new WindowEnsureVisibleEvent.Handler() { @Override public void onWindowEnsureVisible(WindowEnsureVisibleEvent event) { final LogicalWindow window = getLogicalWindow(event.getWindowFrame()); if (window == null) return; // If we're currently zooming a pane, and we're now ensuring // a separate window is visible (e.g. a pane raising itself), // then transfer zoom to that window. if (maximizedWindow_ != null && !maximizedWindow_.equals(window)) { fullyMaximizeWindow(window, lastSelectedTab_); return; } int width = window.getActiveWidget().getOffsetWidth(); // If the widget is already visible horizontally, then bail // (other logic handles vertical visibility) if (width > 0) return; final Command afterAnimation = new Command() { @Override public void execute() { window.getNormal().onResize(); } }; int newWidth = computeAppropriateWidth(); resizeHorizontally(0, newWidth, afterAnimation); } }); eventBus_.addHandler( ManageLayoutCommandsEvent.TYPE, new ManageLayoutCommandsEvent.Handler() { @Override public void onManageLayoutCommands(ManageLayoutCommandsEvent event) { manageLayoutCommands(); } }); manageLayoutCommands(); new ZoomedTabStateValue(); } int computeAppropriateWidth() { double windowWidth = Window.getClientWidth(); double candidateWidth = 2.0 * windowWidth / 5.0; return (int) candidateWidth; } LogicalWindow getLogicalWindow(WindowFrame frame) { for (LogicalWindow window : panes_) if (window.getNormal() == frame) return window; return null; } LogicalWindow getParentLogicalWindow(Element el) { LogicalWindow targetWindow = null; while (el != null && targetWindow == null) { el = el.getParentElement(); for (LogicalWindow window : panes_) { Widget activeWidget = window.getActiveWidget(); if (activeWidget == null) continue; Element activeEl = activeWidget.getElement(); if (el.equals(activeEl)) { targetWindow = window; break; } } } return targetWindow; } public LogicalWindow getActiveLogicalWindow() { Element activeEl = DomUtils.getActiveElement(); return getParentLogicalWindow(activeEl); } @Handler public void onActivateConsolePane() { // Ensure that the console window is activated LogicalWindow consoleWindow = getConsoleLogicalWindow(); if (consoleWindow.getState().equals(WindowState.MINIMIZE)) { WindowStateChangeEvent event = new WindowStateChangeEvent(WindowState.NORMAL); consoleWindow.onWindowStateChange(event); } // The console tab panel is initialized lazily -- while a console // pane will always be available, the owning tab panel will only // be constructed once a neighbor (e.g. the Terminal) has been // created. if (consoleTabPanel_.isEmpty()) { consolePane_.focus(); } else { LogicalWindow activeWindow = getActiveLogicalWindow(); if (consoleWindow.equals(activeWindow)) { consoleTabPanel_.selectNextTab(); } else { consoleTabPanel_.selectTab(consoleTabPanel_.getSelectedIndex()); } } } @Handler public void onLayoutZoomConsolePane() { if (consoleTabPanel_.isEmpty()) consolePane_.focus(); else consoleTabPanel_.selectTab(consoleTabPanel_.getSelectedIndex()); eventBus_.fireEvent(new ZoomPaneEvent("Console")); } @Handler public void onLayoutZoomCurrentPane() { LogicalWindow activeWindow = getActiveLogicalWindow(); if (activeWindow == null) return; toggleWindowZoom(activeWindow, null); } @Handler public void onLayoutEndZoom() { restoreLayout(); } @Handler public void onLayoutConsoleOnLeft() { if (!commands_.layoutConsoleOnLeft().isChecked()) { PaneConfig paneConfig = getCurrentConfig(); int consoleTargetIndex = paneConfig.getConsoleLeftOnTop() ? 0 : 1; swapConsolePane(paneConfig, consoleTargetIndex); } } @Handler public void onLayoutConsoleOnRight() { if (!commands_.layoutConsoleOnRight().isChecked()) { PaneConfig paneConfig = getCurrentConfig(); int consoleTargetIndex = paneConfig.getConsoleRightOnTop() ? 2 : 3; swapConsolePane(paneConfig, consoleTargetIndex); } } private void swapConsolePane(PaneConfig paneConfig, int consoleTargetIndex) { int consoleCurrentIndex = paneConfig.getConsoleIndex(); if (consoleCurrentIndex != consoleTargetIndex) { JsArrayString panes = JsArrayUtil.copy(paneConfig.getPanes()); panes.set(consoleCurrentIndex, panes.get(consoleTargetIndex)); panes.set(consoleTargetIndex, "Console"); uiPrefs_.paneConfig().setGlobalValue(PaneConfig.create( panes, paneConfig.getTabSet1(), paneConfig.getTabSet2(), paneConfig.getConsoleLeftOnTop(), paneConfig.getConsoleRightOnTop())); uiPrefs_.writeUIPrefs(); } } @Handler public void onPaneLayout() { optionsLoader_.showOptions(PaneLayoutPreferencesPane.class); } private <T> boolean equals(T lhs, T rhs) { if (lhs == null) return rhs == null; return lhs.equals(rhs); } public void toggleWindowZoom(LogicalWindow window, Tab tab) { if (isAnimating_) return; boolean hasZoom = maximizedWindow_ != null; if (hasZoom) { if (equals(window, maximizedWindow_)) { // If we're zooming a different tab in the same window, // just activate that tab. if (!equals(tab, maximizedTab_)) { maximizedTab_ = tab; manageLayoutCommands(); activateTab(tab); } // Otherwise, we're trying to maximize the same tab // and the same window. Interpret this as a toggle off. else { restoreLayout(); } } else { // We're transferring zoom from one window to another -- // maximize the new window. fullyMaximizeWindow(window, tab); } } else { // No zoom currently on -- just zoom the selected window + tab. fullyMaximizeWindow(window, tab); } } private void fullyMaximizeWindow(final LogicalWindow window, final Tab tab) { if (window.equals(getSourceLogicalWindow())) maximizedTab_ = Tab.Source; else if (window.equals(getConsoleLogicalWindow())) maximizedTab_ = Tab.Console; else maximizedTab_ = tab; maximizedWindow_ = window; manageLayoutCommands(); panel_.setSplitterEnabled(false); if (widgetSizePriorToZoom_ < 0) widgetSizePriorToZoom_ = panel_.getWidgetSize(right_); // Put all of the panes in NORMAL mode, just to ensure an appropriate // transfer to EXCLUSIVE mode works. (It seems that 'exclusive' -> 'exclusive' // transfers don't always propagate as expected) for (LogicalWindow pane : panes_) pane.onWindowStateChange(new WindowStateChangeEvent(WindowState.NORMAL)); boolean isLeftWidget = DomUtils.contains(left_.getElement(), window.getActiveWidget().getElement()); window.onWindowStateChange(new WindowStateChangeEvent(WindowState.EXCLUSIVE)); final double initialSize = panel_.getWidgetSize(right_); double targetSize = isLeftWidget ? 0 : panel_.getOffsetWidth(); if (targetSize < 0) targetSize = 0; // Ensure focus is sent to Help iframe on activation. Command onActivation = null; if (maximizedTab_.equals(Tab.Help)) { onActivation = new Command() { @Override public void execute() { commands_.activateHelp().execute(); } }; } resizeHorizontally(initialSize, targetSize, onActivation); } private void resizeHorizontally(final double start, final double end) { resizeHorizontally(start, end, null); } private void resizeHorizontally(final double start, final double end, final Command afterComplete) { horizontalResizeAnimation(start, end, afterComplete).run(300); } private Animation horizontalResizeAnimation(final double start, final double end, final Command afterComplete) { return new Animation() { @Override protected void onUpdate(double progress) { double size = (1 - progress) * start + progress * end; panel_.setWidgetSize(right_, size); } @Override protected void onStart() { isAnimating_ = true; super.onStart(); } @Override protected void onComplete() { isAnimating_ = false; panel_.onSplitterResized(new SplitterResizedEvent()); super.onComplete(); if (afterComplete != null) afterComplete.execute(); } }; } private void restoreLayout() { // If we're currently zoomed, then use that to provide the previous // 'non-zoom' state. if (maximizedWindow_ != null) restoreSavedLayout(); else restoreFourPaneLayout(); } private void invalidateSavedLayoutState(boolean enableSplitter) { maximizedWindow_ = null; maximizedTab_ = null; widgetSizePriorToZoom_ = -1; panel_.setSplitterEnabled(enableSplitter); manageLayoutCommands(); } private void restoreFourPaneLayout() { // Ensure that all windows are in the 'normal' state. This allows // hidden windows to display themselves, and so on. This also forces // widgets to size themselves vertically. for (LogicalWindow window : panes_) window.onWindowStateChange(new WindowStateChangeEvent(WindowState.NORMAL, true)); double rightWidth = panel_.getWidgetSize(right_); double panelWidth = panel_.getOffsetWidth(); double minThreshold = (2.0 / 5.0) * panelWidth; double maxThreshold = (3.0 / 5.0) * panelWidth; if (rightWidth <= minThreshold) resizeHorizontally(rightWidth, minThreshold); else if (rightWidth >= maxThreshold) resizeHorizontally(rightWidth, maxThreshold); invalidateSavedLayoutState(true); } private void restoreSavedLayout() { // Ensure that all windows are in the 'normal' state. This allows // hidden windows to display themselves, and so on. for (LogicalWindow window : panes_) window.onWindowStateChange(new WindowStateChangeEvent(WindowState.NORMAL, true)); maximizedWindow_.onWindowStateChange(new WindowStateChangeEvent(WindowState.NORMAL, true)); resizeHorizontally(panel_.getWidgetSize(right_), widgetSizePriorToZoom_); invalidateSavedLayoutState(true); } @Handler public void onMaximizeConsole() { LogicalWindow consoleWindow = panesByName_.get("Console"); if (consoleWindow.getState() != WindowState.MAXIMIZE) { consoleWindow.onWindowStateChange( new WindowStateChangeEvent(WindowState.MAXIMIZE)); } } private ArrayList<LogicalWindow> createPanes(PaneConfig config) { ArrayList<LogicalWindow> results = new ArrayList<LogicalWindow>(); JsArrayString panes = config.getPanes(); for (int i = 0; i < 4; i++) { results.add(panesByName_.get(panes.get(i))); } return results; } private void initPanes(PaneConfig config) { panesByName_ = new HashMap<String, LogicalWindow>(); panesByName_.put("Console", createConsole()); panesByName_.put("Source", createSource()); Triad<LogicalWindow, WorkbenchTabPanel, MinimizedModuleTabLayoutPanel> ts1 = createTabSet( "TabSet1", tabNamesToTabs(config.getTabSet1())); panesByName_.put("TabSet1", ts1.first); tabSet1TabPanel_ = ts1.second; tabSet1MinPanel_ = ts1.third; Triad<LogicalWindow, WorkbenchTabPanel, MinimizedModuleTabLayoutPanel> ts2 = createTabSet( "TabSet2", tabNamesToTabs(config.getTabSet2())); panesByName_.put("TabSet2", ts2.first); tabSet2TabPanel_ = ts2.second; tabSet2MinPanel_ = ts2.third; } private ArrayList<Tab> tabNamesToTabs(JsArrayString tabNames) { ArrayList<Tab> tabList = new ArrayList<Tab>(); for (int j = 0; j < tabNames.length(); j++) tabList.add(Enum.valueOf(Tab.class, tabNames.get(j))); return tabList; } private PaneConfig validateConfig(PaneConfig config) { if (config == null) config = PaneConfig.createDefault(); if (!config.validateAndAutoCorrect()) { Debug.log("Pane config is not valid"); config = PaneConfig.createDefault(); } return config; } public MainSplitPanel getPanel() { return panel_; } public WorkbenchTab getTab(Tab tab) { switch (tab) { case History: return historyTab_; case Files: return filesTab_; case Plots: return plotsTab_; case Packages: return packagesTab_; case Help: return helpTab_; case VCS: return vcsTab_; case Build: return buildTab_; case Presentation: return presentationTab_; case Environment: return environmentTab_; case Viewer: return viewerTab_; case Connections: return connectionsTab_; case Source: case Console: // not 'real' tabs so should be an error to ask for their tabs } throw new IllegalArgumentException("Unknown tab"); } public WorkbenchTab[] getAllTabs() { return new WorkbenchTab[] { historyTab_, filesTab_, plotsTab_, packagesTab_, helpTab_, vcsTab_, buildTab_, presentationTab_, environmentTab_, viewerTab_, connectionsTab_}; } public void activateTab(Tab tab) { lastSelectedTab_ = tab; WorkbenchTabPanel panel = getOwnerTabPanel(tab); // Ensure that the pane is visible (otherwise tab selection will fail) LogicalWindow parent = panel.getParentWindow(); if (parent.getState() == WindowState.MINIMIZE || parent.getState() == WindowState.HIDE) { parent.onWindowStateChange(new WindowStateChangeEvent(WindowState.NORMAL)); } if (tabToIndex_.containsKey(tab)) { int index = tabToIndex_.get(tab); panel.selectTab(index); } else { // unexpected; why are we trying to activate a suppressed tab? Debug.logWarning("Attempt to activate suppressed or unavailable " + "tab '" + tab.name() + "')"); } } public void activateTab(String tabName) { Tab tab = tabForName(tabName); if (tab != null) activateTab(tab); } public void zoomTab(Tab tab) { activateTab(tab); WorkbenchTabPanel tabPanel = getOwnerTabPanel(tab); LogicalWindow parentWindow = tabPanel.getParentWindow(); if (parentWindow == null) return; toggleWindowZoom(parentWindow, tab); } public ConsolePane getConsole() { return consolePane_; } public WorkbenchTabPanel getOwnerTabPanel(Tab tab) { return tabToPanel_.get(tab); } public LogicalWindow getSourceLogicalWindow() { return sourceLogicalWindow_; } public LogicalWindow getConsoleLogicalWindow() { return panesByName_.get("Console"); } private DualWindowLayoutPanel createSplitWindow(LogicalWindow top, LogicalWindow bottom, String name, double bottomDefaultPct) { return new DualWindowLayoutPanel( eventBus_, top, bottom, session_, name, WindowState.NORMAL, (int) (Window.getClientHeight()*bottomDefaultPct)); } private LogicalWindow createConsole() { PrimaryWindowFrame frame = new PrimaryWindowFrame("Console", null); ToolbarButton goToWorkingDirButton = commands_.goToWorkingDir().createToolbarButton(); goToWorkingDirButton.addStyleName( ThemeResources.INSTANCE.themeStyles().windowFrameToolbarButton()); LogicalWindow logicalWindow = new LogicalWindow(frame, new MinimizedWindowFrame("Console")); consoleTabPanel_ = new ConsoleTabPanel( frame, logicalWindow, consolePane_, compilePdfTab_, findOutputTab_, sourceCppTab_, renderRmdTab_, deployContentTab_, markersTab_, terminalTab_, eventBus_, goToWorkingDirButton); consoleTabPanel_.addLayoutStyles(frame.getElement()); return logicalWindow; } private LogicalWindow createSource() { WindowFrame sourceFrame = new WindowFrame(); sourceFrame.setFillWidget(source_.asWidget()); source_.forceLoad(); return sourceLogicalWindow_ = new LogicalWindow( sourceFrame, new MinimizedWindowFrame("Source")); } private Triad<LogicalWindow, WorkbenchTabPanel, MinimizedModuleTabLayoutPanel> createTabSet(String persisterName, ArrayList<Tab> tabs) { final WindowFrame frame = new WindowFrame(); final MinimizedModuleTabLayoutPanel minimized = new MinimizedModuleTabLayoutPanel(); final LogicalWindow logicalWindow = new LogicalWindow(frame, minimized); final WorkbenchTabPanel tabPanel = new WorkbenchTabPanel(frame, logicalWindow); populateTabPanel(tabs, tabPanel, minimized); frame.setFillWidget(tabPanel); minimized.addSelectionHandler(new SelectionHandler<Integer>() { public void onSelection(SelectionEvent<Integer> integerSelectionEvent) { int tab = integerSelectionEvent.getSelectedItem(); tabPanel.selectTab(tab); } }); tabPanel.addSelectionHandler(new SelectionHandler<Integer>() { public void onSelection(SelectionEvent<Integer> integerSelectionEvent) { int index = integerSelectionEvent.getSelectedItem(); WorkbenchTab selected = tabPanel.getTab(index); lastSelectedTab_ = workbenchTabToTab(selected); session_.persistClientState(); } }); new SelectedTabStateValue(persisterName, tabPanel); return new Triad<LogicalWindow, WorkbenchTabPanel, MinimizedModuleTabLayoutPanel>( logicalWindow, tabPanel, minimized); } private Tab workbenchTabToTab(WorkbenchTab tab) { return wbTabToTab_.get(tab); } private void populateTabPanel(ArrayList<Tab> tabs, WorkbenchTabPanel tabPanel, MinimizedModuleTabLayoutPanel minimized) { ArrayList<WorkbenchTab> tabList = new ArrayList<WorkbenchTab>(); int tabIdx = 0; for (int i = 0; i < tabs.size(); i++) { Tab tab = tabs.get(i); WorkbenchTab wbTab = getTab(tab); wbTabToTab_.put(wbTab, tab); tabToPanel_.put(tab, tabPanel); // exclude suppressed tabs from the index since they aren't added to // the panel if (!wbTab.isSuppressed()) tabToIndex_.put(tab, tabIdx++); tabList.add(wbTab); } tabPanel.setTabs(tabList); ArrayList<String> labels = new ArrayList<String>(); for (Tab tab : tabs) { if (!getTab(tab).isSuppressed()) labels.add(getTabLabel(tab)); } minimized.setTabs(labels.toArray(new String[labels.size()])); } private String getTabLabel(Tab tab) { switch (tab) { case VCS: return getTab(tab).getTitle(); case Presentation: return getTab(tab).getTitle(); case Connections: return getTab(tab).getTitle(); default: return tab.toString(); } } private Tab tabForName(String name) { if (name.equalsIgnoreCase("history")) return Tab.History; if (name.equalsIgnoreCase("files")) return Tab.Files; if (name.equalsIgnoreCase("plots")) return Tab.Plots; if (name.equalsIgnoreCase("packages")) return Tab.Packages; if (name.equalsIgnoreCase("help")) return Tab.Help; if (name.equalsIgnoreCase("vcs")) return Tab.VCS; if (name.equalsIgnoreCase("build")) return Tab.Build; if (name.equalsIgnoreCase("presentation")) return Tab.Presentation; if (name.equalsIgnoreCase("environment")) return Tab.Environment; if (name.equalsIgnoreCase("viewer")) return Tab.Viewer; if (name.equalsIgnoreCase("connections")) return Tab.Connections; if (name.equalsIgnoreCase("source")) return Tab.Source; if (name.equalsIgnoreCase("console")) return Tab.Console; return null; } private AppCommand getLayoutCommandForTab(Tab tab) { if (tab == null) return commands_.layoutEndZoom(); switch (tab) { case Build: return commands_.layoutZoomBuild(); case Console: return commands_.layoutZoomConsole(); case Environment: return commands_.layoutZoomEnvironment(); case Files: return commands_.layoutZoomFiles(); case Help: return commands_.layoutZoomHelp(); case History: return commands_.layoutZoomHistory(); case Packages: return commands_.layoutZoomPackages(); case Plots: return commands_.layoutZoomPlots(); case Source: return commands_.layoutZoomSource(); case VCS: return commands_.layoutZoomVcs(); case Viewer: return commands_.layoutZoomViewer(); case Connections: return commands_.layoutZoomConnections(); default: throw new IllegalArgumentException("Unexpected tab '" + tab.toString() + "'"); } } private void manageLayoutCommands() { List<AppCommand> layoutCommands = getLayoutCommands(); AppCommand activeCommand = getLayoutCommandForTab(maximizedTab_); for (AppCommand command : layoutCommands) command.setChecked(activeCommand.equals(command)); // manage console left/right commands boolean maximized = maximizedTab_ != null; commands_.layoutConsoleOnLeft().setVisible(!maximized); commands_.layoutConsoleOnRight().setVisible(!maximized); if (!maximized) { PaneConfig config = getCurrentConfig(); commands_.layoutConsoleOnLeft().setChecked(config.getConsoleLeft()); commands_.layoutConsoleOnRight().setChecked(config.getConsoleRight()); } else { commands_.layoutConsoleOnLeft().setVisible(false); commands_.layoutConsoleOnRight().setVisible(false); } } private List<AppCommand> getLayoutCommands() { List<AppCommand> commands = new ArrayList<AppCommand>(); commands.add(commands_.layoutEndZoom()); commands.add(commands_.layoutZoomBuild()); commands.add(commands_.layoutZoomConsole()); commands.add(commands_.layoutZoomEnvironment()); commands.add(commands_.layoutZoomFiles()); commands.add(commands_.layoutZoomHelp()); commands.add(commands_.layoutZoomHistory()); commands.add(commands_.layoutZoomPackages()); commands.add(commands_.layoutZoomPlots()); commands.add(commands_.layoutZoomSource()); commands.add(commands_.layoutZoomVcs()); commands.add(commands_.layoutZoomViewer()); commands.add(commands_.layoutZoomConnections()); return commands; } private PaneConfig getCurrentConfig() { PaneConfig config = uiPrefs_.paneConfig().getValue(); // use default config if pref isn't set yet if (config == null) return PaneConfig.createDefault(); return config; } private final EventBus eventBus_; private final Session session_; private final Commands commands_; private final UIPrefs uiPrefs_; private final FindOutputTab findOutputTab_; private final WorkbenchTab compilePdfTab_; private final WorkbenchTab sourceCppTab_; private final ConsolePane consolePane_; private final SourceShim source_; private final WorkbenchTab historyTab_; private final WorkbenchTab filesTab_; private final WorkbenchTab plotsTab_; private final WorkbenchTab packagesTab_; private final WorkbenchTab helpTab_; private final WorkbenchTab vcsTab_; private final WorkbenchTab buildTab_; private final WorkbenchTab presentationTab_; private final WorkbenchTab connectionsTab_; private final WorkbenchTab environmentTab_; private final WorkbenchTab viewerTab_; private final WorkbenchTab renderRmdTab_; private final WorkbenchTab deployContentTab_; private final MarkersOutputTab markersTab_; private final WorkbenchTab terminalTab_; private final OptionsLoader.Shim optionsLoader_; private MainSplitPanel panel_; private LogicalWindow sourceLogicalWindow_; private final HashMap<Tab, WorkbenchTabPanel> tabToPanel_ = new HashMap<Tab, WorkbenchTabPanel>(); private final HashMap<Tab, Integer> tabToIndex_ = new HashMap<Tab, Integer>(); private final HashMap<WorkbenchTab, Tab> wbTabToTab_ = new HashMap<WorkbenchTab, Tab>(); private HashMap<String, LogicalWindow> panesByName_; private DualWindowLayoutPanel left_; private DualWindowLayoutPanel right_; private ArrayList<LogicalWindow> panes_; private ConsoleTabPanel consoleTabPanel_; private WorkbenchTabPanel tabSet1TabPanel_; private MinimizedModuleTabLayoutPanel tabSet1MinPanel_; private WorkbenchTabPanel tabSet2TabPanel_; private MinimizedModuleTabLayoutPanel tabSet2MinPanel_; // Zoom-related members ---- private Tab lastSelectedTab_ = null; private LogicalWindow maximizedWindow_ = null; private Tab maximizedTab_ = null; private double widgetSizePriorToZoom_ = -1; private boolean isAnimating_ = false; }