Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

Channel.java 21KB

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