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.

WritableFrameContainer.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. /*
  2. * Copyright (c) 2006-2011 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.interfaces.ActionType;
  25. import com.dmdirc.commandparser.parsers.CommandParser;
  26. import com.dmdirc.config.ConfigManager;
  27. import com.dmdirc.logger.ErrorLevel;
  28. import com.dmdirc.logger.Logger;
  29. import com.dmdirc.ui.WindowManager;
  30. import com.dmdirc.ui.input.TabCompleter;
  31. import com.dmdirc.ui.interfaces.InputWindow;
  32. import java.util.ArrayList;
  33. import java.util.Date;
  34. import java.util.List;
  35. /**
  36. * The writable frame container adds additional methods to the frame container
  37. * class that allow the sending of lines back to whatever the container's
  38. * data source is (e.g. an IRC channel or server).
  39. *
  40. * @param <T> The type of window which should be used for this frame container.
  41. * @author chris
  42. */
  43. public abstract class WritableFrameContainer<T extends InputWindow> extends FrameContainer<T> {
  44. /** The name of the server notification target. */
  45. protected static final String NOTIFICATION_SERVER = "server".intern();
  46. /** The name of the channel notification target. */
  47. protected static final String NOTIFICATION_CHANNEL = "channel".intern();
  48. /** The command parser used for commands in this container. */
  49. protected final CommandParser commandParser;
  50. /**
  51. * Creates a new WritableFrameContainer.
  52. *
  53. * @param icon The icon to use for this container
  54. * @param name The name of this container
  55. * @param title The title of this container
  56. * @param windowClass The class of window to use to represent this container
  57. * @param config The config manager for this container
  58. * @param parser The command parser for this container
  59. * @since 0.6.4
  60. */
  61. public WritableFrameContainer(final String icon, final String name,
  62. final String title, final Class<T> windowClass,
  63. final ConfigManager config, final CommandParser parser) {
  64. super(icon, name, title, windowClass, config);
  65. this.commandParser = parser;
  66. parser.setOwner(this);
  67. }
  68. /**
  69. * Sends a line of text to this container's source.
  70. *
  71. * @param line The line to be sent
  72. */
  73. public abstract void sendLine(String line);
  74. /**
  75. * Retrieves the command parser to be used for this container.
  76. *
  77. * @return This container's command parser
  78. */
  79. public CommandParser getCommandParser() {
  80. return commandParser;
  81. }
  82. /**
  83. * Retrieves the tab completer which should be used for this cotnainer.
  84. *
  85. * @return This container's tab completer
  86. */
  87. public abstract TabCompleter getTabCompleter();
  88. /**
  89. * Returns the maximum length that a line passed to sendLine() should be,
  90. * in order to prevent it being truncated or causing protocol violations.
  91. *
  92. * @return The maximum line length for this container
  93. */
  94. public abstract int getMaxLineLength();
  95. /**
  96. * Splits the specified line into chunks that contain a number of bytes
  97. * less than or equal to the value returned by {@link #getMaxLineLength()}.
  98. *
  99. * @param line The line to be split
  100. * @return An ordered list of chunks of the desired length
  101. */
  102. protected List<String> splitLine(final String line) {
  103. final List<String> result = new ArrayList<String>();
  104. if (line.indexOf('\n') > -1) {
  105. for (String part : line.split("\n")) {
  106. result.addAll(splitLine(part));
  107. }
  108. } else {
  109. final StringBuilder remaining = new StringBuilder(line);
  110. while (getMaxLineLength() > -1 && remaining.toString().getBytes().length
  111. > getMaxLineLength()) {
  112. int number = Math.min(remaining.length(), getMaxLineLength());
  113. while (remaining.substring(0, number).getBytes().length > getMaxLineLength()) {
  114. number--;
  115. }
  116. result.add(remaining.substring(0, number));
  117. remaining.delete(0, number);
  118. }
  119. result.add(remaining.toString());
  120. }
  121. return result;
  122. }
  123. /**
  124. * Returns the number of lines that the specified string would be sent as.
  125. *
  126. * @param line The string to be split and sent
  127. * @return The number of lines required to send the specified string
  128. */
  129. public final int getNumLines(final String line) {
  130. final String[] splitLines = line.split("(\n|\r\n|\r)", Integer.MAX_VALUE);
  131. int lines = 0;
  132. for (String splitLine : splitLines) {
  133. if (getMaxLineLength() <= 0) {
  134. lines++;
  135. } else {
  136. lines += (int) Math.ceil(splitLine.getBytes().length
  137. / (double) getMaxLineLength());
  138. }
  139. }
  140. return lines;
  141. }
  142. /**
  143. * Processes and displays a notification.
  144. *
  145. * @param messageType The name of the formatter to be used for the message
  146. * @param actionType The action type to be used
  147. * @param args The arguments for the message
  148. * @return True if any further behaviour should be executed, false otherwise
  149. */
  150. public boolean doNotification(final String messageType,
  151. final ActionType actionType, final Object... args) {
  152. return doNotification(new Date(), messageType, actionType, args);
  153. }
  154. /**
  155. * Processes and displays a notification.
  156. *
  157. * @param date The date/time at which the event occured
  158. * @param messageType The name of the formatter to be used for the message
  159. * @param actionType The action type to be used
  160. * @param args The arguments for the message
  161. * @return True if any further behaviour should be executed, false otherwise
  162. */
  163. public boolean doNotification(final Date date, final String messageType,
  164. final ActionType actionType, final Object... args) {
  165. final List<Object> messageArgs = new ArrayList<Object>();
  166. final List<Object> actionArgs = new ArrayList<Object>();
  167. final StringBuffer buffer = new StringBuffer(messageType);
  168. actionArgs.add(this);
  169. for (Object arg : args) {
  170. actionArgs.add(arg);
  171. if (!processNotificationArg(arg, messageArgs)) {
  172. messageArgs.add(arg);
  173. }
  174. }
  175. modifyNotificationArgs(actionArgs, messageArgs);
  176. final boolean res = ActionManager.processEvent(actionType, buffer, actionArgs.toArray());
  177. handleNotification(date, buffer.toString(), messageArgs.toArray());
  178. return res;
  179. }
  180. /**
  181. * Allows subclasses to modify the lists of arguments for notifications.
  182. *
  183. * @param actionArgs The list of arguments to be passed to the actions system
  184. * @param messageArgs The list of arguments to be passed to the formatter
  185. */
  186. protected void modifyNotificationArgs(final List<Object> actionArgs,
  187. final List<Object> messageArgs) {
  188. // Do nothing
  189. }
  190. /**
  191. * Allows subclasses to process specific types of notification arguments.
  192. *
  193. * @param arg The argument to be processed
  194. * @param args The list of arguments that any data should be appended to
  195. * @return True if the arg has been processed, false otherwise
  196. */
  197. protected boolean processNotificationArg(final Object arg, final List<Object> args) {
  198. return false;
  199. }
  200. /**
  201. * Handles general server notifications (i.e., ones not tied to a
  202. * specific window). The user can select where the notifications should
  203. * go in their config.
  204. *
  205. * @param messageType The type of message that is being sent
  206. * @param args The arguments for the message
  207. */
  208. public void handleNotification(final String messageType, final Object... args) {
  209. handleNotification(new Date(), messageType, args);
  210. }
  211. /**
  212. * Handles general server notifications (i.e., ones not tied to a
  213. * specific window). The user can select where the notifications should
  214. * go in their config.
  215. *
  216. * @param date The date/time at which the event occured
  217. * @param messageType The type of message that is being sent
  218. * @param args The arguments for the message
  219. */
  220. public void handleNotification(final Date date, final String messageType, final Object... args) {
  221. despatchNotification(date, messageType, getConfigManager().hasOptionString("notifications",
  222. messageType) ? getConfigManager().getOption("notifications", messageType)
  223. : "self", args);
  224. }
  225. /**
  226. * Despatches a notification of the specified type to the specified target.
  227. *
  228. * @param date The date/time at which the event occured
  229. * @param messageType The type of the message that is being sent
  230. * @param messageTarget The target of the message
  231. * @param args The arguments for the message
  232. */
  233. protected void despatchNotification(final Date date, final String messageType,
  234. final String messageTarget, final Object... args) {
  235. String target = messageTarget;
  236. String format = messageType;
  237. if (target.startsWith("format:")) {
  238. format = target.substring(7);
  239. format = format.substring(0, format.indexOf(':'));
  240. target = target.substring(8 + format.length());
  241. }
  242. if (target.startsWith("group:")) {
  243. target = getConfigManager().hasOptionString("notifications", target.substring(6))
  244. ? getConfigManager().getOption("notifications", target.substring(6))
  245. : "self";
  246. }
  247. if (target.startsWith("fork:")) {
  248. for (String newtarget : target.substring(5).split("\\|")) {
  249. despatchNotification(date, format, newtarget, args);
  250. }
  251. return;
  252. }
  253. if ("self".equals(target)) {
  254. addLine(format, date, args);
  255. } else if (NOTIFICATION_SERVER.equals(target)) {
  256. getServer().addLine(format, date, args);
  257. } else if ("all".equals(target)) {
  258. getServer().addLineToAll(format, args);
  259. } else if ("active".equals(target)) {
  260. getServer().addLineToActive(format, args);
  261. } else if (target.startsWith("window:")) {
  262. final String windowName = target.substring(7);
  263. FrameContainer<?> targetWindow = WindowManager.findCustomWindow(getServer(), windowName);
  264. if (targetWindow == null) {
  265. targetWindow = new CustomWindow(windowName, windowName, getServer());
  266. }
  267. targetWindow.addLine(format, date, args);
  268. } else if (target.startsWith("lastcommand:")) {
  269. final Object[] escapedargs = new Object[args.length];
  270. for (int i = 0; i < args.length; i++) {
  271. escapedargs[i] = "\\Q" + args[i] + "\\E";
  272. }
  273. final String command = String.format(target.substring(12), escapedargs);
  274. WritableFrameContainer<?> best = this;
  275. long besttime = 0;
  276. final List<FrameContainer<?>> containers = new ArrayList<FrameContainer<?>>();
  277. containers.add(getServer());
  278. containers.addAll(getServer().getChildren());
  279. for (FrameContainer<?> container : containers) {
  280. if (container == null || !(container instanceof WritableFrameContainer<?>)) {
  281. continue;
  282. }
  283. final long time = ((WritableFrameContainer<?>) container)
  284. .getCommandParser().getCommandTime(command);
  285. if (time > besttime) {
  286. besttime = time;
  287. best = (WritableFrameContainer<?>) container;
  288. }
  289. }
  290. best.addLine(format, date, args);
  291. } else if (target.startsWith(NOTIFICATION_CHANNEL + ":")) {
  292. final int sp = target.indexOf(' ');
  293. final String channel = String.format(
  294. target.substring(8, sp > -1 ? sp : target.length()), args);
  295. if (getServer().hasChannel(channel)) {
  296. getServer().getChannel(channel).addLine(messageType, date, args);
  297. } else if (sp > -1) {
  298. // They specified a fallback
  299. despatchNotification(date, format, target.substring(sp + 1), args);
  300. } else {
  301. // No fallback specified
  302. addLine(format, date, args);
  303. Logger.userError(ErrorLevel.LOW,
  304. "Invalid notification target for type " + messageType
  305. + ": channel " + channel + " doesn't exist");
  306. }
  307. } else if (target.startsWith("comchans:")) {
  308. final int sp = target.indexOf(' ');
  309. final String user = String.format(
  310. target.substring(9, sp > -1 ? sp : target.length()), args);
  311. boolean found = false;
  312. for (String channelName : getServer().getChannels()) {
  313. final Channel channel = getServer().getChannel(channelName);
  314. if (channel.getChannelInfo().getChannelClient(user) != null) {
  315. channel.addLine(messageType, date, args);
  316. found = true;
  317. }
  318. }
  319. if (!found) {
  320. if (sp > -1) {
  321. // They specified a fallback
  322. despatchNotification(date, format, target.substring(sp + 1), args);
  323. } else {
  324. // No fallback specified
  325. addLine(messageType, date, args);
  326. Logger.userError(ErrorLevel.LOW,
  327. "Invalid notification target for type " + messageType
  328. + ": no common channels with " + user);
  329. }
  330. }
  331. } else if (!"none".equals(target)) {
  332. addLine(format, date, args);
  333. Logger.userError(ErrorLevel.MEDIUM,
  334. "Invalid notification target for type " + messageType + ": " + target);
  335. }
  336. }
  337. }