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 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. /*
  2. * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
  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.FrameInfoListener;
  28. import com.dmdirc.interfaces.NotificationListener;
  29. import com.dmdirc.interfaces.SelectionListener;
  30. import com.dmdirc.ui.WindowManager;
  31. import com.dmdirc.ui.interfaces.Window;
  32. import com.dmdirc.ui.messages.Formatter;
  33. import com.dmdirc.ui.messages.IRCDocument;
  34. import com.dmdirc.ui.messages.Styliser;
  35. import com.dmdirc.util.ListenerList;
  36. import com.dmdirc.util.StringTranscoder;
  37. import java.awt.Color;
  38. import java.nio.charset.Charset;
  39. import java.util.Collection;
  40. import java.util.Collections;
  41. import java.util.Date;
  42. import java.util.LinkedList;
  43. import java.util.List;
  44. import java.util.concurrent.CopyOnWriteArrayList;
  45. /**
  46. * The frame container implements basic methods that should be present in
  47. * all objects that handle a frame.
  48. *
  49. * @param <T> The type of window which should be used for this frame container.
  50. * @author chris
  51. */
  52. public abstract class FrameContainer<T extends Window> {
  53. /** The colour of our frame's notifications. */
  54. protected Color notification = Color.BLACK;
  55. /** A list of listeners for this containers's events. */
  56. protected final ListenerList listeners = new ListenerList();
  57. /** The document used to store this container's content. */
  58. protected final IRCDocument document;
  59. /** The children of this frame. */
  60. protected final Collection<FrameContainer<?>> children
  61. = new CopyOnWriteArrayList<FrameContainer<?>>();
  62. /** The windows representing of this frame. */
  63. protected final Collection<T> windows
  64. = new CopyOnWriteArrayList<T>();
  65. /** The class of windows we want to represent this container. */
  66. protected final Class<T> windowClass;
  67. /** The parent of this frame. */
  68. protected FrameContainer<?> parent;
  69. /** The name of the icon being used for this container's frame. */
  70. private String icon;
  71. /** The name of this container. */
  72. private String name;
  73. /** The title of this container. */
  74. private String title;
  75. /** The transcoder to use for this container. */
  76. private final StringTranscoder transcoder;
  77. /** The config manager for this container. */
  78. private final ConfigManager config;
  79. /** The IconChanger for this container. */
  80. private final IconChanger changer = new IconChanger();
  81. /** The styliser used by this container. */
  82. private final Styliser styliser;
  83. /**
  84. * Instantiate new frame container.
  85. *
  86. * @param icon The icon to use for this container
  87. * @param name The name of this container
  88. * @param title The title of this container
  89. * @param windowClass The class of windows required to represent this container
  90. * @param config The config manager for this container
  91. * @since 0.6.4
  92. */
  93. protected FrameContainer(final String icon, final String name,
  94. final String title, final Class<T> windowClass,
  95. final ConfigManager config) {
  96. this.config = config;
  97. this.name = name;
  98. this.title = title;
  99. this.windowClass = windowClass;
  100. this.styliser = new Styliser(this);
  101. this.document = new IRCDocument(getConfigManager(), getStyliser());
  102. // Can't assign directly to transcoder as it's final, and Java doesn't
  103. // like the two paths in the try/catch.
  104. StringTranscoder tempTranscoder;
  105. try {
  106. tempTranscoder = new StringTranscoder(Charset.forName(
  107. config.getOption("channel", "encoding")));
  108. } catch (IllegalArgumentException ex) {
  109. tempTranscoder = new StringTranscoder(Charset.forName("UTF-8"));
  110. }
  111. transcoder = tempTranscoder;
  112. setIcon(icon);
  113. }
  114. /**
  115. * Returns the internal frame associated with this object.
  116. *
  117. * @return The internal frame associated with this object
  118. * @deprecated Use {@link #getWindows()} instead
  119. */
  120. @Deprecated
  121. public final T getFrame() {
  122. return getWindows().isEmpty() ? null : getWindows().iterator().next();
  123. }
  124. /**
  125. * Returns a collection of direct children of this frame.
  126. *
  127. * @return This frame's children
  128. * @since 0.6.4
  129. */
  130. public Collection<FrameContainer<?>> getChildren() {
  131. return Collections.unmodifiableCollection(children);
  132. }
  133. /**
  134. * Determines whether the specified target is a child of this container.
  135. * Children may be indirect (i.e., a child of another child).
  136. *
  137. * @param target The window to be tested
  138. * @return True if the specified container is a child of this one
  139. */
  140. public boolean isChild(final FrameContainer<?> target) {
  141. if (children.contains(target)) {
  142. return true;
  143. }
  144. for (FrameContainer<?> child : children) {
  145. if (child.isChild(target)) {
  146. return true;
  147. }
  148. }
  149. return false;
  150. }
  151. /**
  152. * Adds a new child window to this frame.
  153. *
  154. * @param child The window to be added
  155. * @since 0.6.4
  156. */
  157. public void addChild(final FrameContainer<?> child) {
  158. children.add(child);
  159. child.setParent(this);
  160. }
  161. /**
  162. * Removes a child window from this frame.
  163. *
  164. * @param child The window to be removed
  165. * @since 0.6.4
  166. */
  167. public void removeChild(final FrameContainer<?> child) {
  168. children.remove(child);
  169. }
  170. /**
  171. * Sets the parent of this container to the one specified. If this
  172. * container already had a parent, it will deregister itself with the
  173. * old parent.
  174. *
  175. * @param parent The new parent for this container
  176. * @since 0.6.4
  177. */
  178. public synchronized void setParent(final FrameContainer<?> parent) {
  179. if (this.parent != null && !parent.equals(this.parent)) {
  180. this.parent.removeChild(this);
  181. }
  182. this.parent = parent;
  183. }
  184. /**
  185. * Retrieves the parent of this container, if there is one.
  186. *
  187. * @return This container's parent, or null if it is a top level window.
  188. * @since 0.6.4
  189. */
  190. public FrameContainer<?> getParent() {
  191. return parent;
  192. }
  193. /**
  194. * Retrieves the {@link IRCDocument} used to store this frame's content.
  195. *
  196. * @return This frame's document
  197. * @since 0.6.4
  198. */
  199. public IRCDocument getDocument() {
  200. return document;
  201. }
  202. /**
  203. * Retrieves the {@link StringTranscoder} used to transcode this frame's
  204. * text.
  205. *
  206. * @return This frame's transcoder
  207. * @since 0.6.4
  208. */
  209. public StringTranscoder getTranscoder() {
  210. return transcoder;
  211. }
  212. /** {@inheritDoc} */
  213. @Override
  214. public String toString() {
  215. return name;
  216. }
  217. /**
  218. * Retrieves the name of this container.
  219. *
  220. * @return This container's name
  221. * @since 0.6.3m2
  222. */
  223. public String getName() {
  224. return name;
  225. }
  226. /**
  227. * Changes the name of this container, and notifies any
  228. * {@link FrameInfoListener}s of the change.
  229. *
  230. * @param name The new name for this frame.
  231. */
  232. protected void setName(final String name) {
  233. this.name = name;
  234. for (FrameInfoListener listener : listeners.get(
  235. FrameInfoListener.class)) {
  236. listener.nameChanged(this, name);
  237. }
  238. }
  239. /**
  240. * Retrieves the title which should be used by this container's windows.
  241. *
  242. * @return This container's title
  243. * @since 0.6.4
  244. */
  245. public String getTitle() {
  246. return title;
  247. }
  248. /**
  249. * Changes the title of this container, and notifies any
  250. * {@link FrameInfoListener}s of the change.
  251. *
  252. * @param title The new title for this frame.
  253. */
  254. public void setTitle(final String title) {
  255. this.title = title;
  256. for (FrameInfoListener listener : listeners.get(
  257. FrameInfoListener.class)) {
  258. listener.titleChanged(this, title);
  259. }
  260. }
  261. /**
  262. * Closes this container (and it's associated frame).
  263. */
  264. public void close() {
  265. for (Window window : getWindows()) {
  266. window.close();
  267. }
  268. }
  269. /**
  270. * Returns the server instance associated with this container.
  271. *
  272. * @return the associated server connection
  273. */
  274. public abstract Server getServer();
  275. /**
  276. * Sets the icon to be used by this frame container.
  277. *
  278. * @param icon The new icon to be used
  279. */
  280. public final void setIcon(final String icon) {
  281. this.icon = icon;
  282. iconUpdated();
  283. config.removeListener(changer);
  284. config.addChangeListener("icon", icon, changer);
  285. }
  286. /**
  287. * Called when this container's icon is updated.
  288. */
  289. private void iconUpdated() {
  290. for (FrameInfoListener listener : listeners.get(
  291. FrameInfoListener.class)) {
  292. listener.iconChanged(this, icon);
  293. }
  294. }
  295. /**
  296. * Retrieves the name of the icon used by this container's window.
  297. *
  298. * @return This container's icon
  299. */
  300. public final String getIcon() {
  301. return icon;
  302. }
  303. /**
  304. * Returns the config manager for this container.
  305. *
  306. * @return the associated config manager
  307. */
  308. public ConfigManager getConfigManager() {
  309. return config;
  310. }
  311. /**
  312. * Retrieves the styliser which should be used by this container.
  313. *
  314. * @return this container's styliser
  315. */
  316. public Styliser getStyliser() {
  317. return styliser;
  318. }
  319. /**
  320. * Requests that this object's frame be activated.
  321. */
  322. public void activateFrame() {
  323. for (Window window : getWindows()) {
  324. window.activateFrame();
  325. }
  326. }
  327. /**
  328. * Clears any outstanding notifications this frame has set.
  329. */
  330. protected void clearNotification() {
  331. // TODO: This should default ot something colour independent
  332. notification = Color.BLACK;
  333. for (NotificationListener listener : listeners.get(
  334. NotificationListener.class)) {
  335. listener.notificationCleared(this);
  336. }
  337. }
  338. /**
  339. * Sends a notification to the frame manager if this fame isn't active.
  340. *
  341. * @param colour The colour to use for the notification
  342. */
  343. public void sendNotification(final Color colour) {
  344. final FrameContainer<?> activeWindow = WindowManager.getActiveWindow();
  345. if (activeWindow != null && !activeWindow.equals(this)
  346. && !colour.equals(notification)) {
  347. notification = colour;
  348. for (NotificationListener listener : listeners.get(
  349. NotificationListener.class)) {
  350. listener.notificationSet(this, colour);
  351. }
  352. }
  353. }
  354. /**
  355. * Retrieves the current notification colour of this channel.
  356. *
  357. * @return This channel's notification colour
  358. */
  359. public Color getNotification() {
  360. return notification;
  361. }
  362. /**
  363. * Determines if the specified frame is owned by this object.
  364. *
  365. * @param target Window to check ownership of
  366. * @return True iff frame is owned by this container, false otherwise
  367. */
  368. @SuppressWarnings("element-type-mismatch")
  369. public boolean ownsFrame(final Window target) {
  370. return windows.contains(target);
  371. }
  372. /**
  373. * Invoked when our window has been opened.
  374. * @deprecated Pointless. Stop calling me.
  375. */
  376. @Deprecated
  377. public void windowOpened() {
  378. }
  379. /**
  380. * Invoked when our window is closing.
  381. * <p>
  382. * Frame containers must perform the following actions in this order:
  383. * <ol>
  384. * <li>Make the window non-visible (so it appears 'closed' to the user)</li>
  385. * <li>Remove any callbacks or listeners (events should not be processed
  386. * once a window has been requested to close)</li>
  387. * <li>Trigger any actions necessary (terminating any TCP connections,
  388. * disconnecting parsers, closing children, etc)</li>
  389. * <li>Trigger action for the window closing (raise a DMDirc action for
  390. * the closure of the window, if required)</li>
  391. * <li>Inform any parents that the window is closing (this includes
  392. * unregistering the window with any specific managers, or from the
  393. * parent windows if they track children)</li>
  394. * <li>Remove the window from the window manager (by calling
  395. * {@link WindowManager#removeWindow(com.dmdirc.ui.interfaces.Window)}</li>
  396. * </ol>
  397. * <p>
  398. * While resources may be relinquished in step three, references MUST NOT
  399. * be removed yet. That is, if a window holds a resource, the resource may
  400. * be closed, but the relevant object MUST still be available for
  401. * interrogation at the end of this method.
  402. * <p>
  403. * This behaviour is required so that parties receiving windowDeleted events
  404. * from the WindowManager may inspect the closing window and perform actions
  405. * on its frame, parser, etc. The resources should be completely freed in
  406. * the {@link #windowClosed()} method.
  407. */
  408. public abstract void windowClosing();
  409. /**
  410. * Invoked when our window has been closed.
  411. * <p>
  412. * At this point, all interested parties have been told that the window
  413. * has been closed, and therefore any references to frames or other
  414. * resources may be completely freed.
  415. */
  416. public abstract void windowClosed();
  417. /**
  418. * Invoked when our window is activated.
  419. */
  420. public void windowActivated() {
  421. for (SelectionListener listener : listeners.get(
  422. SelectionListener.class)) {
  423. listener.selectionChanged(this);
  424. }
  425. clearNotification();
  426. if (getServer() != null) {
  427. getServer().setActiveFrame(this);
  428. }
  429. }
  430. /**
  431. * Invoked when our window is deactivated.
  432. * @deprecated Not used. Stop calling me.
  433. */
  434. @Deprecated
  435. public void windowDeactivated() {
  436. }
  437. /**
  438. * Adds a line to this container's window. If the window is null for some
  439. * reason, the line is silently discarded.
  440. *
  441. * @param type The message type to use
  442. * @param timestamp The timestamp to use for this line
  443. * @param args The message's arguments
  444. * @since 0.6.4
  445. */
  446. public void addLine(final String type, final Date timestamp,
  447. final Object ... args) {
  448. if (type != null && !type.isEmpty()) {
  449. addLine(Formatter.formatMessage(getConfigManager(), type, args),
  450. timestamp);
  451. }
  452. }
  453. /**
  454. * Adds a line to this container's window. If the window is null for some
  455. * reason, the line is silently discarded.
  456. *
  457. * @param type The message type to use
  458. * @param args The message's arguments
  459. */
  460. public void addLine(final String type, final Object ... args) {
  461. addLine(type, new Date(), args);
  462. }
  463. /**
  464. * Adds a line to this container's window. If the window is null for some
  465. * reason, the line is silently discarded.
  466. *
  467. * @param type The message type to use
  468. * @param timestamp The timestamp to use for this line
  469. * @param args The message's arguments
  470. * @since 0.6.4
  471. */
  472. public void addLine(final StringBuffer type, final Date timestamp,
  473. final Object ... args) {
  474. if (type != null) {
  475. addLine(type.toString(), timestamp, args);
  476. }
  477. }
  478. /**
  479. * Adds a line to this container's window. If the window is null for some
  480. * reason, the line is silently discarded.
  481. *
  482. * @param type The message type to use
  483. * @param args The message's arguments
  484. */
  485. public void addLine(final StringBuffer type, final Object ... args) {
  486. addLine(type, new Date(), args);
  487. }
  488. /**
  489. * Adds the specified raw line to the window, without using a formatter.
  490. *
  491. * @param line The line to be added
  492. * @param timestamp Whether or not to display the timestamp for this line
  493. */
  494. public void addLine(final String line, final boolean timestamp) {
  495. addLine(line, timestamp ? new Date() : null);
  496. }
  497. /**
  498. * Adds the specified raw line to the window, without using a formatter,
  499. * and using the specified timestamp. If the timestamp is <code>null</code>,
  500. * no timestamp is added.
  501. *
  502. * @param line The line to be added
  503. * @param timestamp The timestamp to use for the line
  504. * @since 0.6.4
  505. */
  506. public void addLine(final String line, final Date timestamp) {
  507. final String encodedLine = transcoder.decode(line);
  508. final List<String[]> lines = new LinkedList<String[]>();
  509. for (final String myLine : encodedLine.split("\n")) {
  510. if (timestamp != null) {
  511. lines.add(new String[]{
  512. Formatter.formatMessage(getConfigManager(), "timestamp",
  513. timestamp),
  514. myLine,
  515. });
  516. } else {
  517. lines.add(new String[]{
  518. myLine,
  519. });
  520. }
  521. ActionManager.processEvent(CoreActionType.CLIENT_LINE_ADDED,
  522. null, this, myLine);
  523. }
  524. document.addText(lines);
  525. }
  526. /**
  527. * Adds a notification listener for this frame container.
  528. *
  529. * @param listener The listener to be added
  530. */
  531. public void addNotificationListener(final NotificationListener listener) {
  532. listeners.add(NotificationListener.class, listener);
  533. }
  534. /**
  535. * Removes a notification listener from this frame container.
  536. *
  537. * @param listener The listener to be removed
  538. */
  539. public void removeNotificationListener(final NotificationListener listener) {
  540. listeners.remove(NotificationListener.class, listener);
  541. }
  542. /**
  543. * Adds a selection listener for this frame container.
  544. *
  545. * @param listener The listener to be added
  546. */
  547. public void addSelectionListener(final SelectionListener listener) {
  548. listeners.add(SelectionListener.class, listener);
  549. }
  550. /**
  551. * Removes a selection listener from this frame container.
  552. *
  553. * @param listener The listener to be removed
  554. */
  555. public void removeSelectionListener(final SelectionListener listener) {
  556. listeners.remove(SelectionListener.class, listener);
  557. }
  558. /**
  559. * Adds a frame info listener for this frame container.
  560. *
  561. * @param listener The listener to be added
  562. */
  563. public void addFrameInfoListener(final FrameInfoListener listener) {
  564. listeners.add(FrameInfoListener.class, listener);
  565. }
  566. /**
  567. * Removes a frame info listener from this frame container.
  568. *
  569. * @param listener The listener to be removed
  570. */
  571. public void removeFrameInfoListener(final FrameInfoListener listener) {
  572. listeners.remove(FrameInfoListener.class, listener);
  573. }
  574. /**
  575. * Adds a new window to this container.
  576. *
  577. * @param window The window to be added
  578. * @since 0.6.4
  579. */
  580. public void addWindow(final T window) {
  581. windows.add(window);
  582. }
  583. /**
  584. * Removes the specified window from this container.
  585. *
  586. * @param window The window to be removed
  587. * @since 0.6.4
  588. */
  589. public void removeWindow(final T window) {
  590. windows.remove(window);
  591. }
  592. /**
  593. * Retrieves a collection of windows that represent this container.
  594. *
  595. * @return The collection of windows currently representing this container
  596. * @since 0.6.4
  597. */
  598. public Collection<T> getWindows() {
  599. return windows;
  600. }
  601. /**
  602. * Retrieves the class of windows which should be used to represent this
  603. * container.
  604. *
  605. * @return This container's window class
  606. * @since 0.6.4
  607. */
  608. public Class<T> getWindowClass() {
  609. return windowClass;
  610. }
  611. /**
  612. * Updates the icon of this frame if its config setting is changed.
  613. */
  614. private class IconChanger implements ConfigChangeListener {
  615. /** {@inheritDoc} */
  616. @Override
  617. public void configChanged(final String domain, final String key) {
  618. iconUpdated();
  619. }
  620. }
  621. }