| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.openapi.wm.impl; |
| |
| import com.intellij.Patches; |
| import com.intellij.ide.FrameStateManager; |
| import com.intellij.ide.IdeEventQueue; |
| import com.intellij.ide.actions.ActivateToolWindowAction; |
| import com.intellij.ide.ui.LafManager; |
| import com.intellij.ide.ui.LafManagerListener; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.actionSystem.ex.AnActionListener; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.ModalityState; |
| import com.intellij.openapi.components.ProjectComponent; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.fileEditor.FileEditorManager; |
| import com.intellij.openapi.fileEditor.FileEditorManagerEvent; |
| import com.intellij.openapi.fileEditor.FileEditorManagerListener; |
| import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; |
| import com.intellij.openapi.fileEditor.impl.EditorsSplitters; |
| import com.intellij.openapi.keymap.Keymap; |
| import com.intellij.openapi.keymap.KeymapManager; |
| import com.intellij.openapi.project.DumbAwareRunnable; |
| import com.intellij.openapi.project.DumbService; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.startup.StartupManager; |
| import com.intellij.openapi.ui.DialogWrapper; |
| import com.intellij.openapi.ui.MessageType; |
| import com.intellij.openapi.ui.Splitter; |
| import com.intellij.openapi.ui.popup.Balloon; |
| import com.intellij.openapi.ui.popup.JBPopupFactory; |
| import com.intellij.openapi.util.*; |
| import com.intellij.openapi.util.registry.Registry; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.wm.*; |
| import com.intellij.openapi.wm.ex.*; |
| import com.intellij.openapi.wm.impl.commands.*; |
| import com.intellij.ui.BalloonImpl; |
| import com.intellij.ui.ColorUtil; |
| import com.intellij.ui.awt.RelativePoint; |
| import com.intellij.ui.switcher.QuickAccessSettings; |
| import com.intellij.ui.switcher.SwitchManager; |
| import com.intellij.util.Alarm; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.EventDispatcher; |
| import com.intellij.util.IJSwingUtilities; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.HashMap; |
| import com.intellij.util.ui.PositionTracker; |
| import com.intellij.util.ui.UIUtil; |
| import com.intellij.util.ui.update.UiNotifyConnector; |
| import org.jdom.Element; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.event.HyperlinkEvent; |
| import javax.swing.event.HyperlinkListener; |
| import java.awt.*; |
| import java.awt.event.InputEvent; |
| import java.awt.event.KeyEvent; |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.util.*; |
| import java.util.List; |
| |
| /** |
| * @author Anton Katilin |
| * @author Vladimir Kondratyev |
| */ |
| public final class ToolWindowManagerImpl extends ToolWindowManagerEx implements ProjectComponent, JDOMExternalizable { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.ToolWindowManagerImpl"); |
| |
| private final Project myProject; |
| private final WindowManagerEx myWindowManager; |
| private final EventDispatcher<ToolWindowManagerListener> myDispatcher = EventDispatcher.create(ToolWindowManagerListener.class); |
| private final DesktopLayout myLayout; |
| private final Map<String, InternalDecorator> myId2InternalDecorator; |
| private final Map<String, FloatingDecorator> myId2FloatingDecorator; |
| private final Map<String, StripeButton> myId2StripeButton; |
| private final Map<String, FocusWatcher> myId2FocusWatcher; |
| private final Set<String> myDumbAwareIds = Collections.synchronizedSet(ContainerUtil.<String>newTroveSet()); |
| |
| private final EditorComponentFocusWatcher myEditorComponentFocusWatcher; |
| private final MyToolWindowPropertyChangeListener myToolWindowPropertyChangeListener; |
| private final InternalDecoratorListener myInternalDecoratorListener; |
| |
| private boolean myEditorWasActive; |
| |
| private final ActiveStack myActiveStack; |
| private final SideStack mySideStack; |
| |
| private ToolWindowsPane myToolWindowsPane; |
| private IdeFrameImpl myFrame; |
| private DesktopLayout myLayoutToRestoreLater = null; |
| @NonNls private static final String EDITOR_ELEMENT = "editor"; |
| @NonNls private static final String ACTIVE_ATTR_VALUE = "active"; |
| @NonNls private static final String FRAME_ELEMENT = "frame"; |
| @NonNls private static final String X_ATTR = "x"; |
| @NonNls private static final String Y_ATTR = "y"; |
| @NonNls private static final String WIDTH_ATTR = "width"; |
| @NonNls private static final String HEIGHT_ATTR = "height"; |
| @NonNls private static final String EXTENDED_STATE_ATTR = "extended-state"; |
| @NonNls private static final String LAYOUT_TO_RESTORE = "layout-to-restore"; |
| |
| private final FileEditorManager myFileEditorManager; |
| private final LafManager myLafManager; |
| private final Map<String, Balloon> myWindow2Balloon = new HashMap<String, Balloon>(); |
| |
| private KeyState myCurrentState = KeyState.waiting; |
| private final Alarm myWaiterForSecondPress = new Alarm(); |
| private final Runnable mySecondPressRunnable = new Runnable() { |
| @Override |
| public void run() { |
| if (myCurrentState != KeyState.hold) { |
| resetHoldState(); |
| } |
| } |
| }; |
| private final PropertyChangeListener myFocusListener; |
| |
| public boolean isToolWindowRegistered(String id) { |
| return myLayout.isToolWindowRegistered(id); |
| } |
| |
| private enum KeyState { |
| waiting, pressed, released, hold |
| } |
| |
| private final Alarm myUpdateHeadersAlarm = new Alarm(); |
| private final Runnable myUpdateHeadersRunnable = new Runnable() { |
| @Override |
| public void run() { |
| updateToolWindowHeaders(); |
| } |
| }; |
| |
| /** |
| * invoked by reflection |
| */ |
| public ToolWindowManagerImpl(final Project project, |
| final WindowManagerEx windowManagerEx, |
| final FileEditorManager fem, |
| final ActionManager actionManager, |
| final LafManager lafManager) { |
| myProject = project; |
| myWindowManager = windowManagerEx; |
| myFileEditorManager = fem; |
| myLafManager = lafManager; |
| |
| if (!project.isDefault()) { |
| actionManager.addAnActionListener(new AnActionListener() { |
| @Override |
| public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { |
| if (myCurrentState != KeyState.hold) { |
| resetHoldState(); |
| } |
| } |
| |
| @Override |
| public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { |
| } |
| |
| @Override |
| public void beforeEditorTyping(char c, DataContext dataContext) { |
| } |
| }, project); |
| } |
| |
| myLayout = new DesktopLayout(); |
| myLayout.copyFrom(windowManagerEx.getLayout()); |
| |
| myId2InternalDecorator = new HashMap<String, InternalDecorator>(); |
| myId2FloatingDecorator = new HashMap<String, FloatingDecorator>(); |
| myId2StripeButton = new HashMap<String, StripeButton>(); |
| myId2FocusWatcher = new HashMap<String, FocusWatcher>(); |
| |
| myEditorComponentFocusWatcher = new EditorComponentFocusWatcher(); |
| myToolWindowPropertyChangeListener = new MyToolWindowPropertyChangeListener(); |
| myInternalDecoratorListener = new MyInternalDecoratorListener(); |
| |
| myActiveStack = new ActiveStack(); |
| mySideStack = new SideStack(); |
| |
| project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() { |
| @Override |
| public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) { |
| } |
| |
| @Override |
| public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) { |
| getFocusManagerImpl(myProject).doWhenFocusSettlesDown(new ExpirableRunnable.ForProject(myProject) { |
| @Override |
| public void run() { |
| if (!hasOpenEditorFiles()) { |
| focusToolWinowByDefault(null); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public void selectionChanged(@NotNull FileEditorManagerEvent event) { |
| } |
| }); |
| |
| myFocusListener = new PropertyChangeListener() { |
| @Override |
| public void propertyChange(PropertyChangeEvent evt) { |
| if ("focusOwner".equals(evt.getPropertyName())) { |
| myUpdateHeadersAlarm.cancelAllRequests(); |
| myUpdateHeadersAlarm.addRequest(myUpdateHeadersRunnable, 50); |
| } |
| } |
| }; |
| KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(myFocusListener); |
| } |
| |
| |
| private void updateToolWindowHeaders() { |
| getFocusManager().doWhenFocusSettlesDown(new ExpirableRunnable.ForProject(myProject) { |
| @Override |
| public void run() { |
| WindowInfoImpl[] infos = myLayout.getInfos(); |
| for (WindowInfoImpl each : infos) { |
| if (each.isVisible()) { |
| ToolWindow tw = getToolWindow(each.getId()); |
| if (tw instanceof ToolWindowImpl) { |
| InternalDecorator decorator = ((ToolWindowImpl)tw).getDecorator(); |
| if (decorator != null) { |
| decorator.repaint(); |
| } |
| } |
| } |
| } |
| } |
| }); |
| } |
| |
| public boolean dispatchKeyEvent(KeyEvent e) { |
| if (e.getKeyCode() != KeyEvent.VK_CONTROL && |
| e.getKeyCode() != KeyEvent.VK_ALT && |
| e.getKeyCode() != KeyEvent.VK_SHIFT && |
| e.getKeyCode() != KeyEvent.VK_META) { |
| if (e.getModifiers() == 0) { |
| resetHoldState(); |
| } |
| return false; |
| } |
| if (e.getID() != KeyEvent.KEY_PRESSED && e.getID() != KeyEvent.KEY_RELEASED) return false; |
| |
| Component parent = UIUtil.findUltimateParent(e.getComponent()); |
| if (parent instanceof IdeFrame) { |
| if (((IdeFrame)parent).getProject() != myProject) { |
| resetHoldState(); |
| return false; |
| } |
| } |
| |
| Set<Integer> vks = getActivateToolWindowVKs(); |
| |
| if (vks.isEmpty()) { |
| resetHoldState(); |
| return false; |
| } |
| |
| if (vks.contains(e.getKeyCode())) { |
| boolean pressed = e.getID() == KeyEvent.KEY_PRESSED; |
| int modifiers = e.getModifiers(); |
| |
| int mouseMask = InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON2_DOWN_MASK | InputEvent.BUTTON3_DOWN_MASK; |
| if ((e.getModifiersEx() & mouseMask) == 0) { |
| if (SwitchManager.areAllModifiersPressed(modifiers, vks) || !pressed) { |
| processState(pressed); |
| } |
| else { |
| resetHoldState(); |
| } |
| } |
| } |
| |
| |
| return false; |
| } |
| |
| public static Set<Integer> getActivateToolWindowVKs() { |
| if (ApplicationManager.getApplication() == null) return new HashSet<Integer>(); |
| |
| Keymap keymap = KeymapManager.getInstance().getActiveKeymap(); |
| Shortcut[] baseShortcut = keymap.getShortcuts("ActivateProjectToolWindow"); |
| int baseModifiers = 0; |
| for (Shortcut each : baseShortcut) { |
| if (each instanceof KeyboardShortcut) { |
| KeyStroke keyStroke = ((KeyboardShortcut)each).getFirstKeyStroke(); |
| baseModifiers = keyStroke.getModifiers(); |
| if (baseModifiers > 0) { |
| break; |
| } |
| } |
| } |
| return QuickAccessSettings.getModifiersVKs(baseModifiers); |
| } |
| |
| private void resetHoldState() { |
| myCurrentState = KeyState.waiting; |
| processHoldState(); |
| } |
| |
| private void processState(boolean pressed) { |
| if (pressed) { |
| if (myCurrentState == KeyState.waiting) { |
| myCurrentState = KeyState.pressed; |
| } |
| else if (myCurrentState == KeyState.released) { |
| myCurrentState = KeyState.hold; |
| processHoldState(); |
| } |
| } |
| else { |
| if (myCurrentState == KeyState.pressed) { |
| myCurrentState = KeyState.released; |
| restartWaitingForSecondPressAlarm(); |
| } |
| else { |
| resetHoldState(); |
| } |
| } |
| } |
| |
| private void processHoldState() { |
| if (myToolWindowsPane != null) { |
| myToolWindowsPane.setStripesOverlayed(myCurrentState == KeyState.hold); |
| } |
| } |
| |
| private void restartWaitingForSecondPressAlarm() { |
| myWaiterForSecondPress.cancelAllRequests(); |
| myWaiterForSecondPress.addRequest(mySecondPressRunnable, Registry.intValue("actionSystem.keyGestureDblClickTime")); |
| } |
| |
| private boolean hasOpenEditorFiles() { |
| return myFileEditorManager.getOpenFiles().length > 0; |
| } |
| |
| private static IdeFocusManager getFocusManagerImpl(Project project) { |
| return IdeFocusManager.getInstance(project); |
| } |
| |
| @NotNull |
| public Project getProject() { |
| return myProject; |
| } |
| |
| @Override |
| public void initComponent() { |
| } |
| |
| @Override |
| public void disposeComponent() { |
| for (String id : new ArrayList<String>(myId2StripeButton.keySet())) { |
| unregisterToolWindow(id); |
| } |
| |
| assert myId2StripeButton.isEmpty(); |
| |
| KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener(myFocusListener); |
| } |
| |
| @Override |
| public void projectOpened() { |
| final MyUIManagerPropertyChangeListener uiManagerPropertyListener = new MyUIManagerPropertyChangeListener(); |
| final MyLafManagerListener lafManagerListener = new MyLafManagerListener(); |
| |
| UIManager.addPropertyChangeListener(uiManagerPropertyListener); |
| myLafManager.addLafManagerListener(lafManagerListener); |
| |
| Disposer.register(myProject, new Disposable() { |
| @Override |
| public void dispose() { |
| UIManager.removePropertyChangeListener(uiManagerPropertyListener); |
| myLafManager.removeLafManagerListener(lafManagerListener); |
| } |
| }); |
| myFrame = myWindowManager.allocateFrame(myProject); |
| LOG.assertTrue(myFrame != null); |
| |
| final ArrayList<FinalizableCommand> commandsList = new ArrayList<FinalizableCommand>(); |
| |
| myToolWindowsPane = new ToolWindowsPane(myFrame, this); |
| Disposer.register(myProject, myToolWindowsPane); |
| ((IdeRootPane)myFrame.getRootPane()).setToolWindowsPane(myToolWindowsPane); |
| appendUpdateToolWindowsPaneCmd(commandsList); |
| |
| myFrame.setTitle(FrameTitleBuilder.getInstance().getProjectTitle(myProject)); |
| JComponent editorComponent = createEditorComponent(myProject); |
| myEditorComponentFocusWatcher.install(editorComponent); |
| |
| appendSetEditorComponentCmd(editorComponent, commandsList); |
| if (myEditorWasActive && editorComponent instanceof EditorsSplitters) { |
| activateEditorComponentImpl(FileEditorManagerEx.getInstanceEx(myProject).getSplitters(), commandsList, true); |
| } |
| execute(commandsList); |
| |
| final DumbService.DumbModeListener dumbModeListener = new DumbService.DumbModeListener() { |
| @Override |
| public void enteredDumbMode() { |
| disableStripeButtons(); |
| } |
| |
| @Override |
| public void exitDumbMode() { |
| for (final String id : getToolWindowIds()) { |
| getStripeButton(id).setEnabled(true); |
| } |
| } |
| }; |
| myProject.getMessageBus().connect().subscribe(DumbService.DUMB_MODE, dumbModeListener); |
| |
| StartupManager.getInstance(myProject).registerPostStartupActivity(new DumbAwareRunnable() { |
| @Override |
| public void run() { |
| registerToolWindowsFromBeans(); |
| if (DumbService.getInstance(myProject).isDumb()) { |
| disableStripeButtons(); |
| } |
| } |
| }); |
| |
| IdeEventQueue.getInstance().addDispatcher(new IdeEventQueue.EventDispatcher() { |
| @Override |
| public boolean dispatch(AWTEvent e) { |
| if (e instanceof KeyEvent) { |
| dispatchKeyEvent((KeyEvent)e); |
| } |
| return false; |
| } |
| }, myProject); |
| } |
| |
| private void disableStripeButtons() { |
| for (final String id : getToolWindowIds()) { |
| if (!myDumbAwareIds.contains(id)) { |
| if (isToolWindowVisible(id)) { |
| hideToolWindow(id, true); |
| } |
| StripeButton button = getStripeButton(id); |
| if (button != null) { |
| button.setEnabled(false); |
| } |
| } |
| } |
| } |
| |
| private JComponent createEditorComponent(Project project) { |
| return FrameEditorComponentProvider.EP.getExtensions()[0].createEditorComponent(project); |
| } |
| |
| private void registerToolWindowsFromBeans() { |
| ToolWindowEP[] beans = Extensions.getExtensions(ToolWindowEP.EP_NAME); |
| for (final ToolWindowEP bean : beans) { |
| final Condition<Project> condition = bean.getCondition(); |
| if (condition == null || condition.value(myProject)) { |
| initToolWindow(bean); |
| } |
| } |
| } |
| |
| @Override |
| public void initToolWindow(@NotNull ToolWindowEP bean) { |
| JLabel label = new JLabel("Initializing...", SwingConstants.CENTER); |
| label.setOpaque(true); |
| final Color treeBg = UIManager.getColor("Tree.background"); |
| label.setBackground(ColorUtil.toAlpha(treeBg, 180)); |
| final Color treeFg = UIUtil.getTreeForeground(); |
| label.setForeground(ColorUtil.toAlpha(treeFg, 180)); |
| ToolWindowAnchor toolWindowAnchor = ToolWindowAnchor.fromText(bean.anchor); |
| final ToolWindowFactory factory = bean.getToolWindowFactory(); |
| final ToolWindowImpl toolWindow = |
| (ToolWindowImpl)registerToolWindow(bean.id, label, toolWindowAnchor, myProject, DumbService.isDumbAware(factory), |
| bean.canCloseContents); |
| toolWindow.setContentFactory(factory); |
| if (bean.icon != null && toolWindow.getIcon() == null) { |
| Icon icon = IconLoader.findIcon(bean.icon, factory.getClass()); |
| if (icon == null) { |
| try { |
| icon = IconLoader.getIcon(bean.icon); |
| } |
| catch (Exception ignored) { |
| } |
| } |
| toolWindow.setIcon(icon); |
| } |
| |
| WindowInfoImpl info = getInfo(bean.id); |
| if (!info.isSplit() && bean.secondary && !info.wasRead()) { |
| toolWindow.setSplitMode(bean.secondary, null); |
| } |
| |
| final ActionCallback activation = toolWindow.setActivation(new ActionCallback()); |
| |
| final DumbAwareRunnable runnable = new DumbAwareRunnable() { |
| @Override |
| public void run() { |
| if (toolWindow.isDisposed()) return; |
| |
| toolWindow.ensureContentInitialized(); |
| activation.setDone(); |
| } |
| }; |
| if (ApplicationManager.getApplication().isUnitTestMode()) { |
| runnable.run(); |
| } |
| else { |
| UiNotifyConnector.doWhenFirstShown(label, new Runnable() { |
| @Override |
| public void run() { |
| ApplicationManager.getApplication().invokeLater(runnable); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void projectClosed() { |
| final ArrayList<FinalizableCommand> commandsList = new ArrayList<FinalizableCommand>(); |
| final String[] ids = getToolWindowIds(); |
| |
| // Remove ToolWindowsPane |
| if (myFrame != null) { |
| ((IdeRootPane)myFrame.getRootPane()).setToolWindowsPane(null); |
| myWindowManager.releaseFrame(myFrame); |
| } |
| appendUpdateToolWindowsPaneCmd(commandsList); |
| |
| // Hide all tool windows |
| |
| for (final String id : ids) { |
| deactivateToolWindowImpl(id, true, commandsList); |
| } |
| |
| // Remove editor component |
| |
| final JComponent editorComponent = FileEditorManagerEx.getInstanceEx(myProject).getComponent(); |
| myEditorComponentFocusWatcher.deinstall(editorComponent); |
| appendSetEditorComponentCmd(null, commandsList); |
| execute(commandsList); |
| } |
| |
| @Override |
| public void addToolWindowManagerListener(@NotNull ToolWindowManagerListener l) { |
| myDispatcher.addListener(l); |
| } |
| |
| @Override |
| public void removeToolWindowManagerListener(@NotNull ToolWindowManagerListener l) { |
| myDispatcher.removeListener(l); |
| } |
| |
| /** |
| * This is helper method. It delegated its functionality to the WindowManager. |
| * Before delegating it fires state changed. |
| */ |
| public void execute(@NotNull List<FinalizableCommand> commandList) { |
| for (FinalizableCommand each : commandList) { |
| if (each.willChangeState()) { |
| fireStateChanged(); |
| break; |
| } |
| } |
| |
| for (FinalizableCommand each : commandList) { |
| each.beforeExecute(this); |
| } |
| myWindowManager.getCommandProcessor().execute(commandList, myProject.getDisposed()); |
| } |
| |
| @Override |
| public void activateEditorComponent() { |
| activateEditorComponent(true); |
| } |
| |
| private void activateEditorComponent(final boolean forced) { |
| activateEditorComponent(forced, false); //TODO[kirillk]: runnable in activateEditorComponent(boolean, boolean) never runs |
| } |
| |
| private void activateEditorComponent(final boolean forced, boolean now) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("enter: activateEditorComponent()"); |
| } |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| |
| final ExpirableRunnable runnable = new ExpirableRunnable.ForProject(myProject) { |
| @Override |
| public void run() { |
| final ArrayList<FinalizableCommand> commandList = new ArrayList<FinalizableCommand>(); |
| activateEditorComponentImpl(getSplittersFromFocus(), commandList, forced); |
| execute(commandList); |
| } |
| }; |
| if (now) { |
| if (!runnable.isExpired()) { |
| runnable.run(); |
| } |
| } |
| else { |
| final FocusRequestor requestor = getFocusManager().getFurtherRequestor(); |
| getFocusManager().doWhenFocusSettlesDown(new ExpirableRunnable.ForProject(myProject) { |
| @Override |
| public void run() { |
| requestor.requestFocus(new FocusCommand() { |
| @NotNull |
| @Override |
| public ActionCallback run() { |
| runnable.run(); |
| return new ActionCallback.Done(); |
| } |
| }.setExpirable(runnable), forced); |
| } |
| }); |
| } |
| } |
| |
| private EditorsSplitters getSplittersFromFocus() { |
| return FileEditorManagerEx.getInstanceEx(myProject).getSplitters(); |
| } |
| |
| private void activateEditorComponentImpl(EditorsSplitters splitters, |
| List<FinalizableCommand> commandList, |
| final boolean forced) { |
| final String active = getActiveToolWindowId(); |
| // Now we have to request focus into most recent focused editor |
| appendRequestFocusInEditorComponentCmd(splitters, commandList, forced).doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| final ArrayList<FinalizableCommand> postExecute = new ArrayList<FinalizableCommand>(); |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("editor activated"); |
| } |
| deactivateWindows(postExecute, null); |
| myActiveStack.clear(); |
| |
| execute(postExecute); |
| } |
| }).doWhenRejected(new Runnable() { |
| @Override |
| public void run() { |
| if (forced) { |
| getFocusManagerImpl(myProject).requestFocus(new FocusCommand() { |
| @NotNull |
| @Override |
| public ActionCallback run() { |
| final ArrayList<FinalizableCommand> cmds = new ArrayList<FinalizableCommand>(); |
| |
| final WindowInfoImpl toReactivate = getInfo(active); |
| final boolean reactivateLastActive = toReactivate != null && !isToHide(toReactivate); |
| deactivateWindows(cmds, reactivateLastActive ? active : null); |
| execute(cmds); |
| |
| if (reactivateLastActive) { |
| activateToolWindow(active, false, true); |
| } |
| else { |
| if (active != null) { |
| myActiveStack.remove(active, false); |
| } |
| |
| if (!myActiveStack.isEmpty()) { |
| activateToolWindow(myActiveStack.peek(), false, true); |
| } |
| } |
| return new ActionCallback.Done(); |
| } |
| }, false); |
| } |
| } |
| }); |
| } |
| |
| private void deactivateWindows(final ArrayList<FinalizableCommand> postExecute, @Nullable String idToIgnore) { |
| final WindowInfoImpl[] infos = myLayout.getInfos(); |
| for (final WindowInfoImpl info : infos) { |
| final boolean shouldHide = isToHide(info); |
| if (idToIgnore != null && idToIgnore.equals(info.getId())) { |
| continue; |
| } |
| deactivateToolWindowImpl(info.getId(), shouldHide, postExecute); |
| } |
| } |
| |
| private boolean isToHide(final WindowInfoImpl info) { |
| return (info.isAutoHide() || info.isSliding()) && !(info.isFloating() && hasModalChild(info)); |
| } |
| |
| /** |
| * Helper method. It makes window visible, activates it and request focus into the tool window. |
| * But it doesn't deactivate other tool windows. Use <code>prepareForActivation</code> method to |
| * deactivates other tool windows. |
| * |
| * @param dirtyMode if <code>true</code> then all UI operations are performed in "dirty" mode. |
| * It means that UI isn't validated and repainted just after each add/remove operation. |
| * @see ToolWindowManagerImpl#prepareForActivation |
| */ |
| private void showAndActivate(final String id, |
| final boolean dirtyMode, |
| List<FinalizableCommand> commandsList, |
| boolean autoFocusContents, |
| boolean forcedFocusRequest) { |
| if (!getToolWindow(id).isAvailable()) { |
| return; |
| } |
| // show activated |
| final WindowInfoImpl info = getInfo(id); |
| boolean toApplyInfo = false; |
| if (!info.isActive()) { |
| info.setActive(true); |
| toApplyInfo = true; |
| } |
| showToolWindowImpl(id, dirtyMode, commandsList); |
| |
| // activate |
| if (toApplyInfo) { |
| appendApplyWindowInfoCmd(info, commandsList); |
| myActiveStack.push(id); |
| } |
| |
| if (autoFocusContents && ApplicationManager.getApplication().isActive()) { |
| appendRequestFocusInToolWindowCmd(id, commandsList, forcedFocusRequest); |
| } |
| } |
| |
| void activateToolWindow(final String id, boolean forced, boolean autoFocusContents) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("enter: activateToolWindow(" + id + ")"); |
| } |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| checkId(id); |
| if (DumbService.getInstance(myProject).isDumb() && !myDumbAwareIds.contains(id)) { |
| return; |
| } |
| |
| final ArrayList<FinalizableCommand> commandList = new ArrayList<FinalizableCommand>(); |
| activateToolWindowImpl(id, commandList, forced, autoFocusContents); |
| execute(commandList); |
| } |
| |
| private void activateToolWindowImpl(final String id, |
| List<FinalizableCommand> commandList, |
| boolean forced, |
| boolean autoFocusContents) { |
| if (!FocusManagerImpl.getInstance().isUnforcedRequestAllowed() && !forced) return; |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("enter: activateToolWindowImpl(" + id + ")"); |
| } |
| if (!getToolWindow(id).isAvailable()) { |
| // Tool window can be "logically" active but not focused. For example, |
| // when the user switched to another application. So we just need to bring |
| // tool window's window to front. |
| final InternalDecorator decorator = getInternalDecorator(id); |
| if (!decorator.hasFocus() && autoFocusContents) { |
| appendRequestFocusInToolWindowCmd(id, commandList, forced); |
| } |
| return; |
| } |
| prepareForActivation(id, commandList); |
| showAndActivate(id, false, commandList, autoFocusContents, forced); |
| } |
| |
| /** |
| * Checkes whether the specified <code>id</code> defines installed tool |
| * window. If it's not then throws <code>IllegalStateException</code>. |
| * |
| * @throws IllegalStateException if tool window isn't installed. |
| */ |
| private void checkId(final String id) { |
| if (!myLayout.isToolWindowRegistered(id)) { |
| throw new IllegalStateException("window with id=\"" + id + "\" isn't registered"); |
| } |
| } |
| |
| /** |
| * Helper method. It deactivates (and hides) window with specified <code>id</code>. |
| * |
| * @param id <code>id</code> of the tool window to be deactivated. |
| * @param shouldHide if <code>true</code> then also hides specified tool window. |
| */ |
| private void deactivateToolWindowImpl(final String id, final boolean shouldHide, final List<FinalizableCommand> commandsList) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("enter: deactivateToolWindowImpl(" + id + "," + shouldHide + ")"); |
| } |
| final WindowInfoImpl info = getInfo(id); |
| if (shouldHide && info.isVisible()) { |
| info.setVisible(false); |
| if (info.isFloating()) { |
| appendRemoveFloatingDecoratorCmd(info, commandsList); |
| } |
| else { // docked and sliding windows |
| appendRemoveDecoratorCmd(id, false, commandsList); |
| } |
| } |
| info.setActive(false); |
| appendApplyWindowInfoCmd(info, commandsList); |
| } |
| |
| @NotNull |
| @Override |
| public String[] getToolWindowIds() { |
| final WindowInfoImpl[] infos = myLayout.getInfos(); |
| final String[] ids = ArrayUtil.newStringArray(infos.length); |
| for (int i = 0; i < infos.length; i++) { |
| ids[i] = infos[i].getId(); |
| } |
| return ids; |
| } |
| |
| @Override |
| public String getActiveToolWindowId() { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| return myLayout.getActiveId(); |
| } |
| |
| @Override |
| public String getLastActiveToolWindowId() { |
| return getLastActiveToolWindowId(null); |
| } |
| |
| @Override |
| @Nullable |
| public String getLastActiveToolWindowId(@Nullable Condition<JComponent> condition) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| String lastActiveToolWindowId = null; |
| for (int i = 0; i < myActiveStack.getPersistentSize(); i++) { |
| final String id = myActiveStack.peekPersistent(i); |
| final ToolWindow toolWindow = getToolWindow(id); |
| LOG.assertTrue(toolWindow != null); |
| if (toolWindow.isAvailable()) { |
| if (condition == null || condition.value(toolWindow.getComponent())) { |
| lastActiveToolWindowId = id; |
| break; |
| } |
| } |
| } |
| return lastActiveToolWindowId; |
| } |
| |
| /** |
| * @return floating decorator for the tool window with specified <code>ID</code>. |
| */ |
| private FloatingDecorator getFloatingDecorator(final String id) { |
| return myId2FloatingDecorator.get(id); |
| } |
| |
| /** |
| * @return internal decorator for the tool window with specified <code>ID</code>. |
| */ |
| private InternalDecorator getInternalDecorator(final String id) { |
| return myId2InternalDecorator.get(id); |
| } |
| |
| /** |
| * @return tool button for the window with specified <code>ID</code>. |
| */ |
| private StripeButton getStripeButton(final String id) { |
| return myId2StripeButton.get(id); |
| } |
| |
| /** |
| * @return info for the tool window with specified <code>ID</code>. |
| */ |
| private WindowInfoImpl getInfo(final String id) { |
| return myLayout.getInfo(id, true); |
| } |
| |
| @Override |
| public List<String> getIdsOn(@NotNull final ToolWindowAnchor anchor) { |
| return myLayout.getVisibleIdsOn(anchor, this); |
| } |
| |
| @Override |
| public ToolWindow getToolWindow(final String id) { |
| if (!myLayout.isToolWindowRegistered(id)) { |
| return null; |
| } |
| InternalDecorator decorator = getInternalDecorator(id); |
| return decorator != null ? decorator.getToolWindow() : null; |
| } |
| |
| void showToolWindow(final String id) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("enter: showToolWindow(" + id + ")"); |
| } |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| final ArrayList<FinalizableCommand> commandList = new ArrayList<FinalizableCommand>(); |
| showToolWindowImpl(id, false, commandList); |
| execute(commandList); |
| } |
| |
| @Override |
| public void hideToolWindow(@NotNull final String id, final boolean hideSide) { |
| hideToolWindow(id, hideSide, true); |
| } |
| |
| public void hideToolWindow(final String id, final boolean hideSide, final boolean moveFocus) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| checkId(id); |
| final WindowInfoImpl info = getInfo(id); |
| if (!info.isVisible()) return; |
| final ArrayList<FinalizableCommand> commandList = new ArrayList<FinalizableCommand>(); |
| final boolean wasActive = info.isActive(); |
| |
| // hide and deactivate |
| |
| deactivateToolWindowImpl(id, true, commandList); |
| |
| if (hideSide && !info.isFloating()) { |
| final List<String> ids = myLayout.getVisibleIdsOn(info.getAnchor(), this); |
| for (String each : ids) { |
| myActiveStack.remove(each, true); |
| } |
| |
| |
| while (!mySideStack.isEmpty(info.getAnchor())) { |
| mySideStack.pop(info.getAnchor()); |
| } |
| |
| final String[] all = getToolWindowIds(); |
| for (String eachId : all) { |
| final WindowInfoImpl eachInfo = getInfo(eachId); |
| if (eachInfo.isVisible() && eachInfo.getAnchor() == info.getAnchor()) { |
| deactivateToolWindowImpl(eachId, true, commandList); |
| } |
| } |
| |
| activateEditorComponentImpl(getSplittersFromFocus(), commandList, true); |
| } |
| else if (isStackEnabled()) { |
| |
| // first of all we have to find tool window that was located at the same side and |
| // was hidden. |
| |
| WindowInfoImpl info2 = null; |
| while (!mySideStack.isEmpty(info.getAnchor())) { |
| final WindowInfoImpl storedInfo = mySideStack.pop(info.getAnchor()); |
| if (storedInfo.isSplit() != info.isSplit()) { |
| continue; |
| } |
| |
| final WindowInfoImpl currentInfo = getInfo(storedInfo.getId()); |
| LOG.assertTrue(currentInfo != null); |
| // SideStack contains copies of real WindowInfos. It means that |
| // these stored infos can be invalid. The following loop removes invalid WindowInfos. |
| if (storedInfo.getAnchor() == currentInfo.getAnchor() && |
| storedInfo.getType() == currentInfo.getType() && |
| storedInfo.isAutoHide() == currentInfo.isAutoHide()) { |
| info2 = storedInfo; |
| break; |
| } |
| } |
| if (info2 != null) { |
| showToolWindowImpl(info2.getId(), false, commandList); |
| } |
| |
| // If we hide currently active tool window then we should activate the previous |
| // one which is located in the tool window stack. |
| // Activate another tool window if no active tool window exists and |
| // window stack is enabled. |
| |
| myActiveStack.remove(id, false); // hidden window should be at the top of stack |
| |
| if (wasActive && moveFocus) { |
| if (myActiveStack.isEmpty()) { |
| if (hasOpenEditorFiles()) { |
| activateEditorComponentImpl(getSplittersFromFocus(), commandList, false); |
| } |
| else { |
| focusToolWinowByDefault(id); |
| } |
| } |
| else { |
| final String toBeActivatedId = myActiveStack.pop(); |
| if (toBeActivatedId != null && (getInfo(toBeActivatedId).isVisible() || isStackEnabled())) { |
| activateToolWindowImpl(toBeActivatedId, commandList, false, true); |
| } |
| else { |
| focusToolWinowByDefault(id); |
| } |
| } |
| } |
| } |
| //todo[kb] it's just a temporary solution due a number of focus issues in JDK 7 |
| if (SystemInfo.isJavaVersionAtLeast("1.7")) { |
| if (hasOpenEditorFiles()) { |
| activateEditorComponentImpl(getSplittersFromFocus(), commandList, false); |
| } |
| else { |
| focusToolWinowByDefault(id); |
| } |
| } |
| |
| execute(commandList); |
| } |
| |
| private static boolean isStackEnabled() { |
| return Registry.is("ide.enable.toolwindow.stack"); |
| } |
| |
| /** |
| * @param dirtyMode if <code>true</code> then all UI operations are performed in dirty mode. |
| */ |
| private void showToolWindowImpl(final String id, final boolean dirtyMode, final List<FinalizableCommand> commandsList) { |
| final WindowInfoImpl toBeShownInfo = getInfo(id); |
| if (toBeShownInfo.isVisible() || !getToolWindow(id).isAvailable()) { |
| return; |
| } |
| |
| if (DumbService.getInstance(myProject).isDumb() && !myDumbAwareIds.contains(id)) { |
| return; |
| } |
| |
| toBeShownInfo.setVisible(true); |
| final InternalDecorator decorator = getInternalDecorator(id); |
| |
| if (toBeShownInfo.isFloating()) { |
| commandsList.add(new AddFloatingDecoratorCmd(decorator, toBeShownInfo)); |
| } |
| else { // docked and sliding windows |
| |
| // If there is tool window on the same side then we have to hide it, i.e. |
| // clear place for tool window to be shown. |
| // |
| // We store WindowInfo of hidden tool window in the SideStack (if the tool window |
| // is docked and not auto-hide one). Therefore it's possible to restore the |
| // hidden tool window when showing tool window will be closed. |
| |
| final WindowInfoImpl[] infos = myLayout.getInfos(); |
| for (final WindowInfoImpl info : infos) { |
| if (id.equals(info.getId())) { |
| continue; |
| } |
| if (info.isVisible() && |
| info.getType() == toBeShownInfo.getType() && |
| info.getAnchor() == toBeShownInfo.getAnchor() && |
| info.isSplit() == toBeShownInfo.isSplit()) { |
| // hide and deactivate tool window |
| info.setVisible(false); |
| appendRemoveDecoratorCmd(info.getId(), false, commandsList); |
| if (info.isActive()) { |
| info.setActive(false); |
| } |
| appendApplyWindowInfoCmd(info, commandsList); |
| // store WindowInfo into the SideStack |
| if (info.isDocked() && !info.isAutoHide()) { |
| mySideStack.push(info); |
| } |
| } |
| } |
| appendAddDecoratorCmd(decorator, toBeShownInfo, dirtyMode, commandsList); |
| |
| // Remove tool window from the SideStack. |
| |
| mySideStack.remove(id); |
| } |
| |
| appendApplyWindowInfoCmd(toBeShownInfo, commandsList); |
| } |
| |
| @NotNull |
| @Override |
| public ToolWindow registerToolWindow(@NotNull final String id, |
| @NotNull final JComponent component, |
| @NotNull final ToolWindowAnchor anchor) { |
| return registerToolWindow(id, component, anchor, false); |
| } |
| |
| @NotNull |
| @Override |
| public ToolWindow registerToolWindow(@NotNull final String id, |
| @NotNull JComponent component, |
| @NotNull ToolWindowAnchor anchor, |
| @NotNull Disposable parentDisposable) { |
| return registerToolWindow(id, component, anchor, parentDisposable, false, false); |
| } |
| |
| @NotNull |
| @Override |
| public ToolWindow registerToolWindow(@NotNull String id, |
| @NotNull JComponent component, |
| @NotNull ToolWindowAnchor anchor, |
| @NotNull Disposable parentDisposable, |
| boolean canWorkInDumbMode) { |
| return registerToolWindow(id, component, anchor, parentDisposable, canWorkInDumbMode, false); |
| } |
| |
| @NotNull |
| @Override |
| public ToolWindow registerToolWindow(@NotNull final String id, |
| @NotNull JComponent component, |
| @NotNull ToolWindowAnchor anchor, |
| @NotNull Disposable parentDisposable, |
| boolean canWorkInDumbMode, boolean canCloseContents) { |
| return registerDisposable(id, parentDisposable, registerToolWindow(id, component, anchor, false, canCloseContents, canWorkInDumbMode)); |
| } |
| |
| @NotNull |
| private ToolWindow registerToolWindow(@NotNull final String id, |
| @NotNull final JComponent component, |
| @NotNull final ToolWindowAnchor anchor, |
| boolean canWorkInDumbMode) { |
| return registerToolWindow(id, component, anchor, false, false, canWorkInDumbMode); |
| } |
| |
| @NotNull |
| @Override |
| public ToolWindow registerToolWindow(@NotNull final String id, final boolean canCloseContent, @NotNull final ToolWindowAnchor anchor) { |
| return registerToolWindow(id, null, anchor, false, canCloseContent, false); |
| } |
| |
| @NotNull |
| @Override |
| public ToolWindow registerToolWindow(@NotNull final String id, |
| final boolean canCloseContent, |
| @NotNull final ToolWindowAnchor anchor, |
| final boolean secondary) { |
| return registerToolWindow(id, null, anchor, secondary, canCloseContent, false); |
| } |
| |
| |
| @NotNull |
| @Override |
| public ToolWindow registerToolWindow(@NotNull final String id, |
| final boolean canCloseContent, |
| @NotNull final ToolWindowAnchor anchor, |
| @NotNull final Disposable parentDisposable, |
| final boolean canWorkInDumbMode) { |
| ToolWindow window = registerToolWindow(id, null, anchor, false, canCloseContent, canWorkInDumbMode); |
| return registerDisposable(id, parentDisposable, window); |
| } |
| |
| @NotNull |
| private ToolWindow registerToolWindow(@NotNull final String id, |
| @Nullable final JComponent component, |
| @NotNull final ToolWindowAnchor anchor, |
| boolean sideTool, |
| boolean canCloseContent, |
| final boolean canWorkInDumbMode) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("enter: installToolWindow(" + id + "," + component + "," + anchor + "\")"); |
| } |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| if (myLayout.isToolWindowRegistered(id)) { |
| throw new IllegalArgumentException("window with id=\"" + id + "\" is already registered"); |
| } |
| |
| final WindowInfoImpl info = myLayout.register(id, anchor, sideTool); |
| final boolean wasActive = info.isActive(); |
| final boolean wasVisible = info.isVisible(); |
| info.setActive(false); |
| info.setVisible(false); |
| |
| // Create decorator |
| |
| ToolWindowImpl toolWindow = new ToolWindowImpl(this, id, canCloseContent, component); |
| InternalDecorator decorator = new InternalDecorator(myProject, info.copy(), toolWindow); |
| ActivateToolWindowAction.ensureToolWindowActionRegistered(toolWindow); |
| myId2InternalDecorator.put(id, decorator); |
| decorator.addInternalDecoratorListener(myInternalDecoratorListener); |
| toolWindow.addPropertyChangeListener(myToolWindowPropertyChangeListener); |
| myId2FocusWatcher.put(id, new ToolWindowFocusWatcher(toolWindow)); |
| |
| // Create and show tool button |
| |
| final StripeButton button = new StripeButton(decorator, myToolWindowsPane); |
| myId2StripeButton.put(id, button); |
| List<FinalizableCommand> commandsList = new ArrayList<FinalizableCommand>(); |
| appendAddButtonCmd(button, info, commandsList); |
| |
| if (canWorkInDumbMode) { |
| myDumbAwareIds.add(id); |
| } |
| else if (DumbService.getInstance(getProject()).isDumb()) { |
| button.setEnabled(false); |
| } |
| |
| // If preloaded info is visible or active then we have to show/activate the installed |
| // tool window. This step has sense only for windows which are not in the autohide |
| // mode. But if tool window was active but its mode doen't allow to activate it again |
| // (for example, tool window is in autohide mode) then we just activate editor component. |
| |
| if (!info.isAutoHide() && (info.isDocked() || info.isFloating())) { |
| if (wasActive) { |
| activateToolWindowImpl(info.getId(), commandsList, true, true); |
| } |
| else if (wasVisible) { |
| showToolWindowImpl(info.getId(), false, commandsList); |
| } |
| } |
| else if (wasActive) { // tool window was active but it cannot be activate again |
| activateEditorComponentImpl(getSplittersFromFocus(), commandsList, true); |
| } |
| |
| execute(commandsList); |
| fireToolWindowRegistered(id); |
| return toolWindow; |
| } |
| |
| @NotNull |
| private ToolWindow registerDisposable(@NotNull final String id, @NotNull final Disposable parentDisposable, @NotNull ToolWindow window) { |
| Disposer.register(parentDisposable, new Disposable() { |
| @Override |
| public void dispose() { |
| unregisterToolWindow(id); |
| } |
| }); |
| return window; |
| } |
| |
| @Override |
| public void unregisterToolWindow(@NotNull final String id) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("enter: unregisterToolWindow(" + id + ")"); |
| } |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| if (!myLayout.isToolWindowRegistered(id)) { |
| return; |
| } |
| |
| final WindowInfoImpl info = getInfo(id); |
| final ToolWindowEx toolWindow = (ToolWindowEx)getToolWindow(id); |
| // Save recent appearance of tool window |
| myLayout.unregister(id); |
| // Remove decorator and tool button from the screen |
| final ArrayList<FinalizableCommand> commandsList = new ArrayList<FinalizableCommand>(); |
| if (info.isVisible()) { |
| info.setVisible(false); |
| if (info.isFloating()) { |
| appendRemoveFloatingDecoratorCmd(info, commandsList); |
| } |
| else { // floating and sliding windows |
| appendRemoveDecoratorCmd(id, false, commandsList); |
| } |
| } |
| appendRemoveButtonCmd(id, commandsList); |
| appendApplyWindowInfoCmd(info, commandsList); |
| execute(commandsList); |
| // Remove all references on tool window and save its last properties |
| toolWindow.removePropertyChangeListener(myToolWindowPropertyChangeListener); |
| myActiveStack.remove(id, true); |
| mySideStack.remove(id); |
| // Destroy stripe button |
| final StripeButton button = getStripeButton(id); |
| button.dispose(); |
| myId2StripeButton.remove(id); |
| // |
| ToolWindowFocusWatcher watcher = (ToolWindowFocusWatcher)myId2FocusWatcher.remove(id); |
| watcher.deinstall(); |
| |
| // Destroy decorator |
| final InternalDecorator decorator = getInternalDecorator(id); |
| decorator.dispose(); |
| decorator.removeInternalDecoratorListener(myInternalDecoratorListener); |
| myId2InternalDecorator.remove(id); |
| } |
| |
| @Override |
| public DesktopLayout getLayout() { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| return myLayout; |
| } |
| |
| @Override |
| public void setLayoutToRestoreLater(DesktopLayout layout) { |
| myLayoutToRestoreLater = layout; |
| } |
| |
| @Override |
| public DesktopLayout getLayoutToRestoreLater() { |
| return myLayoutToRestoreLater; |
| } |
| |
| @Override |
| public void setLayout(@NotNull final DesktopLayout layout) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| final ArrayList<FinalizableCommand> commandList = new ArrayList<FinalizableCommand>(); |
| // hide tool window that are invisible in new layout |
| final WindowInfoImpl[] currentInfos = myLayout.getInfos(); |
| for (final WindowInfoImpl currentInfo : currentInfos) { |
| final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false); |
| if (info == null) { |
| continue; |
| } |
| if (currentInfo.isVisible() && !info.isVisible()) { |
| deactivateToolWindowImpl(currentInfo.getId(), true, commandList); |
| } |
| } |
| // change anchor of tool windows |
| for (final WindowInfoImpl currentInfo : currentInfos) { |
| final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false); |
| if (info == null) { |
| continue; |
| } |
| if (currentInfo.getAnchor() != info.getAnchor() || currentInfo.getOrder() != info.getOrder()) { |
| setToolWindowAnchorImpl(currentInfo.getId(), info.getAnchor(), info.getOrder(), commandList); |
| } |
| } |
| // change types of tool windows |
| for (final WindowInfoImpl currentInfo : currentInfos) { |
| final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false); |
| if (info == null) { |
| continue; |
| } |
| if (currentInfo.getType() != info.getType()) { |
| setToolWindowTypeImpl(currentInfo.getId(), info.getType(), commandList); |
| } |
| } |
| // change auto-hide state |
| for (final WindowInfoImpl currentInfo : currentInfos) { |
| final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false); |
| if (info == null) { |
| continue; |
| } |
| if (currentInfo.isAutoHide() != info.isAutoHide()) { |
| setToolWindowAutoHideImpl(currentInfo.getId(), info.isAutoHide(), commandList); |
| } |
| } |
| // restore visibility |
| for (final WindowInfoImpl currentInfo : currentInfos) { |
| final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false); |
| if (info == null) { |
| continue; |
| } |
| if (info.isVisible()) { |
| showToolWindowImpl(currentInfo.getId(), false, commandList); |
| } |
| } |
| // if there is no any active tool window and editor is also inactive |
| // then activate editor |
| if (!myEditorWasActive && getActiveToolWindowId() == null) { |
| activateEditorComponentImpl(getSplittersFromFocus(), commandList, true); |
| } |
| execute(commandList); |
| } |
| |
| @Override |
| public void invokeLater(@NotNull final Runnable runnable) { |
| List<FinalizableCommand> commandList = new ArrayList<FinalizableCommand>(); |
| commandList.add(new InvokeLaterCmd(runnable, myWindowManager.getCommandProcessor())); |
| execute(commandList); |
| } |
| |
| @NotNull |
| @Override |
| public IdeFocusManager getFocusManager() { |
| return IdeFocusManager.getInstance(myProject); |
| } |
| |
| @Override |
| public boolean canShowNotification(@NotNull final String toolWindowId) { |
| if (!Arrays.asList(getToolWindowIds()).contains(toolWindowId)) { |
| return false; |
| } |
| final Stripe stripe = myToolWindowsPane.getStripeFor(toolWindowId); |
| return stripe.getButtonFor(toolWindowId) != null; |
| } |
| |
| @Override |
| public void notifyByBalloon(@NotNull final String toolWindowId, @NotNull final MessageType type, @NotNull final String htmlBody) { |
| notifyByBalloon(toolWindowId, type, htmlBody, null, null); |
| } |
| |
| @Override |
| public void notifyByBalloon(@NotNull final String toolWindowId, |
| @NotNull final MessageType type, |
| @NotNull final String text, |
| @Nullable final Icon icon, |
| @Nullable final HyperlinkListener listener) { |
| checkId(toolWindowId); |
| |
| |
| Balloon existing = myWindow2Balloon.get(toolWindowId); |
| if (existing != null) { |
| existing.hide(); |
| } |
| |
| final Stripe stripe = myToolWindowsPane.getStripeFor(toolWindowId); |
| final ToolWindowImpl window = getInternalDecorator(toolWindowId).getToolWindow(); |
| if (!window.isAvailable()) { |
| window.setPlaceholderMode(true); |
| stripe.updatePresentation(); |
| stripe.revalidate(); |
| stripe.repaint(); |
| } |
| |
| final ToolWindowAnchor anchor = getInfo(toolWindowId).getAnchor(); |
| final Ref<Balloon.Position> position = Ref.create(Balloon.Position.below); |
| if (ToolWindowAnchor.TOP == anchor) { |
| position.set(Balloon.Position.below); |
| } |
| else if (ToolWindowAnchor.BOTTOM == anchor) { |
| position.set(Balloon.Position.above); |
| } |
| else if (ToolWindowAnchor.LEFT == anchor) { |
| position.set(Balloon.Position.atRight); |
| } |
| else if (ToolWindowAnchor.RIGHT == anchor) { |
| position.set(Balloon.Position.atLeft); |
| } |
| |
| final BalloonHyperlinkListener listenerWrapper = new BalloonHyperlinkListener(listener); |
| final Balloon balloon = |
| JBPopupFactory.getInstance() |
| .createHtmlTextBalloonBuilder(text.replace("\n", "<br>"), icon, type.getPopupBackground(), listenerWrapper) |
| .setHideOnClickOutside(false).setHideOnFrameResize(false).createBalloon(); |
| FrameStateManager.getInstance().getApplicationActive().doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| final Alarm alarm = new Alarm(); |
| alarm.addRequest(new Runnable() { |
| @Override |
| public void run() { |
| ((BalloonImpl)balloon).setHideOnClickOutside(true); |
| Disposer.dispose(alarm); |
| } |
| }, 100); |
| } |
| }); |
| listenerWrapper.myBalloon = balloon; |
| myWindow2Balloon.put(toolWindowId, balloon); |
| Disposer.register(balloon, new Disposable() { |
| @Override |
| public void dispose() { |
| window.setPlaceholderMode(false); |
| stripe.updatePresentation(); |
| stripe.revalidate(); |
| stripe.repaint(); |
| myWindow2Balloon.remove(toolWindowId); |
| } |
| }); |
| Disposer.register(getProject(), balloon); |
| |
| execute(new ArrayList<FinalizableCommand>(Arrays.<FinalizableCommand>asList(new FinalizableCommand(null) { |
| @Override |
| public void run() { |
| final StripeButton button = stripe.getButtonFor(toolWindowId); |
| LOG.assertTrue(button != null, "Button was not found, popup won't be shown. Toolwindow id: " + |
| toolWindowId + |
| ", message: " + |
| text + |
| ", message type: " + |
| type); |
| if (button == null) return; |
| |
| final Runnable show = new Runnable() { |
| @Override |
| public void run() { |
| if (button.isShowing()) { |
| PositionTracker<Balloon> tracker = new PositionTracker<Balloon>(button) { |
| @Override |
| @Nullable |
| public RelativePoint recalculateLocation(Balloon object) { |
| Stripe twStripe = myToolWindowsPane.getStripeFor(toolWindowId); |
| StripeButton twButton = twStripe != null ? twStripe.getButtonFor(toolWindowId) : null; |
| |
| if (twButton == null) return null; |
| |
| if (getToolWindow(toolWindowId).getAnchor() != anchor) { |
| object.hide(); |
| return null; |
| } |
| |
| final Point point = new Point(twButton.getBounds().width / 2, twButton.getHeight() / 2 - 2); |
| return new RelativePoint(twButton, point); |
| } |
| }; |
| if (!balloon.isDisposed()) { |
| balloon.show(tracker, position.get()); |
| } |
| } |
| else { |
| final Rectangle bounds = myToolWindowsPane.getBounds(); |
| final Point target = UIUtil.getCenterPoint(bounds, new Dimension(1, 1)); |
| if (ToolWindowAnchor.TOP == anchor) { |
| target.y = 0; |
| } |
| else if (ToolWindowAnchor.BOTTOM == anchor) { |
| target.y = bounds.height - 3; |
| } |
| else if (ToolWindowAnchor.LEFT == anchor) { |
| target.x = 0; |
| } |
| else if (ToolWindowAnchor.RIGHT == anchor) { |
| target.x = bounds.width; |
| } |
| if (!balloon.isDisposed()) { |
| balloon.show(new RelativePoint(myToolWindowsPane, target), position.get()); |
| } |
| } |
| } |
| }; |
| |
| if (!button.isValid()) { |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| show.run(); |
| } |
| }); |
| } |
| else { |
| show.run(); |
| } |
| } |
| }))); |
| } |
| |
| @Override |
| public Balloon getToolWindowBalloon(String id) { |
| return myWindow2Balloon.get(id); |
| } |
| |
| @Override |
| public boolean isEditorComponentActive() { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| |
| Component owner = getFocusManager().getFocusOwner(); |
| EditorsSplitters splitters = UIUtil.getParentOfType(EditorsSplitters.class, owner); |
| return splitters != null; |
| } |
| |
| ToolWindowAnchor getToolWindowAnchor(final String id) { |
| checkId(id); |
| return getInfo(id).getAnchor(); |
| } |
| |
| void setToolWindowAnchor(final String id, final ToolWindowAnchor anchor) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| setToolWindowAnchor(id, anchor, -1); |
| } |
| |
| void setToolWindowAnchor(final String id, final ToolWindowAnchor anchor, final int order) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| final ArrayList<FinalizableCommand> commandList = new ArrayList<FinalizableCommand>(); |
| setToolWindowAnchorImpl(id, anchor, order, commandList); |
| execute(commandList); |
| } |
| |
| private void setToolWindowAnchorImpl(final String id, |
| final ToolWindowAnchor anchor, |
| final int order, |
| final ArrayList<FinalizableCommand> commandsList) { |
| checkId(id); |
| final WindowInfoImpl info = getInfo(id); |
| if (anchor == info.getAnchor() && order == info.getOrder()) { |
| return; |
| } |
| // if tool window isn't visible or only order number is changed then just remove/add stripe button |
| if (!info.isVisible() || anchor == info.getAnchor() || info.isFloating()) { |
| appendRemoveButtonCmd(id, commandsList); |
| myLayout.setAnchor(id, anchor, order); |
| // update infos for all window. Actually we have to update only infos affected by |
| // setAnchor method |
| final WindowInfoImpl[] infos = myLayout.getInfos(); |
| for (WindowInfoImpl info1 : infos) { |
| appendApplyWindowInfoCmd(info1, commandsList); |
| } |
| appendAddButtonCmd(getStripeButton(id), info, commandsList); |
| } |
| else { // for docked and sliding windows we have to move buttons and window's decorators |
| info.setVisible(false); |
| appendRemoveDecoratorCmd(id, false, commandsList); |
| appendRemoveButtonCmd(id, commandsList); |
| myLayout.setAnchor(id, anchor, order); |
| // update infos for all window. Actually we have to update only infos affected by |
| // setAnchor method |
| final WindowInfoImpl[] infos = myLayout.getInfos(); |
| for (WindowInfoImpl info1 : infos) { |
| appendApplyWindowInfoCmd(info1, commandsList); |
| } |
| appendAddButtonCmd(getStripeButton(id), info, commandsList); |
| showToolWindowImpl(id, false, commandsList); |
| if (info.isActive()) { |
| appendRequestFocusInToolWindowCmd(id, commandsList, true); |
| } |
| } |
| } |
| |
| boolean isSplitMode(String id) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| checkId(id); |
| return getInfo(id).isSplit(); |
| } |
| |
| ToolWindowContentUiType getContentUiType(String id) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| checkId(id); |
| return getInfo(id).getContentUiType(); |
| } |
| |
| void setSideTool(String id, boolean isSide) { |
| final ArrayList<FinalizableCommand> commandList = new ArrayList<FinalizableCommand>(); |
| setSplitModeImpl(id, isSide, commandList); |
| execute(commandList); |
| } |
| |
| public void setContentUiType(String id, ToolWindowContentUiType type) { |
| final ArrayList<FinalizableCommand> commandList = new ArrayList<FinalizableCommand>(); |
| checkId(id); |
| WindowInfoImpl info = getInfo(id); |
| info.setContentUiType(type); |
| appendApplyWindowInfoCmd(info, commandList); |
| execute(commandList); |
| } |
| |
| void setSideToolAndAnchor(String id, ToolWindowAnchor anchor, int order, boolean isSide) { |
| final ArrayList<FinalizableCommand> commandList = new ArrayList<FinalizableCommand>(); |
| setToolWindowAnchor(id, anchor, order); |
| setSplitModeImpl(id, isSide, commandList); |
| execute(commandList); |
| } |
| |
| private void setSplitModeImpl(final String id, final boolean isSplit, final ArrayList<FinalizableCommand> commandList) { |
| checkId(id); |
| final WindowInfoImpl info = getInfo(id); |
| if (isSplit == info.isSplit()) { |
| return; |
| } |
| |
| myLayout.setSplitMode(id, isSplit); |
| |
| boolean wasActive = info.isActive(); |
| if (wasActive) { |
| deactivateToolWindowImpl(id, true, commandList); |
| } |
| final WindowInfoImpl[] infos = myLayout.getInfos(); |
| for (WindowInfoImpl info1 : infos) { |
| appendApplyWindowInfoCmd(info1, commandList); |
| } |
| if (wasActive) { |
| activateToolWindowImpl(id, commandList, true, true); |
| } |
| commandList.add(myToolWindowsPane.createUpdateButtonPositionCmd(id, myWindowManager.getCommandProcessor())); |
| } |
| |
| ToolWindowType getToolWindowInternalType(final String id) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| checkId(id); |
| return getInfo(id).getInternalType(); |
| } |
| |
| ToolWindowType getToolWindowType(final String id) { |
| checkId(id); |
| return getInfo(id).getType(); |
| } |
| |
| private void fireToolWindowRegistered(final String id) { |
| myDispatcher.getMulticaster().toolWindowRegistered(id); |
| } |
| |
| private void fireStateChanged() { |
| myDispatcher.getMulticaster().stateChanged(); |
| } |
| |
| boolean isToolWindowActive(final String id) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| checkId(id); |
| return getInfo(id).isActive(); |
| } |
| |
| boolean isToolWindowAutoHide(final String id) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| checkId(id); |
| return getInfo(id).isAutoHide(); |
| } |
| |
| boolean isToolWindowVisible(final String id) { |
| checkId(id); |
| return getInfo(id).isVisible(); |
| } |
| |
| void setToolWindowAutoHide(final String id, final boolean autoHide) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| final ArrayList<FinalizableCommand> commandList = new ArrayList<FinalizableCommand>(); |
| setToolWindowAutoHideImpl(id, autoHide, commandList); |
| execute(commandList); |
| } |
| |
| private void setToolWindowAutoHideImpl(final String id, final boolean autoHide, final ArrayList<FinalizableCommand> commandsList) { |
| checkId(id); |
| final WindowInfoImpl info = getInfo(id); |
| if (info.isAutoHide() == autoHide) { |
| return; |
| } |
| info.setAutoHide(autoHide); |
| appendApplyWindowInfoCmd(info, commandsList); |
| if (info.isVisible()) { |
| prepareForActivation(id, commandsList); |
| showAndActivate(id, false, commandsList, true, true); |
| } |
| } |
| |
| void setToolWindowType(final String id, final ToolWindowType type) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| final ArrayList<FinalizableCommand> commandList = new ArrayList<FinalizableCommand>(); |
| setToolWindowTypeImpl(id, type, commandList); |
| execute(commandList); |
| } |
| |
| private void setToolWindowTypeImpl(final String id, final ToolWindowType type, final ArrayList<FinalizableCommand> commandsList) { |
| checkId(id); |
| final WindowInfoImpl info = getInfo(id); |
| if (info.getType() == type) { |
| return; |
| } |
| if (info.isVisible()) { |
| final boolean dirtyMode = info.isDocked() || info.isSliding(); |
| info.setVisible(false); |
| if (info.isFloating()) { |
| appendRemoveFloatingDecoratorCmd(info, commandsList); |
| } |
| else { // docked and sliding windows |
| appendRemoveDecoratorCmd(id, dirtyMode, commandsList); |
| } |
| info.setType(type); |
| appendApplyWindowInfoCmd(info, commandsList); |
| prepareForActivation(id, commandsList); |
| showAndActivate(id, dirtyMode, commandsList, true, true); |
| appendUpdateToolWindowsPaneCmd(commandsList); |
| } |
| else { |
| info.setType(type); |
| appendApplyWindowInfoCmd(info, commandsList); |
| } |
| } |
| |
| private void appendApplyWindowInfoCmd(final WindowInfoImpl info, final List<FinalizableCommand> commandsList) { |
| final StripeButton button = getStripeButton(info.getId()); |
| final InternalDecorator decorator = getInternalDecorator(info.getId()); |
| commandsList.add(new ApplyWindowInfoCmd(info, button, decorator, myWindowManager.getCommandProcessor())); |
| } |
| |
| /** |
| * @see com.intellij.openapi.wm.impl.ToolWindowsPane#createAddDecoratorCmd |
| */ |
| private void appendAddDecoratorCmd(final InternalDecorator decorator, |
| final WindowInfoImpl info, |
| final boolean dirtyMode, |
| final List<FinalizableCommand> commandsList) { |
| final CommandProcessor commandProcessor = myWindowManager.getCommandProcessor(); |
| final FinalizableCommand command = myToolWindowsPane.createAddDecoratorCmd(decorator, info, dirtyMode, commandProcessor); |
| commandsList.add(command); |
| } |
| |
| /** |
| * @see com.intellij.openapi.wm.impl.ToolWindowsPane#createRemoveDecoratorCmd |
| */ |
| private void appendRemoveDecoratorCmd(final String id, final boolean dirtyMode, final List<FinalizableCommand> commandsList) { |
| final FinalizableCommand command = myToolWindowsPane.createRemoveDecoratorCmd(id, dirtyMode, myWindowManager.getCommandProcessor()); |
| commandsList.add(command); |
| } |
| |
| private void appendRemoveFloatingDecoratorCmd(final WindowInfoImpl info, final List<FinalizableCommand> commandsList) { |
| final RemoveFloatingDecoratorCmd command = new RemoveFloatingDecoratorCmd(info); |
| commandsList.add(command); |
| } |
| |
| /** |
| * @see com.intellij.openapi.wm.impl.ToolWindowsPane#createAddButtonCmd |
| */ |
| private void appendAddButtonCmd(final StripeButton button, final WindowInfoImpl info, final List<FinalizableCommand> commandsList) { |
| final Comparator<StripeButton> comparator = myLayout.comparator(info.getAnchor()); |
| final CommandProcessor commandProcessor = myWindowManager.getCommandProcessor(); |
| final FinalizableCommand command = myToolWindowsPane.createAddButtonCmd(button, info, comparator, commandProcessor); |
| commandsList.add(command); |
| } |
| |
| /** |
| * @see com.intellij.openapi.wm.impl.ToolWindowsPane#createAddButtonCmd |
| */ |
| private void appendRemoveButtonCmd(final String id, final List<FinalizableCommand> commandsList) { |
| final FinalizableCommand command = myToolWindowsPane.createRemoveButtonCmd(id, myWindowManager.getCommandProcessor()); |
| commandsList.add(command); |
| } |
| |
| private ActionCallback appendRequestFocusInEditorComponentCmd(EditorsSplitters splitters, |
| List<FinalizableCommand> commandList, |
| boolean forced) { |
| if (myProject.isDisposed()) return new ActionCallback.Done(); |
| final CommandProcessor commandProcessor = myWindowManager.getCommandProcessor(); |
| final RequestFocusInEditorComponentCmd command = |
| new RequestFocusInEditorComponentCmd(splitters, getFocusManager(), commandProcessor, forced); |
| commandList.add(command); |
| return command.getDoneCallback(); |
| } |
| |
| private void appendRequestFocusInToolWindowCmd(final String id, List<FinalizableCommand> commandList, boolean forced) { |
| final ToolWindowImpl toolWindow = (ToolWindowImpl)getToolWindow(id); |
| final FocusWatcher focusWatcher = myId2FocusWatcher.get(id); |
| commandList |
| .add(new RequestFocusInToolWindowCmd(getFocusManager(), toolWindow, focusWatcher, myWindowManager.getCommandProcessor(), forced)); |
| } |
| |
| /** |
| * @see com.intellij.openapi.wm.impl.ToolWindowsPane#createSetEditorComponentCmd |
| */ |
| public void appendSetEditorComponentCmd(@Nullable final JComponent component, final List<FinalizableCommand> commandsList) { |
| final CommandProcessor commandProcessor = myWindowManager.getCommandProcessor(); |
| final FinalizableCommand command = myToolWindowsPane.createSetEditorComponentCmd(component, commandProcessor); |
| commandsList.add(command); |
| } |
| |
| private void appendUpdateToolWindowsPaneCmd(final List<FinalizableCommand> commandsList) { |
| final JRootPane rootPane = myFrame.getRootPane(); |
| final FinalizableCommand command = new UpdateRootPaneCmd(rootPane, myWindowManager.getCommandProcessor()); |
| commandsList.add(command); |
| } |
| |
| /** |
| * @return <code>true</code> if tool window with the specified <code>id</code> |
| * is floating and has modal showing child dialog. Such windows should not be closed |
| * when auto-hide windows are gone. |
| */ |
| private boolean hasModalChild(final WindowInfoImpl info) { |
| if (!info.isVisible() || !info.isFloating()) { |
| return false; |
| } |
| final FloatingDecorator decorator = getFloatingDecorator(info.getId()); |
| LOG.assertTrue(decorator != null); |
| return isModalOrHasModalChild(decorator); |
| } |
| |
| private static boolean isModalOrHasModalChild(final Window window) { |
| if (window instanceof Dialog) { |
| final Dialog dialog = (Dialog)window; |
| if (dialog.isModal() && dialog.isShowing()) { |
| return true; |
| } |
| final Window[] ownedWindows = dialog.getOwnedWindows(); |
| for (int i = ownedWindows.length - 1; i >= 0; i--) { |
| if (isModalOrHasModalChild(ownedWindows[i])) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Helper method. It deactivates all tool windows excepting the tool window |
| * which should be activated. |
| */ |
| private void prepareForActivation(final String id, final List<FinalizableCommand> commandList) { |
| final WindowInfoImpl toBeActivatedInfo = getInfo(id); |
| final WindowInfoImpl[] infos = myLayout.getInfos(); |
| for (final WindowInfoImpl info : infos) { |
| if (id.equals(info.getId())) { |
| continue; |
| } |
| if (toBeActivatedInfo.isDocked() || toBeActivatedInfo.isSliding()) { |
| deactivateToolWindowImpl(info.getId(), info.isAutoHide() || info.isSliding(), commandList); |
| } |
| else { // floating window is being activated |
| deactivateToolWindowImpl(info.getId(), info.isAutoHide() && info.isFloating() && !hasModalChild(info), commandList); |
| } |
| } |
| } |
| |
| @Override |
| public void clearSideStack() { |
| mySideStack.clear(); |
| } |
| |
| @Override |
| public void readExternal(final Element element) { |
| for (final Object o : element.getChildren()) { |
| final Element e = (Element)o; |
| if (EDITOR_ELEMENT.equals(e.getName())) { |
| myEditorWasActive = Boolean.valueOf(e.getAttributeValue(ACTIVE_ATTR_VALUE)).booleanValue(); |
| } |
| else if (DesktopLayout.TAG.equals(e.getName())) { // read layout of tool windows |
| myLayout.readExternal(e); |
| } |
| else if (LAYOUT_TO_RESTORE.equals(e.getName())) { |
| myLayoutToRestoreLater = new DesktopLayout(); |
| myLayoutToRestoreLater.readExternal(e); |
| } |
| } |
| } |
| |
| @Override |
| public void writeExternal(final Element element) { |
| if (myFrame == null) { |
| // do nothing if the project was not opened |
| return; |
| } |
| final String[] ids = getToolWindowIds(); |
| |
| // Update size of all open floating windows. See SCR #18439 |
| for (final String id : ids) { |
| final WindowInfoImpl info = getInfo(id); |
| if (info.isVisible()) { |
| final InternalDecorator decorator = getInternalDecorator(id); |
| LOG.assertTrue(decorator != null); |
| decorator.fireResized(); |
| } |
| } |
| |
| // Save frame's bounds |
| final Rectangle frameBounds = myFrame.getBounds(); |
| final Element frameElement = new Element(FRAME_ELEMENT); |
| element.addContent(frameElement); |
| frameElement.setAttribute(X_ATTR, Integer.toString(frameBounds.x)); |
| frameElement.setAttribute(Y_ATTR, Integer.toString(frameBounds.y)); |
| frameElement.setAttribute(WIDTH_ATTR, Integer.toString(frameBounds.width)); |
| frameElement.setAttribute(HEIGHT_ATTR, Integer.toString(frameBounds.height)); |
| frameElement.setAttribute(EXTENDED_STATE_ATTR, Integer.toString(myFrame.getExtendedState())); |
| // Save whether editor is active or not |
| final Element editorElement = new Element(EDITOR_ELEMENT); |
| editorElement.setAttribute(ACTIVE_ATTR_VALUE, isEditorComponentActive() ? Boolean.TRUE.toString() : Boolean.FALSE.toString()); |
| element.addContent(editorElement); |
| // Save layout of tool windows |
| final Element layoutElement = new Element(DesktopLayout.TAG); |
| element.addContent(layoutElement); |
| myLayout.writeExternal(layoutElement); |
| if (myLayoutToRestoreLater != null) { |
| Element layoutToRestoreElement = new Element(LAYOUT_TO_RESTORE); |
| element.addContent(layoutToRestoreElement); |
| myLayoutToRestoreLater.writeExternal(layoutToRestoreElement); |
| } |
| } |
| |
| public void setDefaultState(@NotNull final ToolWindowImpl toolWindow, |
| @Nullable final ToolWindowAnchor anchor, |
| @Nullable final ToolWindowType type, |
| @Nullable final Rectangle floatingBounds) { |
| |
| final WindowInfoImpl info = getInfo(toolWindow.getId()); |
| if (info.wasRead()) return; |
| |
| if (floatingBounds != null) { |
| info.setFloatingBounds(floatingBounds); |
| } |
| |
| if (anchor != null) { |
| toolWindow.setAnchor(anchor, null); |
| } |
| |
| if (type != null) { |
| toolWindow.setType(type, null); |
| } |
| } |
| |
| public void setDefaultContentUiType(ToolWindowImpl toolWindow, ToolWindowContentUiType type) { |
| final WindowInfoImpl info = getInfo(toolWindow.getId()); |
| if (info.wasRead()) return; |
| toolWindow.setContentUiType(type, null); |
| } |
| |
| |
| public void stretchWidth(ToolWindowImpl toolWindow, int value) { |
| myToolWindowsPane.stretchWidth(toolWindow, value); |
| } |
| |
| public void stretchHeight(ToolWindowImpl toolWindow, int value) { |
| myToolWindowsPane.stretchHeight(toolWindow, value); |
| } |
| |
| private static class BalloonHyperlinkListener implements HyperlinkListener { |
| private Balloon myBalloon; |
| private final HyperlinkListener myListener; |
| |
| public BalloonHyperlinkListener(HyperlinkListener listener) { |
| myListener = listener; |
| } |
| |
| @Override |
| public void hyperlinkUpdate(HyperlinkEvent e) { |
| if (myBalloon != null && e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { |
| myBalloon.hide(); |
| } |
| if (myListener != null) { |
| myListener.hyperlinkUpdate(e); |
| } |
| } |
| } |
| |
| |
| /** |
| * This command creates and shows <code>FloatingDecorator</code>. |
| */ |
| private final class AddFloatingDecoratorCmd extends FinalizableCommand { |
| private final FloatingDecorator myFloatingDecorator; |
| |
| /** |
| * Creates floating decorator for specified floating decorator. |
| */ |
| private AddFloatingDecoratorCmd(final InternalDecorator decorator, final WindowInfoImpl info) { |
| super(myWindowManager.getCommandProcessor()); |
| myFloatingDecorator = new FloatingDecorator(myFrame, info.copy(), decorator); |
| myId2FloatingDecorator.put(info.getId(), myFloatingDecorator); |
| final Rectangle bounds = info.getFloatingBounds(); |
| if (bounds != null && |
| bounds.width > 0 && |
| bounds.height > 0 && |
| myWindowManager.isInsideScreenBounds(bounds.x, bounds.y, bounds.width)) { |
| myFloatingDecorator.setBounds(bounds); |
| } |
| else { // place new frame at the center of main frame if there are no floating bounds |
| Dimension size = decorator.getSize(); |
| if (size.width == 0 || size.height == 0) { |
| size = decorator.getPreferredSize(); |
| } |
| myFloatingDecorator.setSize(size); |
| myFloatingDecorator.setLocationRelativeTo(myFrame); |
| } |
| } |
| |
| @Override |
| public void run() { |
| try { |
| myFloatingDecorator.show(); |
| } |
| finally { |
| finish(); |
| } |
| } |
| } |
| |
| /** |
| * This command hides and destroys floating decorator for tool window |
| * with specified <code>ID</code>. |
| */ |
| private final class RemoveFloatingDecoratorCmd extends FinalizableCommand { |
| private final FloatingDecorator myFloatingDecorator; |
| |
| private RemoveFloatingDecoratorCmd(final WindowInfoImpl info) { |
| super(myWindowManager.getCommandProcessor()); |
| myFloatingDecorator = getFloatingDecorator(info.getId()); |
| myId2FloatingDecorator.remove(info.getId()); |
| info.setFloatingBounds(myFloatingDecorator.getBounds()); |
| } |
| |
| @Override |
| public void run() { |
| try { |
| if (Patches.SPECIAL_INPUT_METHOD_PROCESSING) { |
| myFloatingDecorator.remove(myFloatingDecorator.getRootPane()); |
| } |
| myFloatingDecorator.dispose(); |
| } |
| finally { |
| finish(); |
| } |
| } |
| |
| @Override |
| @Nullable |
| public Condition getExpireCondition() { |
| return ApplicationManager.getApplication().getDisposed(); |
| } |
| } |
| |
| private final class EditorComponentFocusWatcher extends FocusWatcher { |
| @Override |
| protected void focusedComponentChanged(final Component component, final AWTEvent cause) { |
| if (myWindowManager.getCommandProcessor().getCommandCount() > 0 || component == null) { |
| return; |
| } |
| final KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager(); |
| final Component owner = mgr.getFocusOwner(); |
| |
| IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(new ExpirableRunnable.ForProject(myProject) { |
| @Override |
| public void run() { |
| if (mgr.getFocusOwner() == owner) { |
| activateEditorComponent(false); |
| } |
| } |
| }); |
| } |
| } |
| |
| |
| /** |
| * Notifies window manager about focus traversal in tool window |
| */ |
| private final class ToolWindowFocusWatcher extends FocusWatcher { |
| private final String myId; |
| private final ToolWindowImpl myToolWindow; |
| |
| |
| private ToolWindowFocusWatcher(final ToolWindowImpl toolWindow) { |
| myId = toolWindow.getId(); |
| install(toolWindow.getComponent()); |
| myToolWindow = toolWindow; |
| } |
| |
| public void deinstall() { |
| deinstall(myToolWindow.getComponent()); |
| } |
| |
| @Override |
| protected boolean isFocusedComponentChangeValid(final Component comp, final AWTEvent cause) { |
| return myWindowManager.getCommandProcessor().getCommandCount() == 0 && comp != null; |
| } |
| |
| @Override |
| protected void focusedComponentChanged(final Component component, final AWTEvent cause) { |
| if (myWindowManager.getCommandProcessor().getCommandCount() > 0 || component == null) { |
| return; |
| } |
| final WindowInfoImpl info = getInfo(myId); |
| //getFocusManagerImpl(myProject)..cancelAllRequests(); |
| |
| if (!info.isActive()) { |
| getFocusManagerImpl(myProject).doWhenFocusSettlesDown(new EdtRunnable() { |
| @Override |
| public void runEdt() { |
| if (!myLayout.isToolWindowRegistered(myId)) return; |
| activateToolWindow(myId, false, false); |
| } |
| }); |
| } |
| } |
| } |
| |
| /** |
| * Spies on IdeToolWindow properties and applies them to the window |
| * state. |
| */ |
| private final class MyToolWindowPropertyChangeListener implements PropertyChangeListener { |
| @Override |
| public void propertyChange(final PropertyChangeEvent e) { |
| ToolWindowImpl toolWindow = (ToolWindowImpl)e.getSource(); |
| if (ToolWindowEx.PROP_AVAILABLE.equals(e.getPropertyName())) { |
| final WindowInfoImpl info = getInfo(toolWindow.getId()); |
| if (!toolWindow.isAvailable() && info.isVisible()) { |
| hideToolWindow(toolWindow.getId(), false); |
| } |
| } |
| StripeButton button = myId2StripeButton.get(toolWindow.getId()); |
| if (button != null) button.updatePresentation(); |
| ActivateToolWindowAction.updateToolWindowActionPresentation(toolWindow); |
| } |
| } |
| |
| /** |
| * Translates events from InternalDecorator into ToolWindowManager method invocations. |
| */ |
| private final class MyInternalDecoratorListener implements InternalDecoratorListener { |
| @Override |
| public void anchorChanged(final InternalDecorator source, final ToolWindowAnchor anchor) { |
| setToolWindowAnchor(source.getToolWindow().getId(), anchor); |
| } |
| |
| @Override |
| public void autoHideChanged(final InternalDecorator source, final boolean autoHide) { |
| setToolWindowAutoHide(source.getToolWindow().getId(), autoHide); |
| } |
| |
| @Override |
| public void hidden(final InternalDecorator source) { |
| hideToolWindow(source.getToolWindow().getId(), false); |
| } |
| |
| @Override |
| public void hiddenSide(final InternalDecorator source) { |
| hideToolWindow(source.getToolWindow().getId(), true); |
| } |
| |
| @Override |
| public void contentUiTypeChanges(InternalDecorator source, ToolWindowContentUiType type) { |
| setContentUiType(source.getToolWindow().getId(), type); |
| } |
| |
| /** |
| * Handles event from decorator and modify weight/floating bounds of the |
| * tool window depending on decoration type. |
| */ |
| @Override |
| public void resized(final InternalDecorator source) { |
| if (!source.isShowing()) { |
| return; // do not recalculate the tool window size if it is not yet shown (and, therefore, has 0,0,0,0 bounds) |
| } |
| |
| final WindowInfoImpl info = getInfo(source.getToolWindow().getId()); |
| InternalDecorator another = null; |
| if (info.isFloating()) { |
| final Window owner = SwingUtilities.getWindowAncestor(source); |
| if (owner != null) { |
| info.setFloatingBounds(owner.getBounds()); |
| } |
| } |
| else { // docked and sliding windows |
| ToolWindowAnchor anchor = info.getAnchor(); |
| if (source.getParent() instanceof Splitter) { |
| float sizeInSplit = anchor.isSplitVertically() ? source.getHeight() : source.getWidth(); |
| Splitter splitter = (Splitter)source.getParent(); |
| if (splitter.getSecondComponent() == source) { |
| sizeInSplit += splitter.getDividerWidth(); |
| another = (InternalDecorator)splitter.getFirstComponent(); |
| } |
| else { |
| another = (InternalDecorator)splitter.getSecondComponent(); |
| } |
| if (anchor.isSplitVertically()) { |
| info.setSideWeight(sizeInSplit / (float)splitter.getHeight()); |
| } |
| else { |
| info.setSideWeight(sizeInSplit / (float)splitter.getWidth()); |
| } |
| } |
| |
| float paneWeight = anchor.isHorizontal() |
| ? (float)source.getHeight() / (float)myToolWindowsPane.getMyLayeredPane().getHeight() |
| : (float)source.getWidth() / (float)myToolWindowsPane.getMyLayeredPane().getWidth(); |
| info.setWeight(paneWeight); |
| if (another != null && anchor.isSplitVertically()) { |
| paneWeight = anchor.isHorizontal() |
| ? (float)another.getHeight() / (float)myToolWindowsPane.getMyLayeredPane().getHeight() |
| : (float)another.getWidth() / (float)myToolWindowsPane.getMyLayeredPane().getWidth(); |
| another.getWindowInfo().setWeight(paneWeight); |
| } |
| } |
| } |
| |
| @Override |
| public void activated(final InternalDecorator source) { |
| activateToolWindow(source.getToolWindow().getId(), true, true); |
| } |
| |
| @Override |
| public void typeChanged(final InternalDecorator source, final ToolWindowType type) { |
| setToolWindowType(source.getToolWindow().getId(), type); |
| } |
| |
| @Override |
| public void sideStatusChanged(final InternalDecorator source, final boolean isSideTool) { |
| setSideTool(source.getToolWindow().getId(), isSideTool); |
| } |
| } |
| |
| private void updateComponentTreeUI() { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| final WindowInfoImpl[] infos = myLayout.getInfos(); |
| for (WindowInfoImpl info : infos) { |
| // the main goal is to update hidden TW components because they are not in the hierarchy |
| // and will not be updated automatically but unfortunately the visibility of a TW may change |
| // during the same actionPerformed() so we can't optimize and have to process all of them |
| IJSwingUtilities.updateComponentTreeUI(getInternalDecorator(info.getId())); |
| } |
| } |
| |
| private final class MyUIManagerPropertyChangeListener implements PropertyChangeListener { |
| @Override |
| public void propertyChange(final PropertyChangeEvent e) { |
| updateComponentTreeUI(); |
| } |
| } |
| |
| private final class MyLafManagerListener implements LafManagerListener { |
| @Override |
| public void lookAndFeelChanged(final LafManager source) { |
| updateComponentTreeUI(); |
| } |
| } |
| |
| |
| @Override |
| @NotNull |
| public String getComponentName() { |
| return "ToolWindowManager"; |
| } |
| |
| @NotNull |
| public ActionCallback requestDefaultFocus(final boolean forced) { |
| return getFocusManagerImpl(myProject).requestFocus(new FocusCommand() { |
| @NotNull |
| @Override |
| public ActionCallback run() { |
| return processDefaultFocusRequest(forced); |
| } |
| }, forced); |
| } |
| |
| private void focusToolWinowByDefault(@Nullable String idToIngore) { |
| String toFocus = null; |
| |
| for (String each : myActiveStack.getStack()) { |
| if (idToIngore != null && idToIngore.equalsIgnoreCase(each)) continue; |
| |
| if (getInfo(each).isVisible()) { |
| toFocus = each; |
| break; |
| } |
| } |
| |
| if (toFocus == null) { |
| for (String each : myActiveStack.getPersistentStack()) { |
| if (idToIngore != null && idToIngore.equalsIgnoreCase(each)) continue; |
| |
| if (getInfo(each).isVisible()) { |
| toFocus = each; |
| break; |
| } |
| } |
| } |
| |
| if (toFocus != null) { |
| activateToolWindow(toFocus, false, true); |
| } |
| } |
| |
| private ActionCallback processDefaultFocusRequest(boolean forced) { |
| if (ModalityState.NON_MODAL.equals(ModalityState.current())) { |
| final String activeId = getActiveToolWindowId(); |
| if (isEditorComponentActive() || activeId == null || getToolWindow(activeId) == null) { |
| activateEditorComponent(forced, true); |
| } |
| else { |
| activateToolWindow(activeId, forced, true); |
| } |
| |
| return new ActionCallback.Done(); |
| } |
| Window activeWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); |
| if (activeWindow != null) { |
| JRootPane root = null; |
| if (activeWindow instanceof JDialog) { |
| root = ((JDialog)activeWindow).getRootPane(); |
| } |
| else if (activeWindow instanceof JFrame) { |
| root = ((JFrame)activeWindow).getRootPane(); |
| } |
| |
| if (root != null) { |
| JComponent toFocus = IdeFocusTraversalPolicy.getPreferredFocusedComponent(root); |
| if (toFocus != null) { |
| if (DialogWrapper.findInstance(toFocus) != null) { |
| return new ActionCallback.Done(); //IDEA-80929 |
| } |
| return IdeFocusManager.findInstanceByComponent(toFocus).requestFocus(toFocus, forced); |
| } |
| } |
| } |
| return new ActionCallback.Rejected(); |
| } |
| |
| |
| /** |
| * Delegate method for compatibility with older versions of IDEA |
| */ |
| @NotNull |
| public ActionCallback requestFocus(@NotNull Component c, boolean forced) { |
| return IdeFocusManager.getInstance(myProject).requestFocus(c, forced); |
| } |
| |
| @NotNull |
| public ActionCallback requestFocus(@NotNull FocusCommand command, boolean forced) { |
| return IdeFocusManager.getInstance(myProject).requestFocus(command, forced); |
| } |
| |
| public void doWhenFocusSettlesDown(@NotNull Runnable runnable) { |
| IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(runnable); |
| } |
| |
| public boolean dispatch(@NotNull KeyEvent e) { |
| return IdeFocusManager.getInstance(myProject).dispatch(e); |
| } |
| |
| public Expirable getTimestamp(boolean trackOnlyForcedCommands) { |
| return IdeFocusManager.getInstance(myProject).getTimestamp(trackOnlyForcedCommands); |
| } |
| } |