You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FrameContainer.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  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.WindowModel;
  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.input.TabCompleter;
  37. import com.dmdirc.ui.messages.BackBuffer;
  38. import com.dmdirc.ui.messages.BackBufferFactory;
  39. import com.dmdirc.ui.messages.Formatter;
  40. import com.dmdirc.ui.messages.UnreadStatusManager;
  41. import com.dmdirc.ui.messages.sink.MessageSinkManager;
  42. import com.dmdirc.util.ChildEventBusManager;
  43. import com.dmdirc.util.collections.ListenerList;
  44. import java.util.ArrayList;
  45. import java.util.Collection;
  46. import java.util.Collections;
  47. import java.util.Date;
  48. import java.util.HashSet;
  49. import java.util.List;
  50. import java.util.Optional;
  51. import java.util.Set;
  52. import java.util.concurrent.CopyOnWriteArrayList;
  53. import javax.annotation.Nullable;
  54. import static com.google.common.base.Preconditions.checkState;
  55. /**
  56. * The frame container implements basic methods that should be present in all objects that handle a
  57. * frame.
  58. */
  59. public abstract class FrameContainer implements WindowModel {
  60. /** Listeners not yet using ListenerSupport. */
  61. protected final ListenerList listeners = new ListenerList();
  62. /** The children of this frame. */
  63. private final Collection<WindowModel> children = new CopyOnWriteArrayList<>();
  64. /** The parent of this frame. */
  65. private final Optional<WindowModel> parent;
  66. /** The name of the icon being used for this container's frame. */
  67. private String icon;
  68. /** The name of this container. */
  69. private String name;
  70. /** The title of this container. */
  71. private String title;
  72. /** The config manager for this container. */
  73. private final AggregateConfigProvider configManager;
  74. /** The IconChanger for this container. */
  75. private final ConfigChangeListener changer = (d, k) -> iconUpdated();
  76. /** The UI components that this frame requires. */
  77. private final Set<String> components;
  78. /** The manager to use to manage our event bus. */
  79. private final ChildEventBusManager eventBusManager;
  80. /** Event bus to dispatch events to. */
  81. private final DMDircMBassador eventBus;
  82. /** The manager handling this frame's unread status. */
  83. private final UnreadStatusManager unreadStatusManager;
  84. /** Whether or not this container is writable. */
  85. private final boolean writable;
  86. /** The back buffer factory. */
  87. private final BackBufferFactory backBufferFactory;
  88. /** The back buffer for this container. */
  89. private BackBuffer backBuffer;
  90. /**
  91. * The manager to use to dispatch messages to sinks.
  92. * <p>
  93. * Only defined if this container is {@link #writable}.
  94. */
  95. private final Optional<MessageSinkManager> messageSinkManager;
  96. /**
  97. * The tab completer to use.
  98. * <p>
  99. * Only defined if this container is {@link #writable}.
  100. */
  101. private final Optional<TabCompleter> tabCompleter;
  102. /**
  103. * The command parser used for commands in this container.
  104. * <p>
  105. * Only defined if this container is {@link #writable}.
  106. */
  107. private Optional<CommandParser> commandParser = Optional.empty();
  108. /**
  109. * Instantiate new frame container.
  110. */
  111. protected FrameContainer(
  112. @Nullable final WindowModel parent,
  113. final String icon,
  114. final String name,
  115. final String title,
  116. final AggregateConfigProvider config,
  117. final BackBufferFactory backBufferFactory,
  118. final DMDircMBassador eventBus,
  119. final Collection<String> components) {
  120. this.parent = Optional.ofNullable(parent);
  121. this.configManager = config;
  122. this.name = name;
  123. this.title = title;
  124. this.components = new HashSet<>(components);
  125. this.writable = false;
  126. this.tabCompleter = Optional.empty();
  127. this.messageSinkManager = Optional.empty();
  128. this.backBufferFactory = backBufferFactory;
  129. eventBusManager = new ChildEventBusManager(eventBus);
  130. eventBusManager.connect();
  131. this.eventBus = eventBusManager.getChildBus();
  132. this.unreadStatusManager = new UnreadStatusManager(this);
  133. this.eventBus.subscribe(unreadStatusManager);
  134. configManager.getBinder().bind(unreadStatusManager, UnreadStatusManager.class);
  135. setIcon(icon);
  136. }
  137. /**
  138. * Instantiate new frame container that accepts user input.
  139. */
  140. protected FrameContainer(
  141. @Nullable final WindowModel parent,
  142. final String icon,
  143. final String name,
  144. final String title,
  145. final AggregateConfigProvider config,
  146. final BackBufferFactory backBufferFactory,
  147. final TabCompleter tabCompleter,
  148. final MessageSinkManager messageSinkManager,
  149. final DMDircMBassador eventBus,
  150. final Collection<String> components) {
  151. this.parent = Optional.ofNullable(parent);
  152. this.configManager = config;
  153. this.name = name;
  154. this.title = title;
  155. this.components = new HashSet<>(components);
  156. this.writable = true;
  157. this.tabCompleter = Optional.of(tabCompleter);
  158. this.messageSinkManager = Optional.of(messageSinkManager);
  159. this.backBufferFactory = backBufferFactory;
  160. eventBusManager = new ChildEventBusManager(eventBus);
  161. eventBusManager.connect();
  162. this.eventBus = eventBusManager.getChildBus();
  163. this.unreadStatusManager = new UnreadStatusManager(this);
  164. this.eventBus.subscribe(unreadStatusManager);
  165. configManager.getBinder().bind(unreadStatusManager, UnreadStatusManager.class);
  166. setIcon(icon);
  167. }
  168. protected void initBackBuffer() {
  169. backBuffer = backBufferFactory.getBackBuffer(this);
  170. backBuffer.startAddingEvents();
  171. }
  172. public void setCommandParser(final CommandParser commandParser) {
  173. this.commandParser = Optional.ofNullable(commandParser);
  174. }
  175. @Override
  176. public Optional<WindowModel> getParent() {
  177. return parent;
  178. }
  179. @Override
  180. public String getIcon() {
  181. return icon;
  182. }
  183. @Override
  184. public String getName() {
  185. return name;
  186. }
  187. @Override
  188. public String getTitle() {
  189. return title;
  190. }
  191. @Override
  192. public AggregateConfigProvider getConfigManager() {
  193. return configManager;
  194. }
  195. @Override
  196. public DMDircMBassador getEventBus() {
  197. return eventBus;
  198. }
  199. @Override
  200. public boolean isWritable() {
  201. return writable;
  202. }
  203. @Override
  204. public Collection<WindowModel> getChildren() {
  205. return Collections.unmodifiableCollection(children);
  206. }
  207. @Override
  208. public void addChild(final WindowModel child) {
  209. children.add(child);
  210. }
  211. @Override
  212. public void removeChild(final WindowModel child) {
  213. children.remove(child);
  214. }
  215. /**
  216. * Changes the name of this container, and fires a {@link FrameNameChangedEvent}.
  217. *
  218. * @param name The new name for this frame.
  219. */
  220. protected void setName(final String name) {
  221. this.name = name;
  222. eventBus.publishAsync(new FrameNameChangedEvent(this, name));
  223. }
  224. @Override
  225. public void setTitle(final String title) {
  226. this.title = title;
  227. eventBus.publishAsync(new FrameTitleChangedEvent(this, title));
  228. }
  229. @Override
  230. public Set<String> getComponents() {
  231. return Collections.unmodifiableSet(components);
  232. }
  233. @Override
  234. public void addComponent(final String component) {
  235. components.add(component);
  236. eventBus.publishAsync(new FrameComponentAddedEvent(this, component));
  237. }
  238. @Override
  239. public void removeComponent(final String component) {
  240. components.remove(component);
  241. eventBus.publishAsync(new FrameComponentRemovedEvent(this, component));
  242. }
  243. @Override
  244. public void close() {
  245. eventBus.unsubscribe(unreadStatusManager);
  246. configManager.getBinder().unbind(unreadStatusManager);
  247. eventBus.publish(new FrameClosingEvent(this));
  248. eventBusManager.disconnect();
  249. getBackBuffer().stopAddingEvents();
  250. }
  251. @Override
  252. public final void setIcon(final String icon) {
  253. this.icon = icon;
  254. iconUpdated();
  255. configManager.removeListener(changer);
  256. configManager.addChangeListener("icon", icon, changer);
  257. }
  258. /**
  259. * Called when this container's icon is updated.
  260. */
  261. private void iconUpdated() {
  262. eventBus.publish(new FrameIconChangedEvent(this, icon));
  263. }
  264. @Override
  265. public BackBuffer getBackBuffer() {
  266. return backBuffer;
  267. }
  268. @Override
  269. public void addLine(final String type, final Date timestamp, final Object... args) {
  270. if (type != null && !type.isEmpty()) {
  271. addLine(Formatter.formatMessage(getConfigManager(), type, args), timestamp);
  272. }
  273. }
  274. @Override
  275. public void addLine(final String type, final Object... args) {
  276. addLine(type, new Date(), args);
  277. }
  278. @Override
  279. public void addLine(final StringBuffer type, final Date timestamp, final Object... args) {
  280. if (type != null) {
  281. addLine(type.toString(), timestamp, args);
  282. }
  283. }
  284. @Override
  285. public void addLine(final StringBuffer type, final Object... args) {
  286. addLine(type, new Date(), args);
  287. }
  288. @Override
  289. @Deprecated
  290. public void addLine(final String line, final boolean timestamp) {
  291. addLine(line, new Date());
  292. }
  293. @Override
  294. public void addLine(final String line, final Date timestamp) {
  295. for (final String myLine : line.split("\n")) {
  296. getBackBuffer().getDocument().addText(
  297. timestamp.getTime(), DisplayPropertyMap.EMPTY, myLine);
  298. eventBus.publishAsync(new ClientLineAddedEvent(this, myLine));
  299. }
  300. }
  301. @Override
  302. public void sendLine(final String line) {
  303. throw new UnsupportedOperationException("Container doesn't override sendLine");
  304. }
  305. @Override
  306. public CommandParser getCommandParser() {
  307. checkState(writable);
  308. return commandParser.get();
  309. }
  310. @Override
  311. public TabCompleter getTabCompleter() {
  312. checkState(writable);
  313. return tabCompleter.get();
  314. }
  315. @Override
  316. public int getMaxLineLength() {
  317. throw new UnsupportedOperationException("Container doesn't override getMaxLineLength");
  318. }
  319. /**
  320. * Splits the specified line into chunks that contain a number of bytes less than or equal to
  321. * the value returned by {@link #getMaxLineLength()}.
  322. *
  323. * @param line The line to be split
  324. *
  325. * @return An ordered list of chunks of the desired length
  326. */
  327. protected List<String> splitLine(final String line) {
  328. final List<String> result = new ArrayList<>();
  329. if (line.indexOf('\n') > -1) {
  330. for (String part : line.split("\n")) {
  331. result.addAll(splitLine(part));
  332. }
  333. } else {
  334. final StringBuilder remaining = new StringBuilder(line);
  335. while (getMaxLineLength() > -1 && remaining.toString().getBytes().length
  336. > getMaxLineLength()) {
  337. int number = Math.min(remaining.length(), getMaxLineLength());
  338. while (remaining.substring(0, number).getBytes().length > getMaxLineLength()) {
  339. number--;
  340. }
  341. result.add(remaining.substring(0, number));
  342. remaining.delete(0, number);
  343. }
  344. result.add(remaining.toString());
  345. }
  346. return result;
  347. }
  348. @Override
  349. public final int getNumLines(final String line) {
  350. final String[] splitLines = line.split("(\n|\r\n|\r)", Integer.MAX_VALUE);
  351. int lines = 0;
  352. for (String splitLine : splitLines) {
  353. if (getMaxLineLength() <= 0) {
  354. lines++;
  355. } else {
  356. lines += (int) Math.ceil(splitLine.getBytes().length
  357. / (double) getMaxLineLength());
  358. }
  359. }
  360. return lines;
  361. }
  362. /**
  363. * Processes and displays a notification.
  364. *
  365. * @param messageType The name of the formatter to be used for the message
  366. * @param args The arguments for the message
  367. *
  368. * @return True if any further behaviour should be executed, false otherwise
  369. */
  370. public boolean doNotification(final String messageType, final Object... args) {
  371. return doNotification(new Date(), messageType, args);
  372. }
  373. /**
  374. * Processes and displays a notification.
  375. *
  376. * @param date The date/time at which the event occurred
  377. * @param messageType The name of the formatter to be used for the message
  378. * @param args The arguments for the message
  379. *
  380. * @return True if any further behaviour should be executed, false otherwise
  381. */
  382. public boolean doNotification(final Date date, final String messageType, final Object... args) {
  383. final List<Object> messageArgs = new ArrayList<>();
  384. final List<Object> actionArgs = new ArrayList<>();
  385. actionArgs.add(this);
  386. for (Object arg : args) {
  387. actionArgs.add(arg);
  388. if (!processNotificationArg(arg, messageArgs)) {
  389. messageArgs.add(arg);
  390. }
  391. }
  392. modifyNotificationArgs(actionArgs, messageArgs);
  393. handleNotification(date, messageType, messageArgs.toArray());
  394. return true;
  395. }
  396. /**
  397. * Allows subclasses to modify the lists of arguments for notifications.
  398. *
  399. * @param actionArgs The list of arguments to be passed to the actions system
  400. * @param messageArgs The list of arguments to be passed to the formatter
  401. */
  402. protected void modifyNotificationArgs(final List<Object> actionArgs,
  403. final List<Object> messageArgs) {
  404. // Do nothing
  405. }
  406. /**
  407. * Allows subclasses to process specific types of notification arguments.
  408. *
  409. * @param arg The argument to be processed
  410. * @param args The list of arguments that any data should be appended to
  411. *
  412. * @return True if the arg has been processed, false otherwise
  413. */
  414. protected boolean processNotificationArg(final Object arg, final List<Object> args) {
  415. return false;
  416. }
  417. /**
  418. * Handles general server notifications (i.e., ones not tied to a specific window). The user can
  419. * select where the notifications should go in their config.
  420. *
  421. * @param messageType The type of message that is being sent
  422. * @param args The arguments for the message
  423. */
  424. public void handleNotification(final String messageType, final Object... args) {
  425. handleNotification(new Date(), messageType, args);
  426. }
  427. /**
  428. * Handles general server notifications (i.e., ones not tied to a specific window). The user can
  429. * select where the notifications should go in their config.
  430. *
  431. * @param date The date/time at which the event occurred
  432. * @param messageType The type of message that is being sent
  433. * @param args The arguments for the message
  434. */
  435. public void handleNotification(final Date date, final String messageType, final Object... args) {
  436. checkState(writable);
  437. messageSinkManager.get().dispatchMessage(this, date, messageType, args);
  438. }
  439. /**
  440. * Sets the composition state for the local user for this chat.
  441. *
  442. * @param state The new composition state
  443. */
  444. public void setCompositionState(final CompositionState state) {
  445. // Default implementation does nothing. Subclasses that support
  446. // composition should override this.
  447. }
  448. @Override
  449. public UnreadStatusManager getUnreadStatusManager() {
  450. return unreadStatusManager;
  451. }
  452. }