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

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