/* This file is part of leafdigital leafChat. leafChat is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. leafChat is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with leafChat. If not, see <http://www.gnu.org/licenses/>. Copyright 2011 Samuel Marshall. */ package com.leafdigital.ircui; import java.util.*; import com.leafdigital.idle.api.Idle; import com.leafdigital.irc.api.*; import com.leafdigital.logs.api.Logger; import com.leafdigital.notification.api.Notification; import com.leafdigital.ui.api.*; import leafchat.core.api.*; /** Message window */ @UIHandler("msgwindow") public class MsgWindow extends ServerChatWindow { private String nick; private IRCUserAddress lastAddress; private boolean maskAdded; /** List of existing message windows */ private static List<MsgWindow> lExisting = new LinkedList<MsgWindow>(); /** * Finds existing message window, if there is one. * @param s Server * @param sNick Nickname * @return Window or null if none */ static MsgWindow find(Server s, String sNick) { for(MsgWindow mw : lExisting) { if(mw.getServer()==s && mw.nick==sNick) { return mw; } } return null; } private boolean firstMessage = false; /** * @param pc Plugin context for messages etc * @param uim IRC message that we're responding to * @param bVisible True if window should appear, false to minimise * @throws GeneralException */ public MsgWindow(PluginContext pc, UserIRCMsg uim, boolean bVisible) throws GeneralException { this(pc, uim.getServer(), uim.getSourceUser().getNick(), bVisible, true); // Dispatch msg to appropriate msg() method firstMessage = true; pc.dispatchMsgToTarget(uim, this); lExisting.add(this); } @Override protected void reportActualMessage(String title, String text) { if(firstMessage) { getPluginContext().getSingle(Notification.class).notify( IRCUIPlugin.NOTIFICATION_NEWWINDOW, title, text + "\n\n(New window opened)"); firstMessage = false; } else { super.reportActualMessage(title, text); } } @Override @UIAction public void windowClosed() throws GeneralException { // Remove watchlist if(isConnected() && maskAdded) { getPluginContext().getSingle(WatchList.class). removeTemporaryMask(getServer(), new IRCUserAddress(nick, true)); maskAdded = false; } super.windowClosed(); lExisting.remove(this); } /** * Set once we know what the user's status is. Once status is known, we * start displaying messages about it. */ private boolean statusKnown=false; @Override public String toString() { String server = getServer()==null ? "unknown server" : getServer().getReportedOrConnectedHost(); return "[MsgWindow: " + nick + " on " + server + "]"; } /** * @param pc Context * @param s Server * @param nick Nickname * @param visible True if window should be visible * @throws GeneralException */ public MsgWindow(PluginContext pc, Server s, String nick, boolean visible) throws GeneralException { this(pc, s, nick, visible, false); } private int requestUser, requestQuit, requestNick; private void requestMessages(boolean removeOld) { if(removeOld) { getPluginContext().unrequestMessages(UserIRCMsg.class, this, requestUser); getPluginContext().unrequestMessages(QuitIRCMsg.class, this, requestQuit); getPluginContext().unrequestMessages(NickIRCMsg.class, this, requestNick); } NickAndServerFilter usf = new NickAndServerFilter(getServer(), nick); requestUser = getPluginContext().requestMessages(UserIRCMsg.class, this, usf, Msg.PRIORITY_NORMAL); requestQuit = getPluginContext().requestMessages(QuitIRCMsg.class, this, usf, Msg.PRIORITY_NORMAL); requestNick = getPluginContext().requestMessages(NickIRCMsg.class, this, usf, Msg.PRIORITY_NORMAL); } @Override protected void initServerInner(Server s, boolean firstTime) { if(s != null) { requestMessages(!firstTime); if(!firstTime) // Can't do it here first time 'cause nick is not set { getPluginContext().getSingle(WatchList.class). addTemporaryMask(s, new IRCUserAddress(nick, true)); maskAdded = true; } } } /** * @param pc Context * @param s Server * @param nick Nick * @param visible Visible * @param definitelyOnline True if they're definitely online (b/c we just * received a message) * @throws GeneralException */ public MsgWindow(PluginContext pc, Server s, String nick, boolean visible, boolean definitelyOnline) throws GeneralException { super(pc, s, "msgwindow", false, visible); this.nick = nick; setTitle(); // Start listening initServerInner(s, true); pc.requestMessages(WatchMsg.class, this, null, Msg.PRIORITY_EARLY); // Watch this user WatchList wl = pc.getSingle(WatchList.class); wl.addTemporaryMask(s, new IRCUserAddress(nick, true)); maskAdded = true; if(!(definitelyOnline || (!wl.isKnown(s, nick) || wl.isOnline(s, nick)))) { addLine(EVENTSYMBOL+"<nick>"+esc(nick)+"</nick> is <key>not online</key>"); } statusKnown=definitelyOnline || wl.isKnown(s, nick); // Show window getWindow().setRemember("message", nick); getCommandEdit().setRemember("message", nick); ((IRCUIPlugin)getPluginContext().getPlugin()).informShown(this); getWindow().show(!visible); } /** * Watch message: user logged on/off. * @param wm Messge */ public void msg(WatchMsg wm) { if(!wm.getNick().equalsIgnoreCase(nick) || !(wm.getServer()==getServer())) { return; } boolean on = wm instanceof OnWatchMsg; EditBox command = (EditBox)getWindow().getWidget("command"); if(on && !command.isEnabled()) { if(statusKnown) { addLine(EVENTSYMBOL + "<nick>" + esc(nick) + "</nick> logged <key>on</key>"); } statusKnown=true; } else if(!on && command.isEnabled()) { if(statusKnown) { addLine(EVENTSYMBOL + "<nick>" + esc(nick) + "</nick> logged <key>off</key>"); } else { addLine(EVENTSYMBOL + "<nick>" + esc(nick) + "</nick> is <key>not online</key>"); } statusKnown=true; } wm.markHandled(); } @Override protected void setTitle() { getWindow().setTitle(nick + " (" + getServer().getCurrentShortName() + ")"); } @Override protected void doCommand(Commands c, String line) throws GeneralException { getPluginContext().getSingle(Idle.class).userAwake( line.equals("/away") ? Idle.AWAKE_UNAWAY : Idle.AWAKE_COMMAND); IRCUserAddress contextUser; if(lastAddress != null && lastAddress.getNick().equals(nick)) { contextUser = lastAddress; } else { contextUser = new IRCUserAddress(nick, false); } c.doCommand(line, getServer(), contextUser, null, this, true); } /** * Server message: standard message. * @param m Message * @throws GeneralException */ public void msg(UserMessageIRCMsg m) throws GeneralException { if(m.isHandled()) { return; } IRCUserAddress ua = m.getSourceUser(); lastAddress = ua; addLine("<<nick>" + esc(ua.getNick()) + "</nick>> " + esc(m.convertEncoding(m.getText())), "msg"); reportActualMessage(nick, m.convertEncoding(m.getText())); m.markHandled(); } /** * Server message: action. * @param m Message * @throws GeneralException */ public void msg(UserActionIRCMsg m) throws GeneralException { if(m.isHandled()) { return; } IRCUserAddress ua = m.getSourceUser(); lastAddress = ua; addLine(ACTIONSYMBOL + "<nick>" + esc(ua.getNick()) + "</nick> " + esc(m.convertEncoding(m.getText())), "action"); reportActualMessage(nick, ACTIONSYMBOL + ua.getNick() + " " + m.convertEncoding(m.getText())); m.markHandled(); } @Override public void msg(NickIRCMsg m) throws GeneralException { WatchList wl = getPluginContext().getSingle(WatchList.class); if(maskAdded) { wl.removeTemporaryMask(getServer(), new IRCUserAddress(nick, true)); maskAdded = false; } nick = m.getNewNick(); wl.addTemporaryMask(getServer(), new IRCUserAddress(nick, true)); maskAdded = true; getWindow().setTitle(nick + " (" + getServer() + ")"); getWindow().setRemember("message", nick); getCommandEdit().setRemember("message", nick); requestMessages(true); super.msg(m); } /** * Server message: other user message (shouldn't be called) * @param m Message */ public void msg(UserIRCMsg m) { // Called for types we don't handle specifically, like UserNotice and CTCP // which both ought to go to current window } @Override protected boolean isUs(String sTarget) { return sTarget.equalsIgnoreCase(nick); } @Override protected int getAvailableBytes() throws GeneralException { if(nick==null) { return 400; } // RFC limit is 512 per line including CRLF. The system will send to other users: // :nick!user@host PRIVMSG <target> :<message><CRLF> // So there are 499 characters available for the prefix+nick+message // Nicks etc. are always converted using ISO one byte per character. return 499 -nick.length() -getServer().getApproxPrefixLength(); } @Override protected String getLogCategory() { return Logger.CATEGORY_USER; } @Override protected String getLogItem() { return nick; } @Override public void fillTabCompletionList(TabCompletionList options) { options.add(nick, true); } @Override protected boolean displayTimeStamps() { return true; } @Override protected String getContextNick() { return nick; } }