您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

Channel.java 21KB

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