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.

WindowManager.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. /*
  2. * Copyright (c) 2006-2015 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.ui;
  23. import com.dmdirc.CustomWindow;
  24. import com.dmdirc.Precondition;
  25. import com.dmdirc.events.FrameClosingEvent;
  26. import com.dmdirc.events.FrameOpenedEvent;
  27. import com.dmdirc.interfaces.EventBus;
  28. import com.dmdirc.interfaces.WindowModel;
  29. import com.dmdirc.interfaces.ui.FrameListener;
  30. import com.dmdirc.util.collections.ListenerList;
  31. import com.google.common.collect.ArrayListMultimap;
  32. import com.google.common.collect.Multimap;
  33. import java.util.Collection;
  34. import java.util.Collections;
  35. import java.util.HashMap;
  36. import java.util.Map;
  37. import java.util.Optional;
  38. import java.util.concurrent.CopyOnWriteArrayList;
  39. import java.util.concurrent.atomic.AtomicLong;
  40. import javax.inject.Inject;
  41. import javax.inject.Singleton;
  42. import net.engio.mbassy.listener.Handler;
  43. import static com.google.common.base.Preconditions.checkArgument;
  44. import static com.google.common.base.Preconditions.checkNotNull;
  45. /**
  46. * The WindowManager maintains a list of all open windows, and their parent/child relations.
  47. */
  48. @Singleton
  49. public class WindowManager {
  50. /** A list of root windows. */
  51. private final Collection<WindowModel> rootWindows = new CopyOnWriteArrayList<>();
  52. /** Mapping of windows to their parents. */
  53. private final Map<WindowModel, WindowModel> parents = new HashMap<>();
  54. /** Mapping of parents to their children. */
  55. private final Multimap<WindowModel, WindowModel> children = ArrayListMultimap.create();
  56. /** Mapping of IDs to windows. */
  57. private final Map<String, WindowModel> windowsById = new HashMap<>();
  58. /** A list of frame listeners. */
  59. private final ListenerList listeners = new ListenerList();
  60. /** Counter to use for ID assignments. */
  61. private final AtomicLong nextId = new AtomicLong(0L);
  62. /** Event bus to dispatch window events on. */
  63. private final EventBus eventBus;
  64. /**
  65. * Creates a new instance of {@link WindowManager}.
  66. */
  67. @Inject
  68. public WindowManager(final EventBus eventBus) {
  69. this.eventBus = eventBus;
  70. eventBus.subscribe(this);
  71. }
  72. /**
  73. * Registers a FrameListener with the WindowManager.
  74. *
  75. * @param frameListener The frame listener to be registered
  76. */
  77. @Precondition("The specified FrameListener is not null")
  78. public void addListener(final FrameListener frameListener) {
  79. checkNotNull(frameListener);
  80. listeners.add(FrameListener.class, frameListener);
  81. }
  82. /**
  83. * Registers a FrameListener with the WindowManager, and then calls the relevant methods on it
  84. * for all existing windows.
  85. *
  86. * @param frameListener The frame listener to be registered
  87. *
  88. * @since 0.6.6
  89. */
  90. @Precondition("The specified FrameListener is not null")
  91. public void addListenerAndSync(final FrameListener frameListener) {
  92. addListener(frameListener);
  93. for (WindowModel root : rootWindows) {
  94. frameListener.addWindow(root, true);
  95. for (WindowModel child : getChildren(root)) {
  96. fireAddWindow(frameListener, root, child);
  97. }
  98. }
  99. }
  100. /**
  101. * Recursively fires the addWindow callback for the specified windows and listener.
  102. *
  103. * @param listener The listener to be fired
  104. * @param parent The parent window
  105. * @param child The new child window that was added
  106. *
  107. */
  108. private void fireAddWindow(final FrameListener listener,
  109. final WindowModel parent, final WindowModel child) {
  110. listener.addWindow(parent, child, true);
  111. for (WindowModel grandchild : getChildren(child)) {
  112. fireAddWindow(listener, child, grandchild);
  113. }
  114. }
  115. /**
  116. * Unregisters a FrameListener with the WindowManager.
  117. *
  118. * @param frameListener The frame listener to be removed
  119. */
  120. public void removeListener(final FrameListener frameListener) {
  121. listeners.remove(FrameListener.class, frameListener);
  122. }
  123. /**
  124. * Gets the parent of the specified window, if there is one.
  125. *
  126. * @param window The window to find the parent of.
  127. * @return The window's parent, if one exists.
  128. */
  129. public Optional<WindowModel> getParent(final WindowModel window) {
  130. return Optional.ofNullable(parents.get(window));
  131. }
  132. /**
  133. * Gets the collection of children belonging to the specified window.
  134. *
  135. * @param window The window to find the children on.
  136. * @return A (possibly empty) collection of children of the given window.
  137. */
  138. public Collection<WindowModel> getChildren(final WindowModel window) {
  139. return Collections.unmodifiableCollection(children.get(window));
  140. }
  141. /**
  142. * Adds a new root window to the Window Manager.
  143. *
  144. * @param window The window to be added
  145. *
  146. * @since 0.6.4
  147. */
  148. @Precondition({
  149. "The specified Window is not null",
  150. "The specified Window has not already been added"
  151. })
  152. public void addWindow(final WindowModel window) {
  153. addWindow(window, true);
  154. }
  155. /**
  156. * Adds a new root window to the Window Manager.
  157. *
  158. * @param window The window to be added
  159. * @param focus Should this window become focused
  160. *
  161. * @since 0.6.4
  162. */
  163. @Precondition({
  164. "The specified Window is not null",
  165. "The specified Window has not already been added"
  166. })
  167. public void addWindow(final WindowModel window, final boolean focus) {
  168. checkNotNull(window);
  169. checkArgument(!rootWindows.contains(window));
  170. rootWindows.add(window);
  171. assignId(window);
  172. fireAddWindow(window, focus);
  173. }
  174. /**
  175. * Adds a new child window to the Window Manager.
  176. *
  177. * @param parent The parent window
  178. * @param child The child window to be added
  179. *
  180. * @since 0.6.4
  181. */
  182. @Precondition("The specified Windows are not null")
  183. public void addWindow(final WindowModel parent, final WindowModel child) {
  184. addWindow(parent, child, true);
  185. }
  186. /**
  187. * Adds a new child window to the Window Manager.
  188. *
  189. * @param parent The parent window
  190. * @param child The child window to be added
  191. * @param focus Should this window become focused
  192. *
  193. * @since 0.6.4
  194. */
  195. @Precondition({
  196. "The specified containers are not null",
  197. "The specified parent is in the window hierarchy already",
  198. "The specified child is NOT in the window hierarchy already"
  199. })
  200. public void addWindow(final WindowModel parent, final WindowModel child, final boolean focus) {
  201. checkNotNull(parent);
  202. checkArgument(isInHierarchy(parent));
  203. checkNotNull(child);
  204. parents.put(child, parent);
  205. children.put(parent, child);
  206. assignId(child);
  207. fireAddWindow(parent, child, focus);
  208. }
  209. /**
  210. * Recursively determines if the specified target is in the known hierarchy of containers. That
  211. * is, whether or not the specified target or any of its parents are root windows.
  212. *
  213. * @since 0.6.4
  214. * @param target The container to be tested
  215. *
  216. * @return True if the target is in the hierarchy, false otherwise
  217. */
  218. private boolean isInHierarchy(final WindowModel target) {
  219. return rootWindows.contains(target) || parents.containsKey(target);
  220. }
  221. /**
  222. * Removes a window from the Window Manager. If the specified window has child windows, they are
  223. * recursively removed before the target window. If the window hasn't previously been added, the
  224. * request to remove it is ignored.
  225. *
  226. * @param window The window to be removed
  227. *
  228. * @since 0.6.4
  229. */
  230. @Precondition({
  231. "The specified window is not null",
  232. "The specified window is in the window hierarchy"
  233. })
  234. public void removeWindow(final WindowModel window) {
  235. checkNotNull(window);
  236. checkArgument(isInHierarchy(window));
  237. children.get(window).forEach(WindowModel::close);
  238. children.removeAll(window);
  239. windowsById.remove(window.getId());
  240. if (rootWindows.contains(window)) {
  241. fireDeleteWindow(window);
  242. rootWindows.remove(window);
  243. } else {
  244. final WindowModel parent = parents.get(window);
  245. fireDeleteWindow(parent, window);
  246. parents.remove(parent);
  247. }
  248. }
  249. /**
  250. * Finds and returns a global custom window with the specified name. If a custom window with the
  251. * specified name isn't found, null is returned.
  252. *
  253. * @param name The name of the custom window to search for
  254. *
  255. * @return The specified custom window, or null
  256. */
  257. @Precondition("The specified window name is not null")
  258. public WindowModel findCustomWindow(final String name) {
  259. checkNotNull(name);
  260. return findCustomWindow(rootWindows, name);
  261. }
  262. /**
  263. * Finds and returns a non-global custom window with the specified name. If a custom window with
  264. * the specified name isn't found, null is returned.
  265. *
  266. * @param parent The parent whose children should be searched
  267. * @param name The name of the custom window to search for
  268. *
  269. * @return The specified custom window, or null
  270. */
  271. @Precondition({
  272. "The specified window name is not null",
  273. "The specified parent window is not null",
  274. "The specified parent window has been added to the Window Manager"
  275. })
  276. public WindowModel findCustomWindow(final WindowModel parent, final String name) {
  277. checkNotNull(parent);
  278. checkNotNull(name);
  279. return findCustomWindow(getChildren(parent), name);
  280. }
  281. /**
  282. * Finds a custom window with the specified name among the specified list of windows. If the
  283. * custom window is not found, returns null.
  284. *
  285. * @param windows The list of windows to search
  286. * @param name The name of the custom window to search for
  287. *
  288. * @return The custom window if found, or null otherwise
  289. */
  290. private WindowModel findCustomWindow(final Iterable<WindowModel> windows, final String name) {
  291. for (WindowModel window : windows) {
  292. if (window instanceof CustomWindow && window.getName().equals(name)) {
  293. return window;
  294. }
  295. }
  296. return null;
  297. }
  298. /**
  299. * Retrieves all known root (parent-less) windows.
  300. *
  301. * @since 0.6.4
  302. * @return A collection of all known root windows.
  303. */
  304. public Collection<WindowModel> getRootWindows() {
  305. return Collections.unmodifiableCollection(rootWindows);
  306. }
  307. /**
  308. * Retrieves the window with the specified ID.
  309. *
  310. * @param id The ID of the window to retrieve
  311. * @return The window with the given ID, if it exists.
  312. */
  313. public Optional<WindowModel> getWindowById(final String id) {
  314. return Optional.ofNullable(windowsById.get(id));
  315. }
  316. /**
  317. * Assigns a unique ID to the given window.
  318. *
  319. * @param window The window to assign an ID to.
  320. */
  321. private void assignId(final WindowModel window) {
  322. final String id = "WINDOW/" + nextId.getAndIncrement();
  323. window.setId(id);
  324. windowsById.put(id, window);
  325. }
  326. /**
  327. * Fires the addWindow(Window) callback.
  328. *
  329. * @param window The window that was added
  330. * @param focus Should this window become focused
  331. */
  332. private void fireAddWindow(final WindowModel window, final boolean focus) {
  333. eventBus.publishAsync(new FrameOpenedEvent(window));
  334. for (FrameListener listener : listeners.get(FrameListener.class)) {
  335. listener.addWindow(window, focus);
  336. }
  337. }
  338. /**
  339. * Fires the addWindow(Window, Window) callback.
  340. *
  341. * @param parent The parent window
  342. * @param child The new child window that was added
  343. * @param focus Should this window become focused
  344. *
  345. */
  346. private void fireAddWindow(final WindowModel parent,
  347. final WindowModel child, final boolean focus) {
  348. eventBus.publishAsync(new FrameOpenedEvent(child, parent));
  349. for (FrameListener listener : listeners.get(FrameListener.class)) {
  350. listener.addWindow(parent, child, focus);
  351. }
  352. }
  353. /**
  354. * Fires the delWindow(Window) callback.
  355. *
  356. * @param window The window that was removed
  357. */
  358. private void fireDeleteWindow(final WindowModel window) {
  359. for (FrameListener listener : listeners.get(FrameListener.class)) {
  360. listener.delWindow(window);
  361. }
  362. }
  363. /**
  364. * Fires the delWindow(Window, Window) callback.
  365. *
  366. * @param parent The parent window
  367. * @param child The child window that was removed
  368. */
  369. private void fireDeleteWindow(final WindowModel parent, final WindowModel child) {
  370. for (FrameListener listener : listeners.get(FrameListener.class)) {
  371. listener.delWindow(parent, child);
  372. }
  373. }
  374. @Handler
  375. public void frameClosing(final FrameClosingEvent event) {
  376. removeWindow(event.getSource());
  377. }
  378. }