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