replaced <code></code> with more concise {@code}
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / wm / impl / ToolWindowImpl.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.UiActivity;
19 import com.intellij.ide.UiActivityMonitor;
20 import com.intellij.ide.impl.ContentManagerWatcher;
21 import com.intellij.notification.EventLog;
22 import com.intellij.openapi.actionSystem.ActionGroup;
23 import com.intellij.openapi.actionSystem.AnAction;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.application.ModalityState;
26 import com.intellij.openapi.components.ServiceManager;
27 import com.intellij.openapi.diagnostic.Logger;
28 import com.intellij.openapi.util.ActionCallback;
29 import com.intellij.openapi.util.BusyObject;
30 import com.intellij.openapi.util.Disposer;
31 import com.intellij.openapi.wm.*;
32 import com.intellij.openapi.wm.ex.ToolWindowEx;
33 import com.intellij.openapi.wm.ex.WindowManagerEx;
34 import com.intellij.openapi.wm.impl.commands.FinalizableCommand;
35 import com.intellij.openapi.wm.impl.content.ToolWindowContentUi;
36 import com.intellij.ui.LayeredIcon;
37 import com.intellij.ui.content.Content;
38 import com.intellij.ui.content.ContentFactory;
39 import com.intellij.ui.content.ContentManager;
40 import com.intellij.ui.content.impl.ContentImpl;
41 import com.intellij.util.ObjectUtils;
42 import com.intellij.util.containers.HashSet;
43 import com.intellij.util.ui.JBUI;
44 import com.intellij.util.ui.update.Activatable;
45 import com.intellij.util.ui.update.UiNotifyConnector;
46 import org.jetbrains.annotations.NotNull;
47 import org.jetbrains.annotations.Nullable;
48
49 import javax.swing.*;
50 import java.awt.*;
51 import java.awt.event.InputEvent;
52 import java.awt.event.KeyEvent;
53 import java.beans.PropertyChangeListener;
54 import java.beans.PropertyChangeSupport;
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.Set;
58
59 /**
60  * @author Anton Katilin
61  * @author Vladimir Kondratyev
62  */
63 public final class ToolWindowImpl implements ToolWindowEx {
64   private final PropertyChangeSupport myChangeSupport = new PropertyChangeSupport(this);
65   private final ToolWindowManagerImpl myToolWindowManager;
66   private final String myId;
67   private final JComponent myComponent;
68   private boolean myAvailable = true;
69   private final ContentManager myContentManager;
70   private Icon myIcon;
71   private String myStripeTitle;
72
73   private static final Content EMPTY_CONTENT = new ContentImpl(new JLabel(), "", false);
74   private final ToolWindowContentUi myContentUI;
75
76   private InternalDecorator myDecorator;
77
78   private boolean myHideOnEmptyContent;
79   private boolean myPlaceholderMode;
80   private ToolWindowFactory myContentFactory;
81
82   private static final Set<KeyStroke> FORWARD_TRAVERSAL_KEYSTROKES = new HashSet<>(Arrays.asList(
83     new KeyStroke[]{
84       KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)
85     }
86   ));
87
88   private static final Set<KeyStroke> BACKWARD_TRAVERSAL_KEYSTROKES = new HashSet<>(Arrays.asList(
89     new KeyStroke[]{
90       KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK)
91     }
92   ));
93
94   @NotNull
95   private ActionCallback myActivation = ActionCallback.DONE;
96   private final BusyObject.Impl myShowing = new BusyObject.Impl() {
97     @Override
98     public boolean isReady() {
99       return myComponent != null && myComponent.isShowing();
100     }
101   };
102   private boolean myUseLastFocused = true;
103
104   private static final Logger LOG = Logger.getInstance(ToolWindowImpl.class);
105
106   ToolWindowImpl(@NotNull ToolWindowManagerImpl toolWindowManager, @NotNull String id, boolean canCloseContent, @Nullable final JComponent component) {
107     myToolWindowManager = toolWindowManager;
108     myId = id;
109
110     final ContentFactory contentFactory = ServiceManager.getService(ContentFactory.class);
111     myContentUI = new ToolWindowContentUi(this);
112     myContentManager = contentFactory.createContentManager(myContentUI, canCloseContent, toolWindowManager.getProject());
113
114     if (component != null) {
115       final Content content = contentFactory.createContent(component, "", false);
116       myContentManager.addContent(content);
117       myContentManager.setSelectedContent(content, false);
118     }
119
120     myComponent = myContentManager.getComponent();
121
122     installToolwindowFocusPolicy();
123
124     UiNotifyConnector notifyConnector = new UiNotifyConnector(myComponent, new Activatable.Adapter() {
125       @Override
126       public void showNotify() {
127         myShowing.onReady();
128       }
129     });
130     Disposer.register(myContentManager, notifyConnector);
131   }
132
133   /**
134    * Installs a focus traversal policy for the tool window.
135    * If the policy cannot handle a keystroke, it delegates the handling to
136    * the nearest ancestors focus traversal policy. For instance,
137    * this policy does not handle KeyEvent.VK_ESCAPE, so it can delegate the handling
138    * to a ThreeComponentSplitter instance.
139    */
140   private void installToolwindowFocusPolicy() {
141
142     myComponent.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, FORWARD_TRAVERSAL_KEYSTROKES);
143     myComponent.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, BACKWARD_TRAVERSAL_KEYSTROKES);
144
145     FocusTraversalPolicy layoutFocusTraversalPolicy = new LayoutFocusTraversalPolicy();
146
147     myComponent.setFocusCycleRoot(true);
148     myComponent.setFocusTraversalPolicyProvider(true);
149     myComponent.setFocusTraversalPolicy(new FocusTraversalPolicy() {
150       @Override
151       public Component getComponentAfter(Container container, Component component) {
152         return layoutFocusTraversalPolicy.getComponentAfter(container, component);
153       }
154
155       @Override
156       public Component getComponentBefore(Container container, Component component) {
157         return layoutFocusTraversalPolicy.getComponentBefore(container, component);
158       }
159
160       @Override
161       public Component getFirstComponent(Container container) {
162         return layoutFocusTraversalPolicy.getFirstComponent(container);
163       }
164
165       @Override
166       public Component getLastComponent(Container container) {
167         return layoutFocusTraversalPolicy.getLastComponent(container);
168       }
169
170       @Override
171       public Component getDefaultComponent(Container container) {
172         return layoutFocusTraversalPolicy.getDefaultComponent(container);
173       }
174     });
175   }
176
177   public final void addPropertyChangeListener(final PropertyChangeListener l) {
178     myChangeSupport.addPropertyChangeListener(l);
179   }
180
181   @Override
182   public final void removePropertyChangeListener(final PropertyChangeListener l) {
183     myChangeSupport.removePropertyChangeListener(l);
184   }
185
186   @Override
187   public final void activate(final Runnable runnable) {
188     activate(runnable, true);
189   }
190
191   @Override
192   public void activate(@Nullable final Runnable runnable, final boolean autoFocusContents) {
193     activate(runnable, autoFocusContents, true);
194   }
195
196   @Override
197   public void activate(@Nullable final Runnable runnable, boolean autoFocusContents, boolean forced) {
198     ApplicationManager.getApplication().assertIsDispatchThread();
199
200     final UiActivity activity = new UiActivity.Focus("toolWindow:" + myId);
201     UiActivityMonitor.getInstance().addActivity(myToolWindowManager.getProject(), activity, ModalityState.NON_MODAL);
202
203     myToolWindowManager.activateToolWindow(myId, forced, autoFocusContents);
204
205     getActivation().doWhenDone(() -> myToolWindowManager.invokeLater(() -> {
206       if (runnable != null) {
207         runnable.run();
208       }
209       UiActivityMonitor.getInstance().removeActivity(myToolWindowManager.getProject(), activity);
210     }));
211   }
212
213   @Override
214   public final boolean isActive() {
215     ApplicationManager.getApplication().assertIsDispatchThread();
216
217     IdeFrameImpl frame = WindowManagerEx.getInstanceEx().getFrame(myToolWindowManager.getProject());
218     if (frame == null || !frame.isActive()) return false;
219
220     if (myToolWindowManager.isEditorComponentActive()) return false;
221     return myToolWindowManager.isToolWindowActive(myId) || myDecorator != null && myDecorator.isFocused();
222   }
223
224   @NotNull
225   @Override
226   public ActionCallback getReady(@NotNull final Object requestor) {
227     final ActionCallback result = new ActionCallback();
228     myShowing.getReady(this).doWhenDone(() -> {
229       ArrayList<FinalizableCommand> cmd = new ArrayList<>();
230       cmd.add(new FinalizableCommand(null) {
231         @Override
232         public void run() {
233           IdeFocusManager.getInstance(myToolWindowManager.getProject()).doWhenFocusSettlesDown(() -> {
234             if (myContentManager.isDisposed()) return;
235             myContentManager.getReady(requestor).notify(result);
236           });
237         }
238       });
239       myToolWindowManager.execute(cmd);
240     });
241     return result;
242   }
243
244   @Override
245   public final void show(final Runnable runnable) {
246     ApplicationManager.getApplication().assertIsDispatchThread();
247     myToolWindowManager.showToolWindow(myId);
248     if (runnable != null) {
249       getActivation().doWhenDone(() -> myToolWindowManager.invokeLater(runnable));
250     }
251   }
252
253   @Override
254   public final void hide(@Nullable final Runnable runnable) {
255     ApplicationManager.getApplication().assertIsDispatchThread();
256     myToolWindowManager.hideToolWindow(myId, false);
257     if (runnable != null) {
258       myToolWindowManager.invokeLater(runnable);
259     }
260   }
261
262   @Override
263   public final boolean isVisible() {
264     return myToolWindowManager.isToolWindowVisible(myId);
265   }
266
267   @Override
268   public final ToolWindowAnchor getAnchor() {
269     return myToolWindowManager.getToolWindowAnchor(myId);
270   }
271
272   @Override
273   public final void setAnchor(@NotNull final ToolWindowAnchor anchor, @Nullable final Runnable runnable) {
274     ApplicationManager.getApplication().assertIsDispatchThread();
275     myToolWindowManager.setToolWindowAnchor(myId, anchor);
276     if (runnable != null) {
277       myToolWindowManager.invokeLater(runnable);
278     }
279   }
280
281   @Override
282   public boolean isSplitMode() {
283     ApplicationManager.getApplication().assertIsDispatchThread();
284     return myToolWindowManager.isSplitMode(myId);
285   }
286
287   @Override
288   public void setContentUiType(@NotNull ToolWindowContentUiType type, @Nullable Runnable runnable) {
289     ApplicationManager.getApplication().assertIsDispatchThread();
290     myToolWindowManager.setContentUiType(myId, type);
291     if (runnable != null) {
292       myToolWindowManager.invokeLater(runnable);
293     }
294   }
295
296   @Override
297   public void setDefaultContentUiType(@NotNull ToolWindowContentUiType type) {
298     myToolWindowManager.setDefaultContentUiType(this, type);
299   }
300
301   @NotNull
302   @Override
303   public ToolWindowContentUiType getContentUiType() {
304     ApplicationManager.getApplication().assertIsDispatchThread();
305     return myToolWindowManager.getContentUiType(myId);
306   }
307
308   @Override
309   public void setSplitMode(final boolean isSideTool, @Nullable final Runnable runnable) {
310     ApplicationManager.getApplication().assertIsDispatchThread();
311     myToolWindowManager.setSideTool(myId, isSideTool);
312     if (runnable != null) {
313       myToolWindowManager.invokeLater(runnable);
314     }
315   }
316
317   @Override
318   public final void setAutoHide(final boolean state) {
319     ApplicationManager.getApplication().assertIsDispatchThread();
320     myToolWindowManager.setToolWindowAutoHide(myId, state);
321   }
322
323   @Override
324   public final boolean isAutoHide() {
325     ApplicationManager.getApplication().assertIsDispatchThread();
326     return myToolWindowManager.isToolWindowAutoHide(myId);
327   }
328
329   @Override
330   public final ToolWindowType getType() {
331     return myToolWindowManager.getToolWindowType(myId);
332   }
333
334   @Override
335   public final void setType(@NotNull final ToolWindowType type, @Nullable final Runnable runnable) {
336     ApplicationManager.getApplication().assertIsDispatchThread();
337     myToolWindowManager.setToolWindowType(myId, type);
338     if (runnable != null) {
339       myToolWindowManager.invokeLater(runnable);
340     }
341   }
342
343   @Override
344   public final ToolWindowType getInternalType() {
345     ApplicationManager.getApplication().assertIsDispatchThread();
346     return myToolWindowManager.getToolWindowInternalType(myId);
347   }
348
349   @Override
350   public void stretchWidth(int value) {
351     myToolWindowManager.stretchWidth(this, value);
352   }
353
354   @Override
355   public void stretchHeight(int value) {
356     myToolWindowManager.stretchHeight(this, value);
357   }
358
359   @Override
360   public InternalDecorator getDecorator() {
361     return myDecorator;
362   }
363
364   @Override
365   public void setAdditionalGearActions(ActionGroup additionalGearActions) {
366     getDecorator().setAdditionalGearActions(additionalGearActions);
367   }
368
369   @Override
370   public void setTitleActions(AnAction... actions) {
371     getDecorator().setTitleActions(actions);
372   }
373
374   @Override
375   public final void setAvailable(final boolean available, final Runnable runnable) {
376     ApplicationManager.getApplication().assertIsDispatchThread();
377     final Boolean oldAvailable = myAvailable ? Boolean.TRUE : Boolean.FALSE;
378     myAvailable = available;
379     myChangeSupport.firePropertyChange(PROP_AVAILABLE, oldAvailable, myAvailable ? Boolean.TRUE : Boolean.FALSE);
380     if (runnable != null) {
381       myToolWindowManager.invokeLater(runnable);
382     }
383   }
384
385   @Override
386   public void installWatcher(ContentManager contentManager) {
387     new ContentManagerWatcher(this, contentManager);
388   }
389
390   /**
391    * @return {@code true} if the component passed into constructor is not instance of
392    *         {@code ContentManager} class. Otherwise it delegates the functionality to the
393    *         passed content manager.
394    */
395   @Override
396   public final boolean isAvailable() {
397     return myAvailable && myComponent != null;
398   }
399
400   @Override
401   public final JComponent getComponent() {
402     return myComponent;
403   }
404
405   @Override
406   public ContentManager getContentManager() {
407     ensureContentInitialized();
408     return myContentManager;
409   }
410
411   public ToolWindowContentUi getContentUI() {
412     return myContentUI;
413   }
414
415   @Override
416   public final Icon getIcon() {
417     ApplicationManager.getApplication().assertIsDispatchThread();
418     return myIcon;
419     //return getSelectedContent().getIcon();
420   }
421
422   @NotNull
423   public final String getId() {
424     return myId;
425   }
426
427   @Override
428   public final String getTitle() {
429     ApplicationManager.getApplication().assertIsDispatchThread();
430     return getSelectedContent().getDisplayName();
431   }
432
433   @Override
434   @NotNull
435   public final String getStripeTitle() {
436     ApplicationManager.getApplication().assertIsDispatchThread();
437     return ObjectUtils.notNull(myStripeTitle, myId);
438   }
439
440   @Override
441   public final void setIcon(final Icon icon) {
442     ApplicationManager.getApplication().assertIsDispatchThread();
443     final Icon oldIcon = getIcon();
444     if (!EventLog.LOG_TOOL_WINDOW_ID.equals(getId())) {
445       if (oldIcon != icon && icon != null && !(icon instanceof LayeredIcon) && (icon.getIconHeight() != Math.floor(JBUI.scale(13f)) ||
446                                                                                 icon.getIconWidth() != Math.floor(JBUI.scale(13f)))) {
447         LOG.warn("ToolWindow icons should be 13x13. Please fix ToolWindow (ID:  " + getId() + ") or icon " + icon);
448       }
449     }
450     //getSelectedContent().setIcon(icon);
451     myIcon = icon;
452     myChangeSupport.firePropertyChange(PROP_ICON, oldIcon, icon);
453   }
454
455   @Override
456   public final void setTitle(String title) {
457     ApplicationManager.getApplication().assertIsDispatchThread();
458     String oldTitle = getTitle();
459     getSelectedContent().setDisplayName(title);
460     myChangeSupport.firePropertyChange(PROP_TITLE, oldTitle, title);
461   }
462
463   @Override
464   public final void setStripeTitle(@NotNull String stripeTitle) {
465     ApplicationManager.getApplication().assertIsDispatchThread();
466     String oldTitle = myStripeTitle;
467     myStripeTitle = stripeTitle;
468     myChangeSupport.firePropertyChange(PROP_STRIPE_TITLE, oldTitle, stripeTitle);
469   }
470
471   private Content getSelectedContent() {
472     final Content selected = getContentManager().getSelectedContent();
473     return selected != null ? selected : EMPTY_CONTENT;
474   }
475
476   public void setDecorator(final InternalDecorator decorator) {
477     myDecorator = decorator;
478   }
479
480   public void fireActivated() {
481     if (myDecorator != null) {
482       myDecorator.fireActivated();
483     }
484   }
485
486   public void fireHidden() {
487     if (myDecorator != null) {
488       myDecorator.fireHidden();
489     }
490   }
491
492   public void fireHiddenSide() {
493     if (myDecorator != null) {
494       myDecorator.fireHiddenSide();
495     }
496   }
497
498
499   public ToolWindowManagerImpl getToolWindowManager() {
500     return myToolWindowManager;
501   }
502
503   @Nullable
504   public ActionGroup getPopupGroup() {
505     return myDecorator != null ? myDecorator.createPopupGroup() : null;
506   }
507
508   @Override
509   public void setDefaultState(@Nullable final ToolWindowAnchor anchor, @Nullable final ToolWindowType type, @Nullable final Rectangle floatingBounds) {
510     myToolWindowManager.setDefaultState(this, anchor, type, floatingBounds);
511   }
512
513   @Override
514   public void setToHideOnEmptyContent(final boolean hideOnEmpty) {
515     myHideOnEmptyContent = hideOnEmpty;
516   }
517
518   @Override
519   public boolean isToHideOnEmptyContent() {
520     return myHideOnEmptyContent;
521   }
522
523   @Override
524   public void setShowStripeButton(boolean show) {
525     myToolWindowManager.setShowStripeButton(myId, show);
526   }
527
528   @Override
529   public boolean isShowStripeButton() {
530     return myToolWindowManager.isShowStripeButton(myId);
531   }
532
533   @Override
534   public boolean isDisposed() {
535     return myContentManager.isDisposed();
536   }
537
538   boolean isPlaceholderMode() {
539     return myPlaceholderMode;
540   }
541
542   void setPlaceholderMode(final boolean placeholderMode) {
543     myPlaceholderMode = placeholderMode;
544   }
545
546   @Override
547   @NotNull
548   public ActionCallback getActivation() {
549     return myActivation;
550   }
551
552   @NotNull
553   ActionCallback setActivation(@NotNull ActionCallback activation) {
554     if (!myActivation.isProcessed() && !myActivation.equals(activation)) {
555       myActivation.setRejected();
556     }
557
558     myActivation = activation;
559     return myActivation;
560   }
561
562   public void setContentFactory(ToolWindowFactory contentFactory) {
563     myContentFactory = contentFactory;
564     contentFactory.init(this);
565   }
566
567   public void ensureContentInitialized() {
568     if (myContentFactory != null) {
569       ToolWindowFactory contentFactory = myContentFactory;
570       // clear it first to avoid SOE
571       myContentFactory = null;
572       myContentManager.removeAllContents(false);
573       contentFactory.createToolWindowContent(myToolWindowManager.getProject(), this);
574     }
575   }
576
577   @Override
578   public void showContentPopup(InputEvent inputEvent) {
579     myContentUI.toggleContentPopup();
580   }
581
582   @Override
583   public void setUseLastFocusedOnActivation(boolean focus) {
584     myUseLastFocused = focus;
585   }
586
587   @Override
588   public boolean isUseLastFocusedOnActivation() {
589     return myUseLastFocused;
590   }
591 }