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

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