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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  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.util.ChildEventBusManager;
  42. import com.dmdirc.util.collections.ListenerList;
  43. import java.util.ArrayList;
  44. import java.util.Collection;
  45. import java.util.Collections;
  46. import java.util.Date;
  47. import java.util.HashSet;
  48. import java.util.List;
  49. import java.util.Optional;
  50. import java.util.Set;
  51. import java.util.concurrent.CopyOnWriteArrayList;
  52. import javax.annotation.Nullable;
  53. import static com.google.common.base.Preconditions.checkState;
  54. /**
  55. * The frame container implements basic methods that should be present in all objects that handle a
  56. * frame.
  57. */
  58. public abstract class FrameContainer implements WindowModel {
  59. /** Listeners not yet using ListenerSupport. */
  60. protected final ListenerList listeners = new ListenerList();
  61. /** The children of this frame. */
  62. private final Collection<WindowModel> children = new CopyOnWriteArrayList<>();
  63. /** The parent of this frame. */
  64. private final Optional<WindowModel> parent;
  65. /** The name of the icon being used for this container's frame. */
  66. private String icon;
  67. /** The name of this container. */
  68. private String name;
  69. /** The title of this container. */
  70. private String title;
  71. /** The config manager for this container. */
  72. private final AggregateConfigProvider configManager;
  73. /** The IconChanger for this container. */
  74. private final ConfigChangeListener changer = (d, k) -> iconUpdated();
  75. /** The UI components that this frame requires. */
  76. private final Set<String> components;
  77. /** The manager to use to manage our event bus. */
  78. private final ChildEventBusManager eventBusManager;
  79. /** Event bus to dispatch events to. */
  80. private final DMDircMBassador eventBus;
  81. /** The manager handling this frame's unread status. */
  82. private final UnreadStatusManager unreadStatusManager;
  83. /** Whether or not this container is writable. */
  84. private final boolean writable;
  85. /** The back buffer factory. */
  86. private final BackBufferFactory backBufferFactory;
  87. /** The back buffer for this container. */
  88. private BackBuffer backBuffer;
  89. /**
  90. * The tab completer to use.
  91. * <p>
  92. * Only defined if this container is {@link #writable}.
  93. */
  94. private final Optional<TabCompleter> tabCompleter;
  95. /**
  96. * The command parser used for commands in this container.
  97. * <p>
  98. * Only defined if this container is {@link #writable}.
  99. */
  100. private Optional<CommandParser> commandParser = Optional.empty();
  101. /**
  102. * Instantiate new frame container.
  103. */
  104. protected FrameContainer(
  105. @Nullable final WindowModel parent,
  106. final String icon,
  107. final String name,
  108. final String title,
  109. final AggregateConfigProvider config,
  110. final BackBufferFactory backBufferFactory,
  111. final DMDircMBassador eventBus,
  112. final Collection<String> components) {
  113. this.parent = Optional.ofNullable(parent);
  114. this.configManager = config;
  115. this.name = name;
  116. this.title = title;
  117. this.components = new HashSet<>(components);
  118. this.writable = false;
  119. this.tabCompleter = Optional.empty();
  120. this.backBufferFactory = backBufferFactory;
  121. eventBusManager = new ChildEventBusManager(eventBus);
  122. eventBusManager.connect();
  123. this.eventBus = eventBusManager.getChildBus();
  124. this.unreadStatusManager = new UnreadStatusManager(this);
  125. this.eventBus.subscribe(unreadStatusManager);
  126. configManager.getBinder().bind(unreadStatusManager, UnreadStatusManager.class);
  127. setIcon(icon);
  128. }
  129. /**
  130. * Instantiate new frame container that accepts user input.
  131. */
  132. protected FrameContainer(
  133. @Nullable final WindowModel parent,
  134. final String icon,
  135. final String name,
  136. final String title,
  137. final AggregateConfigProvider config,
  138. final BackBufferFactory backBufferFactory,
  139. final TabCompleter tabCompleter,
  140. final DMDircMBassador eventBus,
  141. final Collection<String> components) {
  142. this.parent = Optional.ofNullable(parent);
  143. this.configManager = config;
  144. this.name = name;
  145. this.title = title;
  146. this.components = new HashSet<>(components);
  147. this.writable = true;
  148. this.tabCompleter = Optional.of(tabCompleter);
  149. this.backBufferFactory = backBufferFactory;
  150. eventBusManager = new ChildEventBusManager(eventBus);
  151. eventBusManager.connect();
  152. this.eventBus = eventBusManager.getChildBus();
  153. this.unreadStatusManager = new UnreadStatusManager(this);
  154. this.eventBus.subscribe(unreadStatusManager);
  155. configManager.getBinder().bind(unreadStatusManager, UnreadStatusManager.class);
  156. setIcon(icon);
  157. }
  158. protected void initBackBuffer() {
  159. backBuffer = backBufferFactory.getBackBuffer(this);
  160. backBuffer.startAddingEvents();
  161. }
  162. public void setCommandParser(final CommandParser commandParser) {
  163. this.commandParser = Optional.ofNullable(commandParser);
  164. }
  165. @Override
  166. public Optional<WindowModel> getParent() {
  167. return parent;
  168. }
  169. @Override
  170. public String getIcon() {
  171. return icon;
  172. }
  173. @Override
  174. public String getName() {
  175. return name;
  176. }
  177. @Override
  178. public String getTitle() {
  179. return title;
  180. }
  181. @Override
  182. public AggregateConfigProvider getConfigManager() {
  183. return configManager;
  184. }
  185. @Override
  186. public DMDircMBassador getEventBus() {
  187. return eventBus;
  188. }
  189. @Override
  190. public boolean isWritable() {
  191. return writable;
  192. }
  193. @Override
  194. public Collection<WindowModel> getChildren() {
  195. return Collections.unmodifiableCollection(children);
  196. }
  197. @Override
  198. public void addChild(final WindowModel child) {
  199. children.add(child);
  200. }
  201. @Override
  202. public void removeChild(final WindowModel child) {
  203. children.remove(child);
  204. }
  205. /**
  206. * Changes the name of this container, and fires a {@link FrameNameChangedEvent}.
  207. *
  208. * @param name The new name for this frame.
  209. */
  210. protected void setName(final String name) {
  211. this.name = name;
  212. eventBus.publishAsync(new FrameNameChangedEvent(this, name));
  213. }
  214. @Override
  215. public void setTitle(final String title) {
  216. this.title = title;
  217. eventBus.publishAsync(new FrameTitleChangedEvent(this, title));
  218. }
  219. @Override
  220. public Set<String> getComponents() {
  221. return Collections.unmodifiableSet(components);
  222. }
  223. @Override
  224. public void addComponent(final String component) {
  225. components.add(component);
  226. eventBus.publishAsync(new FrameComponentAddedEvent(this, component));
  227. }
  228. @Override
  229. public void removeComponent(final String component) {
  230. components.remove(component);
  231. eventBus.publishAsync(new FrameComponentRemovedEvent(this, component));
  232. }
  233. @Override
  234. public void close() {
  235. eventBus.unsubscribe(unreadStatusManager);
  236. configManager.getBinder().unbind(unreadStatusManager);
  237. eventBus.publish(new FrameClosingEvent(this));
  238. eventBusManager.disconnect();
  239. getBackBuffer().stopAddingEvents();
  240. }
  241. @Override
  242. public final void setIcon(final String icon) {
  243. this.icon = icon;
  244. iconUpdated();
  245. configManager.removeListener(changer);
  246. configManager.addChangeListener("icon", icon, changer);
  247. }
  248. /**
  249. * Called when this container's icon is updated.
  250. */
  251. private void iconUpdated() {
  252. eventBus.publish(new FrameIconChangedEvent(this, icon));
  253. }
  254. @Override
  255. public BackBuffer getBackBuffer() {
  256. return backBuffer;
  257. }
  258. @Override
  259. @Deprecated
  260. public void addLine(final String type, final Date timestamp, final Object... args) {
  261. if (type != null && !type.isEmpty()) {
  262. addLine(Formatter.formatMessage(getConfigManager(), type, args), timestamp);
  263. }
  264. }
  265. @Override
  266. @Deprecated
  267. public void addLine(final String type, final Object... args) {
  268. addLine(type, new Date(), args);
  269. }
  270. @Override
  271. @Deprecated
  272. public void addLine(final String line, final Date timestamp) {
  273. for (final String myLine : line.split("\n")) {
  274. getBackBuffer().getDocument().addText(
  275. timestamp.getTime(), DisplayPropertyMap.EMPTY, myLine);
  276. eventBus.publishAsync(new ClientLineAddedEvent(this, myLine));
  277. }
  278. }
  279. @Override
  280. public void sendLine(final String line) {
  281. throw new UnsupportedOperationException("Container doesn't override sendLine");
  282. }
  283. @Override
  284. public CommandParser getCommandParser() {
  285. checkState(writable);
  286. return commandParser.get();
  287. }
  288. @Override
  289. public TabCompleter getTabCompleter() {
  290. checkState(writable);
  291. return tabCompleter.get();
  292. }
  293. @Override
  294. public int getMaxLineLength() {
  295. throw new UnsupportedOperationException("Container doesn't override getMaxLineLength");
  296. }
  297. /**
  298. * Splits the specified line into chunks that contain a number of bytes less than or equal to
  299. * the value returned by {@link #getMaxLineLength()}.
  300. *
  301. * @param line The line to be split
  302. *
  303. * @return An ordered list of chunks of the desired length
  304. */
  305. protected List<String> splitLine(final String line) {
  306. final List<String> result = new ArrayList<>();
  307. if (line.indexOf('\n') > -1) {
  308. for (String part : line.split("\n")) {
  309. result.addAll(splitLine(part));
  310. }
  311. } else {
  312. final StringBuilder remaining = new StringBuilder(line);
  313. while (getMaxLineLength() > -1 && remaining.toString().getBytes().length
  314. > getMaxLineLength()) {
  315. int number = Math.min(remaining.length(), getMaxLineLength());
  316. while (remaining.substring(0, number).getBytes().length > getMaxLineLength()) {
  317. number--;
  318. }
  319. result.add(remaining.substring(0, number));
  320. remaining.delete(0, number);
  321. }
  322. result.add(remaining.toString());
  323. }
  324. return result;
  325. }
  326. @Override
  327. public final int getNumLines(final String line) {
  328. final String[] splitLines = line.split("(\n|\r\n|\r)", Integer.MAX_VALUE);
  329. int lines = 0;
  330. for (String splitLine : splitLines) {
  331. if (getMaxLineLength() <= 0) {
  332. lines++;
  333. } else {
  334. lines += (int) Math.ceil(splitLine.getBytes().length
  335. / (double) getMaxLineLength());
  336. }
  337. }
  338. return lines;
  339. }
  340. /**
  341. * Sets the composition state for the local user for this chat.
  342. *
  343. * @param state The new composition state
  344. */
  345. public void setCompositionState(final CompositionState state) {
  346. // Default implementation does nothing. Subclasses that support
  347. // composition should override this.
  348. }
  349. @Override
  350. public UnreadStatusManager getUnreadStatusManager() {
  351. return unreadStatusManager;
  352. }
  353. }