replaced <code></code> with more concise {@code}
[idea/community.git] / platform / platform-impl / src / com / intellij / ide / ui / laf / darcula / ui / DarculaRootPaneUI.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.ide.ui.laf.darcula.ui;
17
18 import com.intellij.openapi.util.registry.Registry;
19 import com.intellij.ui.Gray;
20 import com.intellij.ui.ScreenUtil;
21 import com.intellij.ui.WindowMoveListener;
22 import com.intellij.ui.WindowResizeListener;
23 import com.intellij.util.ui.JBUI;
24 import com.intellij.util.ui.UIUtil;
25
26 import javax.swing.*;
27 import javax.swing.event.MouseInputListener;
28 import javax.swing.plaf.ComponentUI;
29 import javax.swing.plaf.basic.BasicRootPaneUI;
30 import java.awt.*;
31 import java.awt.event.*;
32 import java.beans.PropertyChangeEvent;
33 import java.beans.PropertyChangeListener;
34
35 /**
36  * @author Konstantin Bulenkov
37  */
38 public class DarculaRootPaneUI extends BasicRootPaneUI {
39   private Window myWindow;
40
41   private JComponent myTitlePane;
42
43   private MouseInputListener myMouseInputListener;
44
45   private MouseInputListener myTitleMouseInputListener;
46
47   private LayoutManager myLayoutManager;
48
49   private LayoutManager myOldLayout;
50
51   protected JRootPane myRootPane;
52
53   protected WindowListener myWindowListener;
54
55   protected Window myCurrentWindow;
56
57   protected HierarchyListener myHierarchyListener;
58
59   protected ComponentListener myWindowComponentListener;
60
61   protected GraphicsConfiguration currentRootPaneGC;
62
63   protected PropertyChangeListener myPropertyChangeListener;
64
65   @SuppressWarnings("MethodOverridesStaticMethodOfSuperclass")
66   public static ComponentUI createUI(JComponent comp) {
67     return isCustomDecoration() ? new DarculaRootPaneUI() : createDefaultWindowsRootPaneUI();
68   }
69
70   private static ComponentUI createDefaultWindowsRootPaneUI() {
71     try {
72       return (ComponentUI)Class.forName("com.sun.java.swing.plaf.windows.WindowsRootPaneUI").newInstance();
73     } catch (Exception e) {
74       return new BasicRootPaneUI();
75     }
76   }
77
78   @Override
79   public void installUI(JComponent c) {
80     super.installUI(c);
81
82     if (isCustomDecoration()) {
83       myRootPane = (JRootPane)c;
84       int style = myRootPane.getWindowDecorationStyle();
85       if (style != JRootPane.NONE) {
86         installClientDecorations(myRootPane);
87       }
88     }
89   }
90
91   public void installMenuBar(JMenuBar menu) {
92     if (menu != null && isCustomDecoration()) {
93       getTitlePane().add(menu);
94     }
95   }
96
97   @Override
98   public void uninstallUI(JComponent c) {
99     super.uninstallUI(c);
100
101     if (isCustomDecoration()) {
102       uninstallClientDecorations(myRootPane);
103       myLayoutManager = null;
104       myMouseInputListener = null;
105       myRootPane = null;
106     }
107   }
108
109   private static boolean isCustomDecoration() {
110     return Registry.is("ide.win.frame.decoration");
111   }
112
113   public void installBorder(JRootPane root) {
114     int style = root.getWindowDecorationStyle();
115
116     if (style == JRootPane.NONE) {
117       LookAndFeel.uninstallBorder(root);
118     }
119     else {
120       root.setBorder(JBUI.Borders.customLine(Gray._73, 1, 1, 1, 1));
121       //LookAndFeel.installBorder(root, "RootPane.border");
122     }
123   }
124
125   private static void uninstallBorder(JRootPane root) {
126     LookAndFeel.uninstallBorder(root);
127   }
128
129   private void installWindowListeners(JRootPane root, Component parent) {
130     myWindow = parent == null ? null : UIUtil.getWindow(parent);
131
132     if (myWindow != null) {
133       if (myMouseInputListener == null) {
134         //noinspection UseDPIAwareInsets
135         myMouseInputListener = new WindowResizeListener(parent, JBUI.insets(11), null) {
136           @Override
137           protected Insets getResizeOffset(Component view) {
138             return getResizeBorder(view);
139           }
140         };
141       }
142       myWindow.addMouseListener(myMouseInputListener);
143       myWindow.addMouseMotionListener(myMouseInputListener);
144
145       if (myTitlePane != null) {
146         if (myTitleMouseInputListener == null) {
147           myTitleMouseInputListener = new WindowMoveListener(myTitlePane) {
148             @Override
149             protected boolean isDisabled(Component view) {
150               if (view instanceof RootPaneContainer) {
151                 RootPaneContainer container = (RootPaneContainer)view;
152                 JRootPane pane = container.getRootPane();
153                 if (pane != null && JRootPane.NONE == pane.getWindowDecorationStyle()) return true;
154               }
155               return super.isDisabled(view);
156             }
157           };
158         }
159         myTitlePane.addMouseMotionListener(myTitleMouseInputListener);
160         myTitlePane.addMouseListener(myTitleMouseInputListener);
161       }
162       setMaximized();
163     }
164   }
165
166   private void uninstallWindowListeners(JRootPane root) {
167     if (myWindow != null) {
168       myWindow.removeMouseListener(myMouseInputListener);
169       myWindow.removeMouseMotionListener(myMouseInputListener);
170     }
171     if (myTitlePane != null) {
172       myTitlePane.removeMouseListener(myTitleMouseInputListener);
173       myTitlePane.removeMouseMotionListener(myTitleMouseInputListener);
174     }
175   }
176
177   private void installLayout(JRootPane root) {
178     if (myLayoutManager == null) {
179       myLayoutManager = createLayoutManager();
180     }
181     myOldLayout = root.getLayout();
182     root.setLayout(myLayoutManager);
183   }
184
185   @Override
186   protected void installListeners(final JRootPane root) {
187     super.installListeners(root);
188
189     myHierarchyListener = new HierarchyListener() {
190       public void hierarchyChanged(HierarchyEvent e) {
191         Component parent = root.getParent();
192         if (parent == null) {
193           return;
194         }
195         if (parent.getClass().getName().startsWith("org.jdesktop.jdic.tray")
196             || (parent.getClass().getName().compareTo("javax.swing.Popup$HeavyWeightWindow") == 0)) {
197
198           //noinspection SSBasedInspection
199           SwingUtilities.invokeLater(() -> {
200             root.removeHierarchyListener(myHierarchyListener);
201             myHierarchyListener = null;
202           });
203         }
204
205         Window currWindow = UIUtil.getWindow(parent);
206         if (myWindowListener != null) {
207           myCurrentWindow.removeWindowListener(myWindowListener);
208           myWindowListener = null;
209         }
210         if (myWindowComponentListener != null) {
211           myCurrentWindow.removeComponentListener(myWindowComponentListener);
212           myWindowComponentListener = null;
213         }
214         if (currWindow != null) {
215           myWindowListener = new WindowAdapter() {
216             @Override
217             public void windowClosed(WindowEvent e) {
218               //noinspection SSBasedInspection
219               SwingUtilities.invokeLater(() -> {
220                 Frame[] frames = Frame.getFrames();
221                 for (Frame frame : frames) {
222                   if (frame.isDisplayable()) {
223                     return;
224                   }
225                 }
226               });
227             }
228           };
229
230           if (!(parent instanceof JInternalFrame)) {
231             currWindow.addWindowListener(myWindowListener);
232           }
233
234           myWindowComponentListener = new ComponentAdapter() {
235             @Override
236             public void componentMoved(ComponentEvent e) {
237               processNewPosition();
238             }
239
240             @Override
241             public void componentResized(ComponentEvent e) {
242               processNewPosition();
243             }
244
245             private void processNewPosition() {
246               //noinspection SSBasedInspection
247               SwingUtilities.invokeLater(() -> {
248                 if (myWindow == null) {
249                   return;
250                 }
251
252                 if (!myWindow.isShowing() || !myWindow.isDisplayable()) {
253                   currentRootPaneGC = null;
254                   return;
255                 }
256
257                 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
258                 GraphicsDevice[] gds = ge.getScreenDevices();
259                 if (gds.length == 1) {
260                   return;
261                 }
262                 Point midLoc = new Point(myWindow.getLocationOnScreen().x + myWindow.getWidth() / 2,
263                                          myWindow.getLocationOnScreen().y + myWindow.getHeight() / 2);
264
265                 for (GraphicsDevice gd : gds) {
266                   GraphicsConfiguration gc = gd.getDefaultConfiguration();
267                   Rectangle bounds = gc.getBounds();
268                   if (bounds.contains(midLoc)) {
269                     if (gc != currentRootPaneGC) {
270                       currentRootPaneGC = gc;
271                       setMaximized();
272                     }
273                     break;
274                   }
275                 }
276               });
277             }
278           };
279
280           if (parent instanceof JFrame) {
281             currWindow.addComponentListener(myWindowComponentListener);
282           }
283
284           myWindow = currWindow;
285         }
286         myCurrentWindow = currWindow;
287       }
288     };
289     root.addHierarchyListener(myHierarchyListener);
290     root.addPropertyChangeListener(myPropertyChangeListener);
291   }
292
293   @Override
294   protected void uninstallListeners(JRootPane root) {
295     if (myWindow != null) {
296       myWindow.removeWindowListener(myWindowListener);
297       myWindowListener = null;
298       myWindow.removeComponentListener(myWindowComponentListener);
299       myWindowComponentListener = null;
300     }
301     root.removeHierarchyListener(myHierarchyListener);
302     myHierarchyListener = null;
303
304     root.removePropertyChangeListener(myPropertyChangeListener);
305     myPropertyChangeListener = null;
306
307     super.uninstallListeners(root);
308   }
309
310   /**
311    * Uninstalls the previously installed {@code LayoutManager}.
312    *
313    * @param root Root pane.
314    */
315   private void uninstallLayout(JRootPane root) {
316     if (myOldLayout != null) {
317       root.setLayout(myOldLayout);
318       myOldLayout = null;
319     }
320   }
321
322   /**
323    * Installs the necessary state onto the JRootPane to render client
324    * decorations. This is ONLY invoked if the {@code JRootPane} has a
325    * decoration style other than {@code JRootPane.NONE}.
326    *
327    * @param root Root pane.
328    */
329   private void installClientDecorations(JRootPane root) {
330     installBorder(root);
331
332     JComponent titlePane = createTitlePane(root);
333
334     setTitlePane(root, titlePane);
335     installWindowListeners(root, root.getParent());
336     installLayout(root);
337     if (myWindow != null) {
338       root.revalidate();
339       root.repaint();
340     }
341   }
342
343   private void uninstallClientDecorations(JRootPane root) {
344     uninstallBorder(root);
345     uninstallWindowListeners(root);
346     setTitlePane(root, null);
347     uninstallLayout(root);
348     int style = root.getWindowDecorationStyle();
349     if (style == JRootPane.NONE) {
350       root.repaint();
351       root.revalidate();
352     }
353
354     if (myWindow != null) {
355       myWindow.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
356     }
357     myWindow = null;
358   }
359
360   protected JComponent createTitlePane(JRootPane root) {
361     return new DarculaTitlePane(root, this);
362   }
363
364   protected LayoutManager createLayoutManager() {
365     return new DarculaRootLayout();
366   }
367
368   private void setTitlePane(JRootPane root, JComponent titlePane) {
369     JLayeredPane layeredPane = root.getLayeredPane();
370     JComponent oldTitlePane = getTitlePane();
371
372     if (oldTitlePane != null) {
373       layeredPane.remove(oldTitlePane);
374     }
375     if (titlePane != null) {
376       layeredPane.add(titlePane, JLayeredPane.FRAME_CONTENT_LAYER);
377       titlePane.setVisible(true);
378     }
379     myTitlePane = titlePane;
380   }
381
382   public void setMaximized() {
383     if (Registry.is("darcula.fix.maximized.frame.bounds")) return;
384
385     Component tla = myRootPane.getTopLevelAncestor();
386     GraphicsConfiguration gc = (currentRootPaneGC != null) ? currentRootPaneGC : tla.getGraphicsConfiguration();
387     Rectangle screenBounds = gc.getBounds();
388     screenBounds.x = 0;
389     screenBounds.y = 0;
390     Insets screenInsets = ScreenUtil.getScreenInsets(gc);
391     Rectangle maxBounds = new Rectangle(screenBounds.x + screenInsets.left,
392                                         screenBounds.y + screenInsets.top,
393                                         screenBounds.width - (screenInsets.left + screenInsets.right),
394                                         screenBounds.height - (screenInsets.top + screenInsets.bottom));
395     if (tla instanceof JFrame) {
396       ((JFrame)tla).setMaximizedBounds(maxBounds);
397     }
398   }
399
400   public JComponent getTitlePane() {
401     return myTitlePane;
402   }
403
404   protected JRootPane getRootPane() {
405     return myRootPane;
406   }
407
408   @Override
409   public void propertyChange(PropertyChangeEvent e) {
410     super.propertyChange(e);
411
412     String propertyName = e.getPropertyName();
413     if (propertyName == null) {
414       return;
415     }
416
417     if (propertyName.equals("windowDecorationStyle")) {
418       JRootPane root = (JRootPane)e.getSource();
419       int style = root.getWindowDecorationStyle();
420
421       uninstallClientDecorations(root);
422       if (style != JRootPane.NONE) {
423         installClientDecorations(root);
424       }
425     }
426     if (propertyName.equals("ancestor")) {
427       uninstallWindowListeners(myRootPane);
428       if (((JRootPane)e.getSource()).getWindowDecorationStyle() != JRootPane.NONE) {
429         installWindowListeners(myRootPane, myRootPane.getParent());
430       }
431     }
432   }
433
434   protected class DarculaRootLayout implements LayoutManager2 {
435     public Dimension preferredLayoutSize(Container parent) {
436       Dimension cpd, mbd, tpd;
437       int cpWidth = 0;
438       int cpHeight = 0;
439       int mbWidth = 0;
440       int mbHeight = 0;
441       int tpWidth = 0;
442       int tpHeight = 0;
443       Insets i = parent.getInsets();
444       JRootPane root = (JRootPane)parent;
445
446       if (root.getContentPane() != null) {
447         cpd = root.getContentPane().getPreferredSize();
448       }
449       else {
450         cpd = root.getSize();
451       }
452       if (cpd != null) {
453         cpWidth = cpd.width;
454         cpHeight = cpd.height;
455       }
456
457       if (root.getJMenuBar() != null) {
458         mbd = root.getJMenuBar().getPreferredSize();
459         if (mbd != null) {
460           mbWidth = mbd.width;
461           mbHeight = mbd.height;
462         }
463       }
464
465       if ((root.getWindowDecorationStyle() != JRootPane.NONE)
466           && (root.getUI() instanceof DarculaRootPaneUI)) {
467         JComponent titlePane = ((DarculaRootPaneUI)root.getUI()).getTitlePane();
468         if (titlePane != null) {
469           tpd = titlePane.getPreferredSize();
470           if (tpd != null) {
471             tpWidth = tpd.width;
472             tpHeight = tpd.height;
473           }
474         }
475       }
476
477       return new Dimension(max(cpWidth, mbWidth, tpWidth) + i.left + i.right, cpHeight + mbHeight + tpHeight + i.top + i.bottom);
478     }
479
480     public Dimension minimumLayoutSize(Container parent) {
481       Dimension cpd, mbd, tpd;
482       int cpWidth = 0;
483       int cpHeight = 0;
484       int mbWidth = 0;
485       int mbHeight = 0;
486       int tpWidth = 0;
487       int tpHeight = 0;
488       Insets i = parent.getInsets();
489       JRootPane root = (JRootPane)parent;
490
491       if (root.getContentPane() != null) {
492         cpd = root.getContentPane().getMinimumSize();
493       }
494       else {
495         cpd = root.getSize();
496       }
497       if (cpd != null) {
498         cpWidth = cpd.width;
499         cpHeight = cpd.height;
500       }
501
502       if (root.getJMenuBar() != null) {
503         mbd = root.getJMenuBar().getMinimumSize();
504         if (mbd != null) {
505           mbWidth = mbd.width;
506           mbHeight = mbd.height;
507         }
508       }
509       if ((root.getWindowDecorationStyle() != JRootPane.NONE) && (root.getUI() instanceof DarculaRootPaneUI)) {
510         JComponent titlePane = ((DarculaRootPaneUI)root.getUI()).getTitlePane();
511         if (titlePane != null) {
512           tpd = titlePane.getMinimumSize();
513           if (tpd != null) {
514             tpWidth = tpd.width;
515             tpHeight = tpd.height;
516           }
517         }
518       }
519
520       return new Dimension(max(cpWidth, mbWidth, tpWidth) + i.left + i.right, cpHeight + mbHeight + tpHeight + i.top + i.bottom);
521     }
522
523     public Dimension maximumLayoutSize(Container target) {
524       Dimension cpd, mbd, tpd;
525       int cpWidth = Integer.MAX_VALUE;
526       int cpHeight = Integer.MAX_VALUE;
527       int mbWidth = Integer.MAX_VALUE;
528       int mbHeight = Integer.MAX_VALUE;
529       int tpWidth = Integer.MAX_VALUE;
530       int tpHeight = Integer.MAX_VALUE;
531       Insets i = target.getInsets();
532       JRootPane root = (JRootPane)target;
533
534       if (root.getContentPane() != null) {
535         cpd = root.getContentPane().getMaximumSize();
536         if (cpd != null) {
537           cpWidth = cpd.width;
538           cpHeight = cpd.height;
539         }
540       }
541
542       if (root.getJMenuBar() != null) {
543         mbd = root.getJMenuBar().getMaximumSize();
544         if (mbd != null) {
545           mbWidth = mbd.width;
546           mbHeight = mbd.height;
547         }
548       }
549
550       if ((root.getWindowDecorationStyle() != JRootPane.NONE) && (root.getUI() instanceof DarculaRootPaneUI)) {
551         JComponent titlePane = ((DarculaRootPaneUI)root.getUI()).getTitlePane();
552         if (titlePane != null) {
553           tpd = titlePane.getMaximumSize();
554           if (tpd != null) {
555             tpWidth = tpd.width;
556             tpHeight = tpd.height;
557           }
558         }
559       }
560
561       int maxHeight = max(cpHeight, mbHeight, tpHeight);
562       if (maxHeight != Integer.MAX_VALUE) {
563         maxHeight = cpHeight + mbHeight + tpHeight + i.top + i.bottom;
564       }
565
566       int maxWidth = max(cpWidth, mbWidth, tpWidth);
567
568       if (maxWidth != Integer.MAX_VALUE) {
569         maxWidth += i.left + i.right;
570       }
571
572       return new Dimension(maxWidth, maxHeight);
573     }
574
575     public void layoutContainer(Container parent) {
576       JRootPane root = (JRootPane)parent;
577       Rectangle b = root.getBounds();
578       Insets i = root.getInsets();
579       int nextY = 0;
580       int w = b.width - i.right - i.left;
581       int h = b.height - i.top - i.bottom;
582
583       if (root.getLayeredPane() != null) {
584         root.getLayeredPane().setBounds(i.left, i.top, w, h);
585       }
586       if (root.getGlassPane() != null) {
587         root.getGlassPane().setBounds(i.left, i.top, w, h);
588       }
589
590       if ((root.getWindowDecorationStyle() != JRootPane.NONE) && (root.getUI() instanceof DarculaRootPaneUI)) {
591         JComponent titlePane = ((DarculaRootPaneUI)root.getUI()).getTitlePane();
592         if (titlePane != null) {
593           Dimension tpd = titlePane.getPreferredSize();
594           if (tpd != null) {
595             int tpHeight = tpd.height;
596             titlePane.setBounds(0, 0, w, tpHeight);
597             nextY += tpHeight;
598           }
599         }
600       }
601       if (root.getJMenuBar() != null) {
602         Dimension mbd = root.getJMenuBar().getPreferredSize();
603         root.getJMenuBar().setBounds(0, nextY, w, mbd.height);
604         nextY += mbd.height;
605       }
606       if (root.getContentPane() != null) {
607
608         root.getContentPane().setBounds(0, nextY, w, h < nextY ? 0 : h - nextY);
609       }
610     }
611
612     public void addLayoutComponent(String name, Component comp) {
613     }
614
615     public void removeLayoutComponent(Component comp) {
616     }
617
618     public void addLayoutComponent(Component comp, Object constraints) {
619     }
620
621     public float getLayoutAlignmentX(Container target) {
622       return 0.0f;
623     }
624
625     public float getLayoutAlignmentY(Container target) {
626       return 0.0f;
627     }
628
629     public void invalidateLayout(Container target) {
630     }
631   }
632
633   private static int max(int a, int b, int...others) {
634     int result = Math.max(a, b);
635     for (int other : others) {
636       result = Math.max(result, other);
637     }
638     return result;
639   }
640 }