Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

FrameContainer.java 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. /*
  2. * Copyright (c) 2006-2015 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.commandparser.parsers.CommandParser;
  24. import com.dmdirc.events.ClientLineAddedEvent;
  25. import com.dmdirc.events.DisplayPropertyMap;
  26. import com.dmdirc.events.FrameClosingEvent;
  27. import com.dmdirc.events.FrameComponentAddedEvent;
  28. import com.dmdirc.events.FrameComponentRemovedEvent;
  29. import com.dmdirc.events.FrameIconChangedEvent;
  30. import com.dmdirc.events.FrameNameChangedEvent;
  31. import com.dmdirc.events.FrameTitleChangedEvent;
  32. import com.dmdirc.interfaces.Connection;
  33. import com.dmdirc.interfaces.config.AggregateConfigProvider;
  34. import com.dmdirc.interfaces.config.ConfigChangeListener;
  35. import com.dmdirc.parser.common.CompositionState;
  36. import com.dmdirc.ui.IconManager;
  37. import com.dmdirc.ui.input.TabCompleter;
  38. import com.dmdirc.ui.messages.BackBuffer;
  39. import com.dmdirc.ui.messages.BackBufferFactory;
  40. import com.dmdirc.ui.messages.Formatter;
  41. import com.dmdirc.ui.messages.IRCDocument;
  42. import com.dmdirc.ui.messages.Styliser;
  43. import com.dmdirc.ui.messages.UnreadStatusManager;
  44. import com.dmdirc.ui.messages.sink.MessageSinkManager;
  45. import com.dmdirc.util.ChildEventBusManager;
  46. import com.dmdirc.util.URLBuilder;
  47. import com.dmdirc.util.collections.ListenerList;
  48. import java.util.ArrayList;
  49. import java.util.Collection;
  50. import java.util.Collections;
  51. import java.util.Date;
  52. import java.util.HashSet;
  53. import java.util.List;
  54. import java.util.Optional;
  55. import java.util.Set;
  56. import java.util.concurrent.CopyOnWriteArrayList;
  57. import javax.annotation.Nullable;
  58. import static com.google.common.base.Preconditions.checkState;
  59. /**
  60. * The frame container implements basic methods that should be present in all objects that handle a
  61. * frame.
  62. */
  63. public abstract class FrameContainer {
  64. /** Listeners not yet using ListenerSupport. */
  65. protected final ListenerList listeners = new ListenerList();
  66. /** The children of this frame. */
  67. private final Collection<FrameContainer> children = new CopyOnWriteArrayList<>();
  68. /** The parent of this frame. */
  69. private final Optional<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 config manager for this container. */
  77. private final AggregateConfigProvider configManager;
  78. /** The IconChanger for this container. */
  79. private final ConfigChangeListener changer = (d, k) -> iconUpdated();
  80. /** The UI components that this frame requires. */
  81. private final Set<String> components;
  82. /** The manager to use to manage our event bus. */
  83. private final ChildEventBusManager eventBusManager;
  84. /** Event bus to dispatch events to. */
  85. private final DMDircMBassador eventBus;
  86. /** The icon manager to use for this container. */
  87. private final IconManager iconManager;
  88. /** The manager handling this frame's unread status. */
  89. private final UnreadStatusManager unreadStatusManager;
  90. /** Whether or not this container is writable. */
  91. private final boolean writable;
  92. /** The back buffer factory. */
  93. private final BackBufferFactory backBufferFactory;
  94. /** The back buffer for this container. */
  95. private BackBuffer backBuffer;
  96. /** Lock for access to {@link #backBuffer}. */
  97. private final Object backBufferLock = new Object();
  98. /**
  99. * The command parser used for commands in this container.
  100. * <p>
  101. * Only defined if this container is {@link #writable}.
  102. */
  103. private final Optional<CommandParser> commandParser;
  104. /**
  105. * The manager to use to dispatch messages to sinks.
  106. * <p>
  107. * Only defined if this container is {@link #writable}.
  108. */
  109. private final Optional<MessageSinkManager> messageSinkManager;
  110. /**
  111. * The tab completer to use.
  112. * <p>
  113. * Only defined if this container is {@link #writable}.
  114. */
  115. private final Optional<TabCompleter> tabCompleter;
  116. /**
  117. * Instantiate new frame container.
  118. */
  119. protected FrameContainer(
  120. @Nullable final FrameContainer parent,
  121. final String icon,
  122. final String name,
  123. final String title,
  124. final AggregateConfigProvider config,
  125. final BackBufferFactory backBufferFactory,
  126. final URLBuilder urlBuilder,
  127. final DMDircMBassador eventBus,
  128. final Collection<String> components) {
  129. this.parent = Optional.ofNullable(parent);
  130. this.configManager = config;
  131. this.name = name;
  132. this.title = title;
  133. this.components = new HashSet<>(components);
  134. this.iconManager = new IconManager(configManager, urlBuilder);
  135. this.writable = false;
  136. this.commandParser = Optional.empty();
  137. this.tabCompleter = Optional.empty();
  138. this.messageSinkManager = Optional.empty();
  139. this.backBufferFactory = backBufferFactory;
  140. eventBusManager = new ChildEventBusManager(eventBus);
  141. eventBusManager.connect();
  142. this.eventBus = eventBusManager.getChildBus();
  143. this.unreadStatusManager = new UnreadStatusManager(this);
  144. this.eventBus.subscribe(unreadStatusManager);
  145. setIcon(icon);
  146. }
  147. /**
  148. * Instantiate new frame container that accepts user input.
  149. */
  150. protected FrameContainer(
  151. @Nullable final FrameContainer parent,
  152. final String icon,
  153. final String name,
  154. final String title,
  155. final AggregateConfigProvider config,
  156. final BackBufferFactory backBufferFactory,
  157. final URLBuilder urlBuilder,
  158. final CommandParser commandParser,
  159. final TabCompleter tabCompleter,
  160. final MessageSinkManager messageSinkManager,
  161. final DMDircMBassador eventBus,
  162. final Collection<String> components) {
  163. this.parent = Optional.ofNullable(parent);
  164. this.configManager = config;
  165. this.name = name;
  166. this.title = title;
  167. this.components = new HashSet<>(components);
  168. this.iconManager = new IconManager(configManager, urlBuilder);
  169. this.writable = true;
  170. this.commandParser = Optional.of(commandParser);
  171. this.tabCompleter = Optional.of(tabCompleter);
  172. this.messageSinkManager = Optional.of(messageSinkManager);
  173. this.backBufferFactory = backBufferFactory;
  174. commandParser.setOwner(this);
  175. eventBusManager = new ChildEventBusManager(eventBus);
  176. eventBusManager.connect();
  177. this.eventBus = eventBusManager.getChildBus();
  178. this.unreadStatusManager = new UnreadStatusManager(this);
  179. this.eventBus.subscribe(unreadStatusManager);
  180. setIcon(icon);
  181. }
  182. public Optional<FrameContainer> getParent() {
  183. return parent;
  184. }
  185. public String getIcon() {
  186. return icon;
  187. }
  188. public String getName() {
  189. return name;
  190. }
  191. public String getTitle() {
  192. return title;
  193. }
  194. public AggregateConfigProvider getConfigManager() {
  195. return configManager;
  196. }
  197. public DMDircMBassador getEventBus() {
  198. return eventBus;
  199. }
  200. public boolean isWritable() {
  201. return writable;
  202. }
  203. /**
  204. * Returns a collection of direct children of this frame.
  205. *
  206. * @return This frame's children
  207. *
  208. * @since 0.6.4
  209. */
  210. public Collection<FrameContainer> getChildren() {
  211. return Collections.unmodifiableCollection(children);
  212. }
  213. /**
  214. * Adds a new child window to this frame.
  215. *
  216. * @param child The window to be added
  217. *
  218. * @since 0.6.4
  219. */
  220. public void addChild(final FrameContainer child) {
  221. children.add(child);
  222. }
  223. /**
  224. * Removes a child window from this frame.
  225. *
  226. * @param child The window to be removed
  227. *
  228. * @since 0.6.4
  229. */
  230. public void removeChild(final FrameContainer child) {
  231. children.remove(child);
  232. }
  233. /**
  234. * Gets an icon manager for this container.
  235. *
  236. * @return An icon manager for this container.
  237. */
  238. public IconManager getIconManager() {
  239. return iconManager;
  240. }
  241. /**
  242. * Retrieves the {@link IRCDocument} used to store this frame's content.
  243. *
  244. * @return This frame's document
  245. *
  246. * @since 0.6.4
  247. * @deprecated Use {@link #getBackBuffer()}
  248. */
  249. @Deprecated
  250. public IRCDocument getDocument() {
  251. return getBackBuffer().getDocument();
  252. }
  253. /**
  254. * Changes the name of this container, and fires a {@link FrameNameChangedEvent}.
  255. *
  256. * @param name The new name for this frame.
  257. */
  258. protected void setName(final String name) {
  259. this.name = name;
  260. eventBus.publishAsync(new FrameNameChangedEvent(this, name));
  261. }
  262. /**
  263. * Changes the title of this container, and fires a {@link FrameTitleChangedEvent}.
  264. *
  265. * @param title The new title for this frame.
  266. */
  267. public void setTitle(final String title) {
  268. this.title = title;
  269. eventBus.publishAsync(new FrameTitleChangedEvent(this, title));
  270. }
  271. /**
  272. * Returns the collection of UI component identifiers that this frame container requires for its
  273. * display.
  274. *
  275. * @since 0.6.6
  276. * @return Collection of UI component identifiers
  277. */
  278. public Set<String> getComponents() {
  279. return Collections.unmodifiableSet(components);
  280. }
  281. /**
  282. * Adds a new component to this container.
  283. *
  284. * @since 0.6.6
  285. * @param component The component to be added
  286. */
  287. public void addComponent(final String component) {
  288. components.add(component);
  289. eventBus.publishAsync(new FrameComponentAddedEvent(this, component));
  290. }
  291. /**
  292. * Removes a component from this container.
  293. *
  294. * @since 0.6.6
  295. * @param component The component to be removed
  296. */
  297. public void removeComponent(final String component) {
  298. components.remove(component);
  299. eventBus.publishAsync(new FrameComponentRemovedEvent(this, component));
  300. }
  301. /**
  302. * Closes this container (and its associated frame).
  303. */
  304. public void close() {
  305. eventBus.unsubscribe(unreadStatusManager);
  306. eventBus.publish(new FrameClosingEvent(this));
  307. eventBusManager.disconnect();
  308. getBackBuffer().stopAddingEvents();
  309. }
  310. /**
  311. * Returns the connection that this container is associated with.
  312. *
  313. * @return the associated connection.
  314. */
  315. public abstract Optional<Connection> getConnection();
  316. /**
  317. * Sets the icon to be used by this frame container and fires a {@link FrameIconChangedEvent}.
  318. *
  319. * @param icon The new icon to be used
  320. */
  321. public final void setIcon(final String icon) {
  322. this.icon = icon;
  323. iconUpdated();
  324. configManager.removeListener(changer);
  325. configManager.addChangeListener("icon", icon, changer);
  326. }
  327. /**
  328. * Called when this container's icon is updated.
  329. */
  330. private void iconUpdated() {
  331. eventBus.publish(new FrameIconChangedEvent(this, icon));
  332. }
  333. /**
  334. * Retrieves the styliser which should be used by this container.
  335. *
  336. * @return this container's styliser
  337. * @deprecated Use {@link #getBackBuffer()}
  338. */
  339. @Deprecated
  340. public Styliser getStyliser() {
  341. return getBackBuffer().getStyliser();
  342. }
  343. /**
  344. * Gets the back buffer for this container.
  345. *
  346. * @return This container's back buffer.
  347. */
  348. public BackBuffer getBackBuffer() {
  349. synchronized (backBufferLock) {
  350. if (backBuffer == null) {
  351. backBuffer = backBufferFactory.getBackBuffer(this);
  352. backBuffer.startAddingEvents();
  353. }
  354. }
  355. return backBuffer;
  356. }
  357. /**
  358. * Adds a line to this container's window. If the window is null for some reason, the line is
  359. * silently discarded.
  360. *
  361. * @param type The message type to use
  362. * @param timestamp The timestamp to use for this line
  363. * @param args The message's arguments
  364. *
  365. * @since 0.6.4
  366. */
  367. public void addLine(final String type, final Date timestamp, final Object... args) {
  368. if (type != null && !type.isEmpty()) {
  369. addLine(Formatter.formatMessage(getConfigManager(), type, args), timestamp);
  370. }
  371. }
  372. /**
  373. * Adds a line to this container's window. If the window is null for some reason, the line is
  374. * silently discarded.
  375. *
  376. * @param type The message type to use
  377. * @param args The message's arguments
  378. */
  379. public void addLine(final String type, final Object... args) {
  380. addLine(type, new Date(), args);
  381. }
  382. /**
  383. * Adds a line to this container's window. If the window is null for some reason, the line is
  384. * silently discarded.
  385. *
  386. * @param type The message type to use
  387. * @param timestamp The timestamp to use for this line
  388. * @param args The message's arguments
  389. *
  390. * @since 0.6.4
  391. */
  392. public void addLine(final StringBuffer type, final Date timestamp, final Object... args) {
  393. if (type != null) {
  394. addLine(type.toString(), timestamp, args);
  395. }
  396. }
  397. /**
  398. * Adds a line to this container's window. If the window is null for some reason, the line is
  399. * silently discarded.
  400. *
  401. * @param type The message type to use
  402. * @param args The message's arguments
  403. */
  404. public void addLine(final StringBuffer type, final Object... args) {
  405. addLine(type, new Date(), args);
  406. }
  407. /**
  408. * Adds the specified raw line to the window, without using a formatter.
  409. *
  410. * @param line The line to be added
  411. * @param timestamp Whether or not to display the timestamp for this line
  412. */
  413. public void addLine(final String line, final boolean timestamp) {
  414. addLine(line, timestamp ? new Date() : null);
  415. }
  416. /**
  417. * Adds the specified raw line to the window, without using a formatter, and using the specified
  418. * timestamp. If the timestamp is <code>null</code>, no timestamp is added.
  419. *
  420. * @param line The line to be added
  421. * @param timestamp The timestamp to use for the line
  422. *
  423. * @since 0.6.4
  424. */
  425. public void addLine(final String line, final Date timestamp) {
  426. for (final String myLine : line.split("\n")) {
  427. getBackBuffer().getDocument().addText(
  428. timestamp.getTime(), DisplayPropertyMap.EMPTY, myLine);
  429. eventBus.publishAsync(new ClientLineAddedEvent(this, myLine));
  430. }
  431. }
  432. /**
  433. * Sends a line of text to this container's source.
  434. *
  435. * @param line The line to be sent
  436. */
  437. public void sendLine(final String line) {
  438. throw new UnsupportedOperationException("Container doesn't override sendLine");
  439. }
  440. /**
  441. * Retrieves the command parser to be used for this container.
  442. *
  443. * @return This container's command parser
  444. */
  445. public CommandParser getCommandParser() {
  446. checkState(writable);
  447. return commandParser.get();
  448. }
  449. /**
  450. * Retrieves the tab completer which should be used for this container.
  451. *
  452. * @return This container's tab completer
  453. */
  454. public TabCompleter getTabCompleter() {
  455. checkState(writable);
  456. return tabCompleter.get();
  457. }
  458. /**
  459. * Returns the maximum length that a line passed to sendLine() should be, in order to prevent it
  460. * being truncated or causing protocol violations.
  461. *
  462. * @return The maximum line length for this container
  463. */
  464. public int getMaxLineLength() {
  465. throw new UnsupportedOperationException("Container doesn't override getMaxLineLength");
  466. }
  467. /**
  468. * Splits the specified line into chunks that contain a number of bytes less than or equal to
  469. * the value returned by {@link #getMaxLineLength()}.
  470. *
  471. * @param line The line to be split
  472. *
  473. * @return An ordered list of chunks of the desired length
  474. */
  475. protected List<String> splitLine(final String line) {
  476. final List<String> result = new ArrayList<>();
  477. if (line.indexOf('\n') > -1) {
  478. for (String part : line.split("\n")) {
  479. result.addAll(splitLine(part));
  480. }
  481. } else {
  482. final StringBuilder remaining = new StringBuilder(line);
  483. while (getMaxLineLength() > -1 && remaining.toString().getBytes().length
  484. > getMaxLineLength()) {
  485. int number = Math.min(remaining.length(), getMaxLineLength());
  486. while (remaining.substring(0, number).getBytes().length > getMaxLineLength()) {
  487. number--;
  488. }
  489. result.add(remaining.substring(0, number));
  490. remaining.delete(0, number);
  491. }
  492. result.add(remaining.toString());
  493. }
  494. return result;
  495. }
  496. /**
  497. * Returns the number of lines that the specified string would be sent as.
  498. *
  499. * @param line The string to be split and sent
  500. *
  501. * @return The number of lines required to send the specified string
  502. */
  503. public final int getNumLines(final String line) {
  504. final String[] splitLines = line.split("(\n|\r\n|\r)", Integer.MAX_VALUE);
  505. int lines = 0;
  506. for (String splitLine : splitLines) {
  507. if (getMaxLineLength() <= 0) {
  508. lines++;
  509. } else {
  510. lines += (int) Math.ceil(splitLine.getBytes().length
  511. / (double) getMaxLineLength());
  512. }
  513. }
  514. return lines;
  515. }
  516. /**
  517. * Processes and displays a notification.
  518. *
  519. * @param messageType The name of the formatter to be used for the message
  520. * @param args The arguments for the message
  521. *
  522. * @return True if any further behaviour should be executed, false otherwise
  523. */
  524. public boolean doNotification(final String messageType, final Object... args) {
  525. return doNotification(new Date(), messageType, args);
  526. }
  527. /**
  528. * Processes and displays a notification.
  529. *
  530. * @param date The date/time at which the event occurred
  531. * @param messageType The name of the formatter to be used for the message
  532. * @param args The arguments for the message
  533. *
  534. * @return True if any further behaviour should be executed, false otherwise
  535. */
  536. public boolean doNotification(final Date date, final String messageType, final Object... args) {
  537. final List<Object> messageArgs = new ArrayList<>();
  538. final List<Object> actionArgs = new ArrayList<>();
  539. actionArgs.add(this);
  540. for (Object arg : args) {
  541. actionArgs.add(arg);
  542. if (!processNotificationArg(arg, messageArgs)) {
  543. messageArgs.add(arg);
  544. }
  545. }
  546. modifyNotificationArgs(actionArgs, messageArgs);
  547. handleNotification(date, messageType, messageArgs.toArray());
  548. return true;
  549. }
  550. /**
  551. * Allows subclasses to modify the lists of arguments for notifications.
  552. *
  553. * @param actionArgs The list of arguments to be passed to the actions system
  554. * @param messageArgs The list of arguments to be passed to the formatter
  555. */
  556. protected void modifyNotificationArgs(final List<Object> actionArgs,
  557. final List<Object> messageArgs) {
  558. // Do nothing
  559. }
  560. /**
  561. * Allows subclasses to process specific types of notification arguments.
  562. *
  563. * @param arg The argument to be processed
  564. * @param args The list of arguments that any data should be appended to
  565. *
  566. * @return True if the arg has been processed, false otherwise
  567. */
  568. protected boolean processNotificationArg(final Object arg, final List<Object> args) {
  569. return false;
  570. }
  571. /**
  572. * Handles general server notifications (i.e., ones not tied to a specific window). The user can
  573. * select where the notifications should go in their config.
  574. *
  575. * @param messageType The type of message that is being sent
  576. * @param args The arguments for the message
  577. */
  578. public void handleNotification(final String messageType, final Object... args) {
  579. handleNotification(new Date(), messageType, args);
  580. }
  581. /**
  582. * Handles general server notifications (i.e., ones not tied to a specific window). The user can
  583. * select where the notifications should go in their config.
  584. *
  585. * @param date The date/time at which the event occurred
  586. * @param messageType The type of message that is being sent
  587. * @param args The arguments for the message
  588. */
  589. public void handleNotification(final Date date, final String messageType, final Object... args) {
  590. checkState(writable);
  591. messageSinkManager.get().dispatchMessage(this, date, messageType, args);
  592. }
  593. /**
  594. * Sets the composition state for the local user for this chat.
  595. *
  596. * @param state The new composition state
  597. */
  598. public void setCompositionState(final CompositionState state) {
  599. // Default implementation does nothing. Subclasses that support
  600. // composition should override this.
  601. }
  602. public UnreadStatusManager getUnreadStatusManager() {
  603. return unreadStatusManager;
  604. }
  605. }