Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

Channel.java 19KB

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