dialog creation support for dock containers
[idea/community.git] / platform / platform-impl / src / com / intellij / ui / docking / impl / DockManagerImpl.java
1 /*
2  * Copyright 2000-2010 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.ui.docking.impl;
17
18 import com.intellij.ide.IdeEventQueue;
19 import com.intellij.ide.ui.UISettings;
20 import com.intellij.ide.ui.UISettingsListener;
21 import com.intellij.openapi.Disposable;
22 import com.intellij.openapi.components.PersistentStateComponent;
23 import com.intellij.openapi.components.State;
24 import com.intellij.openapi.components.Storage;
25 import com.intellij.openapi.extensions.ExtensionPointListener;
26 import com.intellij.openapi.extensions.Extensions;
27 import com.intellij.openapi.extensions.PluginDescriptor;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.ui.FrameWrapper;
30 import com.intellij.openapi.util.ActionCallback;
31 import com.intellij.openapi.util.BusyObject;
32 import com.intellij.openapi.util.Disposer;
33 import com.intellij.openapi.util.MutualMap;
34 import com.intellij.openapi.wm.IdeFrame;
35 import com.intellij.openapi.wm.IdeRootPaneNorthExtension;
36 import com.intellij.openapi.wm.WindowManager;
37 import com.intellij.openapi.wm.ex.WindowManagerEx;
38 import com.intellij.openapi.wm.impl.IdeFocusManagerImpl;
39 import com.intellij.ui.ScreenUtil;
40 import com.intellij.ui.awt.RelativePoint;
41 import com.intellij.ui.awt.RelativeRectangle;
42 import com.intellij.ui.components.panels.NonOpaquePanel;
43 import com.intellij.ui.components.panels.VerticalBox;
44 import com.intellij.ui.docking.*;
45 import com.intellij.ui.tabs.impl.JBTabsImpl;
46 import com.intellij.util.ui.UIUtil;
47 import com.intellij.util.ui.update.UiNotifyConnector;
48 import org.jdom.Element;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
51
52 import javax.swing.*;
53 import javax.swing.border.LineBorder;
54 import java.awt.*;
55 import java.awt.event.KeyEvent;
56 import java.awt.event.MouseEvent;
57 import java.awt.event.WindowAdapter;
58 import java.awt.event.WindowEvent;
59 import java.awt.image.BufferedImage;
60 import java.util.*;
61 import java.util.List;
62
63 @State(
64   name = "DockManager",
65   storages = {@Storage(
66     file = "$WORKSPACE_FILE$")})
67
68 public class DockManagerImpl extends DockManager implements PersistentStateComponent<Element>{
69
70   private Project myProject;
71
72   private Map<String, DockContainerFactory> myFactories = new HashMap<String, DockContainerFactory>();
73
74   private Set<DockContainer> myContainers = new HashSet<DockContainer>();
75
76   private MutualMap<DockContainer, DockWindow> myWindows = new MutualMap<DockContainer, DockWindow>();
77
78   private MyDragSession myCurrentDragSession;
79
80   private BusyObject.Impl myBusyObject = new BusyObject.Impl() {
81     @Override
82     public boolean isReady() {
83       return myCurrentDragSession == null;
84     }
85   };
86
87   private int myWindowIdCounter = 1;
88
89   private Element myLoadedState;
90
91   public DockManagerImpl(Project project) {
92     myProject = project;
93   }
94
95   public void register(final DockContainer container) {
96     myContainers.add(container);
97     Disposer.register(container, new Disposable() {
98       @Override
99       public void dispose() {
100         myContainers.remove(container);
101       }
102     });
103   }
104
105   @Override
106   public void register(final String id, DockContainerFactory factory) {
107     myFactories.put(id, factory);
108     Disposer.register(factory, new Disposable() {
109       @Override
110       public void dispose() {
111         myFactories.remove(id);
112       }
113     });
114
115     readStateFor(id);
116   }
117
118   @Override
119   public Set<DockContainer> getContainers() {
120     return Collections.unmodifiableSet(myContainers);
121   }
122
123   @Override
124   public IdeFrame getIdeFrame(DockContainer container) {
125     Component parent = UIUtil.findUltimateParent(container.getContainerComponent());
126     if (parent instanceof IdeFrame) {
127       return (IdeFrame)parent;
128     }
129     return null;
130   }
131
132   @Override
133   public String getDimensionKeyForFocus(@NotNull String key) {
134     Component owner = IdeFocusManagerImpl.getInstance(myProject).getFocusOwner();
135     if (owner == null) return key;
136
137     DockWindow wnd = myWindows.getValue(getContainerFor(owner));
138
139     return wnd != null ? key + "#" + wnd.myId : key;
140   }
141
142   public DockContainer getContainerFor(Component c) {
143     if (c == null) return null;
144
145     for (DockContainer eachContainer : myContainers) {
146       if (SwingUtilities.isDescendingFrom(c, eachContainer.getContainerComponent())) {
147         return eachContainer;
148       }
149     }
150
151     Component parent = UIUtil.findUltimateParent(c);
152     if (parent == null) return null;
153
154     for (DockContainer eachContainer : myContainers) {
155       if (parent == UIUtil.findUltimateParent(eachContainer.getContainerComponent())) {
156         return eachContainer;
157       }
158     }
159
160     return null;
161   }
162
163   @Override
164   public DragSession createDragSession(MouseEvent mouseEvent, DockableContent content) {
165     stopCurrentDragSession();
166
167     for (DockContainer each : myContainers) {
168       if (each.isEmpty() && each.isDisposeWhenEmpty()) {
169         DockWindow window = myWindows.getValue(each);
170         if (window != null) {
171           window.setTransparrent(true);
172         }
173       }
174     }
175
176     myCurrentDragSession = new MyDragSession(mouseEvent, content);
177     return myCurrentDragSession;
178   }
179
180
181   private void stopCurrentDragSession() {
182     if (myCurrentDragSession != null) {
183       myCurrentDragSession.cancel();
184       myCurrentDragSession = null;
185       myBusyObject.onReady();
186
187       for (DockContainer each : myContainers) {
188         if (!each.isEmpty()) {
189           DockWindow window = myWindows.getValue(each);
190           if (window != null) {
191             window.setTransparrent(false);
192           }
193         }
194       }
195     }
196   }
197
198   private ActionCallback getReady() {
199     return myBusyObject.getReady(this);
200   }
201
202   @Override
203   public void projectOpened() {
204   }
205
206   @Override
207   public void projectClosed() {
208   }
209
210   @NotNull
211   @Override
212   public String getComponentName() {
213     return "DockManager";
214   }
215
216   @Override
217   public void initComponent() {
218   }
219
220   @Override
221   public void disposeComponent() {
222   }
223
224   private class MyDragSession implements DragSession {
225
226     private JWindow myWindow;
227
228     private Image myDragImage;
229     private Image myDefaultDragImage;
230
231     private DockableContent myContent;
232
233     private DockContainer myCurrentOverContainer;
234     private JLabel myImageContainer;
235
236     private MyDragSession(MouseEvent me, DockableContent content) {
237       myWindow = new JWindow();
238       myContent = content;
239
240       Image previewImage = content.getPreviewImage();
241
242       double requiredSize = 220;
243
244       double width = previewImage.getWidth(null);
245       double height = previewImage.getHeight(null);
246
247       double ratio;
248       if (width > height) {
249         ratio = requiredSize / width;
250       }
251       else {
252         ratio = requiredSize / height;
253       }
254
255       BufferedImage buffer = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_ARGB);
256       buffer.createGraphics().drawImage(previewImage, 0, 0, (int)width, (int)height, null);
257
258       myDefaultDragImage = buffer.getScaledInstance((int)(width * ratio), (int)(height * ratio), Image.SCALE_SMOOTH);
259       myDragImage = myDefaultDragImage;
260
261       myWindow.getContentPane().setLayout(new BorderLayout());
262       myImageContainer = new JLabel(new ImageIcon(myDragImage));
263       myImageContainer.setBorder(new LineBorder(Color.lightGray));
264       myWindow.getContentPane().add(myImageContainer, BorderLayout.CENTER);
265
266       setLocationFrom(me);
267
268       myWindow.setVisible(true);
269
270       WindowManagerEx.getInstanceEx().setAlphaModeEnabled(myWindow, true);
271       WindowManagerEx.getInstanceEx().setAlphaModeRatio(myWindow, 0.1f);
272       myWindow.getRootPane().putClientProperty("Window.shadow", Boolean.FALSE);
273     }
274
275     private void setLocationFrom(MouseEvent me) {
276       Point showPoint = me.getPoint();
277       SwingUtilities.convertPointToScreen(showPoint, me.getComponent());
278
279       showPoint.x -= myDragImage.getWidth(null) / 2;
280       showPoint.y += 10;
281       myWindow.setBounds(new Rectangle(showPoint, new Dimension(myDragImage.getWidth(null), myDragImage.getHeight(null))));
282     }
283
284     @Override
285     public void process(MouseEvent e) {
286       RelativePoint point = new RelativePoint(e);
287
288       Image img = null;
289       if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
290         DockContainer over = findContainerFor(point, myContent);
291         if (myCurrentOverContainer != null && myCurrentOverContainer != over) {
292           myCurrentOverContainer.resetDropOver(myContent);
293           myCurrentOverContainer = null;
294         }
295
296         if (myCurrentOverContainer == null && over != null) {
297           myCurrentOverContainer = over;
298           img = myCurrentOverContainer.startDropOver(myContent, point);
299         }
300
301         if (myCurrentOverContainer != null) {
302           img = myCurrentOverContainer.processDropOver(myContent, point);
303         }
304
305         if (img == null) {
306           img = myDefaultDragImage;
307         }
308
309         if (img != myDragImage) {
310           myDragImage = img;
311           myImageContainer.setIcon(new ImageIcon(myDragImage));
312           myWindow.pack();
313         }
314
315         setLocationFrom(e);
316       }
317       else if (e.getID() == MouseEvent.MOUSE_RELEASED) {
318         if (myCurrentOverContainer == null) {
319           createNewDockContainerFor(myContent, point);
320           stopCurrentDragSession();
321         } else {
322           myCurrentOverContainer.add(myContent, point);
323           stopCurrentDragSession();
324         }
325       }
326     }
327
328     public void cancel() {
329       myWindow.dispose();
330
331       if (myCurrentOverContainer != null) {
332         myCurrentOverContainer.resetDropOver(myContent);
333         myCurrentOverContainer = null;
334       }
335     }
336   }
337
338   @Nullable
339   private DockContainer findContainerFor(RelativePoint point, DockableContent content) {
340     for (DockContainer each : myContainers) {
341       RelativeRectangle rec = each.getAcceptArea();
342       if (rec.contains(point) && each.canAccept(content, point)) {
343         return each;
344       }
345     }
346
347     return null;
348   }
349
350
351
352   private DockContainerFactory getFactory(String type) {
353     assert myFactories.containsKey(type) : "No factory for content type=" + type;
354     return myFactories.get(type);
355   }
356
357   public void createNewDockContainerFor(DockableContent content, RelativePoint point) {
358     DockContainer container = getFactory(content.getDockContainerType()).createContainer(content);
359     register(container);
360
361     final DockWindow window = createWindowFor(null, container);
362
363     Dimension size = content.getPreferredSize();
364     Point showPoint = point.getScreenPoint();
365     showPoint.x -= size.width / 2;
366     showPoint.y -= size.height / 2;
367
368     Rectangle target = new Rectangle(showPoint, size);
369     ScreenUtil.moveRectangleToFitTheScreen(target);
370     ScreenUtil.cropRectangleToFitTheScreen(target);
371
372
373     window.setLocation(target.getLocation());
374     window.myDockContentUiContainer.setPreferredSize(target.getSize());
375
376     window.show(false);
377     window.getFrame().pack();
378
379     container.add(content, new RelativePoint(target.getLocation()));
380
381     SwingUtilities.invokeLater(new Runnable() {
382       @Override
383       public void run() {
384         window.myUiContainer.setPreferredSize(null);
385       }
386     });
387   }
388
389   private DockWindow createWindowFor(@Nullable String id, DockContainer container) {
390     String windowId = id != null ? id : String.valueOf(myWindowIdCounter++);
391     DockWindow window = new DockWindow(windowId, myProject, container, container instanceof DockContainer.Dialog);
392     window.setDimensionKey("dock-window-" + windowId);
393     myWindows.put(container, window);
394     return window;
395   }
396
397   private class DockWindow extends FrameWrapper implements IdeEventQueue.EventDispatcher {
398
399     private String myId;
400     private DockContainer myContainer;
401
402     private VerticalBox myNorthPanel = new VerticalBox();
403     private Map<String, IdeRootPaneNorthExtension> myNorthExtensions = new LinkedHashMap<String, IdeRootPaneNorthExtension>();
404
405     private NonOpaquePanel myUiContainer;
406     private NonOpaquePanel myDockContentUiContainer;
407
408     private DockWindow(String id, Project project, DockContainer container, boolean dialog) {
409       super(project, null, dialog);
410       myId = id;
411       myContainer = container;
412       setProject(project);
413
414       if (!(container instanceof DockContainer.Dialog)) {
415         setStatusBar(WindowManager.getInstance().getStatusBar(project).createChild());
416       }
417
418       myUiContainer = new NonOpaquePanel(new BorderLayout());
419
420       NonOpaquePanel center = new NonOpaquePanel(new BorderLayout(0, 2));
421       if (UIUtil.isUnderAquaLookAndFeel()) {
422         center.setOpaque(true);
423         center.setBackground(JBTabsImpl.MAC_AQUA_BG_COLOR);
424       }
425       
426       center.add(myNorthPanel, BorderLayout.NORTH);
427
428       myDockContentUiContainer = new NonOpaquePanel(new BorderLayout());
429       myDockContentUiContainer.add(myContainer.getContainerComponent(), BorderLayout.CENTER);
430       center.add(myDockContentUiContainer, BorderLayout.CENTER);
431
432       myUiContainer.add(center, BorderLayout.CENTER);
433       if (!(container instanceof DockContainer.Dialog)) {
434         myUiContainer.add(myStatusBar.getComponent(), BorderLayout.SOUTH);
435       }
436
437       setComponent(myUiContainer);
438       addDisposable(container);
439
440       IdeEventQueue.getInstance().addPostprocessor(this, this);
441
442       myContainer.addListener(new DockContainer.Listener.Adapter() {
443         @Override
444         public void contentRemoved(Object key) {
445           getReady().doWhenDone(new Runnable() {
446             @Override
447             public void run() {
448               if (myContainer.isEmpty()) {
449                 close();
450               }
451             }
452           });
453         }
454       }, this);
455
456       Extensions.getArea(myProject).getExtensionPoint(IdeRootPaneNorthExtension.EP_NAME).addExtensionPointListener(
457         new ExtensionPointListener<IdeRootPaneNorthExtension>() {
458           @Override
459           public void extensionAdded(@NotNull IdeRootPaneNorthExtension extension, @Nullable PluginDescriptor pluginDescriptor) {
460             updateNorthPanel();
461           }
462
463           @Override
464           public void extensionRemoved(@NotNull IdeRootPaneNorthExtension extension, @Nullable PluginDescriptor pluginDescriptor) {
465             updateNorthPanel();
466           }
467
468         });
469
470       UISettings.getInstance().addUISettingsListener(new UISettingsListener() {
471         @Override
472         public void uiSettingsChanged(UISettings source) {
473           updateNorthPanel();
474         }
475       }, this);
476
477       updateNorthPanel();
478     }
479
480
481     @Override
482     protected IdeRootPaneNorthExtension getNorthExtension(String key) {
483       return myNorthExtensions.get(key);
484     }
485
486     private void updateNorthPanel() {
487       myNorthPanel.setVisible(UISettings.getInstance().SHOW_NAVIGATION_BAR &&
488                               !(myContainer instanceof DockContainer.Dialog));
489
490       IdeRootPaneNorthExtension[] extensions = Extensions.getArea(myProject).getExtensionPoint(IdeRootPaneNorthExtension.EP_NAME).getExtensions();
491       HashSet<String> processedKeys = new HashSet<String>();
492       for (IdeRootPaneNorthExtension each : extensions) {
493         processedKeys.add(each.getKey());
494         if (myNorthExtensions.containsKey(each.getKey())) continue;
495         IdeRootPaneNorthExtension toInstall = each.copy();
496         myNorthExtensions.put(toInstall.getKey(), toInstall);
497         myNorthPanel.add(toInstall.getComponent());
498       }
499
500       Iterator<String> existing = myNorthExtensions.keySet().iterator();
501       while (existing.hasNext()) {
502         String each = existing.next();
503         if (processedKeys.contains(each)) continue;
504
505         IdeRootPaneNorthExtension toRemove = myNorthExtensions.get(each);
506         myNorthPanel.remove(toRemove.getComponent());
507         existing.remove();
508         Disposer.dispose(toRemove);
509       }
510
511       myNorthPanel.revalidate();
512       myNorthPanel.repaint();
513     }
514
515     public void setTransparrent(boolean transparrent) {
516       if (transparrent) {
517         WindowManagerEx.getInstanceEx().setAlphaModeEnabled(getFrame(), true);
518         WindowManagerEx.getInstanceEx().setAlphaModeRatio(getFrame(), 0.5f);
519       } else {
520         WindowManagerEx.getInstanceEx().setAlphaModeEnabled(getFrame(), true);
521         WindowManagerEx.getInstanceEx().setAlphaModeRatio(getFrame(), 0f);
522       }
523     }
524
525     @Override
526     public void dispose() {
527       super.dispose();
528       myWindows.remove(myContainer);
529
530       for (IdeRootPaneNorthExtension each : myNorthExtensions.values()) {
531         Disposer.dispose(each);
532       }
533       myNorthExtensions.clear();
534     }
535
536     @Override
537     public boolean dispatch(AWTEvent e) {
538       if (e instanceof KeyEvent) {
539         if (myCurrentDragSession != null) {
540           stopCurrentDragSession();
541         }
542       }
543       return false;
544     }
545
546     @Override
547     protected JFrame createJFrame(IdeFrame parent) {
548       JFrame frame = super.createJFrame(parent);
549       installListeners(frame);
550
551       return frame;
552     }
553
554     @Override
555     protected JDialog createJDialog(IdeFrame parent) {
556       JDialog frame = super.createJDialog(parent);
557       installListeners(frame);
558
559       return frame;
560     }
561
562     private void installListeners(Window frame) {
563       frame.addWindowListener(new WindowAdapter() {
564         @Override
565         public void windowClosing(WindowEvent e) {
566           myContainer.closeAll();
567         }
568       });
569
570       new UiNotifyConnector(((RootPaneContainer)frame).getContentPane(), myContainer);
571     }
572   }
573
574
575   @Override
576   public Element getState() {
577     Element root = new Element("DockManager");
578     for (DockContainer each : myContainers) {
579       DockWindow eachWindow = myWindows.getValue(each);
580       if (eachWindow != null) {
581         if (each instanceof DockContainer.Persistent) {
582           DockContainer.Persistent eachContainer = (DockContainer.Persistent)each;
583           Element eachWindowElement = new Element("window");
584           eachWindowElement.setAttribute("id", eachWindow.myId);
585           Element content = new Element("content");
586           content.setAttribute("type", eachContainer.getDockContainerType());
587           content.addContent(eachContainer.getState());
588           eachWindowElement.addContent(content);
589
590           root.addContent(eachWindowElement);
591         }
592       }
593     }
594     return root;
595   }
596
597   @Override
598   public void loadState(Element state) {
599     myLoadedState = state;
600   }
601
602   private void readStateFor(String type) {
603     if (myLoadedState == null) return;
604
605     List windows = myLoadedState.getChildren("window");
606     for (int i = 0; i < windows.size(); i++) {
607       Element eachWindow = (Element)windows.get(i);
608       if (eachWindow == null) continue;
609
610       String eachId = eachWindow.getAttributeValue("id");
611
612       Element eachContent = eachWindow.getChild("content");
613       if (eachContent == null) continue;
614
615       String eachType = eachContent.getAttributeValue("type");
616       if (eachType == null || !type.equals(eachType) || !myFactories.containsKey(eachType)) continue;
617
618       DockContainerFactory factory = myFactories.get(eachType);
619       if (!(factory instanceof DockContainerFactory.Persistent)) continue;
620
621       DockContainerFactory.Persistent persistentFactory = (DockContainerFactory.Persistent)factory;
622       DockContainer container = persistentFactory.loadContainerFrom(eachContent);
623       register(container);
624
625       createWindowFor(eachId, container).show();
626     }
627   }
628 }