1 /*
2 * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 package javax.swing.plaf.metal;
27
28 import java.awt.event;
29 import java.beans.PropertyChangeEvent;
30 import java.beans.PropertyChangeListener;
31 import javax.swing;
32 import javax.swing.border;
33 import javax.swing.event;
34 import javax.swing.plaf;
35 import javax.swing.plaf.basic;
36 import java.awt;
37 import java.io;
38 import java.security;
39
40 /**
41 * Provides the metal look and feel implementation of <code>RootPaneUI</code>.
42 * <p>
43 * <code>MetalRootPaneUI</code> provides support for the
44 * <code>windowDecorationStyle</code> property of <code>JRootPane</code>.
45 * <code>MetalRootPaneUI</code> does this by way of installing a custom
46 * <code>LayoutManager</code>, a private <code>Component</code> to render
47 * the appropriate widgets, and a private <code>Border</code>. The
48 * <code>LayoutManager</code> is always installed, regardless of the value of
49 * the <code>windowDecorationStyle</code> property, but the
50 * <code>Border</code> and <code>Component</code> are only installed/added if
51 * the <code>windowDecorationStyle</code> is other than
52 * <code>JRootPane.NONE</code>.
53 * <p>
54 * <strong>Warning:</strong>
55 * Serialized objects of this class will not be compatible with
56 * future Swing releases. The current serialization support is
57 * appropriate for short term storage or RMI between applications running
58 * the same version of Swing. As of 1.4, support for long term storage
59 * of all JavaBeans<sup><font size="-2">TM</font></sup>
60 * has been added to the <code>java.beans</code> package.
61 * Please see {@link java.beans.XMLEncoder}.
62 *
63 * @author Terry Kellerman
64 * @since 1.4
65 */
66 public class MetalRootPaneUI extends BasicRootPaneUI
67 {
68 /**
69 * Keys to lookup borders in defaults table.
70 */
71 private static final String[] borderKeys = new String[] {
72 null, "RootPane.frameBorder", "RootPane.plainDialogBorder",
73 "RootPane.informationDialogBorder",
74 "RootPane.errorDialogBorder", "RootPane.colorChooserDialogBorder",
75 "RootPane.fileChooserDialogBorder", "RootPane.questionDialogBorder",
76 "RootPane.warningDialogBorder"
77 };
78 /**
79 * The amount of space (in pixels) that the cursor is changed on.
80 */
81 private static final int CORNER_DRAG_WIDTH = 16;
82
83 /**
84 * Region from edges that dragging is active from.
85 */
86 private static final int BORDER_DRAG_THICKNESS = 5;
87
88 /**
89 * Window the <code>JRootPane</code> is in.
90 */
91 private Window window;
92
93 /**
94 * <code>JComponent</code> providing window decorations. This will be
95 * null if not providing window decorations.
96 */
97 private JComponent titlePane;
98
99 /**
100 * <code>MouseInputListener</code> that is added to the parent
101 * <code>Window</code> the <code>JRootPane</code> is contained in.
102 */
103 private MouseInputListener mouseInputListener;
104
105 /**
106 * The <code>LayoutManager</code> that is set on the
107 * <code>JRootPane</code>.
108 */
109 private LayoutManager layoutManager;
110
111 /**
112 * <code>LayoutManager</code> of the <code>JRootPane</code> before we
113 * replaced it.
114 */
115 private LayoutManager savedOldLayout;
116
117 /**
118 * <code>JRootPane</code> providing the look and feel for.
119 */
120 private JRootPane root;
121
122 /**
123 * <code>Cursor</code> used to track the cursor set by the user.
124 * This is initially <code>Cursor.DEFAULT_CURSOR</code>.
125 */
126 private Cursor lastCursor =
127 Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
128
129 /**
130 * Creates a UI for a <code>JRootPane</code>.
131 *
132 * @param c the JRootPane the RootPaneUI will be created for
133 * @return the RootPaneUI implementation for the passed in JRootPane
134 */
135 public static ComponentUI createUI(JComponent c) {
136 return new MetalRootPaneUI();
137 }
138
139 /**
140 * Invokes supers implementation of <code>installUI</code> to install
141 * the necessary state onto the passed in <code>JRootPane</code>
142 * to render the metal look and feel implementation of
143 * <code>RootPaneUI</code>. If
144 * the <code>windowDecorationStyle</code> property of the
145 * <code>JRootPane</code> is other than <code>JRootPane.NONE</code>,
146 * this will add a custom <code>Component</code> to render the widgets to
147 * <code>JRootPane</code>, as well as installing a custom
148 * <code>Border</code> and <code>LayoutManager</code> on the
149 * <code>JRootPane</code>.
150 *
151 * @param c the JRootPane to install state onto
152 */
153 public void installUI(JComponent c) {
154 super.installUI(c);
155 root = (JRootPane)c;
156 int style = root.getWindowDecorationStyle();
157 if (style != JRootPane.NONE) {
158 installClientDecorations(root);
159 }
160 }
161
162
163 /**
164 * Invokes supers implementation to uninstall any of its state. This will
165 * also reset the <code>LayoutManager</code> of the <code>JRootPane</code>.
166 * If a <code>Component</code> has been added to the <code>JRootPane</code>
167 * to render the window decoration style, this method will remove it.
168 * Similarly, this will revert the Border and LayoutManager of the
169 * <code>JRootPane</code> to what it was before <code>installUI</code>
170 * was invoked.
171 *
172 * @param c the JRootPane to uninstall state from
173 */
174 public void uninstallUI(JComponent c) {
175 super.uninstallUI(c);
176 uninstallClientDecorations(root);
177
178 layoutManager = null;
179 mouseInputListener = null;
180 root = null;
181 }
182
183 /**
184 * Installs the appropriate <code>Border</code> onto the
185 * <code>JRootPane</code>.
186 */
187 void installBorder(JRootPane root) {
188 int style = root.getWindowDecorationStyle();
189
190 if (style == JRootPane.NONE) {
191 LookAndFeel.uninstallBorder(root);
192 }
193 else {
194 LookAndFeel.installBorder(root, borderKeys[style]);
195 }
196 }
197
198 /**
199 * Removes any border that may have been installed.
200 */
201 private void uninstallBorder(JRootPane root) {
202 LookAndFeel.uninstallBorder(root);
203 }
204
205 /**
206 * Installs the necessary Listeners on the parent <code>Window</code>,
207 * if there is one.
208 * <p>
209 * This takes the parent so that cleanup can be done from
210 * <code>removeNotify</code>, at which point the parent hasn't been
211 * reset yet.
212 *
213 * @param parent The parent of the JRootPane
214 */
215 private void installWindowListeners(JRootPane root, Component parent) {
216 if (parent instanceof Window) {
217 window = (Window)parent;
218 }
219 else {
220 window = SwingUtilities.getWindowAncestor(parent);
221 }
222 if (window != null) {
223 if (mouseInputListener == null) {
224 mouseInputListener = createWindowMouseInputListener(root);
225 }
226 window.addMouseListener(mouseInputListener);
227 window.addMouseMotionListener(mouseInputListener);
228 }
229 }
230
231 /**
232 * Uninstalls the necessary Listeners on the <code>Window</code> the
233 * Listeners were last installed on.
234 */
235 private void uninstallWindowListeners(JRootPane root) {
236 if (window != null) {
237 window.removeMouseListener(mouseInputListener);
238 window.removeMouseMotionListener(mouseInputListener);
239 }
240 }
241
242 /**
243 * Installs the appropriate LayoutManager on the <code>JRootPane</code>
244 * to render the window decorations.
245 */
246 private void installLayout(JRootPane root) {
247 if (layoutManager == null) {
248 layoutManager = createLayoutManager();
249 }
250 savedOldLayout = root.getLayout();
251 root.setLayout(layoutManager);
252 }
253
254 /**
255 * Uninstalls the previously installed <code>LayoutManager</code>.
256 */
257 private void uninstallLayout(JRootPane root) {
258 if (savedOldLayout != null) {
259 root.setLayout(savedOldLayout);
260 savedOldLayout = null;
261 }
262 }
263
264 /**
265 * Installs the necessary state onto the JRootPane to render client
266 * decorations. This is ONLY invoked if the <code>JRootPane</code>
267 * has a decoration style other than <code>JRootPane.NONE</code>.
268 */
269 private void installClientDecorations(JRootPane root) {
270 installBorder(root);
271
272 JComponent titlePane = createTitlePane(root);
273
274 setTitlePane(root, titlePane);
275 installWindowListeners(root, root.getParent());
276 installLayout(root);
277 if (window != null) {
278 root.revalidate();
279 root.repaint();
280 }
281 }
282
283 /**
284 * Uninstalls any state that <code>installClientDecorations</code> has
285 * installed.
286 * <p>
287 * NOTE: This may be called if you haven't installed client decorations
288 * yet (ie before <code>installClientDecorations</code> has been invoked).
289 */
290 private void uninstallClientDecorations(JRootPane root) {
291 uninstallBorder(root);
292 uninstallWindowListeners(root);
293 setTitlePane(root, null);
294 uninstallLayout(root);
295 // We have to revalidate/repaint root if the style is JRootPane.NONE
296 // only. When we needs to call revalidate/repaint with other styles
297 // the installClientDecorations is always called after this method
298 // imediatly and it will cause the revalidate/repaint at the proper
299 // time.
300 int style = root.getWindowDecorationStyle();
301 if (style == JRootPane.NONE) {
302 root.repaint();
303 root.revalidate();
304 }
305 // Reset the cursor, as we may have changed it to a resize cursor
306 if (window != null) {
307 window.setCursor(Cursor.getPredefinedCursor
308 (Cursor.DEFAULT_CURSOR));
309 }
310 window = null;
311 }
312
313 /**
314 * Returns the <code>JComponent</code> to render the window decoration
315 * style.
316 */
317 private JComponent createTitlePane(JRootPane root) {
318 return new MetalTitlePane(root, this);
319 }
320
321 /**
322 * Returns a <code>MouseListener</code> that will be added to the
323 * <code>Window</code> containing the <code>JRootPane</code>.
324 */
325 private MouseInputListener createWindowMouseInputListener(JRootPane root) {
326 return new MouseInputHandler();
327 }
328
329 /**
330 * Returns a <code>LayoutManager</code> that will be set on the
331 * <code>JRootPane</code>.
332 */
333 private LayoutManager createLayoutManager() {
334 return new MetalRootLayout();
335 }
336
337 /**
338 * Sets the window title pane -- the JComponent used to provide a plaf a
339 * way to override the native operating system's window title pane with
340 * one whose look and feel are controlled by the plaf. The plaf creates
341 * and sets this value; the default is null, implying a native operating
342 * system window title pane.
343 *
344 * @param content the <code>JComponent</code> to use for the window title pane.
345 */
346 private void setTitlePane(JRootPane root, JComponent titlePane) {
347 JLayeredPane layeredPane = root.getLayeredPane();
348 JComponent oldTitlePane = getTitlePane();
349
350 if (oldTitlePane != null) {
351 oldTitlePane.setVisible(false);
352 layeredPane.remove(oldTitlePane);
353 }
354 if (titlePane != null) {
355 layeredPane.add(titlePane, JLayeredPane.FRAME_CONTENT_LAYER);
356 titlePane.setVisible(true);
357 }
358 this.titlePane = titlePane;
359 }
360
361 /**
362 * Returns the <code>JComponent</code> rendering the title pane. If this
363 * returns null, it implies there is no need to render window decorations.
364 *
365 * @return the current window title pane, or null
366 * @see #setTitlePane
367 */
368 private JComponent getTitlePane() {
369 return titlePane;
370 }
371
372 /**
373 * Returns the <code>JRootPane</code> we're providing the look and
374 * feel for.
375 */
376 private JRootPane getRootPane() {
377 return root;
378 }
379
380 /**
381 * Invoked when a property changes. <code>MetalRootPaneUI</code> is
382 * primarily interested in events originating from the
383 * <code>JRootPane</code> it has been installed on identifying the
384 * property <code>windowDecorationStyle</code>. If the
385 * <code>windowDecorationStyle</code> has changed to a value other
386 * than <code>JRootPane.NONE</code>, this will add a <code>Component</code>
387 * to the <code>JRootPane</code> to render the window decorations, as well
388 * as installing a <code>Border</code> on the <code>JRootPane</code>.
389 * On the other hand, if the <code>windowDecorationStyle</code> has
390 * changed to <code>JRootPane.NONE</code>, this will remove the
391 * <code>Component</code> that has been added to the <code>JRootPane</code>
392 * as well resetting the Border to what it was before
393 * <code>installUI</code> was invoked.
394 *
395 * @param e A PropertyChangeEvent object describing the event source
396 * and the property that has changed.
397 */
398 public void propertyChange(PropertyChangeEvent e) {
399 super.propertyChange(e);
400
401 String propertyName = e.getPropertyName();
402 if(propertyName == null) {
403 return;
404 }
405
406 if(propertyName.equals("windowDecorationStyle")) {
407 JRootPane root = (JRootPane) e.getSource();
408 int style = root.getWindowDecorationStyle();
409
410 // This is potentially more than needs to be done,
411 // but it rarely happens and makes the install/uninstall process
412 // simpler. MetalTitlePane also assumes it will be recreated if
413 // the decoration style changes.
414 uninstallClientDecorations(root);
415 if (style != JRootPane.NONE) {
416 installClientDecorations(root);
417 }
418 }
419 else if (propertyName.equals("ancestor")) {
420 uninstallWindowListeners(root);
421 if (((JRootPane)e.getSource()).getWindowDecorationStyle() !=
422 JRootPane.NONE) {
423 installWindowListeners(root, root.getParent());
424 }
425 }
426 return;
427 }
428
429 /**
430 * A custom layout manager that is responsible for the layout of
431 * layeredPane, glassPane, menuBar and titlePane, if one has been
432 * installed.
433 */
434 // NOTE: Ideally this would extends JRootPane.RootLayout, but that
435 // would force this to be non-static.
436 private static class MetalRootLayout implements LayoutManager2 {
437 /**
438 * Returns the amount of space the layout would like to have.
439 *
440 * @param the Container for which this layout manager is being used
441 * @return a Dimension object containing the layout's preferred size
442 */
443 public Dimension preferredLayoutSize(Container parent) {
444 Dimension cpd, mbd, tpd;
445 int cpWidth = 0;
446 int cpHeight = 0;
447 int mbWidth = 0;
448 int mbHeight = 0;
449 int tpWidth = 0;
450 int tpHeight = 0;
451 Insets i = parent.getInsets();
452 JRootPane root = (JRootPane) parent;
453
454 if(root.getContentPane() != null) {
455 cpd = root.getContentPane().getPreferredSize();
456 } else {
457 cpd = root.getSize();
458 }
459 if (cpd != null) {
460 cpWidth = cpd.width;
461 cpHeight = cpd.height;
462 }
463
464 if(root.getMenuBar() != null) {
465 mbd = root.getMenuBar().getPreferredSize();
466 if (mbd != null) {
467 mbWidth = mbd.width;
468 mbHeight = mbd.height;
469 }
470 }
471
472 if (root.getWindowDecorationStyle() != JRootPane.NONE &&
473 (root.getUI() instanceof MetalRootPaneUI)) {
474 JComponent titlePane = ((MetalRootPaneUI)root.getUI()).
475 getTitlePane();
476 if (titlePane != null) {
477 tpd = titlePane.getPreferredSize();
478 if (tpd != null) {
479 tpWidth = tpd.width;
480 tpHeight = tpd.height;
481 }
482 }
483 }
484
485 return new Dimension(Math.max(Math.max(cpWidth, mbWidth), tpWidth) + i.left + i.right,
486 cpHeight + mbHeight + tpWidth + i.top + i.bottom);
487 }
488
489 /**
490 * Returns the minimum amount of space the layout needs.
491 *
492 * @param the Container for which this layout manager is being used
493 * @return a Dimension object containing the layout's minimum size
494 */
495 public Dimension minimumLayoutSize(Container parent) {
496 Dimension cpd, mbd, tpd;
497 int cpWidth = 0;
498 int cpHeight = 0;
499 int mbWidth = 0;
500 int mbHeight = 0;
501 int tpWidth = 0;
502 int tpHeight = 0;
503 Insets i = parent.getInsets();
504 JRootPane root = (JRootPane) parent;
505
506 if(root.getContentPane() != null) {
507 cpd = root.getContentPane().getMinimumSize();
508 } else {
509 cpd = root.getSize();
510 }
511 if (cpd != null) {
512 cpWidth = cpd.width;
513 cpHeight = cpd.height;
514 }
515
516 if(root.getMenuBar() != null) {
517 mbd = root.getMenuBar().getMinimumSize();
518 if (mbd != null) {
519 mbWidth = mbd.width;
520 mbHeight = mbd.height;
521 }
522 }
523 if (root.getWindowDecorationStyle() != JRootPane.NONE &&
524 (root.getUI() instanceof MetalRootPaneUI)) {
525 JComponent titlePane = ((MetalRootPaneUI)root.getUI()).
526 getTitlePane();
527 if (titlePane != null) {
528 tpd = titlePane.getMinimumSize();
529 if (tpd != null) {
530 tpWidth = tpd.width;
531 tpHeight = tpd.height;
532 }
533 }
534 }
535
536 return new Dimension(Math.max(Math.max(cpWidth, mbWidth), tpWidth) + i.left + i.right,
537 cpHeight + mbHeight + tpWidth + i.top + i.bottom);
538 }
539
540 /**
541 * Returns the maximum amount of space the layout can use.
542 *
543 * @param the Container for which this layout manager is being used
544 * @return a Dimension object containing the layout's maximum size
545 */
546 public Dimension maximumLayoutSize(Container target) {
547 Dimension cpd, mbd, tpd;
548 int cpWidth = Integer.MAX_VALUE;
549 int cpHeight = Integer.MAX_VALUE;
550 int mbWidth = Integer.MAX_VALUE;
551 int mbHeight = Integer.MAX_VALUE;
552 int tpWidth = Integer.MAX_VALUE;
553 int tpHeight = Integer.MAX_VALUE;
554 Insets i = target.getInsets();
555 JRootPane root = (JRootPane) target;
556
557 if(root.getContentPane() != null) {
558 cpd = root.getContentPane().getMaximumSize();
559 if (cpd != null) {
560 cpWidth = cpd.width;
561 cpHeight = cpd.height;
562 }
563 }
564
565 if(root.getMenuBar() != null) {
566 mbd = root.getMenuBar().getMaximumSize();
567 if (mbd != null) {
568 mbWidth = mbd.width;
569 mbHeight = mbd.height;
570 }
571 }
572
573 if (root.getWindowDecorationStyle() != JRootPane.NONE &&
574 (root.getUI() instanceof MetalRootPaneUI)) {
575 JComponent titlePane = ((MetalRootPaneUI)root.getUI()).
576 getTitlePane();
577 if (titlePane != null)
578 {
579 tpd = titlePane.getMaximumSize();
580 if (tpd != null) {
581 tpWidth = tpd.width;
582 tpHeight = tpd.height;
583 }
584 }
585 }
586
587 int maxHeight = Math.max(Math.max(cpHeight, mbHeight), tpHeight);
588 // Only overflows if 3 real non-MAX_VALUE heights, sum to > MAX_VALUE
589 // Only will happen if sums to more than 2 billion units. Not likely.
590 if (maxHeight != Integer.MAX_VALUE) {
591 maxHeight = cpHeight + mbHeight + tpHeight + i.top + i.bottom;
592 }
593
594 int maxWidth = Math.max(Math.max(cpWidth, mbWidth), tpWidth);
595 // Similar overflow comment as above
596 if (maxWidth != Integer.MAX_VALUE) {
597 maxWidth += i.left + i.right;
598 }
599
600 return new Dimension(maxWidth, maxHeight);
601 }
602
603 /**
604 * Instructs the layout manager to perform the layout for the specified
605 * container.
606 *
607 * @param the Container for which this layout manager is being used
608 */
609 public void layoutContainer(Container parent) {
610 JRootPane root = (JRootPane) parent;
611 Rectangle b = root.getBounds();
612 Insets i = root.getInsets();
613 int nextY = 0;
614 int w = b.width - i.right - i.left;
615 int h = b.height - i.top - i.bottom;
616
617 if(root.getLayeredPane() != null) {
618 root.getLayeredPane().setBounds(i.left, i.top, w, h);
619 }
620 if(root.getGlassPane() != null) {
621 root.getGlassPane().setBounds(i.left, i.top, w, h);
622 }
623 // Note: This is laying out the children in the layeredPane,
624 // technically, these are not our children.
625 if (root.getWindowDecorationStyle() != JRootPane.NONE &&
626 (root.getUI() instanceof MetalRootPaneUI)) {
627 JComponent titlePane = ((MetalRootPaneUI)root.getUI()).
628 getTitlePane();
629 if (titlePane != null) {
630 Dimension tpd = titlePane.getPreferredSize();
631 if (tpd != null) {
632 int tpHeight = tpd.height;
633 titlePane.setBounds(0, 0, w, tpHeight);
634 nextY += tpHeight;
635 }
636 }
637 }
638 if(root.getMenuBar() != null) {
639 Dimension mbd = root.getMenuBar().getPreferredSize();
640 root.getMenuBar().setBounds(0, nextY, w, mbd.height);
641 nextY += mbd.height;
642 }
643 if(root.getContentPane() != null) {
644 Dimension cpd = root.getContentPane().getPreferredSize();
645 root.getContentPane().setBounds(0, nextY, w,
646 h < nextY ? 0 : h - nextY);
647 }
648 }
649
650 public void addLayoutComponent(String name, Component comp) {}
651 public void removeLayoutComponent(Component comp) {}
652 public void addLayoutComponent(Component comp, Object constraints) {}
653 public float getLayoutAlignmentX(Container target) { return 0.0f; }
654 public float getLayoutAlignmentY(Container target) { return 0.0f; }
655 public void invalidateLayout(Container target) {}
656 }
657
658
659 /**
660 * Maps from positions to cursor type. Refer to calculateCorner and
661 * calculatePosition for details of this.
662 */
663 private static final int[] cursorMapping = new int[]
664 { Cursor.NW_RESIZE_CURSOR, Cursor.NW_RESIZE_CURSOR, Cursor.N_RESIZE_CURSOR,
665 Cursor.NE_RESIZE_CURSOR, Cursor.NE_RESIZE_CURSOR,
666 Cursor.NW_RESIZE_CURSOR, 0, 0, 0, Cursor.NE_RESIZE_CURSOR,
667 Cursor.W_RESIZE_CURSOR, 0, 0, 0, Cursor.E_RESIZE_CURSOR,
668 Cursor.SW_RESIZE_CURSOR, 0, 0, 0, Cursor.SE_RESIZE_CURSOR,
669 Cursor.SW_RESIZE_CURSOR, Cursor.SW_RESIZE_CURSOR, Cursor.S_RESIZE_CURSOR,
670 Cursor.SE_RESIZE_CURSOR, Cursor.SE_RESIZE_CURSOR
671 };
672
673 /**
674 * MouseInputHandler is responsible for handling resize/moving of
675 * the Window. It sets the cursor directly on the Window when then
676 * mouse moves over a hot spot.
677 */
678 private class MouseInputHandler implements MouseInputListener {
679 /**
680 * Set to true if the drag operation is moving the window.
681 */
682 private boolean isMovingWindow;
683
684 /**
685 * Used to determine the corner the resize is occuring from.
686 */
687 private int dragCursor;
688
689 /**
690 * X location the mouse went down on for a drag operation.
691 */
692 private int dragOffsetX;
693
694 /**
695 * Y location the mouse went down on for a drag operation.
696 */
697 private int dragOffsetY;
698
699 /**
700 * Width of the window when the drag started.
701 */
702 private int dragWidth;
703
704 /**
705 * Height of the window when the drag started.
706 */
707 private int dragHeight;
708
709 public void mousePressed(MouseEvent ev) {
710 JRootPane rootPane = getRootPane();
711
712 if (rootPane.getWindowDecorationStyle() == JRootPane.NONE) {
713 return;
714 }
715 Point dragWindowOffset = ev.getPoint();
716 Window w = (Window)ev.getSource();
717 if (w != null) {
718 w.toFront();
719 }
720 Point convertedDragWindowOffset = SwingUtilities.convertPoint(
721 w, dragWindowOffset, getTitlePane());
722
723 Frame f = null;
724 Dialog d = null;
725
726 if (w instanceof Frame) {
727 f = (Frame)w;
728 } else if (w instanceof Dialog) {
729 d = (Dialog)w;
730 }
731
732 int frameState = (f != null) ? f.getExtendedState() : 0;
733
734 if (getTitlePane() != null &&
735 getTitlePane().contains(convertedDragWindowOffset)) {
736 if ((f != null && ((frameState & Frame.MAXIMIZED_BOTH) == 0)
737 || (d != null))
738 && dragWindowOffset.y >= BORDER_DRAG_THICKNESS
739 && dragWindowOffset.x >= BORDER_DRAG_THICKNESS
740 && dragWindowOffset.x < w.getWidth()
741 - BORDER_DRAG_THICKNESS) {
742 isMovingWindow = true;
743 dragOffsetX = dragWindowOffset.x;
744 dragOffsetY = dragWindowOffset.y;
745 }
746 }
747 else if (f != null && f.isResizable()
748 && ((frameState & Frame.MAXIMIZED_BOTH) == 0)
749 || (d != null && d.isResizable())) {
750 dragOffsetX = dragWindowOffset.x;
751 dragOffsetY = dragWindowOffset.y;
752 dragWidth = w.getWidth();
753 dragHeight = w.getHeight();
754 dragCursor = getCursor(calculateCorner(
755 w, dragWindowOffset.x, dragWindowOffset.y));
756 }
757 }
758
759 public void mouseReleased(MouseEvent ev) {
760 if (dragCursor != 0 && window != null && !window.isValid()) {
761 // Some Window systems validate as you resize, others won't,
762 // thus the check for validity before repainting.
763 window.validate();
764 getRootPane().repaint();
765 }
766 isMovingWindow = false;
767 dragCursor = 0;
768 }
769
770 public void mouseMoved(MouseEvent ev) {
771 JRootPane root = getRootPane();
772
773 if (root.getWindowDecorationStyle() == JRootPane.NONE) {
774 return;
775 }
776
777 Window w = (Window)ev.getSource();
778
779 Frame f = null;
780 Dialog d = null;
781
782 if (w instanceof Frame) {
783 f = (Frame)w;
784 } else if (w instanceof Dialog) {
785 d = (Dialog)w;
786 }
787
788 // Update the cursor
789 int cursor = getCursor(calculateCorner(w, ev.getX(), ev.getY()));
790
791 if (cursor != 0 && ((f != null && (f.isResizable() &&
792 (f.getExtendedState() & Frame.MAXIMIZED_BOTH) == 0))
793 || (d != null && d.isResizable()))) {
794 w.setCursor(Cursor.getPredefinedCursor(cursor));
795 }
796 else {
797 w.setCursor(lastCursor);
798 }
799 }
800
801 private void adjust(Rectangle bounds, Dimension min, int deltaX,
802 int deltaY, int deltaWidth, int deltaHeight) {
803 bounds.x += deltaX;
804 bounds.y += deltaY;
805 bounds.width += deltaWidth;
806 bounds.height += deltaHeight;
807 if (min != null) {
808 if (bounds.width < min.width) {
809 int correction = min.width - bounds.width;
810 if (deltaX != 0) {
811 bounds.x -= correction;
812 }
813 bounds.width = min.width;
814 }
815 if (bounds.height < min.height) {
816 int correction = min.height - bounds.height;
817 if (deltaY != 0) {
818 bounds.y -= correction;
819 }
820 bounds.height = min.height;
821 }
822 }
823 }
824
825 public void mouseDragged(MouseEvent ev) {
826 Window w = (Window)ev.getSource();
827 Point pt = ev.getPoint();
828
829 if (isMovingWindow) {
830 Point eventLocationOnScreen = ev.getLocationOnScreen();
831 w.setLocation(eventLocationOnScreen.x - dragOffsetX,
832 eventLocationOnScreen.y - dragOffsetY);
833 }
834 else if (dragCursor != 0) {
835 Rectangle r = w.getBounds();
836 Rectangle startBounds = new Rectangle(r);
837 Dimension min = w.getMinimumSize();
838
839 switch (dragCursor) {
840 case Cursor.E_RESIZE_CURSOR:
841 adjust(r, min, 0, 0, pt.x + (dragWidth - dragOffsetX) -
842 r.width, 0);
843 break;
844 case Cursor.S_RESIZE_CURSOR:
845 adjust(r, min, 0, 0, 0, pt.y + (dragHeight - dragOffsetY) -
846 r.height);
847 break;
848 case Cursor.N_RESIZE_CURSOR:
849 adjust(r, min, 0, pt.y -dragOffsetY, 0,
850 -(pt.y - dragOffsetY));
851 break;
852 case Cursor.W_RESIZE_CURSOR:
853 adjust(r, min, pt.x - dragOffsetX, 0,
854 -(pt.x - dragOffsetX), 0);
855 break;
856 case Cursor.NE_RESIZE_CURSOR:
857 adjust(r, min, 0, pt.y - dragOffsetY,
858 pt.x + (dragWidth - dragOffsetX) - r.width,
859 -(pt.y - dragOffsetY));
860 break;
861 case Cursor.SE_RESIZE_CURSOR:
862 adjust(r, min, 0, 0,
863 pt.x + (dragWidth - dragOffsetX) - r.width,
864 pt.y + (dragHeight - dragOffsetY) -
865 r.height);
866 break;
867 case Cursor.NW_RESIZE_CURSOR:
868 adjust(r, min, pt.x - dragOffsetX,
869 pt.y - dragOffsetY,
870 -(pt.x - dragOffsetX),
871 -(pt.y - dragOffsetY));
872 break;
873 case Cursor.SW_RESIZE_CURSOR:
874 adjust(r, min, pt.x - dragOffsetX, 0,
875 -(pt.x - dragOffsetX),
876 pt.y + (dragHeight - dragOffsetY) - r.height);
877 break;
878 default:
879 break;
880 }
881 if (!r.equals(startBounds)) {
882 w.setBounds(r);
883 // Defer repaint/validate on mouseReleased unless dynamic
884 // layout is active.
885 if (Toolkit.getDefaultToolkit().isDynamicLayoutActive()) {
886 w.validate();
887 getRootPane().repaint();
888 }
889 }
890 }
891 }
892
893 public void mouseEntered(MouseEvent ev) {
894 Window w = (Window)ev.getSource();
895 lastCursor = w.getCursor();
896 mouseMoved(ev);
897 }
898
899 public void mouseExited(MouseEvent ev) {
900 Window w = (Window)ev.getSource();
901 w.setCursor(lastCursor);
902 }
903
904 public void mouseClicked(MouseEvent ev) {
905 Window w = (Window)ev.getSource();
906 Frame f = null;
907
908 if (w instanceof Frame) {
909 f = (Frame)w;
910 } else {
911 return;
912 }
913
914 Point convertedPoint = SwingUtilities.convertPoint(
915 w, ev.getPoint(), getTitlePane());
916
917 int state = f.getExtendedState();
918 if (getTitlePane() != null &&
919 getTitlePane().contains(convertedPoint)) {
920 if ((ev.getClickCount() % 2) == 0 &&
921 ((ev.getModifiers() & InputEvent.BUTTON1_MASK) != 0)) {
922 if (f.isResizable()) {
923 if ((state & Frame.MAXIMIZED_BOTH) != 0) {
924 f.setExtendedState(state & ~Frame.MAXIMIZED_BOTH);
925 }
926 else {
927 f.setExtendedState(state | Frame.MAXIMIZED_BOTH);
928 }
929 return;
930 }
931 }
932 }
933 }
934
935 /**
936 * Returns the corner that contains the point <code>x</code>,
937 * <code>y</code>, or -1 if the position doesn't match a corner.
938 */
939 private int calculateCorner(Window w, int x, int y) {
940 Insets insets = w.getInsets();
941 int xPosition = calculatePosition(x - insets.left,
942 w.getWidth() - insets.left - insets.right);
943 int yPosition = calculatePosition(y - insets.top,
944 w.getHeight() - insets.top - insets.bottom);
945
946 if (xPosition == -1 || yPosition == -1) {
947 return -1;
948 }
949 return yPosition * 5 + xPosition;
950 }
951
952 /**
953 * Returns the Cursor to render for the specified corner. This returns
954 * 0 if the corner doesn't map to a valid Cursor
955 */
956 private int getCursor(int corner) {
957 if (corner == -1) {
958 return 0;
959 }
960 return cursorMapping[corner];
961 }
962
963 /**
964 * Returns an integer indicating the position of <code>spot</code>
965 * in <code>width</code>. The return value will be:
966 * 0 if < BORDER_DRAG_THICKNESS
967 * 1 if < CORNER_DRAG_WIDTH
968 * 2 if >= CORNER_DRAG_WIDTH && < width - BORDER_DRAG_THICKNESS
969 * 3 if >= width - CORNER_DRAG_WIDTH
970 * 4 if >= width - BORDER_DRAG_THICKNESS
971 * 5 otherwise
972 */
973 private int calculatePosition(int spot, int width) {
974 if (spot < BORDER_DRAG_THICKNESS) {
975 return 0;
976 }
977 if (spot < CORNER_DRAG_WIDTH) {
978 return 1;
979 }
980 if (spot >= (width - BORDER_DRAG_THICKNESS)) {
981 return 4;
982 }
983 if (spot >= (width - CORNER_DRAG_WIDTH)) {
984 return 3;
985 }
986 return 2;
987 }
988 }
989 }