switcher - auto selection + fixes
[idea/community.git] / platform / lang-impl / src / com / intellij / execution / ui / layout / impl / GridCellImpl.java
1 /*
2  * Copyright 2000-2009 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
17 package com.intellij.execution.ui.layout.impl;
18
19 import com.intellij.execution.ui.layout.*;
20 import com.intellij.execution.ui.layout.actions.MinimizeViewAction;
21 import com.intellij.openapi.Disposable;
22 import com.intellij.openapi.actionSystem.ActionGroup;
23 import com.intellij.openapi.actionSystem.DataProvider;
24 import com.intellij.openapi.ui.popup.ComponentPopupBuilder;
25 import com.intellij.openapi.ui.popup.JBPopup;
26 import com.intellij.openapi.ui.popup.JBPopupFactory;
27 import com.intellij.openapi.util.*;
28 import com.intellij.openapi.wm.IdeFrame;
29 import com.intellij.openapi.wm.WindowManager;
30 import com.intellij.ui.components.panels.NonOpaquePanel;
31 import com.intellij.ui.components.panels.Wrapper;
32 import com.intellij.ui.content.Content;
33 import com.intellij.ui.switcher.SwitchTarget;
34 import com.intellij.ui.tabs.JBTabs;
35 import com.intellij.ui.tabs.TabInfo;
36 import com.intellij.ui.tabs.TabsListener;
37 import com.intellij.ui.tabs.UiDecorator;
38 import com.intellij.ui.tabs.impl.JBTabsImpl;
39 import com.intellij.util.containers.HashSet;
40 import com.intellij.util.ui.UIUtil;
41 import org.jetbrains.annotations.NonNls;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
44
45 import javax.swing.*;
46 import javax.swing.border.EmptyBorder;
47 import java.awt.*;
48 import java.awt.event.MouseAdapter;
49 import java.awt.event.MouseEvent;
50 import java.util.ArrayList;
51 import java.util.Set;
52
53 public class GridCellImpl implements GridCell, Disposable {
54
55   private final GridImpl myContainer;
56
57   private final MutualMap<Content, TabInfo> myContents = new MutualMap<Content, TabInfo>(true);
58   private final Set<Content> myMinimizedContents = new HashSet<Content>();
59
60   private final JBTabs myTabs;
61   private final GridImpl.Placeholder myPlaceholder;
62   private final PlaceInGrid myPlaceInGrid;
63
64   private final ViewContextEx myContext;
65   private CellTransform.Restore.List myRestoreFromDetach;
66   private JBPopup myPopup;
67   private boolean myDisposed;
68
69   public GridCellImpl(ViewContextEx context, @NotNull GridImpl container, GridImpl.Placeholder placeholder, PlaceInGrid placeInGrid) {
70     myContext = context;
71     myContainer = container;
72
73     Disposer.register(container, this);
74
75     myPlaceInGrid = placeInGrid;
76     myPlaceholder = placeholder;
77     myTabs = new JBTabsImpl(myContext.getProject(), myContext.getActionManager(), myContext.getFocusManager(), container).setDataProvider(new DataProvider() {
78       @Nullable
79       public Object getData(@NonNls final String dataId) {
80         if (ViewContext.CONTENT_KEY.is(dataId)) {
81           TabInfo target = myTabs.getTargetInfo();
82           if (target != null) {
83             return new Content[]{getContentFor(target)};
84           }
85         }
86         else if (ViewContext.CONTEXT_KEY.is(dataId)) {
87           return myContext;
88         }
89
90         return null;
91       }
92     });
93     myTabs.getPresentation().setUiDecorator(new UiDecorator() {
94       @NotNull
95       public UiDecoration getDecoration() {
96         return new UiDecoration(null, new Insets(1, -1, 1, -1));
97       }
98     }).setSideComponentVertical(!context.getLayoutSettings().isToolbarHorizontal())
99       .setStealthTabMode(true)
100       .setFocusCycle(false).setPaintFocus(true).setProvideSwitchTargets(false);
101
102     myTabs.addTabMouseListener(new MouseAdapter() {
103       public void mousePressed(final MouseEvent e) {
104         if (UIUtil.isCloseClick(e)) {
105           if (isDetached()) {
106             myPopup.cancel();
107             myPopup = null;
108           }
109           else {
110             minimize(e);
111           }
112         }
113       }
114     });
115     rebuildPopupGroup();
116     myTabs.addListener(new TabsListener() {
117
118       public void beforeSelectionChanged(TabInfo oldSelection, TabInfo newSelection) {
119         if (oldSelection != null && myContext.isStateBeingRestored()) {
120           saveUiState();
121         }
122       }
123
124       public void selectionChanged(final TabInfo oldSelection, final TabInfo newSelection) {
125         updateSelection(myTabs.getComponent().isShowing());
126
127         if (!myTabs.getComponent().isShowing()) return;
128
129         if (newSelection != null) {
130           newSelection.stopAlerting();
131         }
132       }
133     });
134   }
135
136   public void rebuildPopupGroup() {
137     myTabs.setPopupGroup(myContext.getCellPopupGroup(ViewContext.CELL_POPUP_PLACE),
138                          ViewContext.CELL_POPUP_PLACE, true);
139   }
140
141   public PlaceInGrid getPlaceInGrid() {
142     return myPlaceInGrid;
143   }
144
145   void add(final Content content) {
146     if (myContents.containsKey(content)) return;
147     myContents.put(content, null);
148
149     revalidateCell(new Runnable() {
150       public void run() {
151         myTabs.addTab(createTabInfoFor(content));
152       }
153     });
154
155     updateSelection(myTabs.getComponent().getRootPane() != null);
156   }
157
158   void remove(Content content) {
159     if (!myContents.containsKey(content)) return;
160
161     final TabInfo info = getTabFor(content);
162     myContents.remove(content);
163
164     revalidateCell(new Runnable() {
165       public void run() {
166         myTabs.removeTab(info);
167       }
168     });
169
170     updateSelection(myTabs.getComponent().getRootPane() != null);
171   }
172
173   private void revalidateCell(Runnable contentAction) {
174     if (myContents.size() == 0) {
175       myPlaceholder.removeAll();
176       myTabs.removeAllTabs();
177
178       if (myPopup != null) {
179         myPopup.cancel();
180         myPopup = null;
181       }
182     }
183     else {
184       if (myPlaceholder.isNull() && !isDetached()) {
185         myPlaceholder.setContent(myTabs.getComponent());
186       }
187
188       contentAction.run();
189     }
190
191     restoreProportions();
192
193     myTabs.getComponent().revalidate();
194     myTabs.getComponent().repaint();
195   }
196
197   void setHideTabs(boolean hide) {
198     myTabs.getPresentation().setHideTabs(hide);
199   }
200
201   private TabInfo createTabInfoFor(Content content) {
202     final JComponent c = content.getComponent();
203
204     final TabInfo tabInfo = updatePresentation(new TabInfo(new ProviderWrapper(content, myContext)), content)
205       .setObject(content)
206       .setPreferredFocusableComponent(content.getPreferredFocusableComponent())
207       .setActionsContextComponent(content.getActionsContextComponent());
208
209     myContents.remove(content);
210     myContents.put(content, tabInfo);
211
212     ActionGroup group = (ActionGroup)myContext.getActionManager().getAction(RunnerContentUi.VIEW_TOOLBAR);
213     tabInfo.setTabLabelActions(group, ViewContext.CELL_TOOLBAR_PLACE);
214
215     return tabInfo;
216   }
217
218   @Nullable
219   private static TabInfo updatePresentation(TabInfo info, Content content) {
220     if (info == null) return info;
221     return info.setIcon(content.getIcon()).setText(content.getDisplayName()).setActions(content.getActions(), content.getPlace());
222   }
223
224   public ActionCallback select(final Content content, final boolean requestFocus) {
225     final TabInfo tabInfo = myContents.getValue(content);
226     return tabInfo != null ? myTabs.select(tabInfo, requestFocus) : new ActionCallback.Done();
227   }
228
229   public void processAlert(final Content content, final boolean activate) {
230     if (myMinimizedContents.contains(content)) return;
231
232     TabInfo tab = getTabFor(content);
233     if (tab == null) return;
234     if (myTabs.getSelectedInfo() != tab) {
235       if (activate) {
236         tab.fireAlert();
237       } else {
238         tab.stopAlerting();
239       }
240     }
241   }
242
243   public void updateTabPresentation(Content content) {
244     updatePresentation(myTabs.findInfo(content), content);
245   }
246
247   public boolean isMinimized(Content content) {
248     return myMinimizedContents.contains(content);
249   }
250
251   public java.util.List<SwitchTarget> getTargets(boolean onlyVisible) {
252     if (myTabs.getPresentation().isHideTabs()) return new ArrayList<SwitchTarget>();
253
254     return myTabs.getTargets(onlyVisible, false);
255   }
256
257   public SwitchTarget getTargetForSelection() {
258     return myTabs.getCurrentTarget();
259   }
260
261   public boolean contains(Component c) {
262     return myTabs.getComponent().isAncestorOf(c);
263   }
264
265   private static class ProviderWrapper extends NonOpaquePanel implements DataProvider {
266
267     Content myContent;
268     ViewContext myContext;
269
270     private ProviderWrapper(final Content content, final ViewContext context) {
271       myContent = content;
272       myContext = context;
273       setLayout(new BorderLayout());
274       add(content.getComponent(), BorderLayout.CENTER);
275     }
276
277     @Nullable
278     public Object getData(@NonNls final String dataId) {
279       if (ViewContext.CONTENT_KEY.is(dataId)) {
280         return new Content[]{myContent};
281       }
282       else if (ViewContext.CONTEXT_KEY.is(dataId)) {
283         return myContext;
284       }
285       return null;
286     }
287   }
288
289   @Nullable
290   private TabInfo getTabFor(Content content) {
291     return myContents.getValue(content);
292   }
293
294   @NotNull
295   private Content getContentFor(TabInfo tab) {
296     return myContents.getKey(tab);
297   }
298
299   public void setToolbarHorizontal(final boolean horizontal) {
300     myTabs.getPresentation().setSideComponentVertical(!horizontal);
301   }
302
303   public ActionCallback restoreLastUiState() {
304     final ActionCallback result = new ActionCallback();
305
306     restoreProportions();
307
308     Content[] contents = getContents();
309     for (Content each : contents) {
310       if (myContainer.getStateFor(each).isMinimizedInGrid()) {
311         minimize(each);
312       }
313     }
314
315     if (!isRestoringFromDetach() && myContainer.getTab().isDetached(myPlaceInGrid) && contents.length > 0) {
316       _detach(!myContext.isStateBeingRestored()).notifyWhenDone(result);
317     } else {
318       result.setDone();
319     }
320
321     return result;
322   }
323
324   private Content[] getContents() {
325     return myContents.getKeys().toArray(new Content[myContents.size()]);
326   }
327
328   public int getContentCount() {
329     return myContents.size();
330   }
331
332   public void saveUiState() {
333     saveProportions();
334
335     for (Content each : myContents.getKeys()) {
336       saveState(each, false);
337     }
338
339     for (Content each : myMinimizedContents) {
340       saveState(each, true);
341     }
342   }
343
344   public void saveProportions() {
345     myContainer.saveSplitterProportions(myPlaceInGrid);
346   }
347
348   private void saveState(Content content, boolean minimized) {
349     View state = myContext.getStateFor(content);
350     state.setMinimizedInGrid(minimized);
351     state.setPlaceInGrid(myPlaceInGrid);
352     state.assignTab(myContainer.getTabIndex());
353
354     state.getTab().setDetached(myPlaceInGrid, isDetached());
355   }
356
357   public void restoreProportions() {
358     myContainer.restoreLastSplitterProportions(myPlaceInGrid);
359   }
360
361   public void updateSelection(final boolean isShowing) {
362     for (Content each : myContents.getKeys()) {
363       final TabInfo eachTab = getTabFor(each);
364       boolean isSelected = eachTab != null && myTabs.getSelectedInfo() == eachTab;
365       if (isSelected && (isShowing || isDetached())) {
366         myContext.getContentManager().addSelectedContent(each);
367       }
368       else {
369         myContext.getContentManager().removeFromSelection(each);
370       }
371     }
372
373     for (Content each : myMinimizedContents) {
374       myContext.getContentManager().removeFromSelection(each);
375     }
376   }
377
378   public void minimize(Content[] contents) {
379     myContext.saveUiState();
380
381     for (final Content each : contents) {
382       myMinimizedContents.add(each);
383       remove(each);
384       boolean isShowing = myTabs.getComponent().getRootPane() != null;
385       updateSelection(isShowing);
386       myContainer.minimize(each, new CellTransform.Restore() {
387         public ActionCallback restoreInGrid() {
388           return restore(each);
389         }
390       });
391     }
392   }
393
394   public ActionCallback detach() {
395     return _detach(true);
396   }
397
398   private ActionCallback _detach(final boolean requestFocus) {
399     myContext.saveUiState();
400
401     final DimensionService dimService = DimensionService.getInstance();
402     Point storedLocation = dimService.getLocation(getDimensionKey(), myContext.getProject());
403     Dimension storedSize = dimService.getSize(getDimensionKey(), myContext.getProject());
404
405     final IdeFrame frame = WindowManager.getInstance().getIdeFrame(myContext.getProject());
406     final Rectangle targetBounds = frame.suggestChildFrameBounds();
407
408
409     if (storedLocation != null && storedSize != null) {
410       targetBounds.setLocation(storedLocation);
411       targetBounds.setSize(storedSize);
412     }
413
414     final ActionCallback result = new ActionCallback();
415
416     if (storedLocation == null || storedSize == null) {
417       if (myContents.size() > 0) {
418         myContext.validate(myContents.getKeys().iterator().next(), new ActiveRunnable() {
419           public ActionCallback run() {
420             if (!myTabs.getComponent().isShowing()) {
421               detachTo(targetBounds.getLocation(), targetBounds.getSize(), false, requestFocus).notifyWhenDone(result);
422             } else {
423               detachForShowingTabs(requestFocus).notifyWhenDone(result);
424             }
425
426             return new ActionCallback.Done();
427           }
428         });
429
430         return result;
431       }
432     }
433
434     detachTo(targetBounds.getLocation(), targetBounds.getSize(), false, requestFocus).notifyWhenDone(result);
435
436     return result;
437   }
438
439   private ActionCallback detachForShowingTabs(boolean requestFocus) {
440     return detachTo(myTabs.getComponent().getLocationOnScreen(), myTabs.getComponent().getSize(), false, requestFocus);
441   }
442
443   private ActionCallback detachTo(Point screenPoint, Dimension size, boolean dragging, final boolean requestFocus) {
444     if (isDetached()) {
445       if (myPopup != null) {
446         return new ActionCallback.Done();
447       }
448     }
449
450     final Content[] contents = getContents();
451
452     myRestoreFromDetach = new CellTransform.Restore.List();
453
454     myRestoreFromDetach.add(myPlaceholder.detach());
455     myRestoreFromDetach.add(myContainer.detach(contents));
456     myRestoreFromDetach.add(new CellTransform.Restore() {
457       public ActionCallback restoreInGrid() {
458         ensureVisible();
459         return new ActionCallback.Done();
460       }
461     });
462
463     myPopup = createPopup(dragging, requestFocus);
464     myPopup.setSize(size);
465     myPopup.setLocation(screenPoint);
466     myPopup.show(myContext.getContentManager().getComponent());
467
468     myContext.saveUiState();
469
470     myTabs.updateTabActions(true);
471
472     return new ActionCallback.Done();
473   }
474
475   private void ensureVisible() {
476     if (myTabs.getSelectedInfo() != null) {
477       myContext.select(getContentFor(myTabs.getSelectedInfo()), true);
478     }
479   }
480
481   private JBPopup createPopup(boolean dragging, final boolean requestFocus) {
482     Wrapper wrapper = new Wrapper(myTabs.getComponent());
483     wrapper.setBorder(new EmptyBorder(1, 0, 0, 0));
484     final ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(wrapper, myTabs.getComponent())
485       .setTitle(myContainer.getSessionName())
486       .setMovable(true)
487       .setRequestFocus(requestFocus)
488       .setFocusable(true)
489       .setResizable(true)
490       .setDimensionServiceKey(myContext.getProject(), getDimensionKey(), true)
491       .setCancelOnOtherWindowOpen(false)
492       .setCancelOnClickOutside(false)
493       .setCancelKeyEnabled(true)
494       .setLocateByContent(dragging)
495       .setLocateWithinScreenBounds(!dragging)
496       .setCancelKeyEnabled(false)
497       .setBelongsToGlobalPopupStack(false)
498       .setModalContext(false)
499       .setCancelCallback(new Computable<Boolean>() {
500         public Boolean compute() {
501           if (myDisposed || myContents.size() == 0) return Boolean.TRUE;
502           myRestoreFromDetach.restoreInGrid();
503           myRestoreFromDetach = null;
504           myContext.saveUiState();
505           myTabs.updateTabActions(true);
506           return Boolean.TRUE;
507         }
508       });
509
510     return builder.createPopup();
511   }
512
513   public void attach() {
514     if (isDetached()) {
515       myPopup.cancel();
516       myPopup = null;
517     }
518   }
519
520
521   public boolean isDetached() {
522     return myRestoreFromDetach != null && !myRestoreFromDetach.isRestoringNow();
523   }
524
525   public boolean isRestoringFromDetach() {
526     return myRestoreFromDetach != null && myRestoreFromDetach.isRestoringNow();
527   }
528
529   private String getDimensionKey() {
530     return "GridCell.Tab." + myContainer.getTab().getIndex() + "." + myPlaceInGrid.name();
531   }
532
533   public boolean isValidForCalculatePropertions() {
534     return !isDetached() && getContentCount() > 0;
535   }
536
537   public void minimize(Content content) {
538     minimize(new Content[]{content});
539   }
540
541   public void minimize(MouseEvent e) {
542     if (!MinimizeViewAction.isEnabled(myContext, getContents(), ViewContext.CELL_TOOLBAR_PLACE)) return;
543
544     TabInfo tabInfo = myTabs.findInfo(e);
545     if (tabInfo != null) {
546       minimize(getContentFor(tabInfo));
547     }
548   }
549
550   private ActionCallback restore(Content content) {
551     myMinimizedContents.remove(content);
552     add(content);
553     updateSelection(myTabs.getComponent().getRootPane() != null);
554     return new ActionCallback.Done();
555   }
556
557   public void dispose() {
558     myDisposed = true;
559
560     if (myPopup != null) {
561       myPopup.cancel();
562       myPopup = null;
563     }
564   }
565 }