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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  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 String line, final Date timestamp) {
  280. for (final String myLine : line.split("\n")) {
  281. getBackBuffer().getDocument().addText(
  282. timestamp.getTime(), DisplayPropertyMap.EMPTY, myLine);
  283. eventBus.publishAsync(new ClientLineAddedEvent(this, myLine));
  284. }
  285. }
  286. @Override
  287. public void sendLine(final String line) {
  288. throw new UnsupportedOperationException("Container doesn't override sendLine");
  289. }
  290. @Override
  291. public CommandParser getCommandParser() {
  292. checkState(writable);
  293. return commandParser.get();
  294. }
  295. @Override
  296. public TabCompleter getTabCompleter() {
  297. checkState(writable);
  298. return tabCompleter.get();
  299. }
  300. @Override
  301. public int getMaxLineLength() {
  302. throw new UnsupportedOperationException("Container doesn't override getMaxLineLength");
  303. }
  304. /**
  305. * Splits the specified line into chunks that contain a number of bytes less than or equal to
  306. * the value returned by {@link #getMaxLineLength()}.
  307. *
  308. * @param line The line to be split
  309. *
  310. * @return An ordered list of chunks of the desired length
  311. */
  312. protected List<String> splitLine(final String line) {
  313. final List<String> result = new ArrayList<>();
  314. if (line.indexOf('\n') > -1) {
  315. for (String part : line.split("\n")) {
  316. result.addAll(splitLine(part));
  317. }
  318. } else {
  319. final StringBuilder remaining = new StringBuilder(line);
  320. while (getMaxLineLength() > -1 && remaining.toString().getBytes().length
  321. > getMaxLineLength()) {
  322. int number = Math.min(remaining.length(), getMaxLineLength());
  323. while (remaining.substring(0, number).getBytes().length > getMaxLineLength()) {
  324. number--;
  325. }
  326. result.add(remaining.substring(0, number));
  327. remaining.delete(0, number);
  328. }
  329. result.add(remaining.toString());
  330. }
  331. return result;
  332. }
  333. @Override
  334. public final int getNumLines(final String line) {
  335. final String[] splitLines = line.split("(\n|\r\n|\r)", Integer.MAX_VALUE);
  336. int lines = 0;
  337. for (String splitLine : splitLines) {
  338. if (getMaxLineLength() <= 0) {
  339. lines++;
  340. } else {
  341. lines += (int) Math.ceil(splitLine.getBytes().length
  342. / (double) getMaxLineLength());
  343. }
  344. }
  345. return lines;
  346. }
  347. /**
  348. * Handles general server notifications (i.e., ones not tied to a specific window). The user can
  349. * select where the notifications should go in their config.
  350. *
  351. * @param date The date/time at which the event occurred
  352. * @param messageType The type of message that is being sent
  353. * @param args The arguments for the message
  354. */
  355. public void handleNotification(final Date date, final String messageType, final Object... args) {
  356. checkState(writable);
  357. messageSinkManager.get().dispatchMessage(this, date, messageType, args);
  358. }
  359. /**
  360. * Sets the composition state for the local user for this chat.
  361. *
  362. * @param state The new composition state
  363. */
  364. public void setCompositionState(final CompositionState state) {
  365. // Default implementation does nothing. Subclasses that support
  366. // composition should override this.
  367. }
  368. @Override
  369. public UnreadStatusManager getUnreadStatusManager() {
  370. return unreadStatusManager;
  371. }
  372. }