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.

FrameContainer.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. /*
  2. * Copyright (c) 2006-2012 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;
  23. import com.dmdirc.actions.ActionManager;
  24. import com.dmdirc.actions.CoreActionType;
  25. import com.dmdirc.config.ConfigManager;
  26. import com.dmdirc.interfaces.ConfigChangeListener;
  27. import com.dmdirc.interfaces.FrameCloseListener;
  28. import com.dmdirc.interfaces.FrameComponentChangeListener;
  29. import com.dmdirc.interfaces.FrameInfoListener;
  30. import com.dmdirc.interfaces.NotificationListener;
  31. import com.dmdirc.ui.Colour;
  32. import com.dmdirc.ui.IconManager;
  33. import com.dmdirc.ui.WindowManager;
  34. import com.dmdirc.ui.messages.Formatter;
  35. import com.dmdirc.ui.messages.IRCDocument;
  36. import com.dmdirc.ui.messages.Styliser;
  37. import com.dmdirc.util.collections.ListenerList;
  38. import java.util.Collection;
  39. import java.util.Collections;
  40. import java.util.Date;
  41. import java.util.HashSet;
  42. import java.util.LinkedList;
  43. import java.util.List;
  44. import java.util.Set;
  45. import java.util.concurrent.CopyOnWriteArrayList;
  46. import lombok.Getter;
  47. import lombok.ListenerSupport;
  48. /**
  49. * The frame container implements basic methods that should be present in
  50. * all objects that handle a frame.
  51. */
  52. @ListenerSupport({NotificationListener.class, FrameInfoListener.class})
  53. @SuppressWarnings("PMD.UnusedPrivateField")
  54. public abstract class FrameContainer {
  55. /** Listeners not yet using ListenerSupport. */
  56. protected final ListenerList listeners = new ListenerList();
  57. /** The colour of our frame's notifications. */
  58. @Getter
  59. private Colour notification = Colour.BLACK;
  60. /** The document used to store this container's content. */
  61. private IRCDocument document;
  62. /** The children of this frame. */
  63. private final Collection<FrameContainer> children
  64. = new CopyOnWriteArrayList<FrameContainer>();
  65. /** The parent of this frame. */
  66. @Getter
  67. private FrameContainer parent;
  68. /** The name of the icon being used for this container's frame. */
  69. @Getter
  70. private String icon;
  71. /** The name of this container. */
  72. @Getter
  73. private String name;
  74. /** The title of this container. */
  75. @Getter
  76. private String title;
  77. /** The config manager for this container. */
  78. @Getter
  79. private final ConfigManager configManager;
  80. /** The IconChanger for this container. */
  81. private final IconChanger changer = new IconChanger();
  82. /** The UI components that this frame requires. */
  83. private final Set<String> components;
  84. /** The styliser used by this container. */
  85. private Styliser styliser;
  86. /** Object used to synchronise styliser access. */
  87. private final Object styliserSync = new Object();
  88. /** Object used to synchronise styliser access. */
  89. private final Object documentSync = new Object();
  90. /** The IconManager for this container. */
  91. @Getter
  92. private final IconManager iconManager;
  93. /**
  94. * Instantiate new frame container.
  95. *
  96. * @param icon The icon to use for this container
  97. * @param name The name of this container
  98. * @param title The title of this container
  99. * @param config The config manager for this container
  100. * @param components The UI components that this frame requires
  101. * @since 0.6.4
  102. */
  103. protected FrameContainer(final String icon, final String name,
  104. final String title, final ConfigManager config,
  105. final Collection<String> components) {
  106. this.configManager = config;
  107. this.name = name;
  108. this.title = title;
  109. this.components = new HashSet<String>(components);
  110. iconManager = new IconManager(config);
  111. setIcon(icon);
  112. }
  113. /**
  114. * Returns a collection of direct children of this frame.
  115. *
  116. * @return This frame's children
  117. * @since 0.6.4
  118. */
  119. public Collection<FrameContainer> getChildren() {
  120. return Collections.unmodifiableCollection(children);
  121. }
  122. /**
  123. * Determines whether the specified target is a child of this container.
  124. * Children may be indirect (i.e., a child of another child).
  125. *
  126. * @param target The window to be tested
  127. * @return True if the specified container is a child of this one
  128. */
  129. public boolean isChild(final FrameContainer target) {
  130. if (children.contains(target)) {
  131. return true;
  132. }
  133. for (FrameContainer child : children) {
  134. if (child.isChild(target)) {
  135. return true;
  136. }
  137. }
  138. return false;
  139. }
  140. /**
  141. * Adds a new child window to this frame.
  142. *
  143. * @param child The window to be added
  144. * @since 0.6.4
  145. */
  146. public void addChild(final FrameContainer child) {
  147. children.add(child);
  148. child.setParent(this);
  149. }
  150. /**
  151. * Removes a child window from this frame.
  152. *
  153. * @param child The window to be removed
  154. * @since 0.6.4
  155. */
  156. public void removeChild(final FrameContainer child) {
  157. children.remove(child);
  158. }
  159. /**
  160. * Sets the parent of this container to the one specified. If this
  161. * container already had a parent, it will deregister itself with the
  162. * old parent.
  163. *
  164. * @param parent The new parent for this container
  165. * @since 0.6.4
  166. */
  167. public synchronized void setParent(final FrameContainer parent) {
  168. if (this.parent != null && (parent == null || !parent.equals(this.parent))) {
  169. this.parent.removeChild(this);
  170. }
  171. this.parent = parent;
  172. }
  173. /**
  174. * Retrieves the {@link IRCDocument} used to store this frame's content.
  175. *
  176. * @return This frame's document
  177. * @since 0.6.4
  178. */
  179. public IRCDocument getDocument() {
  180. synchronized (documentSync) {
  181. if (document == null) {
  182. document = new IRCDocument(getConfigManager(), getStyliser());
  183. }
  184. return document;
  185. }
  186. }
  187. /**
  188. * Changes the name of this container, and notifies any
  189. * {@link FrameInfoListener}s of the change.
  190. *
  191. * @param name The new name for this frame.
  192. */
  193. protected void setName(final String name) {
  194. this.name = name;
  195. fireNameChanged(this, name);
  196. }
  197. /**
  198. * Changes the title of this container, and notifies any
  199. * {@link FrameInfoListener}s of the change.
  200. *
  201. * @param title The new title for this frame.
  202. */
  203. public void setTitle(final String title) {
  204. this.title = title;
  205. fireTitleChanged(this, title);
  206. }
  207. /**
  208. * Returns the collection of UI component identifiers that this frame
  209. * container requires for its display.
  210. *
  211. * @since 0.6.6
  212. * @return Collection of UI component identifiers
  213. */
  214. public Set<String> getComponents() {
  215. return Collections.unmodifiableSet(components);
  216. }
  217. /**
  218. * Adds a new component to this container.
  219. *
  220. * @since 0.6.6
  221. * @param component The component to be added
  222. */
  223. public void addComponent(final String component) {
  224. components.add(component);
  225. for (FrameComponentChangeListener listener
  226. : listeners.get(FrameComponentChangeListener.class)) {
  227. listener.componentAdded(this, component);
  228. }
  229. }
  230. /**
  231. * Removes a component from this container.
  232. *
  233. * @since 0.6.6
  234. * @param component The component to be removed
  235. */
  236. public void removeComponent(final String component) {
  237. components.remove(component);
  238. for (FrameComponentChangeListener listener
  239. : listeners.get(FrameComponentChangeListener.class)) {
  240. listener.componentRemoved(this, component);
  241. }
  242. }
  243. /**
  244. * Closes this container (and it's associated frame).
  245. */
  246. public void close() {
  247. for (FrameCloseListener listener : listeners.get(FrameCloseListener.class)) {
  248. listener.windowClosing(this);
  249. }
  250. windowClosing();
  251. WindowManager.getWindowManager().removeWindow(this);
  252. }
  253. /**
  254. * Returns the server instance associated with this container.
  255. *
  256. * @return the associated server connection
  257. */
  258. public abstract Server getServer();
  259. /**
  260. * Sets the icon to be used by this frame container.
  261. *
  262. * @param icon The new icon to be used
  263. */
  264. public final void setIcon(final String icon) {
  265. this.icon = icon;
  266. iconUpdated();
  267. configManager.removeListener(changer);
  268. configManager.addChangeListener("icon", icon, changer);
  269. }
  270. /**
  271. * Called when this container's icon is updated.
  272. */
  273. private void iconUpdated() {
  274. fireIconChanged(this, icon);
  275. }
  276. /**
  277. * Retrieves the styliser which should be used by this container.
  278. *
  279. * @return this container's styliser
  280. */
  281. public Styliser getStyliser() {
  282. synchronized (styliserSync) {
  283. if (styliser == null) {
  284. styliser = new Styliser(getServer(), getConfigManager());
  285. }
  286. return styliser;
  287. }
  288. }
  289. /**
  290. * Clears any outstanding notifications this frame has set.
  291. */
  292. public void clearNotification() {
  293. // TODO: This should default ot something colour independent
  294. notification = Colour.BLACK;
  295. fireNotificationCleared(this);
  296. }
  297. /**
  298. * Sends a notification to the frame manager if this fame isn't active.
  299. *
  300. * @param colour The colour to use for the notification
  301. */
  302. public void sendNotification(final Colour colour) {
  303. if (!colour.equals(notification)) {
  304. notification = colour;
  305. fireNotificationSet(this, colour);
  306. }
  307. }
  308. /**
  309. * Invoked when our window is closing.
  310. * <p>
  311. * Frame containers must perform the following actions in this order:
  312. * <ol>
  313. * <li>Make the window non-visible (so it appears 'closed' to the user)</li>
  314. * <li>Remove any callbacks or listeners (events should not be processed
  315. * once a window has been requested to close)</li>
  316. * <li>Trigger any actions necessary (terminating any TCP connections,
  317. * disconnecting parsers, closing children, etc)</li>
  318. * <li>Trigger action for the window closing (raise a DMDirc action for
  319. * the closure of the window, if required)</li>
  320. * <li>Inform any parents that the window is closing (this includes
  321. * unregistering the window with any specific managers, or from the
  322. * parent windows if they track children)</li>
  323. * <li>Remove the window from the window manager (by calling
  324. * {@link WindowManager#removeWindow(com.dmdirc.ui.interfaces.Window)}</li>
  325. * </ol>
  326. * <p>
  327. * <strong>NB:</strong> As of DMDirc 0.6.5, points 1 and 6 (making windows
  328. * non-visible and removing the window from the window manager) are handled
  329. * by the caller of this method, and should <strong>not</strong> be
  330. * implemented by subclasses.
  331. * </p>
  332. * <p>
  333. * While resources may be relinquished in step three, references MUST NOT
  334. * be removed yet. That is, if a window holds a resource, the resource may
  335. * be closed, but the relevant object MUST still be available for
  336. * interrogation at the end of this method.
  337. * <p>
  338. * This behaviour is required so that parties receiving windowDeleted events
  339. * from the WindowManager may inspect the closing window and perform actions
  340. * on its frame, parser, etc. The resources should be completely freed in
  341. * the {@link #windowClosed()} method.
  342. */
  343. protected abstract void windowClosing();
  344. /**
  345. * Invoked when our window has been closed.
  346. * <p>
  347. * At this point, all interested parties have been told that the window
  348. * has been closed, and therefore any references to frames or other
  349. * resources may be completely freed.
  350. */
  351. public abstract void windowClosed();
  352. /**
  353. * Adds a line to this container's window. If the window is null for some
  354. * reason, the line is silently discarded.
  355. *
  356. * @param type The message type to use
  357. * @param timestamp The timestamp to use for this line
  358. * @param args The message's arguments
  359. * @since 0.6.4
  360. */
  361. public void addLine(final String type, final Date timestamp,
  362. final Object ... args) {
  363. if (type != null && !type.isEmpty()) {
  364. addLine(Formatter.formatMessage(getConfigManager(), type, args),
  365. timestamp);
  366. }
  367. }
  368. /**
  369. * Adds a line to this container's window. If the window is null for some
  370. * reason, the line is silently discarded.
  371. *
  372. * @param type The message type to use
  373. * @param args The message's arguments
  374. */
  375. public void addLine(final String type, final Object ... args) {
  376. addLine(type, new Date(), args);
  377. }
  378. /**
  379. * Adds a line to this container's window. If the window is null for some
  380. * reason, the line is silently discarded.
  381. *
  382. * @param type The message type to use
  383. * @param timestamp The timestamp to use for this line
  384. * @param args The message's arguments
  385. * @since 0.6.4
  386. */
  387. public void addLine(final StringBuffer type, final Date timestamp,
  388. final Object ... args) {
  389. if (type != null) {
  390. addLine(type.toString(), timestamp, args);
  391. }
  392. }
  393. /**
  394. * Adds a line to this container's window. If the window is null for some
  395. * reason, the line is silently discarded.
  396. *
  397. * @param type The message type to use
  398. * @param args The message's arguments
  399. */
  400. public void addLine(final StringBuffer type, final Object ... args) {
  401. addLine(type, new Date(), args);
  402. }
  403. /**
  404. * Adds the specified raw line to the window, without using a formatter.
  405. *
  406. * @param line The line to be added
  407. * @param timestamp Whether or not to display the timestamp for this line
  408. */
  409. public void addLine(final String line, final boolean timestamp) {
  410. addLine(line, timestamp ? new Date() : null);
  411. }
  412. /**
  413. * Adds the specified raw line to the window, without using a formatter,
  414. * and using the specified timestamp. If the timestamp is <code>null</code>,
  415. * no timestamp is added.
  416. *
  417. * @param line The line to be added
  418. * @param timestamp The timestamp to use for the line
  419. * @since 0.6.4
  420. */
  421. public void addLine(final String line, final Date timestamp) {
  422. final List<String[]> lines = new LinkedList<String[]>();
  423. for (final String myLine : line.split("\n")) {
  424. if (timestamp != null) {
  425. lines.add(new String[]{
  426. Formatter.formatMessage(getConfigManager(), "timestamp",
  427. timestamp),
  428. myLine,
  429. });
  430. } else {
  431. lines.add(new String[]{
  432. myLine,
  433. });
  434. }
  435. ActionManager.getActionManager().triggerEvent(
  436. CoreActionType.CLIENT_LINE_ADDED, null, this, myLine);
  437. }
  438. getDocument().addText(lines);
  439. }
  440. /**
  441. * Adds a close listener for this frame container.
  442. *
  443. * @since 0.6.5
  444. * @param listener The listener to be added
  445. */
  446. public void addCloseListener(final FrameCloseListener listener) {
  447. listeners.add(FrameCloseListener.class, listener);
  448. }
  449. /**
  450. * Removes a close listener from this frame container.
  451. *
  452. * @since 0.6.5
  453. * @param listener The listener to be removed
  454. */
  455. public void removeCloseListener(final FrameCloseListener listener) {
  456. listeners.remove(FrameCloseListener.class, listener);
  457. }
  458. /**
  459. * Adds a component listener to this container.
  460. *
  461. * @since 0.6.6
  462. * @param listener The listener to be added
  463. */
  464. public void addComponentListener(final FrameComponentChangeListener listener) {
  465. listeners.add(FrameComponentChangeListener.class, listener);
  466. }
  467. /**
  468. * Removes a component listener from this container.
  469. *
  470. * @since 0.6.6
  471. * @param listener The listener to be removed
  472. */
  473. public void removeComponentListener(final FrameComponentChangeListener listener) {
  474. listeners.remove(FrameComponentChangeListener.class, listener);
  475. }
  476. /**
  477. * Updates the icon of this frame if its config setting is changed.
  478. */
  479. private class IconChanger implements ConfigChangeListener {
  480. /** {@inheritDoc} */
  481. @Override
  482. public void configChanged(final String domain, final String key) {
  483. iconUpdated();
  484. }
  485. }
  486. }