Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

WindowManager.java 14KB

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