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