You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. /**
  2. * Copyright (c) 2006-2009, Alexander Potochkin All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without modification, are permitted
  5. * provided that the following conditions are met:
  6. *
  7. * * Redistributions of source code must retain the above copyright notice, this list of conditions
  8. * and the following disclaimer. * Redistributions in binary form must reproduce the above copyright
  9. * notice, this list of conditions and the following disclaimer in the documentation and/or other
  10. * materials provided with the distribution. * Neither the name of the JXLayer project nor the names
  11. * of its contributors may be used to endorse or promote products derived from this software without
  12. * specific prior written permission.
  13. *
  14. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
  15. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  16. * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  17. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  18. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  19. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  20. * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  21. * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  22. */
  23. package com.dmdirc.addons.ui_swing.components;
  24. import java.awt.Component;
  25. import java.awt.Cursor;
  26. import java.awt.Graphics;
  27. import java.awt.Graphics2D;
  28. import java.awt.KeyboardFocusManager;
  29. import java.awt.event.FocusEvent;
  30. import java.awt.event.FocusListener;
  31. import javax.swing.JComponent;
  32. import javax.swing.SwingUtilities;
  33. import org.jdesktop.jxlayer.JXLayer;
  34. import org.jdesktop.jxlayer.plaf.AbstractBufferedLayerUI;
  35. import org.jdesktop.jxlayer.plaf.effect.LayerEffect;
  36. /**
  37. * An implementation of the {@code BufferedLayerUI} which provides a lightweight disabling for the
  38. * content of its {@link JXLayer}. This allows temporarily blocking a part of the interface with all
  39. * it subcomponents, it is also useful when some kind of action is in progress, e.g. reading data
  40. * from a database.
  41. * <p>
  42. * When {@code true} is passed to the {@link #setLocked(boolean)}, the {@code JXLayer} of this
  43. * {@code LockableLayerUI} becomes "locked". It sets the "wait" mouse cursor and stops reacting on
  44. * mouse, keyboard and focus events.
  45. * <p>
  46. * If {@code setLocked(boolean)} is called with {@code false} parameter after that, the
  47. * {@code JXLayer}, together with all its subcomponents, gets back to live.
  48. * <p>
  49. * Subclasses usually override {@link #paintLayer(Graphics2D,JXLayer)} or
  50. * {@link #getLayerEffects(JXLayer)} to implement some visual effects when {@code JXLayer} is in
  51. * locked state.
  52. * <p>
  53. * Here is an example of using {@code LockableLayerUI}:
  54. * <pre>
  55. * JComponent myComponent = getMyComponent(); // just any component
  56. *
  57. * LockableLayerUI lockableUI = new LockableLayerUI();
  58. * JXLayer&lt;JComponent&gt; layer = new JXLayer&lt;JComponent&gt;(myComponent,
  59. * lockableUI);
  60. *
  61. * // locking the layer, use lockableUI.setLocked(false) to unlock
  62. * lockableUI.setLocked(true);
  63. *
  64. * // add the layer to a frame or a panel, like any other component
  65. * frame.add(layer);
  66. * </pre>
  67. *
  68. * @param <T> Generic type
  69. */
  70. public class LockedLayer<T extends JComponent> extends AbstractBufferedLayerUI<T> {
  71. /**
  72. * A version number for this class. It should be changed whenever the class structure is changed
  73. * (or anything else that would prevent serialized objects being unserialized with the new
  74. * class).
  75. */
  76. private static final long serialVersionUID = 1;
  77. /** Are we locked. */
  78. private boolean locked;
  79. /** Most recent focus owner? */
  80. private Component recentFocusOwner;
  81. /** Cursor to show when locked. */
  82. private Cursor lockedCursor = Cursor.getPredefinedCursor(
  83. Cursor.DEFAULT_CURSOR);
  84. /** Effect to apply when locked. */
  85. private LayerEffect[] lockedEffects = new LayerEffect[0];
  86. /** Focus listener. */
  87. private final transient FocusListener focusListener = new FocusListener() {
  88. /** {@inheritDoc} */
  89. @Override
  90. public void focusGained(final FocusEvent e) {
  91. // we don't want extra repaintings
  92. // when focus comes from another window
  93. if (e.getOppositeComponent() != null) {
  94. setDirty(true);
  95. }
  96. }
  97. /** {@inheritDoc} */
  98. @Override
  99. public void focusLost(final FocusEvent e) {
  100. //Ignore
  101. }
  102. };
  103. /**
  104. * Creates a new instance of LockableUI.
  105. */
  106. public LockedLayer() {
  107. this((LayerEffect[]) null);
  108. }
  109. /**
  110. * Creates a new instance of LockableUI, passed lockedEffects will be used for when this UI in
  111. * the locked state.
  112. *
  113. * @param effects effects to be used when this UI is locked
  114. *
  115. * @see #setLocked(boolean)
  116. * @see #setLockedEffects(LayerEffect...)
  117. */
  118. public LockedLayer(final LayerEffect... effects) {
  119. super();
  120. setLockedEffects(effects);
  121. }
  122. /**
  123. * {@inheritDoc}
  124. */
  125. @Override
  126. public void installUI(final JComponent c) {
  127. super.installUI(c);
  128. // we need to repaint the layer when it receives the focus
  129. // otherwise the focused component will have it on the buffer image
  130. c.addFocusListener(focusListener);
  131. }
  132. /**
  133. * {@inheritDoc}
  134. */
  135. @Override
  136. public void uninstallUI(final JComponent c) {
  137. super.uninstallUI(c);
  138. c.removeFocusListener(focusListener);
  139. }
  140. /**
  141. * Returns {@code true} if this {@code LockableLayerUI} is in locked state and all
  142. * {@link JXLayer}'s mouse, keyboard and focus events are temporarily blocked, otherwise returns
  143. * {@code false}.
  144. *
  145. * @return {@code true} if this {@code LockableLayerUI} is in locked state and all
  146. * {@code JXLayer}'s mouse, keyboard and focus events are temporarily blocked, otherwise
  147. * returns {@code false}
  148. */
  149. public boolean isLocked() {
  150. return locked;
  151. }
  152. /**
  153. * If {@code isLocked} is {@code true} then all mouse, keyboard and focus events from the
  154. * {@link JXLayer} of this {@code LockableLayerUI} will be temporarily blocked.
  155. *
  156. * @param isLocked if {@code true} then all mouse, keyboard and focus events from the
  157. * {@code JXLayer} of this {@code LockableLayerUI} will be temporarily blocked
  158. */
  159. public void setLocked(final boolean isLocked) {
  160. if (isLocked != isLocked()) {
  161. if (getLayer() != null) {
  162. final KeyboardFocusManager kfm = KeyboardFocusManager.
  163. getCurrentKeyboardFocusManager();
  164. final Component focusOwner = kfm.getPermanentFocusOwner();
  165. final boolean focusInLayer = focusOwner != null
  166. && SwingUtilities.isDescendingFrom(focusOwner,
  167. getLayer());
  168. if (isLocked) {
  169. if (focusInLayer && kfm.getFocusedWindow()
  170. == SwingUtilities.getWindowAncestor(getLayer())) {
  171. recentFocusOwner = focusOwner;
  172. // setDirty() will be called from the layer's
  173. //focusListener when focus already left layer's view
  174. //and hiding it in the paintLayer() won't mess the
  175. //focus up getLayer().requestFocusInWindow();
  176. } else {
  177. setDirty(true);
  178. }
  179. // the mouse cursor is set to the glassPane
  180. getLayer().getGlassPane().setCursor(getLockedCursor());
  181. } else {
  182. // show the view again
  183. getLayer().getView().setVisible(true);
  184. // restore the focus if it is still in the layer
  185. if (focusInLayer && recentFocusOwner != null) {
  186. recentFocusOwner.requestFocusInWindow();
  187. }
  188. recentFocusOwner = null;
  189. getLayer().getGlassPane().setCursor(null);
  190. }
  191. }
  192. this.locked = isLocked;
  193. firePropertyChange("locked", !isLocked, isLocked);
  194. }
  195. }
  196. // If it is locked, the buffer image will be updated
  197. // only if the layer changes its size or setDirty(true) was called
  198. /** {@inheritDoc} */
  199. @Override
  200. protected boolean isIncrementalUpdate(
  201. final JXLayer<? extends T> l) {
  202. return !isLocked();
  203. }
  204. /** {@inheritDoc} */
  205. @Override
  206. protected void paintLayer(final Graphics2D g2,
  207. final JXLayer<? extends T> l) {
  208. if (isLocked()) {
  209. // Note: this code will be called only if layer changes its size,
  210. // or setDirty(true) was called,
  211. // otherwise the previously created buffer is used
  212. l.getView().setVisible(true);
  213. l.paint(g2);
  214. // hide the layer's view component
  215. // this is the only way to disable key shortcuts
  216. // installed on its subcomponents
  217. l.getView().setVisible(false);
  218. }
  219. }
  220. /** {@inheritDoc} */
  221. @Override
  222. public void paint(final Graphics g, final JComponent c) {
  223. super.paint(g, c);
  224. // if it is not locked, we need to paint the layer as is
  225. // otherwise we also need to paint it again;
  226. // if there are any glassPane's children
  227. // they should be shown unfiltered
  228. c.paint(g);
  229. }
  230. /**
  231. * Returns the mouse cursor to be used by this {@code LockableLayerUI} when it locked state.
  232. *
  233. * @return the mouse cursor to be used by this {@code LockableLayerUI} when it locked state
  234. *
  235. * @see #getLockedCursor()
  236. * @see #setLocked(boolean)
  237. */
  238. public Cursor getLockedCursor() {
  239. return lockedCursor;
  240. }
  241. /**
  242. * Sets the mouse cursor to be used by this {@code LockableLayerUI} when it locked state.
  243. *
  244. * @param newCursor the mouse cursor to be used by this {@code LockableLayerUI} when it locked
  245. * state
  246. */
  247. public void setLockedCursor(final Cursor newCursor) {
  248. final Cursor oldCursor = getLockedCursor();
  249. this.lockedCursor = newCursor;
  250. firePropertyChange("lockedCursor", oldCursor, newCursor);
  251. if (isLocked()) {
  252. getLayer().getGlassPane().setCursor(newCursor);
  253. }
  254. }
  255. /**
  256. * Returns the effects to be used when this UI is locked.
  257. *
  258. * @return the effects to be used when this UI is locked
  259. *
  260. * @see #setLocked(boolean)
  261. */
  262. public LayerEffect[] getLockedEffects() {
  263. final LayerEffect[] result = new LayerEffect[lockedEffects.length];
  264. System.arraycopy(lockedEffects, 0, result, 0, result.length);
  265. return result;
  266. }
  267. /**
  268. * This method returns the array of {@code LayerEffect}s set using
  269. * {@link #setLockedEffects(LayerEffect...)}
  270. * <p>
  271. * If a {@code LockableUI} provides more extensive API to support different {@code Effect}s
  272. * depending on its state or on the state of the passed {@code JXLayer}, this method should be
  273. * overridden.
  274. *
  275. * @param l Layer to get effects from
  276. *
  277. * @return Locked layer effect
  278. *
  279. * @see #setLockedEffects (LayerEffect...)
  280. * @see #getLockedEffects()
  281. */
  282. protected LayerEffect[] getLockedEffects(
  283. final JXLayer<? extends JComponent> l) {
  284. return getLockedEffects();
  285. }
  286. /**
  287. * Sets the effects to be used when this UI is locked.
  288. *
  289. * @param effects the effects to be used when this UI is locked
  290. *
  291. * @see #setLocked(boolean)
  292. */
  293. public void setLockedEffects(final LayerEffect... effects) {
  294. final LayerEffect[] layerEffect;
  295. final LayerEffect[] oldEffects = getLockedEffects();
  296. if (effects == null) {
  297. layerEffect = new LayerEffect[0];
  298. } else {
  299. layerEffect = effects;
  300. }
  301. for (LayerEffect effect : getLockedEffects()) {
  302. effect.removePropertyChangeListener(this);
  303. }
  304. this.lockedEffects = new LayerEffect[layerEffect.length];
  305. System.arraycopy(layerEffect, 0, this.lockedEffects, 0,
  306. layerEffect.length);
  307. for (LayerEffect lockedEffect : layerEffect) {
  308. lockedEffect.addPropertyChangeListener(this);
  309. }
  310. firePropertyChange("lockedEffects", oldEffects, layerEffect);
  311. }
  312. /**
  313. * {@inheritDoc}
  314. *
  315. * @param l Layer
  316. *
  317. * @return Layer effects
  318. */
  319. @Override
  320. protected LayerEffect[] getLayerEffects(final JXLayer<? extends T> l) {
  321. if (isLocked()) {
  322. return getLockedEffects(l);
  323. } else {
  324. return super.getLayerEffects(l);
  325. }
  326. }
  327. }