123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586 |
- /*
- * Copyright (c) 2006-2014 DMDirc Developers
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
- package com.dmdirc;
-
- import com.dmdirc.commandparser.CommandType;
- import com.dmdirc.commandparser.parsers.ChannelCommandParser;
- import com.dmdirc.events.ChannelActionEvent;
- import com.dmdirc.events.ChannelClosedEvent;
- import com.dmdirc.events.DisplayableEvent;
- import com.dmdirc.events.EventUtils;
- import com.dmdirc.interfaces.CommandController;
- import com.dmdirc.interfaces.Connection;
- import com.dmdirc.interfaces.GroupChat;
- import com.dmdirc.interfaces.NicklistListener;
- import com.dmdirc.interfaces.TopicChangeListener;
- import com.dmdirc.interfaces.config.ConfigChangeListener;
- import com.dmdirc.interfaces.config.ConfigProviderMigrator;
- import com.dmdirc.messages.MessageSinkManager;
- import com.dmdirc.parser.interfaces.ChannelClientInfo;
- import com.dmdirc.parser.interfaces.ChannelInfo;
- import com.dmdirc.parser.interfaces.ClientInfo;
- import com.dmdirc.ui.Colour;
- import com.dmdirc.ui.core.components.WindowComponent;
- import com.dmdirc.ui.input.TabCompleterFactory;
- import com.dmdirc.ui.input.TabCompletionType;
- import com.dmdirc.ui.messages.ColourManager;
- import com.dmdirc.ui.messages.Styliser;
- import com.dmdirc.util.URLBuilder;
- import com.dmdirc.util.annotations.factory.Factory;
- import com.dmdirc.util.annotations.factory.Unbound;
- import com.dmdirc.util.collections.ListenerList;
- import com.dmdirc.util.collections.RollingList;
-
- import com.google.common.base.Optional;
- import com.google.common.eventbus.EventBus;
-
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.List;
- import java.util.Map;
-
- import javax.annotation.Nonnull;
-
- /**
- * The Channel class represents the client's view of the channel. It handles callbacks for channel
- * events from the parser, maintains the corresponding ChannelWindow, and handles user input for the
- * channel.
- */
- @Factory(inject = true, providers = true, singleton = true)
- public class Channel extends MessageTarget implements ConfigChangeListener, GroupChat {
-
- /** List of registered listeners. */
- private final ListenerList listenerList = new ListenerList();
- /** The parser's pChannel class. */
- private ChannelInfo channelInfo;
- /** The server this channel is on. */
- private final Server server;
- /** A list of previous topics we've seen. */
- private final RollingList<Topic> topics;
- /** Our event handler. */
- private final ChannelEventHandler eventHandler;
- /** The migrator to use to migrate our config provider. */
- private final ConfigProviderMigrator configMigrator;
- /** Whether we're in this channel or not. */
- private boolean isOnChannel;
- /** Whether we should send WHO requests for this channel. */
- private volatile boolean sendWho;
- /** Whether we should show mode prefixes in text. */
- private volatile boolean showModePrefix;
- /** Whether we should show colours in nicks. */
- private volatile boolean showColours;
-
- /**
- * Creates a new instance of Channel.
- *
- * @param newServer The server object that this channel belongs to
- * @param newChannelInfo The parser's channel object that corresponds to this channel
- * @param configMigrator The config migrator which provides the config for this channel.
- * @param tabCompleterFactory The factory to use to create tab completers.
- * @param commandController The controller to load commands from.
- * @param messageSinkManager The sink manager to use to despatch messages.
- * @param urlBuilder The URL builder to use when finding icons.
- * @param eventBus The bus to despatch events onto.
- */
- public Channel(
- @Unbound final Server newServer,
- @Unbound final ChannelInfo newChannelInfo,
- @Unbound final ConfigProviderMigrator configMigrator,
- final TabCompleterFactory tabCompleterFactory,
- final CommandController commandController,
- final MessageSinkManager messageSinkManager,
- final URLBuilder urlBuilder,
- final EventBus eventBus) {
- super(newServer, "channel-inactive", newChannelInfo.getName(),
- Styliser.stipControlCodes(newChannelInfo.getName()),
- configMigrator.getConfigProvider(),
- new ChannelCommandParser(newServer, commandController),
- tabCompleterFactory.getTabCompleter(newServer.getTabCompleter(),
- configMigrator.getConfigProvider(), CommandType.TYPE_CHANNEL,
- CommandType.TYPE_CHAT),
- messageSinkManager,
- urlBuilder,
- newServer.getEventBus(),
- Arrays.asList(WindowComponent.TEXTAREA.getIdentifier(),
- WindowComponent.INPUTFIELD.getIdentifier(),
- WindowComponent.TOPICBAR.getIdentifier(),
- WindowComponent.USERLIST.getIdentifier()));
-
- this.configMigrator = configMigrator;
- this.channelInfo = newChannelInfo;
- this.server = newServer;
-
- getConfigManager().addChangeListener("channel", this);
- getConfigManager().addChangeListener("ui", "shownickcoloursintext", this);
-
- topics = new RollingList<>(getConfigManager().getOptionInt("channel",
- "topichistorysize"));
-
- sendWho = getConfigManager().getOptionBool("channel", "sendwho");
- showModePrefix = getConfigManager().getOptionBool("channel", "showmodeprefix");
- showColours = getConfigManager().getOptionBool("ui", "shownickcoloursintext");
-
- eventHandler = new ChannelEventHandler(this, getEventBus());
-
- registerCallbacks();
-
- updateTitle();
- selfJoin();
- }
-
- public ChannelInfo getChannelInfo() {
- return channelInfo;
- }
-
- @Override
- public boolean isOnChannel() {
- return isOnChannel;
- }
-
- /**
- * Registers callbacks with the parser for this channel.
- */
- private void registerCallbacks() {
- eventHandler.registerCallbacks();
- configMigrator.migrate(server.getProtocol(), server.getIrcd(),
- server.getNetwork(), server.getAddress(), channelInfo.getName());
- }
-
- @Override
- public void sendLine(final String line) {
- if (server.getState() != ServerState.CONNECTED
- || server.getParser().getChannel(channelInfo.getName()) == null) {
- // We're not in the channel/connected to the server
- return;
- }
-
- final ClientInfo me = server.getParser().getLocalClient();
- final String[] details = getDetails(channelInfo.getChannelClient(me));
-
- for (String part : splitLine(line)) {
- if (!part.isEmpty()) {
- final DisplayableEvent event = new ChannelActionEvent(this,
- channelInfo.getChannelClient(me), part);
- final String format = EventUtils.postDisplayable(getEventBus(), event,
- "channelSelfMessage");
- addLine(format, details[0], details[1], details[2], details[3], part, channelInfo);
-
- channelInfo.sendMessage(part);
- }
- }
- }
-
- @Override
- public int getMaxLineLength() {
- return server.getState() == ServerState.CONNECTED
- ? server.getParser().getMaxLength("PRIVMSG", getChannelInfo().getName())
- : -1;
- }
-
- @Override
- public void sendAction(final String action) {
- if (server.getState() != ServerState.CONNECTED
- || server.getParser().getChannel(channelInfo.getName()) == null) {
- // We're not on the server/channel
- return;
- }
-
- final ClientInfo me = server.getParser().getLocalClient();
- final String[] details = getDetails(channelInfo.getChannelClient(me));
-
- if (server.getParser().getMaxLength("PRIVMSG", getChannelInfo().getName())
- <= action.length()) {
- addLine("actionTooLong", action.length());
- } else {
- final DisplayableEvent event = new ChannelActionEvent(this,
- channelInfo.getChannelClient(me), action);
- final String format = EventUtils.postDisplayable(getEventBus(), event,
- "channelSelfAction");
- addLine(format, details[0], details[1], details[2], details[3], action, channelInfo);
-
- channelInfo.sendAction(action);
- }
- }
-
- /**
- * Sets this object's ChannelInfo reference to the one supplied. This only needs to be done if
- * the channel window (and hence this channel object) has stayed open while the user has been
- * out of the channel.
- *
- * @param newChannelInfo The new ChannelInfo object
- */
- public void setChannelInfo(final ChannelInfo newChannelInfo) {
- channelInfo = newChannelInfo;
- registerCallbacks();
- }
-
- /**
- * Called when we join this channel. Just needs to output a message.
- */
- public void selfJoin() {
- isOnChannel = true;
-
- final ClientInfo me = server.getParser().getLocalClient();
- addLine("channelSelfJoin", "", me.getNickname(), me.getUsername(),
- me.getHostname(), channelInfo.getName());
-
- checkWho();
- setIcon("channel");
-
- server.removeInvites(channelInfo.getName());
- }
-
- /**
- * Updates the title of the channel window, and of the main window if appropriate.
- */
- private void updateTitle() {
- String temp = Styliser.stipControlCodes(channelInfo.getName());
-
- if (!channelInfo.getTopic().isEmpty()) {
- temp += " - " + Styliser.stipControlCodes(channelInfo.getTopic());
- }
-
- setTitle(temp);
- }
-
- @Override
- public void join() {
- server.getParser().joinChannel(channelInfo.getName());
- }
-
- @Override
- public void part(final String reason) {
- channelInfo.part(reason);
-
- resetWindow();
- }
-
- @Override
- public void retrieveListModes() {
- channelInfo.requestListModes();
- }
-
- /**
- * Resets the window state after the client has left a channel.
- */
- public void resetWindow() {
- isOnChannel = false;
-
- setIcon("channel-inactive");
-
- listenerList.getCallable(NicklistListener.class)
- .clientListUpdated(Collections.<ChannelClientInfo>emptyList());
- }
-
- @Override
- public void close() {
- super.close();
-
- // Remove any callbacks or listeners
- eventHandler.unregisterCallbacks();
-
- if (server.getParser() != null) {
- server.getParser().getCallbackManager().delAllCallback(eventHandler);
- }
-
- // Trigger any actions neccessary
- if (isOnChannel) {
- part(getConfigManager().getOption("general", "partmessage"));
- }
-
- // Trigger action for the window closing
- getEventBus().post(new ChannelClosedEvent(this));
-
- // Inform any parents that the window is closing
- server.delChannel(channelInfo.getName());
- }
-
- /**
- * Called every {general.whotime} seconds to check if the channel needs to send a who request.
- */
- public void checkWho() {
- if (isOnChannel && sendWho) {
- channelInfo.sendWho();
- }
- }
-
- /**
- * Adds a ChannelClient to this Channel.
- *
- * @param client The client to be added
- */
- public void addClient(final ChannelClientInfo client) {
- listenerList.getCallable(NicklistListener.class).clientAdded(client);
-
- getTabCompleter().addEntry(TabCompletionType.CHANNEL_NICK,
- client.getClient().getNickname());
- }
-
- /**
- * Removes the specified ChannelClient from this channel.
- *
- * @param client The client to be removed
- */
- public void removeClient(final ChannelClientInfo client) {
- listenerList.getCallable(NicklistListener.class).clientRemoved(client);
-
- getTabCompleter().removeEntry(TabCompletionType.CHANNEL_NICK,
- client.getClient().getNickname());
-
- if (client.getClient().equals(server.getParser().getLocalClient())) {
- resetWindow();
- }
- }
-
- /**
- * Replaces the list of known clients on this channel with the specified one.
- *
- * @param clients The list of clients to use
- */
- public void setClients(final Collection<ChannelClientInfo> clients) {
- listenerList.getCallable(NicklistListener.class).clientListUpdated(clients);
-
- getTabCompleter().clear(TabCompletionType.CHANNEL_NICK);
-
- for (ChannelClientInfo client : clients) {
- getTabCompleter().addEntry(TabCompletionType.CHANNEL_NICK,
- client.getClient().getNickname());
- }
- }
-
- /**
- * Renames a client that is in this channel.
- *
- * @param oldName The old nickname of the client
- * @param newName The new nickname of the client
- */
- public void renameClient(final String oldName, final String newName) {
- getTabCompleter().removeEntry(TabCompletionType.CHANNEL_NICK, oldName);
- getTabCompleter().addEntry(TabCompletionType.CHANNEL_NICK, newName);
- refreshClients();
- }
-
- /**
- * Refreshes the list of clients stored by this channel. Should be called when (visible) user
- * modes or nicknames change.
- */
- public void refreshClients() {
- if (!isOnChannel) {
- return;
- }
-
- listenerList.getCallable(NicklistListener.class).clientListUpdated();
- }
-
- /**
- * Returns a string containing the most important mode for the specified client.
- *
- * @param channelClient The channel client to check.
- *
- * @return A string containing the most important mode, or an empty string if there are no
- * (known) modes.
- */
- private String getModes(final ChannelClientInfo channelClient) {
- if (channelClient == null || !showModePrefix) {
- return "";
- } else {
- return channelClient.getImportantModePrefix();
- }
- }
-
- @Override
- public void configChanged(final String domain, final String key) {
- switch (key) {
- case "sendwho":
- sendWho = getConfigManager().getOptionBool("channel", "sendwho");
- break;
- case "showmodeprefix":
- showModePrefix = getConfigManager().getOptionBool("channel", "showmodeprefix");
- break;
- case "shownickcoloursintext":
- showColours = getConfigManager().getOptionBool("ui", "shownickcoloursintext");
- break;
- }
- }
-
- /**
- * Returns a string[] containing the nickname/ident/host of a channel client.
- *
- * @param client The channel client to check
- *
- * @return A string[] containing displayable components
- */
- private String[] getDetails(final ChannelClientInfo client) {
- if (client == null) {
- // WTF?
- throw new UnsupportedOperationException("getDetails called with"
- + " null ChannelClientInfo");
- }
-
- final String[] res = new String[]{
- getModes(client),
- Styliser.CODE_NICKNAME + client.getClient().getNickname() + Styliser.CODE_NICKNAME,
- client.getClient().getUsername(),
- client.getClient().getHostname(),};
-
- if (showColours) {
- final Map<?, ?> map = client.getMap();
-
- if (map.containsKey(ChannelClientProperty.TEXT_FOREGROUND)) {
- String prefix;
-
- if (map.containsKey(ChannelClientProperty.TEXT_BACKGROUND)) {
- prefix = "," + ColourManager.getHex((Colour) map.get(
- ChannelClientProperty.TEXT_BACKGROUND));
- } else {
- prefix = Styliser.CODE_HEXCOLOUR + ColourManager.getHex((Colour) map.get(
- ChannelClientProperty.TEXT_FOREGROUND));
- }
-
- res[1] = prefix + res[1] + Styliser.CODE_HEXCOLOUR;
- }
- }
-
- return res;
- }
-
- @Override
- protected boolean processNotificationArg(final Object arg, final List<Object> args) {
- if (arg instanceof ClientInfo) {
- // Format ClientInfos
-
- final ClientInfo clientInfo = (ClientInfo) arg;
- args.add(clientInfo.getNickname());
- args.add(clientInfo.getUsername());
- args.add(clientInfo.getHostname());
-
- return true;
- } else if (arg instanceof ChannelClientInfo) {
- // Format ChannelClientInfos
-
- final ChannelClientInfo clientInfo = (ChannelClientInfo) arg;
- args.addAll(Arrays.asList(getDetails(clientInfo)));
-
- return true;
- } else if (arg instanceof Topic) {
- // Format topics
-
- args.add("");
- args.addAll(Arrays.asList(server.parseHostmask(((Topic) arg).getClient())));
- args.add(((Topic) arg).getTopic());
- args.add(((Topic) arg).getTime() * 1000);
-
- return true;
- } else {
- // Everything else - default formatting
-
- return super.processNotificationArg(arg, args);
- }
- }
-
- @Override
- protected void modifyNotificationArgs(final List<Object> actionArgs,
- final List<Object> messageArgs) {
- messageArgs.add(channelInfo.getName());
- }
-
- // ---------------------------------------------------- TOPIC HANDLING -----
- /**
- * Adds the specified topic to this channel's topic list.
- *
- * @param topic The topic to be added.
- */
- public void addTopic(final Topic topic) {
- synchronized (topics) {
- topics.add(topic);
- }
- updateTitle();
-
- new Thread(new Runnable() {
-
- @Override
- public void run() {
- listenerList.getCallable(TopicChangeListener.class)
- .topicChanged(Channel.this, topic);
- }
- }, "Topic change listener runner").start();
- }
-
- @Override
- public List<Topic> getTopics() {
- synchronized (topics) {
- return new ArrayList<>(topics.getList());
- }
- }
-
- @Override
- public Optional<Topic> getCurrentTopic() {
- synchronized (topics) {
- if (topics.getList().isEmpty()) {
- return Optional.absent();
- } else {
- return Optional.of(topics.get(topics.getList().size() - 1));
- }
- }
- }
-
- // ------------------------------------------ PARSER METHOD DELEGATION -----
- @Override
- public void setTopic(final String topic) {
- channelInfo.setTopic(topic);
- }
-
- @Override
- public int getMaxTopicLength() {
- return server.getParser().getMaxTopicLength();
- }
-
- @Override
- public void addNicklistListener(final NicklistListener listener) {
- listenerList.add(NicklistListener.class, listener);
- }
-
- @Override
- public void removeNicklistListener(final NicklistListener listener) {
- listenerList.remove(NicklistListener.class, listener);
- }
-
- @Override
- public void addTopicChangeListener(final TopicChangeListener listener) {
- listenerList.add(TopicChangeListener.class, listener);
- }
-
- @Override
- public void removeTopicChangeListener(final TopicChangeListener listener) {
- listenerList.remove(TopicChangeListener.class, listener);
- }
-
- @Override
- @Nonnull
- public Connection getConnection() {
- return server;
- }
-
- }
|