[platform] mock update server
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / wm / impl / ToolWindowManagerImpl.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi.wm.impl;
17
18 import com.intellij.ide.FrameStateManager;
19 import com.intellij.ide.IdeEventQueue;
20 import com.intellij.ide.actions.ActivateToolWindowAction;
21 import com.intellij.ide.ui.LafManager;
22 import com.intellij.ide.ui.LafManagerListener;
23 import com.intellij.internal.statistic.UsageTrigger;
24 import com.intellij.openapi.Disposable;
25 import com.intellij.openapi.actionSystem.*;
26 import com.intellij.openapi.actionSystem.ex.AnActionListener;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.application.ModalityState;
29 import com.intellij.openapi.components.ProjectComponent;
30 import com.intellij.openapi.diagnostic.Logger;
31 import com.intellij.openapi.editor.impl.EditorComponentImpl;
32 import com.intellij.openapi.extensions.Extensions;
33 import com.intellij.openapi.fileEditor.FileEditorManager;
34 import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
35 import com.intellij.openapi.fileEditor.FileEditorManagerListener;
36 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
37 import com.intellij.openapi.fileEditor.impl.EditorsSplitters;
38 import com.intellij.openapi.keymap.Keymap;
39 import com.intellij.openapi.keymap.KeymapManager;
40 import com.intellij.openapi.project.DumbAware;
41 import com.intellij.openapi.project.DumbAwareRunnable;
42 import com.intellij.openapi.project.DumbService;
43 import com.intellij.openapi.project.Project;
44 import com.intellij.openapi.startup.StartupActivity;
45 import com.intellij.openapi.ui.DialogWrapper;
46 import com.intellij.openapi.ui.MessageType;
47 import com.intellij.openapi.ui.Splitter;
48 import com.intellij.openapi.ui.popup.Balloon;
49 import com.intellij.openapi.ui.popup.JBPopupFactory;
50 import com.intellij.openapi.util.*;
51 import com.intellij.openapi.util.registry.Registry;
52 import com.intellij.openapi.vfs.VirtualFile;
53 import com.intellij.openapi.wm.*;
54 import com.intellij.openapi.wm.ex.*;
55 import com.intellij.openapi.wm.impl.commands.*;
56 import com.intellij.ui.BalloonImpl;
57 import com.intellij.ui.ColorUtil;
58 import com.intellij.ui.awt.RelativePoint;
59 import com.intellij.ui.switcher.QuickAccessSettings;
60 import com.intellij.ui.switcher.SwitchManager;
61 import com.intellij.util.*;
62 import com.intellij.util.containers.HashMap;
63 import com.intellij.util.ui.EdtInvocationManager;
64 import com.intellij.util.ui.PositionTracker;
65 import com.intellij.util.ui.UIUtil;
66 import com.intellij.util.ui.update.UiNotifyConnector;
67 import org.jdom.Element;
68 import org.jetbrains.annotations.NonNls;
69 import org.jetbrains.annotations.NotNull;
70 import org.jetbrains.annotations.Nullable;
71
72 import javax.swing.*;
73 import javax.swing.event.HyperlinkEvent;
74 import javax.swing.event.HyperlinkListener;
75 import java.awt.*;
76 import java.awt.event.FocusEvent;
77 import java.awt.event.InputEvent;
78 import java.awt.event.KeyEvent;
79 import java.awt.event.WindowEvent;
80 import java.beans.PropertyChangeEvent;
81 import java.beans.PropertyChangeListener;
82 import java.util.*;
83 import java.util.List;
84
85 /**
86  * @author Anton Katilin
87  * @author Vladimir Kondratyev
88  */
89 public final class ToolWindowManagerImpl extends ToolWindowManagerEx implements ProjectComponent, JDOMExternalizable {
90   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.ToolWindowManagerImpl");
91
92   private final Project myProject;
93   private final WindowManagerEx myWindowManager;
94   private final EventDispatcher<ToolWindowManagerListener> myDispatcher = EventDispatcher.create(ToolWindowManagerListener.class);
95   private final DesktopLayout myLayout = new DesktopLayout();
96   private final Map<String, InternalDecorator> myId2InternalDecorator = new HashMap<>();
97   private final Map<String, FloatingDecorator> myId2FloatingDecorator = new HashMap<>();
98   private final Map<String, WindowedDecorator> myId2WindowedDecorator = new HashMap<>();
99   private final Map<String, StripeButton> myId2StripeButton = new HashMap<>();
100   private final Map<String, FocusWatcher> myId2FocusWatcher = new HashMap<>();
101
102   private final EditorComponentFocusWatcher myEditorComponentFocusWatcher = new EditorComponentFocusWatcher();
103   private final MyToolWindowPropertyChangeListener myToolWindowPropertyChangeListener = new MyToolWindowPropertyChangeListener();
104   private final InternalDecoratorListener myInternalDecoratorListener = new MyInternalDecoratorListener();
105
106   private boolean myEditorWasActive;
107
108   private final ActiveStack myActiveStack = new ActiveStack();
109   private final SideStack mySideStack = new SideStack();
110
111   private ToolWindowsPane myToolWindowsPane;
112   private IdeFrameImpl myFrame;
113   private DesktopLayout myLayoutToRestoreLater;
114   @NonNls private static final String EDITOR_ELEMENT = "editor";
115   @NonNls private static final String ACTIVE_ATTR_VALUE = "active";
116   @NonNls private static final String FRAME_ELEMENT = "frame";
117   @NonNls private static final String X_ATTR = "x";
118   @NonNls private static final String Y_ATTR = "y";
119   @NonNls private static final String WIDTH_ATTR = "width";
120   @NonNls private static final String HEIGHT_ATTR = "height";
121   @NonNls private static final String EXTENDED_STATE_ATTR = "extended-state";
122   @NonNls private static final String LAYOUT_TO_RESTORE = "layout-to-restore";
123
124   private final FileEditorManager myFileEditorManager;
125   private final LafManager myLafManager;
126   private final Map<String, Balloon> myWindow2Balloon = new HashMap<>();
127
128   private KeyState myCurrentState = KeyState.waiting;
129   private final Alarm myWaiterForSecondPress = new Alarm();
130   private final Runnable mySecondPressRunnable = () -> {
131     if (myCurrentState != KeyState.hold) {
132       resetHoldState();
133     }
134   };
135   private final PropertyChangeListener myFocusListener;
136
137   boolean isToolWindowRegistered(@NotNull String id) {
138     return myLayout.isToolWindowRegistered(id);
139   }
140
141   private enum KeyState {
142     waiting, pressed, released, hold
143   }
144
145   private final Alarm myUpdateHeadersAlarm = new Alarm();
146
147   /**
148    * invoked by reflection
149    */
150   public ToolWindowManagerImpl(final Project project,
151                                final WindowManagerEx windowManagerEx,
152                                final FileEditorManager fem,
153                                final ActionManager actionManager,
154                                final LafManager lafManager) {
155     myProject = project;
156     myWindowManager = windowManagerEx;
157     myFileEditorManager = fem;
158     myLafManager = lafManager;
159
160     if (!project.isDefault()) {
161       actionManager.addAnActionListener(new AnActionListener() {
162         @Override
163         public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
164           if (myCurrentState != KeyState.hold) {
165             resetHoldState();
166           }
167         }
168
169         @Override
170         public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
171         }
172
173         @Override
174         public void beforeEditorTyping(char c, DataContext dataContext) {
175         }
176       }, project);
177     }
178
179     myLayout.copyFrom(windowManagerEx.getLayout());
180
181     project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() {
182       @Override
183       public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
184       }
185
186       @Override
187       public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
188         getFocusManagerImpl(myProject).doWhenFocusSettlesDown(new ExpirableRunnable.ForProject(myProject) {
189           @Override
190           public void run() {
191             if (!hasOpenEditorFiles()) {
192               focusToolWinowByDefault(null);
193             }
194           }
195         });
196       }
197
198       @Override
199       public void selectionChanged(@NotNull FileEditorManagerEvent event) {
200       }
201     });
202
203     myFocusListener = evt -> {
204       if ("focusOwner".equals(evt.getPropertyName())) {
205         myUpdateHeadersAlarm.cancelAllRequests();
206         myUpdateHeadersAlarm.addRequest(this::updateToolWindowHeaders, 50);
207       }
208     };
209     KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(myFocusListener);
210   }
211
212
213   private void updateToolWindowHeaders() {
214     getFocusManager().doWhenFocusSettlesDown(new ExpirableRunnable.ForProject(myProject) {
215       @Override
216       public void run() {
217         WindowInfoImpl[] infos = myLayout.getInfos();
218         for (WindowInfoImpl each : infos) {
219           if (each.isVisible()) {
220             ToolWindow tw = getToolWindow(each.getId());
221             if (tw instanceof ToolWindowImpl) {
222               InternalDecorator decorator = ((ToolWindowImpl)tw).getDecorator();
223               if (decorator != null) {
224                 decorator.repaint();
225               }
226             }
227           }
228         }
229       }
230     });
231   }
232
233   public boolean dispatchKeyEvent(@NotNull KeyEvent e) {
234     if (e.getKeyCode() != KeyEvent.VK_CONTROL &&
235         e.getKeyCode() != KeyEvent.VK_ALT &&
236         e.getKeyCode() != KeyEvent.VK_SHIFT &&
237         e.getKeyCode() != KeyEvent.VK_META) {
238       if (e.getModifiers() == 0) {
239         resetHoldState();
240       }
241       return false;
242     }
243     if (e.getID() != KeyEvent.KEY_PRESSED && e.getID() != KeyEvent.KEY_RELEASED) return false;
244
245     Component parent = UIUtil.findUltimateParent(e.getComponent());
246     if (parent instanceof IdeFrame) {
247       if (((IdeFrame)parent).getProject() != myProject) {
248         resetHoldState();
249         return false;
250       }
251     }
252
253     Set<Integer> vks = getActivateToolWindowVKs();
254
255     if (vks.isEmpty()) {
256       resetHoldState();
257       return false;
258     }
259
260     if (vks.contains(e.getKeyCode())) {
261       boolean pressed = e.getID() == KeyEvent.KEY_PRESSED;
262       int modifiers = e.getModifiers();
263
264       int mouseMask = InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON2_DOWN_MASK | InputEvent.BUTTON3_DOWN_MASK;
265       if ((e.getModifiersEx() & mouseMask) == 0) {
266         if (SwitchManager.areAllModifiersPressed(modifiers, vks) || !pressed) {
267           processState(pressed);
268         }
269         else {
270           resetHoldState();
271         }
272       }
273     }
274
275
276     return false;
277   }
278
279   @NotNull
280   private static Set<Integer> getActivateToolWindowVKs() {
281     if (ApplicationManager.getApplication() == null) return new HashSet<>();
282
283     Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
284     Shortcut[] baseShortcut = keymap.getShortcuts("ActivateProjectToolWindow");
285     int baseModifiers = SystemInfo.isMac ? InputEvent.META_MASK : InputEvent.ALT_MASK;
286     for (Shortcut each : baseShortcut) {
287       if (each instanceof KeyboardShortcut) {
288         KeyStroke keyStroke = ((KeyboardShortcut)each).getFirstKeyStroke();
289         baseModifiers = keyStroke.getModifiers();
290         if (baseModifiers > 0) {
291           break;
292         }
293       }
294     }
295     return QuickAccessSettings.getModifiersVKs(baseModifiers);
296   }
297
298   private void resetHoldState() {
299     myCurrentState = KeyState.waiting;
300     processHoldState();
301   }
302
303   private void processState(boolean pressed) {
304     if (pressed) {
305       if (myCurrentState == KeyState.waiting) {
306         myCurrentState = KeyState.pressed;
307       }
308       else if (myCurrentState == KeyState.released) {
309         myCurrentState = KeyState.hold;
310         processHoldState();
311       }
312     }
313     else {
314       if (myCurrentState == KeyState.pressed) {
315         myCurrentState = KeyState.released;
316         restartWaitingForSecondPressAlarm();
317       }
318       else {
319         resetHoldState();
320       }
321     }
322   }
323
324   private void processHoldState() {
325     if (myToolWindowsPane != null) {
326       myToolWindowsPane.setStripesOverlayed(myCurrentState == KeyState.hold);
327     }
328   }
329
330   private void restartWaitingForSecondPressAlarm() {
331     myWaiterForSecondPress.cancelAllRequests();
332     myWaiterForSecondPress.addRequest(mySecondPressRunnable, Registry.intValue("actionSystem.keyGestureDblClickTime"));
333   }
334
335   private boolean hasOpenEditorFiles() {
336     return myFileEditorManager.getOpenFiles().length > 0;
337   }
338
339   private static IdeFocusManager getFocusManagerImpl(Project project) {
340     return IdeFocusManager.getInstance(project);
341   }
342
343   @NotNull
344   public Project getProject() {
345     return myProject;
346   }
347
348   @Override
349   public void initComponent() {
350   }
351
352   @Override
353   public void disposeComponent() {
354     for (String id : new ArrayList<>(myId2StripeButton.keySet())) {
355       unregisterToolWindow(id);
356     }
357
358     assert myId2StripeButton.isEmpty();
359
360     KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener(myFocusListener);
361   }
362
363   @Override
364   public void projectOpened() {
365     final MyUIManagerPropertyChangeListener uiManagerPropertyListener = new MyUIManagerPropertyChangeListener();
366     final MyLafManagerListener lafManagerListener = new MyLafManagerListener();
367
368     UIManager.addPropertyChangeListener(uiManagerPropertyListener);
369     myLafManager.addLafManagerListener(lafManagerListener);
370
371     Disposer.register(myProject, () -> {
372       UIManager.removePropertyChangeListener(uiManagerPropertyListener);
373       myLafManager.removeLafManagerListener(lafManagerListener);
374     });
375     myFrame = myWindowManager.allocateFrame(myProject);
376     LOG.assertTrue(myFrame != null);
377
378     myToolWindowsPane = new ToolWindowsPane(myFrame, this);
379     Disposer.register(myProject, myToolWindowsPane);
380     ((IdeRootPane)myFrame.getRootPane()).setToolWindowsPane(myToolWindowsPane);
381     myFrame.setTitle(FrameTitleBuilder.getInstance().getProjectTitle(myProject));
382
383     IdeEventQueue.getInstance().addDispatcher(e -> {
384       if (e instanceof KeyEvent) {
385         dispatchKeyEvent((KeyEvent)e);
386       }
387       if (e instanceof WindowEvent && e.getID() == WindowEvent.WINDOW_LOST_FOCUS && e.getSource() == myFrame) {
388         resetHoldState();
389       }
390       return false;
391     }, myProject);
392   }
393
394   private void initAll(List<FinalizableCommand> commandsList) {
395     appendUpdateToolWindowsPaneCmd(commandsList);
396
397     JComponent editorComponent = createEditorComponent(myProject);
398     myEditorComponentFocusWatcher.install(editorComponent);
399
400     appendSetEditorComponentCmd(editorComponent, commandsList);
401     if (myEditorWasActive && editorComponent instanceof EditorsSplitters) {
402       activateEditorComponentImpl(commandsList, true);
403     }
404   }
405
406   private static JComponent createEditorComponent(@NotNull Project project) {
407     return FrameEditorComponentProvider.EP.getExtensions()[0].createEditorComponent(project);
408   }
409
410   private void registerToolWindowsFromBeans(List<FinalizableCommand> list) {
411     List<ToolWindowEP> beans = Arrays.asList(Extensions.getExtensions(ToolWindowEP.EP_NAME));
412     for (ToolWindowEP bean : beans) {
413       Condition<Project> condition = bean.getCondition();
414       if (condition == null || condition.value(myProject)) {
415         list.add(new FinalizableCommand(EmptyRunnable.INSTANCE) {
416           @Override
417           public void run() {
418             initToolWindow(bean);
419           }
420         });
421       }
422     }
423   }
424
425   @Override
426   public void initToolWindow(@NotNull ToolWindowEP bean) {
427     JLabel label = new JLabel("Initializing...", SwingConstants.CENTER);
428     label.setOpaque(true);
429     final Color treeBg = UIManager.getColor("Tree.background");
430     label.setBackground(ColorUtil.toAlpha(treeBg, 180));
431     final Color treeFg = UIUtil.getTreeForeground();
432     label.setForeground(ColorUtil.toAlpha(treeFg, 180));
433     ToolWindowAnchor toolWindowAnchor = ToolWindowAnchor.fromText(bean.anchor);
434     final ToolWindowFactory factory = bean.getToolWindowFactory();
435     final ToolWindowImpl toolWindow =
436       (ToolWindowImpl)registerToolWindow(bean.id, label, toolWindowAnchor, myProject, DumbService.isDumbAware(factory),
437                                          bean.canCloseContents);
438     toolWindow.setContentFactory(factory);
439     if (bean.icon != null && toolWindow.getIcon() == null) {
440       Icon icon = IconLoader.findIcon(bean.icon, factory.getClass());
441       if (icon == null) {
442         try {
443           icon = IconLoader.getIcon(bean.icon);
444         }
445         catch (Exception ignored) {
446         }
447       }
448       toolWindow.setIcon(icon);
449     }
450
451     WindowInfoImpl info = getInfo(bean.id);
452     if (!info.isSplit() && bean.secondary && !info.wasRead()) {
453       toolWindow.setSplitMode(true, null);
454     }
455
456     final ActionCallback activation = toolWindow.setActivation(new ActionCallback());
457
458     final DumbAwareRunnable runnable = () -> {
459       if (toolWindow.isDisposed()) return;
460
461       toolWindow.ensureContentInitialized();
462       activation.setDone();
463     };
464     if (ApplicationManager.getApplication().isUnitTestMode()) {
465       runnable.run();
466     }
467     else {
468       UiNotifyConnector.doWhenFirstShown(label, () -> ApplicationManager.getApplication().invokeLater(runnable));
469     }
470   }
471
472   @Override
473   public void projectClosed() {
474     final String[] ids = getToolWindowIds();
475
476     // Remove ToolWindowsPane
477     if (myFrame != null) {
478       ((IdeRootPane)myFrame.getRootPane()).setToolWindowsPane(null);
479       myWindowManager.releaseFrame(myFrame);
480     }
481     List<FinalizableCommand> commandsList = new ArrayList<>();
482     appendUpdateToolWindowsPaneCmd(commandsList);
483
484     // Hide all tool windows
485
486     for (final String id : ids) {
487       deactivateToolWindowImpl(id, true, commandsList);
488     }
489
490     // Remove editor component
491
492     final JComponent editorComponent = FileEditorManagerEx.getInstanceEx(myProject).getComponent();
493     myEditorComponentFocusWatcher.deinstall(editorComponent);
494     appendSetEditorComponentCmd(null, commandsList);
495     execute(commandsList);
496   }
497
498   @Override
499   public void addToolWindowManagerListener(@NotNull ToolWindowManagerListener listener) {
500     myDispatcher.addListener(listener);
501   }
502
503   @Override
504   public void addToolWindowManagerListener(@NotNull ToolWindowManagerListener listener, @NotNull Disposable parentDisposable) {
505     myDispatcher.addListener(listener, parentDisposable);
506   }
507
508   @Override
509   public void removeToolWindowManagerListener(@NotNull ToolWindowManagerListener listener) {
510     myDispatcher.removeListener(listener);
511   }
512
513   /**
514    * This is helper method. It delegated its functionality to the WindowManager.
515    * Before delegating it fires state changed.
516    */
517   public void execute(@NotNull List<FinalizableCommand> commandList) {
518     for (FinalizableCommand each : commandList) {
519       if (each.willChangeState()) {
520         fireStateChanged();
521         break;
522       }
523     }
524
525     for (FinalizableCommand each : commandList) {
526       each.beforeExecute(this);
527     }
528     myWindowManager.getCommandProcessor().execute(commandList, myProject.getDisposed());
529   }
530
531   @Override
532   public void activateEditorComponent() {
533     activateEditorComponent(true);
534   }
535
536   private void activateEditorComponent(final boolean forced) {
537     activateEditorComponent(forced, false); //TODO[kirillk]: runnable in activateEditorComponent(boolean, boolean) never runs
538   }
539
540   private void activateEditorComponent(final boolean forced, boolean now) {
541     if (LOG.isDebugEnabled()) {
542       LOG.debug("enter: activateEditorComponent()");
543     }
544     ApplicationManager.getApplication().assertIsDispatchThread();
545
546     final ExpirableRunnable runnable = new ExpirableRunnable.ForProject(myProject) {
547       @Override
548       public void run() {
549         List<FinalizableCommand> commandList = new ArrayList<>();
550         activateEditorComponentImpl(commandList, forced);
551         execute(commandList);
552       }
553     };
554     if (now) {
555       if (!runnable.isExpired()) {
556         runnable.run();
557       }
558     }
559     else {
560       final FocusRequestor requestor = getFocusManager().getFurtherRequestor();
561       getFocusManager().doWhenFocusSettlesDown(new ExpirableRunnable.ForProject(myProject) {
562         @Override
563         public void run() {
564           requestor.requestFocus(new FocusCommand() {
565             @NotNull
566             @Override
567             public ActionCallback run() {
568               runnable.run();
569               return ActionCallback.DONE;
570             }
571           }.setExpirable(runnable), forced);
572         }
573       });
574     }
575   }
576
577   private void activateEditorComponentImpl(@NotNull List<FinalizableCommand> commandList, final boolean forced) {
578     final String active = getActiveToolWindowId();
579     // Now we have to request focus into most recent focused editor
580     appendRequestFocusInEditorComponentCmd(commandList, forced).doWhenDone(() -> {
581       if (LOG.isDebugEnabled()) {
582         LOG.debug("editor activated");
583       }
584       List<FinalizableCommand> commandList12 = new ArrayList<>();
585       deactivateWindows(null, commandList12);
586       myActiveStack.clear();
587
588       execute(commandList12);
589     }).doWhenRejected(() -> {
590       if (forced) {
591         getFocusManagerImpl(myProject).requestFocus(new FocusCommand() {
592           @NotNull
593           @Override
594           public ActionCallback run() {
595             List<FinalizableCommand> commandList1 = new ArrayList<>();
596
597             final WindowInfoImpl toReactivate = active == null ? null : getInfo(active);
598             final boolean reactivateLastActive = toReactivate != null && !isToHideOnDeactivation(toReactivate);
599             deactivateWindows(reactivateLastActive ? active : null, commandList1);
600             execute(commandList1);
601
602             if (reactivateLastActive) {
603               activateToolWindow(active, false, true);
604             }
605             else {
606               if (active != null) {
607                 myActiveStack.remove(active, false);
608               }
609
610               if (!myActiveStack.isEmpty()) {
611                 activateToolWindow(myActiveStack.peek(), false, true);
612               }
613             }
614             return ActionCallback.DONE;
615           }
616         }, false);
617       }
618     });
619   }
620
621   private void deactivateWindows(@Nullable String idToIgnore, @NotNull List<FinalizableCommand> commandList) {
622     final WindowInfoImpl[] infos = myLayout.getInfos();
623     for (final WindowInfoImpl info : infos) {
624       if (idToIgnore != null && idToIgnore.equals(info.getId())) {
625         continue;
626       }
627       deactivateToolWindowImpl(info.getId(), isToHideOnDeactivation(info), commandList);
628     }
629   }
630
631   private static boolean isToHideOnDeactivation(@NotNull final WindowInfoImpl info) {
632     if (info.isFloating() || info.isWindowed()) return false;
633     return info.isAutoHide() || info.isSliding();
634   }
635
636   /**
637    * Helper method. It makes window visible, activates it and request focus into the tool window.
638    * But it doesn't deactivate other tool windows. Use <code>prepareForActivation</code> method to
639    * deactivates other tool windows.
640    *
641    * @param dirtyMode if <code>true</code> then all UI operations are performed in "dirty" mode.
642    *                  It means that UI isn't validated and repainted just after each add/remove operation.
643    */
644   private void showAndActivate(@NotNull String id,
645                                final boolean dirtyMode,
646                                @NotNull List<FinalizableCommand> commandsList,
647                                boolean autoFocusContents,
648                                boolean forcedFocusRequest) {
649     if (!getToolWindow(id).isAvailable()) {
650       return;
651     }
652     // show activated
653     final WindowInfoImpl info = getInfo(id);
654     boolean toApplyInfo = false;
655     if (!info.isActive()) {
656       info.setActive(true);
657       toApplyInfo = true;
658     }
659     showToolWindowImpl(id, dirtyMode, commandsList);
660
661     // activate
662     if (toApplyInfo) {
663       appendApplyWindowInfoCmd(info, commandsList);
664       myActiveStack.push(id);
665     }
666
667     if (autoFocusContents && ApplicationManager.getApplication().isActive()) {
668       appendRequestFocusInToolWindowCmd(id, commandsList, forcedFocusRequest);
669     }
670   }
671
672   void activateToolWindow(@NotNull String id, boolean forced, boolean autoFocusContents) {
673     if (LOG.isDebugEnabled()) {
674       LOG.debug("enter: activateToolWindow(" + id + ")");
675     }
676     ApplicationManager.getApplication().assertIsDispatchThread();
677     checkId(id);
678
679     List<FinalizableCommand> commandList = new ArrayList<>();
680     activateToolWindowImpl(id, commandList, forced, autoFocusContents);
681     execute(commandList);
682   }
683
684   private void activateToolWindowImpl(@NotNull String id,
685                                       @NotNull List<FinalizableCommand> commandList,
686                                       boolean forced,
687                                       boolean autoFocusContents) {
688     autoFocusContents &= FocusManagerImpl.getInstance().isUnforcedRequestAllowed() || forced;
689
690     if (LOG.isDebugEnabled()) {
691       LOG.debug("enter: activateToolWindowImpl(" + id + ")");
692     }
693     if (!getToolWindow(id).isAvailable()) {
694       // Tool window can be "logically" active but not focused. For example,
695       // when the user switched to another application. So we just need to bring
696       // tool window's window to front.
697       final InternalDecorator decorator = getInternalDecorator(id);
698       if (!decorator.hasFocus() && autoFocusContents) {
699         appendRequestFocusInToolWindowCmd(id, commandList, forced);
700       }
701       return;
702     }
703     deactivateWindows(id, commandList);
704     showAndActivate(id, false, commandList, autoFocusContents, forced);
705   }
706
707   /**
708    * Checkes whether the specified <code>id</code> defines installed tool
709    * window. If it's not then throws <code>IllegalStateException</code>.
710    *
711    * @throws IllegalStateException if tool window isn't installed.
712    */
713   private void checkId(@NotNull String id) {
714     if (!myLayout.isToolWindowRegistered(id)) {
715       throw new IllegalStateException("window with id=\"" + id + "\" isn't registered");
716     }
717   }
718
719   /**
720    * Helper method. It deactivates (and hides) window with specified <code>id</code>.
721    *
722    * @param id         <code>id</code> of the tool window to be deactivated.
723    * @param shouldHide if <code>true</code> then also hides specified tool window.
724    */
725   private void deactivateToolWindowImpl(@NotNull String id, final boolean shouldHide, @NotNull List<FinalizableCommand> commandsList) {
726     if (LOG.isDebugEnabled()) {
727       LOG.debug("enter: deactivateToolWindowImpl(" + id + "," + shouldHide + ")");
728     }
729     final WindowInfoImpl info = getInfo(id);
730     if (shouldHide && info.isVisible()) {
731       info.setVisible(false);
732       if (info.isFloating()) {
733         appendRemoveFloatingDecoratorCmd(info, commandsList);
734       }
735       else if (info.isWindowed()) {
736         appendRemoveWindowedDecoratorCmd(info, commandsList);
737       }
738       else { // docked and sliding windows
739         appendRemoveDecoratorCmd(id, false, commandsList);
740       }
741     }
742     info.setActive(false);
743     appendApplyWindowInfoCmd(info, commandsList);
744   }
745
746   @NotNull
747   @Override
748   public String[] getToolWindowIds() {
749     final WindowInfoImpl[] infos = myLayout.getInfos();
750     final String[] ids = ArrayUtil.newStringArray(infos.length);
751     for (int i = 0; i < infos.length; i++) {
752       ids[i] = infos[i].getId();
753     }
754     return ids;
755   }
756
757   @Override
758   public String getActiveToolWindowId() {
759     ApplicationManager.getApplication().assertIsDispatchThread();
760     return myLayout.getActiveId();
761   }
762
763   @Override
764   public String getLastActiveToolWindowId() {
765     return getLastActiveToolWindowId(null);
766   }
767
768   @Override
769   @Nullable
770   public String getLastActiveToolWindowId(@Nullable Condition<JComponent> condition) {
771     ApplicationManager.getApplication().assertIsDispatchThread();
772     String lastActiveToolWindowId = null;
773     for (int i = 0; i < myActiveStack.getPersistentSize(); i++) {
774       final String id = myActiveStack.peekPersistent(i);
775       final ToolWindow toolWindow = getToolWindow(id);
776       LOG.assertTrue(toolWindow != null);
777       if (toolWindow.isAvailable()) {
778         if (condition == null || condition.value(toolWindow.getComponent())) {
779           lastActiveToolWindowId = id;
780           break;
781         }
782       }
783     }
784     return lastActiveToolWindowId;
785   }
786
787   /**
788    * @return floating decorator for the tool window with specified <code>ID</code>.
789    */
790   private FloatingDecorator getFloatingDecorator(@NotNull String id) {
791     return myId2FloatingDecorator.get(id);
792   }
793   /**
794    * @return windowed decorator for the tool window with specified <code>ID</code>.
795    */
796   private WindowedDecorator getWindowedDecorator(@NotNull String id) {
797     return myId2WindowedDecorator.get(id);
798   }
799   /**
800    * @return internal decorator for the tool window with specified <code>ID</code>.
801    */
802   private InternalDecorator getInternalDecorator(@NotNull String id) {
803     return myId2InternalDecorator.get(id);
804   }
805
806   /**
807    * @return tool button for the window with specified <code>ID</code>.
808    */
809   private StripeButton getStripeButton(@NotNull String id) {
810     return myId2StripeButton.get(id);
811   }
812
813   /**
814    * @return info for the tool window with specified <code>ID</code>.
815    */
816   private WindowInfoImpl getInfo(@NotNull String id) {
817     return myLayout.getInfo(id, true);
818   }
819
820   @Override
821   @NotNull
822   public List<String> getIdsOn(@NotNull final ToolWindowAnchor anchor) {
823     return myLayout.getVisibleIdsOn(anchor, this);
824   }
825
826   @Override
827   public ToolWindow getToolWindow(final String id) {
828     if (!myLayout.isToolWindowRegistered(id)) {
829       return null;
830     }
831     InternalDecorator decorator = getInternalDecorator(id);
832     return decorator != null ? decorator.getToolWindow() : null;
833   }
834
835   void showToolWindow(@NotNull String id) {
836     if (LOG.isDebugEnabled()) {
837       LOG.debug("enter: showToolWindow(" + id + ")");
838     }
839     ApplicationManager.getApplication().assertIsDispatchThread();
840     List<FinalizableCommand> commandList = new ArrayList<>();
841     showToolWindowImpl(id, false, commandList);
842     execute(commandList);
843   }
844
845   @Override
846   public void hideToolWindow(@NotNull final String id, final boolean hideSide) {
847     hideToolWindow(id, hideSide, true);
848   }
849
850   public void hideToolWindow(@NotNull String id, final boolean hideSide, final boolean moveFocus) {
851     ApplicationManager.getApplication().assertIsDispatchThread();
852     checkId(id);
853     final WindowInfoImpl info = getInfo(id);
854     if (!info.isVisible()) return;
855     List<FinalizableCommand> commandList = new ArrayList<>();
856     final boolean wasActive = info.isActive();
857
858     // hide and deactivate
859
860     deactivateToolWindowImpl(id, true, commandList);
861
862     if (hideSide && !info.isFloating() && !info.isWindowed()) {
863       final List<String> ids = myLayout.getVisibleIdsOn(info.getAnchor(), this);
864       for (String each : ids) {
865         myActiveStack.remove(each, true);
866       }
867
868
869       while (!mySideStack.isEmpty(info.getAnchor())) {
870         mySideStack.pop(info.getAnchor());
871       }
872
873       final String[] all = getToolWindowIds();
874       for (String eachId : all) {
875         final WindowInfoImpl eachInfo = getInfo(eachId);
876         if (eachInfo.isVisible() && eachInfo.getAnchor() == info.getAnchor()) {
877           deactivateToolWindowImpl(eachId, true, commandList);
878         }
879       }
880
881       activateEditorComponentImpl(commandList, true);
882     }
883     else if (isStackEnabled()) {
884
885       // first of all we have to find tool window that was located at the same side and
886       // was hidden.
887
888       WindowInfoImpl info2 = null;
889       while (!mySideStack.isEmpty(info.getAnchor())) {
890         final WindowInfoImpl storedInfo = mySideStack.pop(info.getAnchor());
891         if (storedInfo.isSplit() != info.isSplit()) {
892           continue;
893         }
894
895         final WindowInfoImpl currentInfo = getInfo(storedInfo.getId());
896         LOG.assertTrue(currentInfo != null);
897         // SideStack contains copies of real WindowInfos. It means that
898         // these stored infos can be invalid. The following loop removes invalid WindowInfos.
899         if (storedInfo.getAnchor() == currentInfo.getAnchor() &&
900             storedInfo.getType() == currentInfo.getType() &&
901             storedInfo.isAutoHide() == currentInfo.isAutoHide()) {
902           info2 = storedInfo;
903           break;
904         }
905       }
906       if (info2 != null) {
907         showToolWindowImpl(info2.getId(), false, commandList);
908       }
909
910       // If we hide currently active tool window then we should activate the previous
911       // one which is located in the tool window stack.
912       // Activate another tool window if no active tool window exists and
913       // window stack is enabled.
914
915       myActiveStack.remove(id, false); // hidden window should be at the top of stack
916
917       if (wasActive && moveFocus) {
918         if (myActiveStack.isEmpty()) {
919           if (hasOpenEditorFiles()) {
920             activateEditorComponentImpl(commandList, false);
921           }
922           else {
923             focusToolWinowByDefault(id);
924           }
925         }
926         else {
927           final String toBeActivatedId = myActiveStack.pop();
928           if (getInfo(toBeActivatedId).isVisible() || isStackEnabled()) {
929             activateToolWindowImpl(toBeActivatedId, commandList, false, true);
930           }
931           else {
932             focusToolWinowByDefault(id);
933           }
934         }
935       }
936     }
937
938     execute(commandList);
939   }
940
941   private static boolean isStackEnabled() {
942     return Registry.is("ide.enable.toolwindow.stack");
943   }
944
945   /**
946    * @param dirtyMode if <code>true</code> then all UI operations are performed in dirty mode.
947    */
948   private void showToolWindowImpl(@NotNull String id, final boolean dirtyMode, @NotNull List<FinalizableCommand> commandsList) {
949     final WindowInfoImpl toBeShownInfo = getInfo(id);
950     ToolWindow window = getToolWindow(id);
951     if (window != null && toBeShownInfo.isWindowed()) {
952       UIUtil.toFront(UIUtil.getWindow(window.getComponent()));
953     }
954     if (toBeShownInfo.isVisible() || window == null || !window.isAvailable()) {
955       return;
956     }
957
958     toBeShownInfo.setVisible(true);
959     final InternalDecorator decorator = getInternalDecorator(id);
960
961     if (toBeShownInfo.isFloating()) {
962       commandsList.add(new AddFloatingDecoratorCmd(decorator, toBeShownInfo));
963     }
964     else if (toBeShownInfo.isWindowed()) {
965       commandsList.add(new AddWindowedDecoratorCmd(decorator, toBeShownInfo));
966     }
967     else { // docked and sliding windows
968
969       // If there is tool window on the same side then we have to hide it, i.e.
970       // clear place for tool window to be shown.
971       //
972       // We store WindowInfo of hidden tool window in the SideStack (if the tool window
973       // is docked and not auto-hide one). Therefore it's possible to restore the
974       // hidden tool window when showing tool window will be closed.
975
976       final WindowInfoImpl[] infos = myLayout.getInfos();
977       for (final WindowInfoImpl info : infos) {
978         if (id.equals(info.getId())) {
979           continue;
980         }
981         if (info.isVisible() &&
982             info.getType() == toBeShownInfo.getType() &&
983             info.getAnchor() == toBeShownInfo.getAnchor() &&
984             info.isSplit() == toBeShownInfo.isSplit()) {
985           // hide and deactivate tool window
986           info.setVisible(false);
987           appendRemoveDecoratorCmd(info.getId(), false, commandsList);
988           if (info.isActive()) {
989             info.setActive(false);
990           }
991           appendApplyWindowInfoCmd(info, commandsList);
992           // store WindowInfo into the SideStack
993           if (info.isDocked() && !info.isAutoHide()) {
994             mySideStack.push(info);
995           }
996         }
997       }
998       appendAddDecoratorCmd(decorator, toBeShownInfo, dirtyMode, commandsList);
999
1000       // Remove tool window from the SideStack.
1001
1002       mySideStack.remove(id);
1003     }
1004
1005     if (!toBeShownInfo.isShowStripeButton()) {
1006       toBeShownInfo.setShowStripeButton(true);
1007     }
1008
1009     appendApplyWindowInfoCmd(toBeShownInfo, commandsList);
1010   }
1011
1012   @NotNull
1013   @Override
1014   public ToolWindow registerToolWindow(@NotNull final String id,
1015                                        @NotNull final JComponent component,
1016                                        @NotNull final ToolWindowAnchor anchor) {
1017     return registerToolWindow(id, component, anchor, false);
1018   }
1019
1020   @NotNull
1021   @Override
1022   public ToolWindow registerToolWindow(@NotNull final String id,
1023                                        @NotNull JComponent component,
1024                                        @NotNull ToolWindowAnchor anchor,
1025                                        @NotNull Disposable parentDisposable) {
1026     return registerToolWindow(id, component, anchor, parentDisposable, false, false);
1027   }
1028
1029   @NotNull
1030   @Override
1031   public ToolWindow registerToolWindow(@NotNull String id,
1032                                        @NotNull JComponent component,
1033                                        @NotNull ToolWindowAnchor anchor,
1034                                        @NotNull Disposable parentDisposable,
1035                                        boolean canWorkInDumbMode) {
1036     return registerToolWindow(id, component, anchor, parentDisposable, canWorkInDumbMode, false);
1037   }
1038
1039   @NotNull
1040   @Override
1041   public ToolWindow registerToolWindow(@NotNull final String id,
1042                                        @NotNull JComponent component,
1043                                        @NotNull ToolWindowAnchor anchor,
1044                                        @NotNull Disposable parentDisposable,
1045                                        boolean canWorkInDumbMode, boolean canCloseContents) {
1046     return registerDisposable(id, parentDisposable, registerToolWindow(id, component, anchor, false, canCloseContents, canWorkInDumbMode));
1047   }
1048
1049   @NotNull
1050   private ToolWindow registerToolWindow(@NotNull final String id,
1051                                         @NotNull final JComponent component,
1052                                         @NotNull final ToolWindowAnchor anchor,
1053                                         boolean canWorkInDumbMode) {
1054     return registerToolWindow(id, component, anchor, false, false, canWorkInDumbMode);
1055   }
1056
1057   @NotNull
1058   @Override
1059   public ToolWindow registerToolWindow(@NotNull final String id, final boolean canCloseContent, @NotNull final ToolWindowAnchor anchor) {
1060     return registerToolWindow(id, null, anchor, false, canCloseContent, false);
1061   }
1062
1063   @NotNull
1064   @Override
1065   public ToolWindow registerToolWindow(@NotNull final String id,
1066                                        final boolean canCloseContent,
1067                                        @NotNull final ToolWindowAnchor anchor,
1068                                        final boolean secondary) {
1069     return registerToolWindow(id, null, anchor, secondary, canCloseContent, false);
1070   }
1071
1072
1073   @NotNull
1074   @Override
1075   public ToolWindow registerToolWindow(@NotNull final String id,
1076                                        final boolean canCloseContent,
1077                                        @NotNull final ToolWindowAnchor anchor,
1078                                        @NotNull final Disposable parentDisposable,
1079                                        final boolean canWorkInDumbMode) {
1080     return registerToolWindow(id, canCloseContent, anchor, parentDisposable, canWorkInDumbMode, false);
1081   }
1082
1083   @NotNull
1084   @Override
1085   public ToolWindow registerToolWindow(@NotNull String id,
1086                                        boolean canCloseContent,
1087                                        @NotNull ToolWindowAnchor anchor,
1088                                        @NotNull Disposable parentDisposable,
1089                                        boolean canWorkInDumbMode,
1090                                        boolean secondary) {
1091     ToolWindow window = registerToolWindow(id, null, anchor, secondary, canCloseContent, canWorkInDumbMode);
1092     return registerDisposable(id, parentDisposable, window);
1093   }
1094
1095   @NotNull
1096   private ToolWindow registerToolWindow(@NotNull final String id,
1097                                         @Nullable final JComponent component,
1098                                         @NotNull final ToolWindowAnchor anchor,
1099                                         boolean sideTool,
1100                                         boolean canCloseContent,
1101                                         final boolean canWorkInDumbMode) {
1102     if (LOG.isDebugEnabled()) {
1103       LOG.debug("enter: installToolWindow(" + id + "," + component + "," + anchor + "\")");
1104     }
1105     ApplicationManager.getApplication().assertIsDispatchThread();
1106     if (myLayout.isToolWindowRegistered(id)) {
1107       throw new IllegalArgumentException("window with id=\"" + id + "\" is already registered");
1108     }
1109
1110     final WindowInfoImpl info = myLayout.register(id, anchor, sideTool);
1111     final boolean wasActive = info.isActive();
1112     final boolean wasVisible = info.isVisible();
1113     info.setActive(false);
1114     info.setVisible(false);
1115
1116     // Create decorator
1117
1118     ToolWindowImpl toolWindow = new ToolWindowImpl(this, id, canCloseContent, component);
1119     InternalDecorator decorator = new InternalDecorator(myProject, info.copy(), toolWindow, canWorkInDumbMode);
1120     ActivateToolWindowAction.ensureToolWindowActionRegistered(toolWindow);
1121     myId2InternalDecorator.put(id, decorator);
1122     decorator.addInternalDecoratorListener(myInternalDecoratorListener);
1123     toolWindow.addPropertyChangeListener(myToolWindowPropertyChangeListener);
1124     myId2FocusWatcher.put(id, new ToolWindowFocusWatcher(toolWindow));
1125
1126     // Create and show tool button
1127
1128     final StripeButton button = new StripeButton(decorator, myToolWindowsPane);
1129     myId2StripeButton.put(id, button);
1130     List<FinalizableCommand> commandsList = new ArrayList<>();
1131     appendAddButtonCmd(button, info, commandsList);
1132
1133     // If preloaded info is visible or active then we have to show/activate the installed
1134     // tool window. This step has sense only for windows which are not in the autohide
1135     // mode. But if tool window was active but its mode doen't allow to activate it again
1136     // (for example, tool window is in autohide mode) then we just activate editor component.
1137
1138     if (!info.isAutoHide() && (info.isDocked() || info.isFloating())) {
1139       if (wasActive) {
1140         activateToolWindowImpl(info.getId(), commandsList, true, true);
1141       }
1142       else if (wasVisible) {
1143         showToolWindowImpl(info.getId(), false, commandsList);
1144       }
1145     }
1146     else if (wasActive) { // tool window was active but it cannot be activate again
1147       activateEditorComponentImpl(commandsList, true);
1148     }
1149
1150     execute(commandsList);
1151     fireToolWindowRegistered(id);
1152     return toolWindow;
1153   }
1154
1155   @NotNull
1156   private ToolWindow registerDisposable(@NotNull final String id, @NotNull final Disposable parentDisposable, @NotNull ToolWindow window) {
1157     Disposer.register(parentDisposable, () -> unregisterToolWindow(id));
1158     return window;
1159   }
1160
1161   @Override
1162   public void unregisterToolWindow(@NotNull final String id) {
1163     if (LOG.isDebugEnabled()) {
1164       LOG.debug("enter: unregisterToolWindow(" + id + ")");
1165     }
1166     ApplicationManager.getApplication().assertIsDispatchThread();
1167     if (!myLayout.isToolWindowRegistered(id)) {
1168       return;
1169     }
1170
1171     final WindowInfoImpl info = getInfo(id);
1172     final ToolWindowEx toolWindow = (ToolWindowEx)getToolWindow(id);
1173     // Save recent appearance of tool window
1174     myLayout.unregister(id);
1175     // Remove decorator and tool button from the screen
1176     List<FinalizableCommand> commandsList = new ArrayList<>();
1177     if (info.isVisible()) {
1178       info.setVisible(false);
1179       if (info.isFloating()) {
1180         appendRemoveFloatingDecoratorCmd(info, commandsList);
1181       }
1182       else  if (info.isWindowed()) {
1183          appendRemoveWindowedDecoratorCmd(info, commandsList);
1184        }
1185       else { // floating and sliding windows
1186         appendRemoveDecoratorCmd(id, false, commandsList);
1187       }
1188     }
1189     appendRemoveButtonCmd(id, commandsList);
1190     appendApplyWindowInfoCmd(info, commandsList);
1191     execute(commandsList);
1192     // Remove all references on tool window and save its last properties
1193     toolWindow.removePropertyChangeListener(myToolWindowPropertyChangeListener);
1194     myActiveStack.remove(id, true);
1195     mySideStack.remove(id);
1196     // Destroy stripe button
1197     final StripeButton button = getStripeButton(id);
1198     Disposer.dispose(button);
1199     myId2StripeButton.remove(id);
1200     //
1201     ToolWindowFocusWatcher watcher = (ToolWindowFocusWatcher)myId2FocusWatcher.remove(id);
1202     watcher.deinstall();
1203
1204     // Destroy decorator
1205     final InternalDecorator decorator = getInternalDecorator(id);
1206     decorator.dispose();
1207     decorator.removeInternalDecoratorListener(myInternalDecoratorListener);
1208     myId2InternalDecorator.remove(id);
1209   }
1210
1211   @Override
1212   public DesktopLayout getLayout() {
1213     ApplicationManager.getApplication().assertIsDispatchThread();
1214     return myLayout;
1215   }
1216
1217   @Override
1218   public void setLayoutToRestoreLater(DesktopLayout layout) {
1219     myLayoutToRestoreLater = layout;
1220   }
1221
1222   @Override
1223   public DesktopLayout getLayoutToRestoreLater() {
1224     return myLayoutToRestoreLater;
1225   }
1226
1227   @Override
1228   public void setLayout(@NotNull final DesktopLayout layout) {
1229     ApplicationManager.getApplication().assertIsDispatchThread();
1230     List<FinalizableCommand> commandList = new ArrayList<>();
1231     // hide tool window that are invisible in new layout
1232     final WindowInfoImpl[] currentInfos = myLayout.getInfos();
1233     for (final WindowInfoImpl currentInfo : currentInfos) {
1234       final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false);
1235       if (info == null) {
1236         continue;
1237       }
1238       if (currentInfo.isVisible() && !info.isVisible()) {
1239         deactivateToolWindowImpl(currentInfo.getId(), true, commandList);
1240       }
1241     }
1242     // change anchor of tool windows
1243     for (final WindowInfoImpl currentInfo : currentInfos) {
1244       final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false);
1245       if (info == null) {
1246         continue;
1247       }
1248       if (currentInfo.getAnchor() != info.getAnchor() || currentInfo.getOrder() != info.getOrder()) {
1249         setToolWindowAnchorImpl(currentInfo.getId(), info.getAnchor(), info.getOrder(), commandList);
1250       }
1251     }
1252     // change types of tool windows
1253     for (final WindowInfoImpl currentInfo : currentInfos) {
1254       final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false);
1255       if (info == null) {
1256         continue;
1257       }
1258       if (currentInfo.getType() != info.getType()) {
1259         setToolWindowTypeImpl(currentInfo.getId(), info.getType(), commandList);
1260       }
1261     }
1262     // change auto-hide state
1263     for (final WindowInfoImpl currentInfo : currentInfos) {
1264       final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false);
1265       if (info == null) {
1266         continue;
1267       }
1268       if (currentInfo.isAutoHide() != info.isAutoHide()) {
1269         setToolWindowAutoHideImpl(currentInfo.getId(), info.isAutoHide(), commandList);
1270       }
1271     }
1272     // restore visibility
1273     for (final WindowInfoImpl currentInfo : currentInfos) {
1274       final WindowInfoImpl info = layout.getInfo(currentInfo.getId(), false);
1275       if (info == null) {
1276         continue;
1277       }
1278       if (info.isVisible()) {
1279         showToolWindowImpl(currentInfo.getId(), false, commandList);
1280       }
1281     }
1282     // if there is no any active tool window and editor is also inactive
1283     // then activate editor
1284     if (!myEditorWasActive && getActiveToolWindowId() == null) {
1285       activateEditorComponentImpl(commandList, true);
1286     }
1287     execute(commandList);
1288   }
1289
1290   @Override
1291   public void invokeLater(@NotNull final Runnable runnable) {
1292     List<FinalizableCommand> commandList = new ArrayList<>();
1293     commandList.add(new InvokeLaterCmd(runnable, myWindowManager.getCommandProcessor()));
1294     execute(commandList);
1295   }
1296
1297   @NotNull
1298   @Override
1299   public IdeFocusManager getFocusManager() {
1300     return IdeFocusManager.getInstance(myProject);
1301   }
1302
1303   @Override
1304   public boolean canShowNotification(@NotNull final String toolWindowId) {
1305     if (!Arrays.asList(getToolWindowIds()).contains(toolWindowId)) {
1306       return false;
1307     }
1308     final Stripe stripe = myToolWindowsPane.getStripeFor(toolWindowId);
1309     return stripe != null && stripe.getButtonFor(toolWindowId) != null;
1310   }
1311
1312   @Override
1313   public void notifyByBalloon(@NotNull final String toolWindowId, @NotNull final MessageType type, @NotNull final String htmlBody) {
1314     notifyByBalloon(toolWindowId, type, htmlBody, null, null);
1315   }
1316
1317   @Override
1318   public void notifyByBalloon(@NotNull final String toolWindowId,
1319                               @NotNull final MessageType type,
1320                               @NotNull final String text,
1321                               @Nullable final Icon icon,
1322                               @Nullable final HyperlinkListener listener) {
1323     checkId(toolWindowId);
1324
1325
1326     Balloon existing = myWindow2Balloon.get(toolWindowId);
1327     if (existing != null) {
1328       existing.hide();
1329     }
1330
1331     final Stripe stripe = myToolWindowsPane.getStripeFor(toolWindowId);
1332     if (stripe == null) {
1333       return;
1334     }
1335     final ToolWindowImpl window = getInternalDecorator(toolWindowId).getToolWindow();
1336     if (!window.isAvailable()) {
1337       window.setPlaceholderMode(true);
1338       stripe.updatePresentation();
1339       stripe.revalidate();
1340       stripe.repaint();
1341     }
1342
1343     final ToolWindowAnchor anchor = getInfo(toolWindowId).getAnchor();
1344     final Ref<Balloon.Position> position = Ref.create(Balloon.Position.below);
1345     if (ToolWindowAnchor.TOP == anchor) {
1346       position.set(Balloon.Position.below);
1347     }
1348     else if (ToolWindowAnchor.BOTTOM == anchor) {
1349       position.set(Balloon.Position.above);
1350     }
1351     else if (ToolWindowAnchor.LEFT == anchor) {
1352       position.set(Balloon.Position.atRight);
1353     }
1354     else if (ToolWindowAnchor.RIGHT == anchor) {
1355       position.set(Balloon.Position.atLeft);
1356     }
1357
1358     final BalloonHyperlinkListener listenerWrapper = new BalloonHyperlinkListener(listener);
1359     final Balloon balloon =
1360       JBPopupFactory.getInstance()
1361         .createHtmlTextBalloonBuilder(text.replace("\n", "<br>"), icon, type.getPopupBackground(), listenerWrapper)
1362         .setHideOnClickOutside(false).setHideOnFrameResize(false).createBalloon();
1363     FrameStateManager.getInstance().getApplicationActive().doWhenDone(() -> {
1364       final Alarm alarm = new Alarm();
1365       alarm.addRequest(() -> {
1366         ((BalloonImpl)balloon).setHideOnClickOutside(true);
1367         Disposer.dispose(alarm);
1368       }, 100);
1369     });
1370     listenerWrapper.myBalloon = balloon;
1371     myWindow2Balloon.put(toolWindowId, balloon);
1372     Disposer.register(balloon, () -> {
1373       window.setPlaceholderMode(false);
1374       stripe.updatePresentation();
1375       stripe.revalidate();
1376       stripe.repaint();
1377       myWindow2Balloon.remove(toolWindowId);
1378     });
1379     Disposer.register(getProject(), balloon);
1380
1381     execute(new ArrayList<>(Collections.<FinalizableCommand>singletonList(new FinalizableCommand(null) {
1382       @Override
1383       public void run() {
1384         final StripeButton button = stripe.getButtonFor(toolWindowId);
1385         LOG.assertTrue(button != null, "Button was not found, popup won't be shown. Toolwindow id: " +
1386                                        toolWindowId +
1387                                        ", message: " +
1388                                        text +
1389                                        ", message type: " +
1390                                        type);
1391         if (button == null) return;
1392
1393         final Runnable show = () -> {
1394           if (button.isShowing()) {
1395             PositionTracker<Balloon> tracker = new PositionTracker<Balloon>(button) {
1396               @Override
1397               @Nullable
1398               public RelativePoint recalculateLocation(Balloon object) {
1399                 Stripe twStripe = myToolWindowsPane.getStripeFor(toolWindowId);
1400                 StripeButton twButton = twStripe != null ? twStripe.getButtonFor(toolWindowId) : null;
1401
1402                 if (twButton == null) return null;
1403
1404                 if (getToolWindow(toolWindowId).getAnchor() != anchor) {
1405                   object.hide();
1406                   return null;
1407                 }
1408
1409                 final Point point = new Point(twButton.getBounds().width / 2, twButton.getHeight() / 2 - 2);
1410                 return new RelativePoint(twButton, point);
1411               }
1412             };
1413             if (!balloon.isDisposed()) {
1414               balloon.show(tracker, position.get());
1415             }
1416           }
1417           else {
1418             final Rectangle bounds = myToolWindowsPane.getBounds();
1419             final Point target = UIUtil.getCenterPoint(bounds, new Dimension(1, 1));
1420             if (ToolWindowAnchor.TOP == anchor) {
1421               target.y = 0;
1422             }
1423             else if (ToolWindowAnchor.BOTTOM == anchor) {
1424               target.y = bounds.height - 3;
1425             }
1426             else if (ToolWindowAnchor.LEFT == anchor) {
1427               target.x = 0;
1428             }
1429             else if (ToolWindowAnchor.RIGHT == anchor) {
1430               target.x = bounds.width;
1431             }
1432             if (!balloon.isDisposed()) {
1433               balloon.show(new RelativePoint(myToolWindowsPane, target), position.get());
1434             }
1435           }
1436         };
1437
1438         if (!button.isValid()) {
1439           SwingUtilities.invokeLater(show);
1440         }
1441         else {
1442           show.run();
1443         }
1444       }
1445     })));
1446   }
1447
1448   @Override
1449   public Balloon getToolWindowBalloon(String id) {
1450     return myWindow2Balloon.get(id);
1451   }
1452
1453   @Override
1454   public boolean isEditorComponentActive() {
1455     ApplicationManager.getApplication().assertIsDispatchThread();
1456
1457     Component owner = getFocusManager().getFocusOwner();
1458     EditorsSplitters splitters = UIUtil.getParentOfType(EditorsSplitters.class, owner);
1459     return splitters != null;
1460   }
1461
1462   @NotNull
1463   ToolWindowAnchor getToolWindowAnchor(@NotNull String id) {
1464     checkId(id);
1465     return getInfo(id).getAnchor();
1466   }
1467
1468   void setToolWindowAnchor(@NotNull String id, @NotNull ToolWindowAnchor anchor) {
1469     ApplicationManager.getApplication().assertIsDispatchThread();
1470     setToolWindowAnchor(id, anchor, -1);
1471   }
1472
1473   private void setToolWindowAnchor(@NotNull String id, @NotNull ToolWindowAnchor anchor, final int order) {
1474     ApplicationManager.getApplication().assertIsDispatchThread();
1475     List<FinalizableCommand> commandList = new ArrayList<>();
1476     setToolWindowAnchorImpl(id, anchor, order, commandList);
1477     execute(commandList);
1478   }
1479
1480   private void setToolWindowAnchorImpl(@NotNull String id,
1481                                        @NotNull ToolWindowAnchor anchor,
1482                                        final int order,
1483                                        @NotNull List<FinalizableCommand> commandsList) {
1484     checkId(id);
1485     final WindowInfoImpl info = getInfo(id);
1486     if (anchor == info.getAnchor() && order == info.getOrder()) {
1487       return;
1488     }
1489     // if tool window isn't visible or only order number is changed then just remove/add stripe button
1490     if (!info.isVisible() || anchor == info.getAnchor() || info.isFloating()) {
1491       appendRemoveButtonCmd(id, commandsList);
1492       myLayout.setAnchor(id, anchor, order);
1493       // update infos for all window. Actually we have to update only infos affected by
1494       // setAnchor method
1495       final WindowInfoImpl[] infos = myLayout.getInfos();
1496       for (WindowInfoImpl info1 : infos) {
1497         appendApplyWindowInfoCmd(info1, commandsList);
1498       }
1499       appendAddButtonCmd(getStripeButton(id), info, commandsList);
1500     }
1501     else { // for docked and sliding windows we have to move buttons and window's decorators
1502       info.setVisible(false);
1503       appendRemoveDecoratorCmd(id, false, commandsList);
1504       appendRemoveButtonCmd(id, commandsList);
1505       myLayout.setAnchor(id, anchor, order);
1506       // update infos for all window. Actually we have to update only infos affected by
1507       // setAnchor method
1508       final WindowInfoImpl[] infos = myLayout.getInfos();
1509       for (WindowInfoImpl info1 : infos) {
1510         appendApplyWindowInfoCmd(info1, commandsList);
1511       }
1512       appendAddButtonCmd(getStripeButton(id), info, commandsList);
1513       showToolWindowImpl(id, false, commandsList);
1514       if (info.isActive()) {
1515         appendRequestFocusInToolWindowCmd(id, commandsList, true);
1516       }
1517     }
1518   }
1519
1520   boolean isSplitMode(@NotNull String id) {
1521     ApplicationManager.getApplication().assertIsDispatchThread();
1522     checkId(id);
1523     return getInfo(id).isSplit();
1524   }
1525
1526   @NotNull
1527   ToolWindowContentUiType getContentUiType(@NotNull String id) {
1528     ApplicationManager.getApplication().assertIsDispatchThread();
1529     checkId(id);
1530     return getInfo(id).getContentUiType();
1531   }
1532
1533   void setSideTool(@NotNull String id, boolean isSide) {
1534     List<FinalizableCommand> commandList = new ArrayList<>();
1535     setSplitModeImpl(id, isSide, commandList);
1536     execute(commandList);
1537   }
1538
1539   void setContentUiType(@NotNull String id, @NotNull ToolWindowContentUiType type) {
1540     checkId(id);
1541     WindowInfoImpl info = getInfo(id);
1542     info.setContentUiType(type);
1543     List<FinalizableCommand> commandList = new ArrayList<>();
1544     appendApplyWindowInfoCmd(info, commandList);
1545     execute(commandList);
1546   }
1547
1548   void setSideToolAndAnchor(@NotNull String id, @NotNull ToolWindowAnchor anchor, int order, boolean isSide) {
1549     setToolWindowAnchor(id, anchor, order);
1550     List<FinalizableCommand> commandList = new ArrayList<>();
1551     setSplitModeImpl(id, isSide, commandList);
1552     execute(commandList);
1553   }
1554
1555   private void setSplitModeImpl(@NotNull String id, final boolean isSplit, @NotNull List<FinalizableCommand> commandList) {
1556     checkId(id);
1557     final WindowInfoImpl info = getInfo(id);
1558     if (isSplit == info.isSplit()) {
1559       return;
1560     }
1561
1562     myLayout.setSplitMode(id, isSplit);
1563
1564     boolean wasActive = info.isActive();
1565     if (wasActive) {
1566       deactivateToolWindowImpl(id, true, commandList);
1567     }
1568     final WindowInfoImpl[] infos = myLayout.getInfos();
1569     for (WindowInfoImpl info1 : infos) {
1570       appendApplyWindowInfoCmd(info1, commandList);
1571     }
1572     if (wasActive) {
1573       activateToolWindowImpl(id, commandList, true, true);
1574     }
1575     commandList.add(myToolWindowsPane.createUpdateButtonPositionCmd(id, myWindowManager.getCommandProcessor()));
1576   }
1577
1578   ToolWindowType getToolWindowInternalType(@NotNull String id) {
1579     ApplicationManager.getApplication().assertIsDispatchThread();
1580     checkId(id);
1581     return getInfo(id).getInternalType();
1582   }
1583
1584   ToolWindowType getToolWindowType(@NotNull String id) {
1585     checkId(id);
1586     return getInfo(id).getType();
1587   }
1588
1589   private void fireToolWindowRegistered(@NotNull String id) {
1590     myDispatcher.getMulticaster().toolWindowRegistered(id);
1591   }
1592
1593   private void fireStateChanged() {
1594     myDispatcher.getMulticaster().stateChanged();
1595   }
1596
1597   boolean isToolWindowActive(@NotNull String id) {
1598     ApplicationManager.getApplication().assertIsDispatchThread();
1599     checkId(id);
1600     return getInfo(id).isActive();
1601   }
1602
1603   boolean isToolWindowAutoHide(@NotNull String id) {
1604     ApplicationManager.getApplication().assertIsDispatchThread();
1605     checkId(id);
1606     return getInfo(id).isAutoHide();
1607   }
1608
1609   boolean isToolWindowVisible(@NotNull String id) {
1610     checkId(id);
1611     return getInfo(id).isVisible();
1612   }
1613
1614   void setToolWindowAutoHide(@NotNull String id, final boolean autoHide) {
1615     ApplicationManager.getApplication().assertIsDispatchThread();
1616     List<FinalizableCommand> commandList = new ArrayList<>();
1617     setToolWindowAutoHideImpl(id, autoHide, commandList);
1618     execute(commandList);
1619   }
1620
1621   private void setToolWindowAutoHideImpl(@NotNull String id, final boolean autoHide, @NotNull List<FinalizableCommand> commandsList) {
1622     checkId(id);
1623     final WindowInfoImpl info = getInfo(id);
1624     if (info.isAutoHide() == autoHide) {
1625       return;
1626     }
1627     info.setAutoHide(autoHide);
1628     appendApplyWindowInfoCmd(info, commandsList);
1629     if (info.isVisible()) {
1630       deactivateWindows(id, commandsList);
1631       showAndActivate(id, false, commandsList, true, true);
1632     }
1633   }
1634
1635   void setToolWindowType(@NotNull String id, @NotNull ToolWindowType type) {
1636     ApplicationManager.getApplication().assertIsDispatchThread();
1637     List<FinalizableCommand> commandList = new ArrayList<>();
1638     setToolWindowTypeImpl(id, type, commandList);
1639     execute(commandList);
1640   }
1641
1642   private void setToolWindowTypeImpl(@NotNull String id, @NotNull ToolWindowType type, @NotNull List<FinalizableCommand> commandsList) {
1643     checkId(id);
1644     final WindowInfoImpl info = getInfo(id);
1645     if (info.getType() == type) {
1646       return;
1647     }
1648     if (info.isVisible()) {
1649       final boolean dirtyMode = info.isDocked() || info.isSliding();
1650       info.setVisible(false);
1651       if (info.isFloating()) {
1652         appendRemoveFloatingDecoratorCmd(info, commandsList);
1653       }
1654       else if (info.isWindowed()) {
1655         appendRemoveWindowedDecoratorCmd(info, commandsList);
1656       }
1657       else { // docked and sliding windows
1658         appendRemoveDecoratorCmd(id, dirtyMode, commandsList);
1659       }
1660       info.setType(type);
1661       appendApplyWindowInfoCmd(info, commandsList);
1662       deactivateWindows(id, commandsList);
1663       showAndActivate(id, dirtyMode, commandsList, true, true);
1664       appendUpdateToolWindowsPaneCmd(commandsList);
1665     }
1666     else {
1667       info.setType(type);
1668       appendApplyWindowInfoCmd(info, commandsList);
1669     }
1670   }
1671
1672   private void appendApplyWindowInfoCmd(@NotNull WindowInfoImpl info, @NotNull List<FinalizableCommand> commandsList) {
1673     final StripeButton button = getStripeButton(info.getId());
1674     final InternalDecorator decorator = getInternalDecorator(info.getId());
1675     commandsList.add(new ApplyWindowInfoCmd(info, button, decorator, myWindowManager.getCommandProcessor()));
1676   }
1677
1678   /**
1679    * @see ToolWindowsPane#createAddDecoratorCmd
1680    */
1681   private void appendAddDecoratorCmd(@NotNull InternalDecorator decorator,
1682                                      @NotNull WindowInfoImpl info,
1683                                      final boolean dirtyMode,
1684                                      @NotNull List<FinalizableCommand> commandsList) {
1685     final CommandProcessor commandProcessor = myWindowManager.getCommandProcessor();
1686     final FinalizableCommand command = myToolWindowsPane.createAddDecoratorCmd(decorator, info, dirtyMode, commandProcessor);
1687     commandsList.add(command);
1688   }
1689
1690   /**
1691    * @see ToolWindowsPane#createRemoveDecoratorCmd
1692    */
1693   private void appendRemoveDecoratorCmd(@NotNull String id, final boolean dirtyMode, @NotNull List<FinalizableCommand> commandsList) {
1694     final FinalizableCommand command = myToolWindowsPane.createRemoveDecoratorCmd(id, dirtyMode, myWindowManager.getCommandProcessor());
1695     commandsList.add(command);
1696   }
1697
1698   private void appendRemoveFloatingDecoratorCmd(@NotNull WindowInfoImpl info, @NotNull List<FinalizableCommand> commandsList) {
1699     final RemoveFloatingDecoratorCmd command = new RemoveFloatingDecoratorCmd(info);
1700     commandsList.add(command);
1701   }
1702
1703   private void appendRemoveWindowedDecoratorCmd(@NotNull WindowInfoImpl info, @NotNull List<FinalizableCommand> commandsList) {
1704     final RemoveWindowedDecoratorCmd command = new RemoveWindowedDecoratorCmd(info);
1705     commandsList.add(command);
1706   }
1707
1708   /**
1709    * @see ToolWindowsPane#createAddButtonCmd
1710    */
1711   private void appendAddButtonCmd(final StripeButton button, @NotNull WindowInfoImpl info, @NotNull List<FinalizableCommand> commandsList) {
1712     final Comparator<StripeButton> comparator = myLayout.comparator(info.getAnchor());
1713     final CommandProcessor commandProcessor = myWindowManager.getCommandProcessor();
1714     final FinalizableCommand command = myToolWindowsPane.createAddButtonCmd(button, info, comparator, commandProcessor);
1715     commandsList.add(command);
1716   }
1717
1718   /**
1719    * @see ToolWindowsPane#createAddButtonCmd
1720    */
1721   private void appendRemoveButtonCmd(@NotNull String id, @NotNull List<FinalizableCommand> commandsList) {
1722     final FinalizableCommand command = myToolWindowsPane.createRemoveButtonCmd(id, myWindowManager.getCommandProcessor());
1723     commandsList.add(command);
1724   }
1725
1726   private ActionCallback appendRequestFocusInEditorComponentCmd(List<FinalizableCommand> commandList, boolean forced) {
1727     if (myProject.isDisposed()) return ActionCallback.DONE;
1728     EditorsSplitters splitters = getSplittersToFocus();
1729     CommandProcessor commandProcessor = myWindowManager.getCommandProcessor();
1730     RequestFocusInEditorComponentCmd command = new RequestFocusInEditorComponentCmd(splitters, getFocusManager(), commandProcessor, forced);
1731     commandList.add(command);
1732     return command.getDoneCallback();
1733   }
1734
1735   private void appendRequestFocusInToolWindowCmd(final String id, List<FinalizableCommand> commandList, boolean forced) {
1736     final ToolWindowImpl toolWindow = (ToolWindowImpl)getToolWindow(id);
1737     final FocusWatcher focusWatcher = myId2FocusWatcher.get(id);
1738     commandList
1739       .add(new RequestFocusInToolWindowCmd(getFocusManager(), toolWindow, focusWatcher, myWindowManager.getCommandProcessor(), forced));
1740   }
1741
1742   /**
1743    * @see ToolWindowsPane#createSetEditorComponentCmd
1744    */
1745   private void appendSetEditorComponentCmd(@Nullable final JComponent component, @NotNull List<FinalizableCommand> commandsList) {
1746     final CommandProcessor commandProcessor = myWindowManager.getCommandProcessor();
1747     final FinalizableCommand command = myToolWindowsPane.createSetEditorComponentCmd(component, commandProcessor);
1748     commandsList.add(command);
1749   }
1750
1751   private void appendUpdateToolWindowsPaneCmd(final List<FinalizableCommand> commandsList) {
1752     final JRootPane rootPane = myFrame.getRootPane();
1753     if (rootPane != null) {
1754       final FinalizableCommand command = new UpdateRootPaneCmd(rootPane, myWindowManager.getCommandProcessor());
1755       commandsList.add(command);
1756     }
1757   }
1758
1759   private EditorsSplitters getSplittersToFocus() {
1760     Window activeWindow = myWindowManager.getMostRecentFocusedWindow();
1761
1762     if (activeWindow instanceof FloatingDecorator) {
1763       IdeFocusManager ideFocusManager = IdeFocusManager.findInstanceByComponent(activeWindow);
1764       IdeFrame lastFocusedFrame = ideFocusManager.getLastFocusedFrame();
1765       JComponent frameComponent = lastFocusedFrame != null ? lastFocusedFrame.getComponent() : null;
1766       Window lastFocusedWindow = frameComponent != null ? SwingUtilities.getWindowAncestor(frameComponent) : null;
1767       activeWindow = ObjectUtils.notNull(lastFocusedWindow, activeWindow);
1768     }
1769
1770     FileEditorManagerEx fem = FileEditorManagerEx.getInstanceEx(myProject);
1771     EditorsSplitters splitters = activeWindow != null ? fem.getSplittersFor(activeWindow) : null;
1772     return splitters != null ? splitters : fem.getSplitters();
1773   }
1774
1775   @Override
1776   public void clearSideStack() {
1777     mySideStack.clear();
1778   }
1779
1780   @Override
1781   public void readExternal(final Element element) {
1782     for (final Object o : element.getChildren()) {
1783       final Element e = (Element)o;
1784       if (EDITOR_ELEMENT.equals(e.getName())) {
1785         myEditorWasActive = Boolean.valueOf(e.getAttributeValue(ACTIVE_ATTR_VALUE)).booleanValue();
1786       }
1787       else if (DesktopLayout.TAG.equals(e.getName())) { // read layout of tool windows
1788         myLayout.readExternal(e);
1789       }
1790       else if (LAYOUT_TO_RESTORE.equals(e.getName())) {
1791         myLayoutToRestoreLater = new DesktopLayout();
1792         myLayoutToRestoreLater.readExternal(e);
1793       }
1794     }
1795   }
1796
1797   @Override
1798   public void writeExternal(final Element element) {
1799     if (myFrame == null) {
1800       // do nothing if the project was not opened
1801       return;
1802     }
1803     final String[] ids = getToolWindowIds();
1804
1805     // Update size of all open floating windows. See SCR #18439
1806     for (final String id : ids) {
1807       final WindowInfoImpl info = getInfo(id);
1808       if (info.isVisible()) {
1809         final InternalDecorator decorator = getInternalDecorator(id);
1810         LOG.assertTrue(decorator != null);
1811         decorator.fireResized();
1812       }
1813     }
1814
1815     // Save frame's bounds
1816     final Rectangle frameBounds = myFrame.getBounds();
1817     final Element frameElement = new Element(FRAME_ELEMENT);
1818     element.addContent(frameElement);
1819     frameElement.setAttribute(X_ATTR, Integer.toString(frameBounds.x));
1820     frameElement.setAttribute(Y_ATTR, Integer.toString(frameBounds.y));
1821     frameElement.setAttribute(WIDTH_ATTR, Integer.toString(frameBounds.width));
1822     frameElement.setAttribute(HEIGHT_ATTR, Integer.toString(frameBounds.height));
1823     frameElement.setAttribute(EXTENDED_STATE_ATTR, Integer.toString(myFrame.getExtendedState()));
1824     // Save whether editor is active or not
1825     final Element editorElement = new Element(EDITOR_ELEMENT);
1826     editorElement.setAttribute(ACTIVE_ATTR_VALUE, isEditorComponentActive() ? Boolean.TRUE.toString() : Boolean.FALSE.toString());
1827     element.addContent(editorElement);
1828     // Save layout of tool windows
1829     final Element layoutElement = new Element(DesktopLayout.TAG);
1830     element.addContent(layoutElement);
1831     myLayout.writeExternal(layoutElement);
1832     if (myLayoutToRestoreLater != null) {
1833       Element layoutToRestoreElement = new Element(LAYOUT_TO_RESTORE);
1834       element.addContent(layoutToRestoreElement);
1835       myLayoutToRestoreLater.writeExternal(layoutToRestoreElement);
1836     }
1837   }
1838
1839   public void setDefaultState(@NotNull final ToolWindowImpl toolWindow,
1840                               @Nullable final ToolWindowAnchor anchor,
1841                               @Nullable final ToolWindowType type,
1842                               @Nullable final Rectangle floatingBounds) {
1843
1844     final WindowInfoImpl info = getInfo(toolWindow.getId());
1845     if (info.wasRead()) return;
1846
1847     if (floatingBounds != null) {
1848       info.setFloatingBounds(floatingBounds);
1849     }
1850
1851     if (anchor != null) {
1852       toolWindow.setAnchor(anchor, null);
1853     }
1854
1855     if (type != null) {
1856       toolWindow.setType(type, null);
1857     }
1858   }
1859
1860   void setDefaultContentUiType(@NotNull ToolWindowImpl toolWindow, @NotNull ToolWindowContentUiType type) {
1861     final WindowInfoImpl info = getInfo(toolWindow.getId());
1862     if (info.wasRead()) return;
1863     toolWindow.setContentUiType(type, null);
1864   }
1865
1866
1867   void stretchWidth(@NotNull ToolWindowImpl toolWindow, int value) {
1868     myToolWindowsPane.stretchWidth(toolWindow, value);
1869   }
1870
1871   @Override
1872   public boolean isMaximized(@NotNull ToolWindow wnd) {
1873     return myToolWindowsPane.isMaximized(wnd);
1874   }
1875
1876   @Override
1877   public void setMaximized(@NotNull ToolWindow wnd, boolean maximized) {
1878     myToolWindowsPane.setMaximized(wnd, maximized);
1879   }
1880
1881   void stretchHeight(ToolWindowImpl toolWindow, int value) {
1882     myToolWindowsPane.stretchHeight(toolWindow, value);
1883   }
1884
1885   private static class BalloonHyperlinkListener implements HyperlinkListener {
1886     private Balloon myBalloon;
1887     private final HyperlinkListener myListener;
1888
1889     BalloonHyperlinkListener(HyperlinkListener listener) {
1890       myListener = listener;
1891     }
1892
1893     @Override
1894     public void hyperlinkUpdate(HyperlinkEvent e) {
1895       if (myBalloon != null && e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
1896         myBalloon.hide();
1897       }
1898       if (myListener != null) {
1899         myListener.hyperlinkUpdate(e);
1900       }
1901     }
1902   }
1903
1904
1905   /**
1906    * This command creates and shows <code>FloatingDecorator</code>.
1907    */
1908   private final class AddFloatingDecoratorCmd extends FinalizableCommand {
1909     private final FloatingDecorator myFloatingDecorator;
1910
1911     /**
1912      * Creates floating decorator for specified internal decorator.
1913      */
1914     private AddFloatingDecoratorCmd(@NotNull InternalDecorator decorator, @NotNull WindowInfoImpl info) {
1915       super(myWindowManager.getCommandProcessor());
1916       myFloatingDecorator = new FloatingDecorator(myFrame, info.copy(), decorator);
1917       myId2FloatingDecorator.put(info.getId(), myFloatingDecorator);
1918       final Rectangle bounds = info.getFloatingBounds();
1919       if (bounds != null &&
1920           bounds.width > 0 &&
1921           bounds.height > 0 &&
1922           myWindowManager.isInsideScreenBounds(bounds.x, bounds.y, bounds.width)) {
1923         myFloatingDecorator.setBounds(bounds);
1924       }
1925       else { // place new frame at the center of main frame if there are no floating bounds
1926         Dimension size = decorator.getSize();
1927         if (size.width == 0 || size.height == 0) {
1928           size = decorator.getPreferredSize();
1929         }
1930         myFloatingDecorator.setSize(size);
1931         myFloatingDecorator.setLocationRelativeTo(myFrame);
1932       }
1933     }
1934
1935     @Override
1936     public void run() {
1937       try {
1938         myFloatingDecorator.show();
1939       }
1940       finally {
1941         finish();
1942       }
1943     }
1944   }
1945
1946   /**
1947    * This command hides and destroys floating decorator for tool window
1948    * with specified <code>ID</code>.
1949    */
1950   private final class RemoveFloatingDecoratorCmd extends FinalizableCommand {
1951     private final FloatingDecorator myFloatingDecorator;
1952
1953     private RemoveFloatingDecoratorCmd(@NotNull WindowInfoImpl info) {
1954       super(myWindowManager.getCommandProcessor());
1955       myFloatingDecorator = getFloatingDecorator(info.getId());
1956       myId2FloatingDecorator.remove(info.getId());
1957       info.setFloatingBounds(myFloatingDecorator.getBounds());
1958     }
1959
1960     @Override
1961     public void run() {
1962       try {
1963         myFloatingDecorator.dispose();
1964       }
1965       finally {
1966         finish();
1967       }
1968     }
1969
1970     @Override
1971     @Nullable
1972     public Condition getExpireCondition() {
1973       return ApplicationManager.getApplication().getDisposed();
1974     }
1975   }
1976
1977   /**
1978    * This command creates and shows <code>WindowedDecorator</code>.
1979    */
1980   private final class AddWindowedDecoratorCmd extends FinalizableCommand {
1981     private final WindowedDecorator myWindowedDecorator;
1982
1983     /**
1984      * Creates windowed decorator for specified internal decorator.
1985      */
1986     private AddWindowedDecoratorCmd(@NotNull InternalDecorator decorator, @NotNull WindowInfoImpl info) {
1987       super(myWindowManager.getCommandProcessor());
1988       myWindowedDecorator = new WindowedDecorator(myProject, info.copy(), decorator);
1989       Window window = myWindowedDecorator.getFrame();
1990       final Rectangle bounds = info.getFloatingBounds();
1991       if (bounds != null &&
1992           bounds.width > 0 &&
1993           bounds.height > 0 &&
1994           myWindowManager.isInsideScreenBounds(bounds.x, bounds.y, bounds.width)) {
1995         window.setBounds(bounds);
1996       }
1997       else { // place new frame at the center of main frame if there are no floating bounds
1998         Dimension size = decorator.getSize();
1999         if (size.width == 0 || size.height == 0) {
2000           size = decorator.getPreferredSize();
2001         }
2002         window.setSize(size);
2003         window.setLocationRelativeTo(myFrame);
2004       }
2005       myId2WindowedDecorator.put(info.getId(), myWindowedDecorator);
2006       myWindowedDecorator.addDisposable(() -> {
2007         if (myId2WindowedDecorator.get(info.getId()) != null) {
2008           hideToolWindow(info.getId(), false);
2009         }
2010       });
2011     }
2012
2013     @Override
2014     public void run() {
2015       try {
2016         myWindowedDecorator.show(false);
2017         Window window = myWindowedDecorator.getFrame();
2018         JRootPane rootPane = ((RootPaneContainer)window).getRootPane();
2019         Rectangle rootPaneBounds = rootPane.getBounds();
2020         Point point = rootPane.getLocationOnScreen();
2021         Rectangle windowBounds = window.getBounds();
2022         //Point windowLocation = windowBounds.getLocation();
2023         //windowLocation.translate(windowLocation.x - point.x, windowLocation.y - point.y);
2024         window.setLocation(2 * windowBounds.x - point.x,  2 * windowBounds.y - point.y);
2025         window.setSize(2 * windowBounds.width - rootPaneBounds.width, 2 * windowBounds.height - rootPaneBounds.height);
2026       }
2027       finally {
2028         finish();
2029       }
2030     }
2031   }
2032
2033   /**
2034    * This command hides and destroys floating decorator for tool window
2035    * with specified <code>ID</code>.
2036    */
2037   private final class RemoveWindowedDecoratorCmd extends FinalizableCommand {
2038     private final WindowedDecorator myWindowedDecorator;
2039
2040     private RemoveWindowedDecoratorCmd(@NotNull WindowInfoImpl info) {
2041       super(myWindowManager.getCommandProcessor());
2042       myWindowedDecorator = getWindowedDecorator(info.getId());
2043       myId2WindowedDecorator.remove(info.getId());
2044
2045       Window frame = myWindowedDecorator.getFrame();
2046       if (!frame.isShowing()) return;
2047       Rectangle bounds = getRootBounds((JFrame)frame);
2048       info.setFloatingBounds(bounds);
2049     }
2050
2051     @Override
2052     public void run() {
2053       try {
2054         Disposer.dispose(myWindowedDecorator);
2055       }
2056       finally {
2057         finish();
2058       }
2059     }
2060
2061     @Override
2062     @Nullable
2063     public Condition getExpireCondition() {
2064       return ApplicationManager.getApplication().getDisposed();
2065     }
2066   }
2067
2068   @NotNull
2069   private static Rectangle getRootBounds(JFrame frame) {
2070     JRootPane rootPane = frame.getRootPane();
2071     Rectangle bounds = rootPane.getBounds();
2072     bounds.setLocation(frame.getX() + rootPane.getX(), frame.getY() + rootPane.getY());
2073     return bounds;
2074   }
2075
2076   private final class EditorComponentFocusWatcher extends FocusWatcher {
2077     @Override
2078     protected void focusedComponentChanged(final Component component, final AWTEvent cause) {
2079       if (myWindowManager.getCommandProcessor().getCommandCount() > 0 || component == null) {
2080         return;
2081       }
2082       final KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager();
2083       final Component owner = mgr.getFocusOwner();
2084
2085       if (owner instanceof EditorComponentImpl && cause instanceof FocusEvent) {
2086         JFrame frame = WindowManager.getInstance().getFrame(myProject);
2087         Component oppositeComponent = ((FocusEvent)cause).getOppositeComponent();
2088         if (oppositeComponent != null && UIUtil.getWindow(oppositeComponent) != frame) {
2089           return;
2090         }
2091       }
2092
2093       IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(new ExpirableRunnable.ForProject(myProject) {
2094         @Override
2095         public void run() {
2096           if (mgr.getFocusOwner() == owner) {
2097             activateEditorComponent(false);
2098           }
2099         }
2100       });
2101     }
2102   }
2103
2104
2105   /**
2106    * Notifies window manager about focus traversal in tool window
2107    */
2108   private final class ToolWindowFocusWatcher extends FocusWatcher {
2109     private final String myId;
2110     private final ToolWindowImpl myToolWindow;
2111
2112     private ToolWindowFocusWatcher(@NotNull ToolWindowImpl toolWindow) {
2113       myId = toolWindow.getId();
2114       install(toolWindow.getComponent());
2115       myToolWindow = toolWindow;
2116     }
2117
2118     public void deinstall() {
2119       deinstall(myToolWindow.getComponent());
2120     }
2121
2122     @Override
2123     protected boolean isFocusedComponentChangeValid(final Component comp, final AWTEvent cause) {
2124       return myWindowManager.getCommandProcessor().getCommandCount() == 0 && comp != null;
2125     }
2126
2127     @Override
2128     protected void focusedComponentChanged(final Component component, final AWTEvent cause) {
2129       if (myWindowManager.getCommandProcessor().getCommandCount() > 0 || component == null) {
2130         return;
2131       }
2132       final WindowInfoImpl info = getInfo(myId);
2133       //getFocusManagerImpl(myProject)..cancelAllRequests();
2134
2135       if (!info.isActive()) {
2136         getFocusManagerImpl(myProject).doWhenFocusSettlesDown(new EdtRunnable() {
2137           @Override
2138           public void runEdt() {
2139             WindowInfoImpl windowInfo = myLayout.getInfo(myId, true);
2140             if (windowInfo == null || !windowInfo.isVisible()) return;
2141             activateToolWindow(myId, false, false);
2142           }
2143         });
2144       }
2145     }
2146   }
2147
2148   /**
2149    * Spies on IdeToolWindow properties and applies them to the window
2150    * state.
2151    */
2152   private final class MyToolWindowPropertyChangeListener implements PropertyChangeListener {
2153     @Override
2154     public void propertyChange(final PropertyChangeEvent e) {
2155       ToolWindowImpl toolWindow = (ToolWindowImpl)e.getSource();
2156       if (ToolWindowEx.PROP_AVAILABLE.equals(e.getPropertyName())) {
2157         final WindowInfoImpl info = getInfo(toolWindow.getId());
2158         if (!toolWindow.isAvailable() && info.isVisible()) {
2159           hideToolWindow(toolWindow.getId(), false);
2160         }
2161       }
2162       StripeButton button = myId2StripeButton.get(toolWindow.getId());
2163       if (button != null) button.updatePresentation();
2164       ActivateToolWindowAction.updateToolWindowActionPresentation(toolWindow);
2165     }
2166   }
2167
2168   /**
2169    * Translates events from InternalDecorator into ToolWindowManager method invocations.
2170    */
2171   private final class MyInternalDecoratorListener implements InternalDecoratorListener {
2172     @Override
2173     public void anchorChanged(@NotNull final InternalDecorator source, @NotNull final ToolWindowAnchor anchor) {
2174       setToolWindowAnchor(source.getToolWindow().getId(), anchor);
2175     }
2176
2177     @Override
2178     public void autoHideChanged(@NotNull final InternalDecorator source, final boolean autoHide) {
2179       setToolWindowAutoHide(source.getToolWindow().getId(), autoHide);
2180     }
2181
2182     @Override
2183     public void hidden(@NotNull final InternalDecorator source) {
2184       hideToolWindow(source.getToolWindow().getId(), false);
2185     }
2186
2187     @Override
2188     public void hiddenSide(@NotNull final InternalDecorator source) {
2189       hideToolWindow(source.getToolWindow().getId(), true);
2190     }
2191
2192     @Override
2193     public void contentUiTypeChanges(@NotNull InternalDecorator source, @NotNull ToolWindowContentUiType type) {
2194       setContentUiType(source.getToolWindow().getId(), type);
2195     }
2196
2197     /**
2198      * Handles event from decorator and modify weight/floating bounds of the
2199      * tool window depending on decoration type.
2200      */
2201     @Override
2202     public void resized(@NotNull final InternalDecorator source) {
2203       if (!source.isShowing()) {
2204         return; // do not recalculate the tool window size if it is not yet shown (and, therefore, has 0,0,0,0 bounds)
2205       }
2206
2207       final WindowInfoImpl info = getInfo(source.getToolWindow().getId());
2208       if (info.isFloating()) {
2209         final Window owner = SwingUtilities.getWindowAncestor(source);
2210         if (owner != null) {
2211           info.setFloatingBounds(owner.getBounds());
2212         }
2213       }
2214       else if (info.isWindowed()) {
2215         WindowedDecorator decorator = getWindowedDecorator(info.getId());
2216         Window frame = decorator != null ? decorator.getFrame() : null;
2217         if (frame == null || !frame.isShowing()) return;
2218         info.setFloatingBounds(getRootBounds((JFrame)frame));
2219       }
2220       else { // docked and sliding windows
2221         ToolWindowAnchor anchor = info.getAnchor();
2222         InternalDecorator another = null;
2223         if (source.getParent() instanceof Splitter) {
2224           float sizeInSplit = anchor.isSplitVertically() ? source.getHeight() : source.getWidth();
2225           Splitter splitter = (Splitter)source.getParent();
2226           if (splitter.getSecondComponent() == source) {
2227             sizeInSplit += splitter.getDividerWidth();
2228             another = (InternalDecorator)splitter.getFirstComponent();
2229           }
2230           else {
2231             another = (InternalDecorator)splitter.getSecondComponent();
2232           }
2233           if (anchor.isSplitVertically()) {
2234             info.setSideWeight(sizeInSplit / (float)splitter.getHeight());
2235           }
2236           else {
2237             info.setSideWeight(sizeInSplit / (float)splitter.getWidth());
2238           }
2239         }
2240
2241         float paneWeight = anchor.isHorizontal()
2242                            ? (float)source.getHeight() / (float)myToolWindowsPane.getMyLayeredPane().getHeight()
2243                            : (float)source.getWidth() / (float)myToolWindowsPane.getMyLayeredPane().getWidth();
2244         info.setWeight(paneWeight);
2245         if (another != null && anchor.isSplitVertically()) {
2246           paneWeight = anchor.isHorizontal()
2247                        ? (float)another.getHeight() / (float)myToolWindowsPane.getMyLayeredPane().getHeight()
2248                        : (float)another.getWidth() / (float)myToolWindowsPane.getMyLayeredPane().getWidth();
2249           another.getWindowInfo().setWeight(paneWeight);
2250         }
2251       }
2252     }
2253
2254     @Override
2255     public void activated(@NotNull final InternalDecorator source) {
2256       activateToolWindow(source.getToolWindow().getId(), true, true);
2257     }
2258
2259     @Override
2260     public void typeChanged(@NotNull final InternalDecorator source, @NotNull final ToolWindowType type) {
2261       setToolWindowType(source.getToolWindow().getId(), type);
2262     }
2263
2264     @Override
2265     public void sideStatusChanged(@NotNull final InternalDecorator source, final boolean isSideTool) {
2266       setSideTool(source.getToolWindow().getId(), isSideTool);
2267     }
2268
2269     @Override
2270     public void visibleStripeButtonChanged(@NotNull InternalDecorator source, boolean visible) {
2271       setShowStripeButton(source.getToolWindow().getId(), visible);
2272     }
2273   }
2274
2275   private void updateComponentTreeUI() {
2276     ApplicationManager.getApplication().assertIsDispatchThread();
2277     final WindowInfoImpl[] infos = myLayout.getInfos();
2278     for (WindowInfoImpl info : infos) {
2279       // the main goal is to update hidden TW components because they are not in the hierarchy
2280       // and will not be updated automatically but unfortunately the visibility of a TW may change
2281       // during the same actionPerformed() so we can't optimize and have to process all of them
2282       IJSwingUtilities.updateComponentTreeUI(getInternalDecorator(info.getId()));
2283     }
2284   }
2285
2286   private final class MyUIManagerPropertyChangeListener implements PropertyChangeListener {
2287     @Override
2288     public void propertyChange(final PropertyChangeEvent e) {
2289       updateComponentTreeUI();
2290     }
2291   }
2292
2293   private final class MyLafManagerListener implements LafManagerListener {
2294     @Override
2295     public void lookAndFeelChanged(final LafManager source) {
2296       updateComponentTreeUI();
2297     }
2298   }
2299
2300
2301   @Override
2302   @NotNull
2303   public String getComponentName() {
2304     return "ToolWindowManager";
2305   }
2306
2307   @NotNull
2308   public ActionCallback requestDefaultFocus(final boolean forced) {
2309     return getFocusManagerImpl(myProject).requestFocus(new FocusCommand() {
2310       @NotNull
2311       @Override
2312       public ActionCallback run() {
2313         return processDefaultFocusRequest(forced);
2314       }
2315     }, forced);
2316   }
2317
2318   private void focusToolWinowByDefault(@Nullable String idToIngore) {
2319     String toFocus = null;
2320
2321     for (String each : myActiveStack.getStack()) {
2322       if (idToIngore != null && idToIngore.equalsIgnoreCase(each)) continue;
2323
2324       if (getInfo(each).isVisible()) {
2325         toFocus = each;
2326         break;
2327       }
2328     }
2329
2330     if (toFocus == null) {
2331       for (String each : myActiveStack.getPersistentStack()) {
2332         if (idToIngore != null && idToIngore.equalsIgnoreCase(each)) continue;
2333
2334         if (getInfo(each).isVisible()) {
2335           toFocus = each;
2336           break;
2337         }
2338       }
2339     }
2340
2341     if (toFocus != null) {
2342       activateToolWindow(toFocus, false, true);
2343     }
2344   }
2345
2346   private ActionCallback processDefaultFocusRequest(boolean forced) {
2347     if (ModalityState.NON_MODAL.equals(ModalityState.current())) {
2348       final String activeId = getActiveToolWindowId();
2349       if (isEditorComponentActive() || activeId == null || getToolWindow(activeId) == null) {
2350         activateEditorComponent(forced, true);
2351       }
2352       else {
2353         activateToolWindow(activeId, forced, true);
2354       }
2355
2356       return ActionCallback.DONE;
2357     }
2358     Window activeWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
2359     if (activeWindow != null) {
2360       JRootPane root = null;
2361       if (activeWindow instanceof JDialog) {
2362         root = ((JDialog)activeWindow).getRootPane();
2363       }
2364       else if (activeWindow instanceof JFrame) {
2365         root = ((JFrame)activeWindow).getRootPane();
2366       }
2367
2368       if (root != null) {
2369         JComponent toFocus = IdeFocusTraversalPolicy.getPreferredFocusedComponent(root);
2370         if (toFocus != null) {
2371           if (DialogWrapper.findInstance(toFocus) != null) {
2372             return ActionCallback.DONE; //IDEA-80929
2373           }
2374           return IdeFocusManager.findInstanceByComponent(toFocus).requestFocus(toFocus, forced);
2375         }
2376       }
2377     }
2378     return ActionCallback.REJECTED;
2379   }
2380
2381
2382   /**
2383    * Delegate method for compatibility with older versions of IDEA
2384    */
2385   @NotNull
2386   public ActionCallback requestFocus(@NotNull Component c, boolean forced) {
2387     return IdeFocusManager.getInstance(myProject).requestFocus(c, forced);
2388   }
2389
2390   @NotNull
2391   public ActionCallback requestFocus(@NotNull FocusCommand command, boolean forced) {
2392     return IdeFocusManager.getInstance(myProject).requestFocus(command, forced);
2393   }
2394
2395   public void doWhenFocusSettlesDown(@NotNull Runnable runnable) {
2396     IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(runnable);
2397   }
2398
2399   public boolean dispatch(@NotNull KeyEvent e) {
2400     return IdeFocusManager.getInstance(myProject).dispatch(e);
2401   }
2402
2403   @NotNull
2404   public Expirable getTimestamp(boolean trackOnlyForcedCommands) {
2405     return IdeFocusManager.getInstance(myProject).getTimestamp(trackOnlyForcedCommands);
2406   }
2407
2408   void setShowStripeButton(@NotNull String id, boolean visibleOnPanel) {
2409     checkId(id);
2410     WindowInfoImpl info = getInfo(id);
2411     if (visibleOnPanel == info.isShowStripeButton()) {
2412       return;
2413     }
2414     info.setShowStripeButton(visibleOnPanel);
2415     UsageTrigger.trigger("StripeButton[" + id + "]." + (visibleOnPanel ? "shown" : "hidden"));
2416
2417     List<FinalizableCommand> commandList = new ArrayList<>();
2418     appendApplyWindowInfoCmd(info, commandList);
2419     execute(commandList);
2420   }
2421
2422   boolean isShowStripeButton(@NotNull String id) {
2423     WindowInfoImpl info = getInfo(id);
2424     return info == null || info.isShowStripeButton();
2425   }
2426
2427   public static class InitToolWindowsActivity implements StartupActivity, DumbAware {
2428     @Override
2429     public void runActivity(@NotNull Project project) {
2430       ToolWindowManagerEx ex = ToolWindowManagerEx.getInstanceEx(project);
2431       if (ex instanceof ToolWindowManagerImpl) {
2432         ToolWindowManagerImpl myManager = (ToolWindowManagerImpl)ex;
2433         List<FinalizableCommand> list = new ArrayList<>();
2434         myManager.registerToolWindowsFromBeans(list);
2435         myManager.initAll(list);
2436         EdtInvocationManager.getInstance().invokeLater(() -> {
2437           myManager.execute(list);
2438         });
2439       }
2440     }
2441   }
2442 }