replaced <code></code> with more concise {@code}
[idea/community.git] / platform / platform-api / src / com / intellij / openapi / ui / ThreeComponentsSplitter.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.ui;
17
18 import com.intellij.icons.AllIcons;
19 import com.intellij.openapi.Disposable;
20 import com.intellij.openapi.util.Disposer;
21 import com.intellij.openapi.util.Weighted;
22 import com.intellij.openapi.util.registry.Registry;
23 import com.intellij.openapi.wm.IdeGlassPane;
24 import com.intellij.openapi.wm.IdeGlassPaneUtil;
25 import com.intellij.ui.ClickListener;
26 import com.intellij.ui.UIBundle;
27 import com.intellij.util.ui.JBUI;
28 import com.intellij.util.ui.UIUtil;
29 import com.intellij.util.ui.update.Activatable;
30 import com.intellij.util.ui.update.UiNotifyConnector;
31 import org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
33
34 import javax.swing.*;
35 import java.awt.*;
36 import java.awt.event.MouseAdapter;
37 import java.awt.event.MouseEvent;
38 import java.util.*;
39 import java.util.List;
40
41 /**
42  * @author Vladimir Kondratyev
43  */
44 public class ThreeComponentsSplitter extends JPanel implements Disposable {
45   private int myDividerWidth;
46   /**
47    *                        /------/
48    *                        |  1   |
49    * This is vertical split |------|
50    *                        |  2   |
51    *                        /------/
52    *
53    *                          /-------/
54    *                          |   |   |
55    * This is horizontal split | 1 | 2 |
56    *                          |   |   |
57    *                          /-------/
58    */
59   private boolean myVerticalSplit;
60   private boolean myHonorMinimumSize = false;
61
62   private final Divider myFirstDivider;
63   private final Divider myLastDivider;
64
65   @Nullable private JComponent myFirstComponent;
66   @Nullable private JComponent myInnerComponent;
67   @Nullable private JComponent myLastComponent;
68
69   private int myFirstSize = 0;
70   private int myLastSize = 0;
71   private int myMinSize = 0;
72
73   private boolean myShowDividerControls;
74   private int myDividerZone;
75
76   private class MyFocusTraversalPolicy extends FocusTraversalPolicy {
77
78     @Override
79     public Component getComponentAfter(Container aContainer, Component aComponent) {
80       if (aComponent == myFirstComponent) {
81         return findChildToFocus(myInnerComponent);
82       }
83       if (aComponent == myInnerComponent) {
84         return findChildToFocus(myLastComponent);
85       }
86       return findChildToFocus(myFirstComponent);
87     }
88
89     @Override
90     public Component getComponentBefore(Container aContainer, Component aComponent) {
91       if (aComponent == myInnerComponent) {
92         return findChildToFocus(myFirstComponent);
93       }
94       if (aComponent == myLastComponent) {
95         return findChildToFocus(myInnerComponent);
96       }
97       return findChildToFocus(myFirstComponent);
98     }
99
100     @Override
101     public Component getFirstComponent(Container aContainer) {
102       return findChildToFocus(myFirstComponent);
103     }
104
105     @Override
106     public Component getLastComponent(Container aContainer) {
107       return findChildToFocus(myLastComponent);
108     }
109
110     @Override
111     public Component getDefaultComponent(Container aContainer) {
112       return findChildToFocus(myInnerComponent);
113     }
114
115     Component findChildToFocus (Component component) {
116
117       if (component instanceof JPanel) {
118         JPanel container = (JPanel)component;
119         final FocusTraversalPolicy policy = container.getFocusTraversalPolicy();
120
121         if (policy == null) {
122           return container;
123         }
124
125         final Component defaultComponent = policy.getDefaultComponent(container);
126         if (defaultComponent == null) {
127           return container;
128         }
129         return policy.getDefaultComponent(container);
130       }
131
132       return component;
133
134     }
135
136   }
137
138   /**
139    * Creates horizontal split with proportion equals to .5f
140    */
141   public ThreeComponentsSplitter() {
142     this(false);
143   }
144
145   public ThreeComponentsSplitter(boolean vertical) {
146     this(vertical, false);
147   }
148
149   public ThreeComponentsSplitter(boolean vertical, boolean onePixelDividers) {
150     myVerticalSplit = vertical;
151     myShowDividerControls = false;
152     myFirstDivider = new Divider(true, onePixelDividers);
153     Disposer.register(this, myFirstDivider);
154     myLastDivider = new Divider(false, onePixelDividers);
155     Disposer.register(this, myLastDivider);
156
157     myDividerWidth = onePixelDividers ? 1 : 7;
158     if (onePixelDividers) {
159       Color bg = UIUtil.CONTRAST_BORDER_COLOR;
160       myFirstDivider.setBackground(bg);
161       myLastDivider.setBackground(bg);
162     }
163     setFocusCycleRoot(true);
164     setFocusTraversalPolicy(new MyFocusTraversalPolicy());
165     setFocusable(false);
166     setOpaque(false);
167     add(myFirstDivider);
168     add(myLastDivider);
169   }
170
171   public void setShowDividerControls(boolean showDividerControls) {
172     myShowDividerControls = showDividerControls;
173     setOrientation(myVerticalSplit);
174   }
175
176   public void setDividerMouseZoneSize(int size) {
177     myDividerZone = JBUI.scale(size);
178   }
179
180   public boolean isHonorMinimumSize() {
181     return myHonorMinimumSize;
182   }
183
184   public void setHonorComponentsMinimumSize(boolean honorMinimumSize) {
185     myHonorMinimumSize = honorMinimumSize;
186   }
187
188   public boolean isVisible() {
189     return super.isVisible() && (firstVisible() || innerVisible() || lastVisible());
190   }
191
192   private boolean lastVisible() {
193     return !Splitter.isNull(myLastComponent) && myLastComponent.isVisible();
194   }
195
196   private boolean innerVisible() {
197     return !Splitter.isNull(myInnerComponent) && myInnerComponent.isVisible();
198   }
199
200   private boolean firstVisible() {
201     return !Splitter.isNull(myFirstComponent) && myFirstComponent.isVisible();
202   }
203
204   private int visibleDividersCount() {
205     int count = 0;
206     if (firstDividerVisible()) count++;
207     if (lastDividerVisible()) count++;
208     return count;
209   }
210
211   private boolean firstDividerVisible() {
212     return firstVisible() && innerVisible() || firstVisible() && lastVisible() && !innerVisible();
213   }
214
215   private boolean lastDividerVisible() {
216     return innerVisible() && lastVisible();
217   }
218
219   public Dimension getMinimumSize() {
220     if (isHonorMinimumSize()) {
221       final int dividerWidth = getDividerWidth();
222       final Dimension firstSize = myFirstComponent != null ? myFirstComponent.getMinimumSize() : JBUI.emptySize();
223       final Dimension lastSize = myLastComponent != null ? myLastComponent.getMinimumSize() : JBUI.emptySize();
224       final Dimension innerSize = myInnerComponent != null ? myInnerComponent.getMinimumSize() : JBUI.emptySize();
225       if (getOrientation()) {
226         int width = Math.max(firstSize.width, Math.max(lastSize.width, innerSize.width));
227         int height = visibleDividersCount() * dividerWidth;
228         height += firstSize.height;
229         height += lastSize.height;
230         height += innerSize.height;
231         return new Dimension(width, height);
232       }
233       else {
234         int heigth = Math.max(firstSize.height, Math.max(lastSize.height, innerSize.height));
235         int width = visibleDividersCount() * dividerWidth;
236         width += firstSize.width;
237         width += lastSize.width;
238         width += innerSize.width;
239         return new Dimension(width, heigth);
240       }
241     }
242     return super.getMinimumSize();
243   }
244
245   public void doLayout() {
246     final int width = getWidth();
247     final int height = getHeight();
248
249     Rectangle firstRect = new Rectangle();
250     Rectangle firstDividerRect = new Rectangle();
251     Rectangle lastDividerRect = new Rectangle();
252     Rectangle lastRect = new Rectangle();
253     Rectangle innerRect = new Rectangle();
254     final int componentSize = getOrientation() ? height : width;
255     int dividerWidth = getDividerWidth();
256     int dividersCount = visibleDividersCount();
257
258     int firstCompontSize;
259     int lastComponentSize;
260     int innerComponentSize;
261     if(componentSize <= dividersCount * dividerWidth) {
262       firstCompontSize = 0;
263       lastComponentSize = 0;
264       innerComponentSize = 0;
265       dividerWidth = componentSize;
266     }
267     else {
268       firstCompontSize = getFirstSize();
269       lastComponentSize = getLastSize();
270       int sizeLack = firstCompontSize + lastComponentSize - (componentSize - dividersCount * dividerWidth - myMinSize);
271       if (sizeLack > 0) {
272         // Lacking size. Reduce first & last component's size, inner -> MIN_SIZE
273         double firstSizeRatio = (double)firstCompontSize / (firstCompontSize + lastComponentSize);
274         if (firstCompontSize > 0) {
275           firstCompontSize -= sizeLack * firstSizeRatio;
276           firstCompontSize = Math.max(myMinSize, firstCompontSize);
277         }
278         if (lastComponentSize > 0) {
279           lastComponentSize -= sizeLack * (1 - firstSizeRatio);
280           lastComponentSize = Math.max(myMinSize, lastComponentSize);
281         }
282         innerComponentSize = getMinSize(myInnerComponent);
283       }
284       else {
285         innerComponentSize = Math.max(getMinSize(myInnerComponent), componentSize - dividersCount * dividerWidth - getFirstSize() - getLastSize());
286       }
287
288       if (!innerVisible()) {
289         lastComponentSize += innerComponentSize;
290         innerComponentSize = 0;
291         if (!lastVisible()) {
292           firstCompontSize = componentSize;
293         }
294       }
295     }
296
297     if (getOrientation()) {
298       int space = firstCompontSize;
299       firstRect.setBounds(0, 0, width, firstCompontSize);
300       if (firstDividerVisible()) {
301         firstDividerRect.setBounds(0, space, width, dividerWidth);
302         space += dividerWidth;
303       }
304
305       innerRect.setBounds(0, space, width, innerComponentSize);
306       space += innerComponentSize;
307
308       if (lastDividerVisible()) {
309         lastDividerRect.setBounds(0, space, width, dividerWidth);
310         space += dividerWidth;
311       }
312
313       lastRect.setBounds(0, space, width, lastComponentSize);
314     }
315     else {
316       int space = firstCompontSize;
317       firstRect.setBounds(0, 0, firstCompontSize, height);
318
319       if (firstDividerVisible()) {
320         firstDividerRect.setBounds(space, 0, dividerWidth, height);
321         space += dividerWidth;
322       }
323
324       innerRect.setBounds(space, 0, innerComponentSize, height);
325       space += innerComponentSize;
326
327       if (lastDividerVisible()) {
328         lastDividerRect.setBounds(space, 0, dividerWidth, height);
329         space += dividerWidth;
330       }
331
332       lastRect.setBounds(space, 0, lastComponentSize, height);
333     }
334
335     myFirstDivider.setVisible(firstDividerVisible());
336     myFirstDivider.setBounds(firstDividerRect);
337     myFirstDivider.doLayout();
338
339     myLastDivider.setVisible(lastDividerVisible());
340     myLastDivider.setBounds(lastDividerRect);
341     myLastDivider.doLayout();
342
343     validateIfNeeded(myFirstComponent, firstRect);
344     validateIfNeeded(myInnerComponent, innerRect);
345     validateIfNeeded(myLastComponent, lastRect);
346   }
347
348   private static void validateIfNeeded(final JComponent c, final Rectangle rect) {
349     if (!Splitter.isNull(c)) {
350       if (!c.getBounds().equals(rect)) {
351         c.setBounds(rect);
352         c.revalidate();
353       }
354     } else {
355       Splitter.hideNull(c);
356     }
357   }
358
359
360   public int getDividerWidth() {
361     return myDividerWidth;
362   }
363
364   public void setDividerWidth(int width) {
365     if (width < 0) {
366       throw new IllegalArgumentException("Wrong divider width: " + width);
367     }
368     if (myDividerWidth != width) {
369       myDividerWidth = width;
370       doLayout();
371       repaint();
372     }
373   }
374
375   /**
376    * @return {@code true} if splitter has vertical orientation, {@code false} otherwise
377    */
378   public boolean getOrientation() {
379     return myVerticalSplit;
380   }
381
382   /**
383    * @param verticalSplit {@code true} means that splitter will have vertical split
384    */
385   public void setOrientation(boolean verticalSplit) {
386     myVerticalSplit = verticalSplit;
387     myFirstDivider.setOrientation(verticalSplit);
388     myLastDivider.setOrientation(verticalSplit);
389     doLayout();
390     repaint();
391   }
392
393   @Nullable
394   public JComponent getFirstComponent() {
395     return myFirstComponent;
396   }
397
398   /**
399    * Sets component which is located as the "first" splitted area. The method doesn't validate and
400    * repaint the splitter. If there is already
401    *
402    */
403   public void setFirstComponent(@Nullable JComponent component) {
404     if (myFirstComponent != component) {
405       if (myFirstComponent != null) {
406         remove(myFirstComponent);
407       }
408       myFirstComponent = component;
409       if (myFirstComponent != null) {
410         add(myFirstComponent);
411         myFirstComponent.invalidate();
412       }
413     }
414   }
415
416   @Nullable
417   public JComponent getLastComponent() {
418     return myLastComponent;
419   }
420
421
422   /**
423    * Sets component which is located as the "secont" splitted area. The method doesn't validate and
424    * repaint the splitter.
425    *
426    */
427   public void setLastComponent(@Nullable JComponent component) {
428     if (myLastComponent != component) {
429       if (myLastComponent != null) {
430         remove(myLastComponent);
431       }
432       myLastComponent = component;
433       if (myLastComponent != null) {
434         add(myLastComponent);
435         myLastComponent.invalidate();
436       }
437     }
438   }
439
440   @Nullable
441   public JComponent getInnerComponent() {
442     return myInnerComponent;
443   }
444
445
446   /**
447    * Sets component which is located as the "inner" splitted area. The method doesn't validate and
448    * repaint the splitter.
449    *
450    */
451   public void setInnerComponent(@Nullable JComponent component) {
452     if (myInnerComponent != component) {
453       if (myInnerComponent != null) {
454         remove(myInnerComponent);
455       }
456       myInnerComponent = component;
457       if (myInnerComponent != null) {
458         add(myInnerComponent);
459         myInnerComponent.invalidate();
460       }
461     }
462   }
463   public void setMinSize(int minSize) {
464     myMinSize = Math.max(0, minSize);
465     doLayout();
466     repaint();
467   }
468
469
470   public void setFirstSize(final int size) {
471     myFirstSize = Math.max(getMinSize(true), size);
472     doLayout();
473     repaint();
474   }
475
476   public void setLastSize(final int size) {
477     myLastSize = Math.max(getMinSize(false), size);
478     doLayout();
479     repaint();
480   }
481
482   public int getFirstSize() {
483     return firstVisible() ? myFirstSize : 0;
484   }
485
486   public int getLastSize() {
487     return lastVisible() ? myLastSize : 0;
488   }
489
490   public int getMinSize(boolean first) {
491     return getMinSize(first? myFirstComponent : myLastComponent);
492   }
493
494   public int getMaxSize(boolean first) {
495     final int size = getOrientation() ? this.getHeight() : this.getWidth();
496     return size - (first? myLastSize: myFirstSize) - myMinSize;
497   }
498
499   private int getMinSize(JComponent component) {
500     if (isHonorMinimumSize()) {
501       if (component != null && myFirstComponent != null && myFirstComponent.isVisible() && myLastComponent != null && myLastComponent.isVisible()) {
502         if (getOrientation()) {
503           return component.getMinimumSize().height;
504         }
505         else {
506           return component.getMinimumSize().width;
507         }
508       }
509     }
510     return myMinSize;
511   }
512
513   @Override
514   public void dispose() {
515     myLastComponent = null;
516     myFirstComponent = null;
517     myInnerComponent = null;
518     removeAll();
519     Container container = getParent();
520     if (container != null) {
521       container.remove(this);
522     }
523   }
524
525   private class Divider extends JPanel implements Disposable {
526     private final boolean myIsOnePixel;
527     protected boolean myDragging;
528     protected Point myPoint;
529     private final boolean myIsFirst;
530
531     @Override
532     public void paint(Graphics g) {
533       super.paint(g);
534     }
535
536     private IdeGlassPane myGlassPane;
537
538     private class MyMouseAdapter extends MouseAdapter implements Weighted {
539       @Override
540       public void mousePressed(MouseEvent e) {
541         _processMouseEvent(e);
542       }
543
544       @Override
545       public void mouseReleased(MouseEvent e) {
546         _processMouseEvent(e);
547       }
548
549       @Override
550       public void mouseMoved(MouseEvent e) {
551         _processMouseMotionEvent(e);
552       }
553
554       @Override
555       public void mouseDragged(MouseEvent e) {
556         _processMouseMotionEvent(e);
557       }
558       @Override
559       public double getWeight() {
560         return 1;
561       }
562       private void _processMouseMotionEvent(MouseEvent e) {
563         MouseEvent event = getTargetEvent(e);
564         if (event == null) {
565           myGlassPane.setCursor(null, myListener);
566           return;
567         }
568
569         processMouseMotionEvent(event);
570         if (event.isConsumed()) {
571           e.consume();
572         }
573       }
574
575       private void _processMouseEvent(MouseEvent e) {
576         MouseEvent event = getTargetEvent(e);
577         if (event == null) {
578           myGlassPane.setCursor(null, myListener);
579           return;
580         }
581
582         processMouseEvent(event);
583         if (event.isConsumed()) {
584           e.consume();
585         }
586       }
587     }
588
589     private final MouseAdapter myListener = new MyMouseAdapter();
590
591
592     private MouseEvent getTargetEvent(MouseEvent e) {
593       return SwingUtilities.convertMouseEvent(e.getComponent(), e, this);
594     }
595
596     private boolean myWasPressedOnMe;
597
598     public Divider(boolean isFirst, boolean isOnePixel) {
599       super(new GridBagLayout());
600       myIsOnePixel = isOnePixel;
601       setFocusable(false);
602       enableEvents(MouseEvent.MOUSE_EVENT_MASK | MouseEvent.MOUSE_MOTION_EVENT_MASK);
603       myIsFirst = isFirst;
604       setOrientation(myVerticalSplit);
605
606       new UiNotifyConnector.Once(this, new Activatable.Adapter() {
607         @Override
608         public void showNotify() {
609           init();
610         }
611       });
612     }
613
614     private boolean isInside(Point p) {
615       if (!isVisible()) return false;
616       Window window = UIUtil.getWindow(this);
617       if (window != null) {
618         Point point = SwingUtilities.convertPoint(this, p, window);
619         Component component = UIUtil.getDeepestComponentAt(window, point.x, point.y);
620         List<Component> components = Arrays.asList(myFirstComponent, myFirstDivider, myInnerComponent, myLastDivider, myLastComponent);
621         if (UIUtil.findParentByCondition(component, c -> c != null && components.contains(c)) == null) return false;
622       }
623
624       int dndOff = myIsOnePixel ? JBUI.scale(Registry.intValue("ide.splitter.mouseZone")) / 2 : 0;
625       if (myVerticalSplit) {
626         if (p.x >= 0 && p.x < getWidth()) {
627           if (getHeight() > 0) {
628             return p.y >= -dndOff && p.y < getHeight() + dndOff;
629           }
630           else {
631             return p.y >= -myDividerZone / 2 && p.y <= myDividerZone / 2;
632           }
633         }
634       }
635       else {
636         if (p.y >= 0 && p.y < getHeight()) {
637           if (getWidth() > 0) {
638             return p.x >= -dndOff && p.x < getWidth() + dndOff;
639           }
640           else {
641             return p.x >= -myDividerZone / 2 && p.x <= myDividerZone / 2;
642           }
643         }
644       }
645
646       return false;
647     }
648
649     private void init() {
650       myGlassPane = IdeGlassPaneUtil.find(this);
651       myGlassPane.addMouseMotionPreprocessor(myListener, this);
652       myGlassPane.addMousePreprocessor(myListener, this);
653     }
654
655     public void dispose() {
656     }
657
658     private void setOrientation(boolean isVerticalSplit) {
659       removeAll();
660
661       if (!myShowDividerControls) {
662         return;
663       }
664
665       int xMask = isVerticalSplit ? 1 : 0;
666       int yMask = isVerticalSplit ? 0 : 1;
667
668       Icon glueIcon = isVerticalSplit ? AllIcons.General.SplitGlueV : AllIcons.General.SplitCenterH;
669       int glueFill = isVerticalSplit ? GridBagConstraints.VERTICAL : GridBagConstraints.HORIZONTAL;
670       add(new JLabel(glueIcon),
671           new GridBagConstraints(0, 0, 1, 1, 0, 0, isVerticalSplit ? GridBagConstraints.EAST : GridBagConstraints.NORTH, glueFill, new Insets(0, 0, 0, 0), 0, 0));
672       JLabel splitDownlabel = new JLabel(isVerticalSplit ? AllIcons.General.SplitDown : AllIcons.General.SplitRight);
673       splitDownlabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
674       splitDownlabel.setToolTipText(isVerticalSplit ? UIBundle.message("splitter.down.tooltip.text") : UIBundle
675         .message("splitter.right.tooltip.text"));
676       new ClickListener() {
677         @Override
678         public boolean onClick(@NotNull MouseEvent e, int clickCount) {
679           if (myInnerComponent != null) {
680             final int income = myVerticalSplit ? myInnerComponent.getHeight() : myInnerComponent.getWidth();
681             if (myIsFirst) {
682               setFirstSize(myFirstSize + income);
683             }
684             else {
685               setLastSize(myLastSize + income);
686             }
687           }
688           return true;
689         }
690       }.installOn(splitDownlabel);
691
692       add(splitDownlabel,
693           new GridBagConstraints(isVerticalSplit ? 1 : 0,
694                                  isVerticalSplit ? 0 : 5,
695                                  1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
696       //
697       add(new JLabel(glueIcon),
698           new GridBagConstraints(2 * xMask, 2 * yMask, 1, 1, 0, 0, GridBagConstraints.CENTER, glueFill, new Insets(0, 0, 0, 0), 0, 0));
699       JLabel splitCenterlabel = new JLabel(isVerticalSplit ? AllIcons.General.SplitCenterV : AllIcons.General.SplitCenterH);
700       splitCenterlabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
701       splitCenterlabel.setToolTipText(UIBundle.message("splitter.center.tooltip.text"));
702       new ClickListener() {
703         @Override
704         public boolean onClick(@NotNull MouseEvent e, int clickCount) {
705           center();
706           return true;
707         }
708       }.installOn(splitCenterlabel);
709       add(splitCenterlabel,
710           new GridBagConstraints(3 * xMask, 3 * yMask, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
711       add(new JLabel(glueIcon),
712           new GridBagConstraints(4 * xMask, 4 * yMask, 1, 1, 0, 0, GridBagConstraints.CENTER, glueFill, new Insets(0, 0, 0, 0), 0, 0));
713       //
714       JLabel splitUpLabel = new JLabel(isVerticalSplit ? AllIcons.General.SplitUp : AllIcons.General.SplitLeft);
715       splitUpLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
716       splitUpLabel.setToolTipText(isVerticalSplit ? UIBundle.message("splitter.up.tooltip.text") : UIBundle
717         .message("splitter.left.tooltip.text"));
718       new ClickListener() {
719         @Override
720         public boolean onClick(@NotNull MouseEvent e, int clickCount) {
721           if (myInnerComponent != null) {
722             final int income = myVerticalSplit ? myInnerComponent.getHeight() : myInnerComponent.getWidth();
723             if (myIsFirst) {
724               setFirstSize(myFirstSize + income);
725             }
726             else {
727               setLastSize(myLastSize + income);
728             }
729           }
730           return true;
731         }
732       }.installOn(splitUpLabel);
733
734       add(splitUpLabel,
735           new GridBagConstraints(isVerticalSplit ? 5 : 0,
736                                  isVerticalSplit ? 0 : 1,
737                                  1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
738       add(new JLabel(glueIcon),
739           new GridBagConstraints(6 * xMask, 6 * yMask, 1, 1, 0, 0,
740                                  isVerticalSplit ? GridBagConstraints.WEST : GridBagConstraints.SOUTH, glueFill, new Insets(0, 0, 0, 0), 0, 0));
741     }
742
743     private void center() {
744       if (myInnerComponent != null) {
745         final int total = myFirstSize + (myVerticalSplit ? myInnerComponent.getHeight() : myInnerComponent.getWidth());
746         if (myIsFirst) {
747           setFirstSize(total / 2);
748         }
749         else {
750           setLastSize(total / 2);
751         }
752       }
753     }
754
755     protected void processMouseMotionEvent(MouseEvent e) {
756       super.processMouseMotionEvent(e);
757
758       if (!isShowing()) return;
759
760       if (MouseEvent.MOUSE_DRAGGED == e.getID() && myWasPressedOnMe) {
761         myDragging = true;
762         setCursor(getResizeCursor());
763
764         if (myGlassPane != null) {
765           myGlassPane.setCursor(getResizeCursor(), myListener);
766         }
767
768         myPoint = SwingUtilities.convertPoint(this, e.getPoint(), ThreeComponentsSplitter.this);
769         final int size = getOrientation() ? ThreeComponentsSplitter.this.getHeight() : ThreeComponentsSplitter.this.getWidth();
770         if (getOrientation()) {
771           if (size > 0 || myDividerZone > 0) {
772             if (myIsFirst) {
773               setFirstSize(Math.min(size - myLastSize - getMinSize(myInnerComponent) - getDividerWidth() * visibleDividersCount(), Math.max(getMinSize(myFirstComponent), myPoint.y)));
774             }
775             else {
776               setLastSize(Math.min(size - myFirstSize - getMinSize(myInnerComponent) - getDividerWidth() * visibleDividersCount(), Math.max(getMinSize(myLastComponent), size - myPoint.y - getDividerWidth())));
777             }
778           }
779         }
780         else {
781           if (size > 0 || myDividerZone > 0) {
782             if (myIsFirst) {
783               setFirstSize(Math.min(size - myLastSize - getMinSize(myInnerComponent) - getDividerWidth() * visibleDividersCount(), Math.max(getMinSize(myFirstComponent), myPoint.x)));
784             }
785             else {
786               setLastSize(Math.min(size - myFirstSize - getMinSize(myInnerComponent) - getDividerWidth() * visibleDividersCount(), Math.max(getMinSize(myLastComponent), size - myPoint.x - getDividerWidth())));
787             }
788           }
789         }
790         ThreeComponentsSplitter.this.doLayout();
791       } else if (MouseEvent.MOUSE_MOVED == e.getID()) {
792         if (myGlassPane != null) {
793           if (isInside(e.getPoint())) {
794             myGlassPane.setCursor(getResizeCursor(), myListener);
795             e.consume();
796           } else {
797             myGlassPane.setCursor(null, myListener);
798           }
799         }
800       }
801
802       if (myWasPressedOnMe) {
803         e.consume();
804       }
805     }
806
807     protected void processMouseEvent(MouseEvent e) {
808       super.processMouseEvent(e);
809       if (!isShowing()) {
810         return;
811       }
812       switch (e.getID()) {
813         case MouseEvent.MOUSE_ENTERED:
814           setCursor(getResizeCursor());
815           break;
816         case MouseEvent.MOUSE_EXITED:
817           if (!myDragging) {
818             setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
819           }
820           break;
821         case MouseEvent.MOUSE_PRESSED:
822           if (isInside(e.getPoint())) {
823             myWasPressedOnMe = true;
824             if (myGlassPane != null) {
825               myGlassPane.setCursor(getResizeCursor(), myListener);
826             }
827             e.consume();
828           } else {
829             myWasPressedOnMe = false;
830           }
831           break;
832         case MouseEvent.MOUSE_RELEASED:
833           if (myWasPressedOnMe) {
834             e.consume();
835           }
836           if (isInside(e.getPoint()) && myGlassPane != null) {
837             myGlassPane.setCursor(getResizeCursor(), myListener);
838           }
839           myWasPressedOnMe = false;
840           myDragging = false;
841           myPoint = null;
842           break;
843         case MouseEvent.MOUSE_CLICKED:
844           if (e.getClickCount() == 2) {
845             center();
846           }
847           break;
848       }
849     }
850   }
851
852   private Cursor getResizeCursor() {
853     return getOrientation() ? Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR) : Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);
854   }
855 }