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.

Channel.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. /*
  2. * Copyright (c) 2006-2010 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.CoreActionType;
  25. import com.dmdirc.commandparser.CommandManager;
  26. import com.dmdirc.commandparser.CommandType;
  27. import com.dmdirc.config.ConfigManager;
  28. import com.dmdirc.interfaces.ConfigChangeListener;
  29. import com.dmdirc.parser.interfaces.ChannelClientInfo;
  30. import com.dmdirc.parser.interfaces.ChannelInfo;
  31. import com.dmdirc.parser.interfaces.ClientInfo;
  32. import com.dmdirc.ui.WindowManager;
  33. import com.dmdirc.ui.input.TabCompleter;
  34. import com.dmdirc.ui.input.TabCompletionType;
  35. import com.dmdirc.ui.interfaces.ChannelWindow;
  36. import com.dmdirc.ui.interfaces.InputWindow;
  37. import com.dmdirc.ui.messages.ColourManager;
  38. import com.dmdirc.ui.messages.Styliser;
  39. import com.dmdirc.util.RollingList;
  40. import java.awt.Color;
  41. import java.io.Serializable;
  42. import java.util.ArrayList;
  43. import java.util.Arrays;
  44. import java.util.Collection;
  45. import java.util.List;
  46. import java.util.Map;
  47. /**
  48. * The Channel class represents the client's view of the channel. It handles
  49. * callbacks for channel events from the parser, maintains the corresponding
  50. * ChannelWindow, and handles user input for the channel.
  51. *
  52. * @author chris
  53. */
  54. public class Channel extends MessageTarget implements ConfigChangeListener,
  55. Serializable {
  56. /**
  57. * A version number for this class. It should be changed whenever the class
  58. * structure is changed (or anything else that would prevent serialized
  59. * objects being unserialized with the new class).
  60. */
  61. private static final long serialVersionUID = 1;
  62. /** The parser's pChannel class. */
  63. private transient ChannelInfo channelInfo;
  64. /** The server this channel is on. */
  65. private Server server;
  66. /** The ChannelWindow used for this channel. */
  67. private ChannelWindow window;
  68. /** The tabcompleter used for this channel. */
  69. private final TabCompleter tabCompleter;
  70. /** A list of previous topics we've seen. */
  71. private final RollingList<Topic> topics;
  72. /** Our event handler. */
  73. private final ChannelEventHandler eventHandler;
  74. /** Whether we're in this channel or not. */
  75. private boolean onChannel;
  76. /** Whether we should send WHO requests for this channel. */
  77. private volatile boolean sendWho;
  78. /** Whether we should show mode prefixes in text. */
  79. private volatile boolean showModePrefix;
  80. /** Whether we should show colours in nicks. */
  81. private volatile boolean showColours;
  82. /**
  83. * Creates a new instance of Channel.
  84. *
  85. * @param newServer The server object that this channel belongs to
  86. * @param newChannelInfo The parser's channel object that corresponds to
  87. * this channel
  88. */
  89. public Channel(final Server newServer, final ChannelInfo newChannelInfo) {
  90. super("channel-inactive", newChannelInfo.getName(),
  91. new ConfigManager(newServer.getIrcd(), newServer.getNetwork(),
  92. newServer.getName(), newChannelInfo.getName()));
  93. channelInfo = newChannelInfo;
  94. server = newServer;
  95. getConfigManager().addChangeListener("channel", this);
  96. getConfigManager().addChangeListener("ui", "shownickcoloursintext", this);
  97. topics = new RollingList<Topic>(getConfigManager().getOptionInt("channel",
  98. "topichistorysize"));
  99. sendWho = getConfigManager().getOptionBool("channel", "sendwho");
  100. showModePrefix = getConfigManager().getOptionBool("channel", "showmodeprefix");
  101. showColours = getConfigManager().getOptionBool("ui", "shownickcoloursintext");
  102. tabCompleter = new TabCompleter(server.getTabCompleter());
  103. tabCompleter.addEntries(TabCompletionType.COMMAND,
  104. CommandManager.getCommandNames(CommandType.TYPE_CHANNEL));
  105. tabCompleter.addEntries(TabCompletionType.COMMAND,
  106. CommandManager.getCommandNames(CommandType.TYPE_CHAT));
  107. window = Main.getUI().getChannel(Channel.this);
  108. WindowManager.addWindow(server.getFrame(), window);
  109. window.getInputHandler().setTabCompleter(tabCompleter);
  110. eventHandler = new ChannelEventHandler(this);
  111. registerCallbacks();
  112. ActionManager.processEvent(CoreActionType.CHANNEL_OPENED, null, this);
  113. updateTitle();
  114. selfJoin();
  115. }
  116. /**
  117. * Registers callbacks with the parser for this channel.
  118. */
  119. private void registerCallbacks() {
  120. eventHandler.registerCallbacks();
  121. getConfigManager().migrate(server.getIrcd(), server.getNetwork(),
  122. server.getName(), channelInfo.getName());
  123. }
  124. /**
  125. * Shows this channel's window.
  126. */
  127. public void show() {
  128. window.open();
  129. }
  130. /** {@inheritDoc} */
  131. @Override
  132. public void sendLine(final String line) {
  133. if (server.getState() != ServerState.CONNECTED
  134. || server.getParser().getChannel(channelInfo.getName()) == null) {
  135. // We're not in the channel/connected to the server
  136. return;
  137. }
  138. final ClientInfo me = server.getParser().getLocalClient();
  139. final String[] details = getDetails(channelInfo.getChannelClient(me), showColours);
  140. for (String part : splitLine(window.getTranscoder().encode(line))) {
  141. if (!part.isEmpty()) {
  142. final StringBuffer buff = new StringBuffer("channelSelfMessage");
  143. ActionManager.processEvent(CoreActionType.CHANNEL_SELF_MESSAGE, buff,
  144. this, channelInfo.getChannelClient(me), part);
  145. addLine(buff, details[0], details[1], details[2], details[3],
  146. part, channelInfo);
  147. channelInfo.sendMessage(part);
  148. }
  149. }
  150. }
  151. /** {@inheritDoc} */
  152. @Override
  153. public int getMaxLineLength() {
  154. return server.getState() == ServerState.CONNECTED
  155. ? server.getParser().getMaxLength("PRIVMSG", getChannelInfo().getName())
  156. : -1;
  157. }
  158. /** {@inheritDoc} */
  159. @Override
  160. public void sendAction(final String action) {
  161. if (server.getState() != ServerState.CONNECTED
  162. || server.getParser().getChannel(channelInfo.getName()) == null) {
  163. // We're not on the server/channel
  164. return;
  165. }
  166. final ClientInfo me = server.getParser().getLocalClient();
  167. final String[] details = getDetails(channelInfo.getChannelClient(me), showColours);
  168. if (server.getParser().getMaxLength("PRIVMSG", getChannelInfo().getName())
  169. <= action.length()) {
  170. addLine("actionTooLong", action.length());
  171. } else {
  172. final StringBuffer buff = new StringBuffer("channelSelfAction");
  173. ActionManager.processEvent(CoreActionType.CHANNEL_SELF_ACTION, buff,
  174. this, channelInfo.getChannelClient(me), action);
  175. addLine(buff, details[0], details[1], details[2], details[3],
  176. window.getTranscoder().encode(action), channelInfo);
  177. channelInfo.sendAction(window.getTranscoder().encode(action));
  178. }
  179. }
  180. /**
  181. * Returns the server object that this channel belongs to.
  182. *
  183. * @return The server object
  184. */
  185. @Override
  186. public Server getServer() {
  187. return server;
  188. }
  189. /**
  190. * Returns the parser's ChannelInfo object that this object is associated
  191. * with.
  192. *
  193. * @return The ChannelInfo object associated with this object
  194. */
  195. public ChannelInfo getChannelInfo() {
  196. return channelInfo;
  197. }
  198. /**
  199. * Sets this object's ChannelInfo reference to the one supplied. This only
  200. * needs to be done if the channel window (and hence this channel object)
  201. * has stayed open while the user has been out of the channel.
  202. *
  203. * @param newChannelInfo The new ChannelInfo object
  204. */
  205. public void setChannelInfo(final ChannelInfo newChannelInfo) {
  206. channelInfo = newChannelInfo;
  207. registerCallbacks();
  208. }
  209. /**
  210. * Returns the internal window belonging to this object.
  211. *
  212. * @return This object's internal window
  213. */
  214. @Override
  215. public InputWindow getFrame() {
  216. return window;
  217. }
  218. /**
  219. * Returns the tab completer for this channel.
  220. *
  221. * @return This channel's tab completer
  222. */
  223. public TabCompleter getTabCompleter() {
  224. return tabCompleter;
  225. }
  226. /**
  227. * Called when we join this channel. Just needs to output a message.
  228. */
  229. public void selfJoin() {
  230. onChannel = true;
  231. final ClientInfo me = server.getParser().getLocalClient();
  232. addLine("channelSelfJoin", "", me.getNickname(), me.getUsername(),
  233. me.getHostname(), channelInfo.getName());
  234. setIcon("channel");
  235. server.removeInvites(channelInfo.getName());
  236. }
  237. /**
  238. * Updates the title of the channel window, and of the main window if
  239. * appropriate.
  240. */
  241. private void updateTitle() {
  242. String temp = Styliser.stipControlCodes(channelInfo.getName());
  243. if (!channelInfo.getTopic().isEmpty()) {
  244. temp = temp + " - " + Styliser.stipControlCodes(channelInfo.getTopic());
  245. }
  246. window.setTitle(temp);
  247. }
  248. /**
  249. * Joins the specified channel. This only makes sense if used after a call
  250. * to part().
  251. */
  252. public void join() {
  253. server.getParser().joinChannel(channelInfo.getName());
  254. activateFrame();
  255. }
  256. /**
  257. * Parts this channel with the specified message. Parting does NOT close the
  258. * channel window.
  259. *
  260. * @param reason The reason for parting the channel
  261. */
  262. public void part(final String reason) {
  263. channelInfo.part(reason);
  264. resetWindow();
  265. }
  266. /**
  267. * Resets the window state after the client has left a channel.
  268. */
  269. public void resetWindow() {
  270. onChannel = false;
  271. setIcon("channel-inactive");
  272. window.updateNames(new ArrayList<ChannelClientInfo>());
  273. }
  274. /** {@inheritDoc} */
  275. @Override
  276. public void windowClosing() {
  277. // 1: Make the window non-visible
  278. window.setVisible(false);
  279. // 2: Remove any callbacks or listeners
  280. eventHandler.unregisterCallbacks();
  281. if (server.getParser() != null) {
  282. server.getParser().getCallbackManager().delAllCallback(eventHandler);
  283. }
  284. // 3: Trigger any actions neccessary
  285. if (onChannel) {
  286. part(getConfigManager().getOption("general", "partmessage"));
  287. }
  288. // 4: Trigger action for the window closing
  289. ActionManager.processEvent(CoreActionType.CHANNEL_CLOSED, null, this);
  290. // 5: Inform any parents that the window is closing
  291. server.delChannel(channelInfo.getName());
  292. // 6: Remove the window from the window manager
  293. WindowManager.removeWindow(window);
  294. // 7: Remove any references to the window and parents
  295. window = null; // NOPMD
  296. server = null; // NOPMD
  297. }
  298. /**
  299. * Called every {general.whotime} seconds to check if the channel needs
  300. * to send a who request.
  301. */
  302. public void checkWho() {
  303. if (onChannel && sendWho) {
  304. channelInfo.sendWho();
  305. }
  306. }
  307. /**
  308. * Adds a ChannelClient to this Channel.
  309. *
  310. * @param client The client to be added
  311. */
  312. public void addClient(final ChannelClientInfo client) {
  313. window.addName(client);
  314. tabCompleter.addEntry(TabCompletionType.CHANNEL_NICK, client.getClient().getNickname());
  315. }
  316. /**
  317. * Removes the specified ChannelClient from this channel.
  318. *
  319. * @param client The client to be removed
  320. */
  321. public void removeClient(final ChannelClientInfo client) {
  322. window.removeName(client);
  323. tabCompleter.removeEntry(TabCompletionType.CHANNEL_NICK, client.getClient().getNickname());
  324. if (client.getClient().equals(server.getParser().getLocalClient())) {
  325. resetWindow();
  326. }
  327. }
  328. /**
  329. * Replaces the list of known clients on this channel with the specified
  330. * one.
  331. *
  332. * @param clients The list of clients to use
  333. */
  334. public void setClients(final Collection<ChannelClientInfo> clients) {
  335. window.updateNames(clients);
  336. tabCompleter.clear(TabCompletionType.CHANNEL_NICK);
  337. for (ChannelClientInfo client : clients) {
  338. tabCompleter.addEntry(TabCompletionType.CHANNEL_NICK, client.getClient().getNickname());
  339. }
  340. }
  341. /**
  342. * Renames a client that is in this channel.
  343. *
  344. * @param oldName The old nickname of the client
  345. * @param newName The new nickname of the client
  346. */
  347. public void renameClient(final String oldName, final String newName) {
  348. tabCompleter.removeEntry(TabCompletionType.CHANNEL_NICK, oldName);
  349. tabCompleter.addEntry(TabCompletionType.CHANNEL_NICK, newName);
  350. refreshClients();
  351. }
  352. /**
  353. * Refreshes the list of clients stored by this channel. Should be called
  354. * when (visible) user modes or nicknames change.
  355. */
  356. public void refreshClients() {
  357. if (window != null && onChannel) {
  358. window.updateNames();
  359. }
  360. }
  361. /**
  362. * Returns a string containing the most important mode for the specified
  363. * client.
  364. *
  365. * @param channelClient The channel client to check.
  366. * @return A string containing the most important mode, or an empty string
  367. * if there are no (known) modes.
  368. */
  369. private String getModes(final ChannelClientInfo channelClient) {
  370. if (channelClient == null || !showModePrefix) {
  371. return "";
  372. } else {
  373. return channelClient.getImportantModePrefix();
  374. }
  375. }
  376. /**
  377. * Adds the specified topic to this channel's topic list.
  378. *
  379. * @param topic The topic to be added.
  380. */
  381. public void addTopic(final Topic topic) {
  382. topics.add(topic);
  383. updateTitle();
  384. }
  385. /**
  386. * Retrieve the topics that have been seen on this channel.
  387. *
  388. * @return A list of topics that have been seen on this channel, including
  389. * the current one.
  390. */
  391. public List<Topic> getTopics() {
  392. return topics.getList();
  393. }
  394. /**
  395. * Returns the current topic for this channel.
  396. *
  397. * @return Current channel topic
  398. */
  399. public Topic getCurrentTopic() {
  400. if (channelInfo.getTopic().isEmpty()) {
  401. return null;
  402. }
  403. return new Topic(channelInfo.getTopic(), channelInfo.getTopicSetter(),
  404. channelInfo.getTopicTime());
  405. }
  406. /** {@inheritDoc} */
  407. @Override
  408. public void configChanged(final String domain, final String key) {
  409. if ("sendwho".equals(key)) {
  410. sendWho = getConfigManager().getOptionBool("channel", "sendwho");
  411. } else if ("showmodeprefix".equals(key)) {
  412. showModePrefix = getConfigManager().getOptionBool("channel", "showmodeprefix");
  413. } else if ("shownickcoloursintext".equals(key)) {
  414. showColours = getConfigManager().getOptionBool("ui", "shownickcoloursintext");
  415. }
  416. }
  417. /**
  418. * Returns a string[] containing the nickname/ident/host of a channel
  419. * client.
  420. *
  421. * @param client The channel client to check
  422. * @param showColours Whether or not to show colours
  423. * @return A string[] containing displayable components
  424. */
  425. private String[] getDetails(final ChannelClientInfo client,
  426. final boolean showColours) {
  427. if (client == null) {
  428. // WTF?
  429. throw new UnsupportedOperationException("getDetails called with"
  430. + " null ChannelClientInfo");
  431. }
  432. final String[] res = new String[4];
  433. res[0] = getModes(client);
  434. res[1] = Styliser.CODE_NICKNAME + client.getClient().getNickname() + Styliser.CODE_NICKNAME;
  435. res[2] = client.getClient().getUsername();
  436. res[3] = client.getClient().getHostname();
  437. if (showColours) {
  438. final Map map = client.getMap();
  439. String prefix = null;
  440. Color colour;
  441. if (map.containsKey(ChannelClientProperty.TEXT_FOREGROUND)) {
  442. colour = (Color) map.get(ChannelClientProperty.TEXT_FOREGROUND);
  443. prefix = Styliser.CODE_HEXCOLOUR + ColourManager.getHex(colour);
  444. if (map.containsKey(ChannelClientProperty.TEXT_BACKGROUND)) {
  445. colour = (Color) map.get(ChannelClientProperty.TEXT_BACKGROUND);
  446. prefix = "," + ColourManager.getHex(colour);
  447. }
  448. }
  449. if (prefix != null) {
  450. res[1] = prefix + res[1] + Styliser.CODE_HEXCOLOUR;
  451. }
  452. }
  453. return res;
  454. }
  455. /** {@inheritDoc} */
  456. @Override
  457. protected boolean processNotificationArg(final Object arg, final List<Object> args) {
  458. if (arg instanceof ClientInfo) {
  459. // Format ClientInfos
  460. final ClientInfo clientInfo = (ClientInfo) arg;
  461. args.add(clientInfo.getNickname());
  462. args.add(clientInfo.getUsername());
  463. args.add(clientInfo.getHostname());
  464. return true;
  465. } else if (arg instanceof ChannelClientInfo) {
  466. // Format ChannelClientInfos
  467. final ChannelClientInfo clientInfo = (ChannelClientInfo) arg;
  468. args.addAll(Arrays.asList(getDetails(clientInfo, showColours)));
  469. return true;
  470. } else if (arg instanceof Topic) {
  471. // Format topics
  472. args.add("");
  473. args.addAll(Arrays.asList(server.getParser().parseHostmask(((Topic) arg).getClient())));
  474. args.add(((Topic) arg).getTopic());
  475. args.add(((Topic) arg).getTime() * 1000);
  476. return true;
  477. } else {
  478. // Everything else - default formatting
  479. return super.processNotificationArg(arg, args);
  480. }
  481. }
  482. /** {@inheritDoc} */
  483. @Override
  484. protected void modifyNotificationArgs(final List<Object> actionArgs,
  485. final List<Object> messageArgs) {
  486. messageArgs.add(channelInfo.getName());
  487. }
  488. // ------------------------------------------ PARSER METHOD DELEGATION -----
  489. /**
  490. * Attempts to set the topic of this channel.
  491. *
  492. * @param topic The new topic to be used. An empty string will clear the
  493. * current topic
  494. */
  495. public void setTopic(final String topic) {
  496. channelInfo.setTopic(topic);
  497. }
  498. }