/* ircchannelcontact.cpp - IRC Channel Contact Copyright (c) 2002 by Nick Betcher Kopete (c) 2002-2004 by the Kopete developers ************************************************************************* * * * This program 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 2 of the License, or * * (at your option) any later version. * * * ************************************************************************* */ #include "irccontactmanager.h" #include "ircchannelcontact.h" #include "ircusercontact.h" #include "ircservercontact.h" #include "ircaccount.h" #include "ircprotocol.h" #include "kopeteview.h" #include "kopeteuiglobal.h" #include "kcodecaction.h" #include "kopetemetacontact.h" #include "kopetestdaction.h" #include "kopetechatsessionmanager.h" #include #include #include #include #include #include #include #include //This is the number of nicknames we will process concurrently when joining a channel //Lower numbers ensure less GUI blocking, but take marginally longer to complete. //Higher numbers are absolute fastest, but block GUI until all members are added #define NICK_BATCH_LENGTH 1 IRCChannelContact::IRCChannelContact(IRCContactManager *contactManager, const TQString &channel, Kopete::MetaContact *metac) : IRCContact(contactManager, channel, metac, "irc_channel") { KIRC::Engine *engine = kircEngine(); mInfoTimer = new TQTimer( this ); TQObject::connect(mInfoTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT( slotUpdateInfo() ) ); TQObject::connect(engine, TQT_SIGNAL(incomingUserIsAway(const TQString &, const TQString &)), this, TQT_SLOT(slotIncomingUserIsAway(const TQString &, const TQString &))); TQObject::connect(engine, TQT_SIGNAL(incomingListedChan(const TQString &, uint, const TQString &)), this, TQT_SLOT(slotChannelListed(const TQString &, uint, const TQString &))); actionJoin = 0L; actionModeT = new TDEToggleAction(i18n("Only Operators Can Change &Topic"), 0, this, TQT_SLOT(slotModeChanged()), this ); actionModeN = new TDEToggleAction(i18n("&No Outside Messages"), 0, this, TQT_SLOT(slotModeChanged()), this ); actionModeS = new TDEToggleAction(i18n("&Secret"), 0, this, TQT_SLOT(slotModeChanged()), this ); actionModeM = new TDEToggleAction(i18n("&Moderated"), 0, this, TQT_SLOT(slotModeChanged()), this ); actionModeI = new TDEToggleAction(i18n("&Invite Only"), 0, this, TQT_SLOT(slotModeChanged()), this ); actionHomePage = 0L; updateStatus(); } IRCChannelContact::~IRCChannelContact() { } void IRCChannelContact::slotUpdateInfo() { /** This woudl be nice, but it generates server errors too often if( !manager(Kopete::Contact::CannotCreate) && onlineStatus() == m_protocol->m_ChannelStatusOnline ) kircEngine()->writeMessage( TQString::fromLatin1("LIST %1").arg(m_nickName) ); else setProperty( TQString::fromLatin1("channelMembers"), i18n("Members"), manager()->members().count() ); */ KIRC::Engine *engine = kircEngine(); if (manager(Kopete::Contact::CannotCreate)) { setProperty(m_protocol->propChannelMembers, manager()->members().count()); engine->writeMessage(TQString::fromLatin1("WHO %1").arg(m_nickName)); } else { removeProperty(m_protocol->propChannelMembers); removeProperty(m_protocol->propChannelTopic); } mInfoTimer->start( 45000, true ); } void IRCChannelContact::slotChannelListed( const TQString &channel, uint members, const TQString &topic ) { if (!manager(Kopete::Contact::CannotCreate) && onlineStatus() == m_protocol->m_ChannelStatusOnline && channel.lower() == m_nickName.lower()) { mTopic = topic; setProperty(m_protocol->propChannelMembers, members); setProperty(m_protocol->propChannelTopic, topic); } } void IRCChannelContact::toggleOperatorActions(bool enabled) { if (enabled) { actionTopic->setEnabled(true); } else if (modeEnabled('t')) { actionTopic->setEnabled(false); } actionModeT->setEnabled(enabled); actionModeN->setEnabled(enabled); actionModeS->setEnabled(enabled); actionModeM->setEnabled(enabled); actionModeI->setEnabled(enabled); } void IRCChannelContact::slotOnlineStatusChanged(Kopete::Contact *c, const Kopete::OnlineStatus &status, const Kopete::OnlineStatus &oldStatus) { Q_UNUSED(oldStatus); if (c == account()->myself()) { if (status.internalStatus() & IRCProtocol::Operator) { kdDebug(14120) << k_funcinfo << "WE NOW HAVE OP STATUS" << endl; toggleOperatorActions(true); } else { kdDebug(14120) << k_funcinfo << "WE NOW dont HAVE OP STATUS" << endl; toggleOperatorActions(false); } } } void IRCChannelContact::updateStatus() { KIRC::Engine::Status status = kircEngine()->status(); switch (status) { case KIRC::Engine::Idle: case KIRC::Engine::Connecting: case KIRC::Engine::Authentifying: setOnlineStatus(m_protocol->m_ChannelStatusOffline); break; case KIRC::Engine::Connected: case KIRC::Engine::Closing: setOnlineStatus(m_protocol->m_ChannelStatusOnline); break; default: setOnlineStatus(m_protocol->m_StatusUnknown); } } void IRCChannelContact::chatSessionDestroyed() { if (manager(Kopete::Contact::CannotCreate)) { part(); Kopete::ContactPtrList contacts = manager()->members(); // remove all the users on the channel for (Kopete::Contact *c = contacts.first(); c; c = contacts.next()) { if (c->metaContact()->isTemporary() && !static_cast(c)->isChatting(manager())) c->deleteLater(); } } IRCContact::chatSessionDestroyed(); } void IRCChannelContact::initConversation() { kircEngine()->join(m_nickName, password()); } void IRCChannelContact::slotConnectedToServer() { setOnlineStatus(m_protocol->m_ChannelStatusOnline); if (manager(Kopete::Contact::CannotCreate)) kircEngine()->join(m_nickName, password()); } void IRCChannelContact::namesList(const TQStringList &nicknames) { mInfoTimer->stop(); mJoinedNicks += nicknames; slotAddNicknames(); } void IRCChannelContact::endOfNames() { setMode(TQString()); slotUpdateInfo(); } void IRCChannelContact::slotAddNicknames() { if( !manager(Kopete::Contact::CannotCreate) || mJoinedNicks.isEmpty()) { return; } IRCAccount *account = ircAccount(); for( uint i = 0; !mJoinedNicks.isEmpty() && i < NICK_BATCH_LENGTH; ++i ) { // Pick a nick from the front of the list. TQString nickToAdd = mJoinedNicks.front(); TQChar firstChar = nickToAdd[0]; if( firstChar == '@' || firstChar == '%' || firstChar == '+' ) nickToAdd = nickToAdd.remove(0, 1); IRCUserContact *user; if ( nickToAdd.lower() != account->mySelf()->nickName().lower() ) { //kdDebug(14120) << k_funcinfo << m_nickName << " nick to add: " << nickToAdd << endl; user = account->contactManager()->findUser(nickToAdd); // If the user is already present in some channel, dont flip the status // back to online, because the other channels listen to // onlineStatusChanged() emits, and they would adjust their statuses. if (account->contactManager()->findChannelsByMember(user).isEmpty()) { //kdDebug(14120) << k_funcinfo << "Setting nick ONLINE" << endl; user->setOnlineStatus(m_protocol->m_UserStatusOnline); } } else { // Handling my nick in the list. user = account->mySelf(); } Kopete::OnlineStatus status; if ( firstChar == '@' || firstChar == '%' ) status = m_protocol->m_UserStatusOp; else if( firstChar == '+') status = m_protocol->m_UserStatusVoice; else status = user->onlineStatus(); if( user != account->mySelf() ) manager()->addContact(user , status, true); else manager()->setContactOnlineStatus(user, status); mJoinedNicks.pop_front(); } TQTimer::singleShot( 0, this, TQT_SLOT( slotAddNicknames() ) ); } void IRCChannelContact::channelTopic(const TQString &topic) { mTopic = topic; setProperty( m_protocol->propChannelTopic, mTopic ); manager()->setDisplayName(caption()); if (mTopic.isEmpty()) { Kopete::Message msg((Kopete::Contact*)this, mMyself, i18n("Topic for %1 is set empty.").arg(m_nickName), Kopete::Message::Internal, Kopete::Message::RichText, CHAT_VIEW); appendMessage(msg); } else { Kopete::Message msg((Kopete::Contact*)this, mMyself, i18n("Topic for %1 is %2").arg(m_nickName).arg(mTopic), Kopete::Message::Internal, Kopete::Message::RichText, CHAT_VIEW); appendMessage(msg); } } void IRCChannelContact::channelHomePage(const TQString &url) { kdDebug(14120) << k_funcinfo << endl; setProperty( m_protocol->propHomepage, url ); } void IRCChannelContact::join() { if (!manager(Kopete::Contact::CannotCreate) && onlineStatus().status() == Kopete::OnlineStatus::Online) { kdDebug() << k_funcinfo << "My nickname:" << m_nickName << endl; kdDebug() << k_funcinfo << "My manager:" << manager(Kopete::Contact::CannotCreate) << endl; if( manager(Kopete::Contact::CannotCreate) ) kdDebug() << k_funcinfo << "My view:" << manager(Kopete::Contact::CannotCreate)->view(false) << endl; startChat(); } if (manager()) { connect(manager(), TQT_SIGNAL(onlineStatusChanged(Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus &)), TQT_SLOT(slotOnlineStatusChanged(Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus &))); } } void IRCChannelContact::partAction() { if (manager()) manager()->view()->closeView(); } void IRCChannelContact::part() { if (manager() && !kircEngine()->isDisconnected()) kircEngine()->part(m_nickName, ircAccount()->defaultPart()); } void IRCChannelContact::slotIncomingUserIsAway( const TQString &nick, const TQString & ) { IRCAccount *account = ircAccount(); if( nick.lower() == account->mySelf()->nickName().lower() ) { IRCUserContact *c = account->mySelf(); if (manager() && manager()->members().contains(c)) { Kopete::OnlineStatus status = manager()->contactOnlineStatus(c); if (status == m_protocol->m_UserStatusOp) manager()->setContactOnlineStatus(c, m_protocol->m_UserStatusOpAway ); else if (status == m_protocol->m_UserStatusOpAway) manager()->setContactOnlineStatus(c, m_protocol->m_UserStatusOp); else if (status == m_protocol->m_UserStatusVoice) manager()->setContactOnlineStatus(c, m_protocol->m_UserStatusVoiceAway); else if (status == m_protocol->m_UserStatusVoiceAway) manager()->setContactOnlineStatus(c, m_protocol->m_UserStatusVoice); else if (status == m_protocol->m_UserStatusAway) manager()->setContactOnlineStatus(c, m_protocol->m_UserStatusOnline); else manager()->setContactOnlineStatus(c, m_protocol->m_UserStatusAway); } } } void IRCChannelContact::userJoinedChannel(const TQString &nickname) { IRCAccount *account = ircAccount(); if (nickname.lower() == account->mySelf()->nickName().lower()) { kdDebug() << k_funcinfo << "Me:" << this << endl; kdDebug() << k_funcinfo << "My nickname:" << m_nickName << endl; kdDebug() << k_funcinfo << "My manager:" << manager(Kopete::Contact::CannotCreate) << endl; if (manager(Kopete::Contact::CannotCreate)) kdDebug() << k_funcinfo << "My view:" << manager(Kopete::Contact::CannotCreate)->view(false) << endl; Kopete::Message msg((Kopete::Contact *)this, mMyself, i18n("You have joined channel %1").arg(m_nickName), Kopete::Message::Internal, Kopete::Message::PlainText, CHAT_VIEW); msg.setImportance( Kopete::Message::Low); //set the importance manualy to low appendMessage(msg); } else { // If we have lag or huge channels, we might receive a JOIN after we have left a channel. if (!manager()) return; IRCUserContact *contact = account->contactManager()->findUser( nickname ); contact->setOnlineStatus( m_protocol->m_UserStatusOnline ); manager()->addContact((Kopete::Contact *)contact, true); Kopete::Message msg((Kopete::Contact *)this, mMyself, i18n("User %1 joined channel %2").arg(nickname).arg(m_nickName), Kopete::Message::Internal, Kopete::Message::RichText, CHAT_VIEW); msg.setImportance( Kopete::Message::Low); //set the importance manualy to low manager()->appendMessage(msg); } } void IRCChannelContact::userPartedChannel(const TQString &nickname,const TQString &reason) { IRCAccount *account = ircAccount(); if (nickname.lower() != account->engine()->nickName().lower()) { Kopete::Contact *c = locateUser( nickname ); if ( c ) { manager()->removeContact( c, Kopete::Message::unescape(reason) ); if( c->metaContact()->isTemporary() && !static_cast(c)->isChatting( manager(Kopete::Contact::CannotCreate) ) ) c->deleteLater(); } } } void IRCChannelContact::userKicked(const TQString &nick, const TQString &nickKicked, const TQString &reason) { IRCAccount *account = ircAccount(); if( nickKicked.lower() != account->engine()->nickName().lower() ) { Kopete::Contact *c = locateUser( nickKicked ); if (c) { TQString r; if ((reason != nick) && (reason != nickKicked)) { r = i18n( "%1 was kicked by %2. Reason: %3" ).arg(nickKicked, nick, reason); } else { r = i18n( "%1 was kicked by %2." ).arg(nickKicked, nick); } manager()->removeContact( c, r ); Kopete::Message msg( this, mMyself, r, Kopete::Message::Internal, Kopete::Message::PlainText, CHAT_VIEW); msg.setImportance(Kopete::Message::Low); appendMessage(msg); if( c->metaContact()->isTemporary() && !static_cast(c)->isChatting( manager() ) ) c->deleteLater(); } } else { TQString r; if ((reason != nick) && (reason != nickKicked)) { r = i18n( "You were kicked from %1 by %2. Reason: %3" ).arg(m_nickName, nickKicked, reason); } else { r = i18n( "You were kicked from %1 by %2." ).arg(m_nickName, nickKicked); } KMessageBox::error(Kopete::UI::Global::mainWidget(), r, i18n("IRC Plugin")); manager()->view()->closeView(); } } void IRCChannelContact::setTopic(const TQString &topic) { IRCAccount *account = ircAccount(); if (manager(Kopete::Contact::CannotCreate)) { if( manager()->contactOnlineStatus( manager()->myself() ) == m_protocol->m_UserStatusOp || !modeEnabled('t') ) { bool okPressed = true; TQString newTopic = topic; if( newTopic.isNull() ) newTopic = KInputDialog::getText( i18n("New Topic"), i18n("Enter the new topic:"), Kopete::Message::unescape(mTopic), &okPressed, 0L ); if( okPressed ) { mTopic = newTopic; kircEngine()->topic(m_nickName, newTopic); } } else { Kopete::Message msg(account->myServer(), manager()->members(), i18n("You must be a channel operator on %1 to do that.").arg(m_nickName), Kopete::Message::Internal, Kopete::Message::PlainText, CHAT_VIEW); manager()->appendMessage(msg); } } } void IRCChannelContact::topicChanged(const TQString &nick, const TQString &newtopic) { IRCAccount *account = ircAccount(); mTopic = newtopic; setProperty( m_protocol->propChannelTopic, mTopic ); manager()->setDisplayName( caption() ); Kopete::Message msg(account->myServer(), mMyself, i18n("%1 has changed the topic to: %2").arg(nick).arg(newtopic), Kopete::Message::Internal, Kopete::Message::RichText, CHAT_VIEW); msg.setImportance(Kopete::Message::Low); //set the importance manualy to low appendMessage(msg); } void IRCChannelContact::topicUser(const TQString &nick, const TQDateTime &time) { IRCAccount *account = ircAccount(); Kopete::Message msg(account->myServer(), mMyself, i18n("Topic set by %1 at %2").arg(nick).arg( TDEGlobal::locale()->formatDateTime(time, true) ), Kopete::Message::Internal, Kopete::Message::PlainText, CHAT_VIEW); msg.setImportance(Kopete::Message::Low); //set the importance manualy to low appendMessage(msg); } void IRCChannelContact::incomingModeChange( const TQString &nick, const TQString &mode ) { Kopete::Message msg(this, mMyself, i18n("%1 sets mode %2 on %3").arg(nick).arg(mode).arg(m_nickName), Kopete::Message::Internal, Kopete::Message::PlainText, CHAT_VIEW); msg.setImportance( Kopete::Message::Low); //set the importance manualy to low appendMessage(msg); bool inParams = false; bool modeEnabled = false; TQString params = TQString(); for( uint i=0; i < mode.length(); i++ ) { switch( mode[i] ) { case '+': modeEnabled = true; break; case '-': modeEnabled = false; break; case ' ': inParams = true; break; default: if( inParams ) params.append( mode[i] ); else toggleMode( mode[i], modeEnabled, false ); break; } } } void IRCChannelContact::incomingChannelMode( const TQString &mode, const TQString &/*params*/ ) { for( uint i=1; i < mode.length(); i++ ) { if( mode[i] != 'l' && mode[i] != 'k' ) toggleMode( mode[i], true, false ); } } void IRCChannelContact::setMode(const TQString &mode) { if (manager(Kopete::Contact::CannotCreate)) kircEngine()->mode(m_nickName, mode); } void IRCChannelContact::slotModeChanged() { toggleMode( 't', actionModeT->isChecked(), true ); toggleMode( 'n', actionModeN->isChecked(), true ); toggleMode( 's', actionModeS->isChecked(), true ); toggleMode( 'm', actionModeM->isChecked(), true ); toggleMode( 'i', actionModeI->isChecked(), true ); } void IRCChannelContact::failedChanBanned() { manager()->deleteLater(); KMessageBox::error( Kopete::UI::Global::mainWidget(), i18n("You can not join %1 because you have been banned.").arg(m_nickName), i18n("IRC Plugin") ); } void IRCChannelContact::failedChanInvite() { manager()->deleteLater(); KMessageBox::error( Kopete::UI::Global::mainWidget(), i18n("You can not join %1 because it is set to invite only, and no one has invited you.").arg(m_nickName), i18n("IRC Plugin") ); } void IRCChannelContact::failedChanFull() { manager()->deleteLater(); KMessageBox::error( Kopete::UI::Global::mainWidget(), i18n("You can not join %1 because it has reached its user limit.").arg(m_nickName), i18n("IRC Plugin") ); } void IRCChannelContact::failedChankey() { bool ok; TQString diaPassword = KInputDialog::getText( i18n( "IRC Plugin" ), i18n( "Please enter key for channel %1: ").arg(m_nickName), TQString(), &ok ); if ( !ok ) manager()->deleteLater(); else { setPassword(diaPassword); kircEngine()->join(m_nickName, password()); } } void IRCChannelContact::toggleMode( TQChar mode, bool enabled, bool update ) { if( manager(Kopete::Contact::CannotCreate) ) { switch( mode ) { case 't': actionModeT->setChecked( enabled ); // If someones sets +t and we're not channel operators, disable the action. if (enabled && !(manager()->contactOnlineStatus(ircAccount()->myself()).internalStatus() & IRCProtocol::Operator)) { actionTopic->setEnabled( false ); } else { actionTopic->setEnabled( true ); } break; case 'n': actionModeN->setChecked( enabled ); break; case 's': actionModeS->setChecked( enabled ); break; case 'm': actionModeM->setChecked( enabled ); break; case 'i': actionModeI->setChecked( enabled ); break; } } if( update ) { if( modeMap[mode] != enabled ) { if( enabled ) setMode( TQString::fromLatin1("+") + mode ); else setMode( TQString::fromLatin1("-") + mode ); } } modeMap[mode] = enabled; } bool IRCChannelContact::modeEnabled( TQChar mode, TQString *value ) { if( !value ) return modeMap[mode]; return false; } TQPtrList *IRCChannelContact::customContextMenuActions() { TQPtrList *mCustomActions = new TQPtrList(); if( !actionJoin ) { actionJoin = new TDEAction(i18n("&Join"), 0, this, TQT_SLOT(join()), this, "actionJoin"); actionPart = new TDEAction(i18n("&Part"), 0, this, TQT_SLOT(partAction()), this, "actionPart"); actionTopic = new TDEAction(i18n("Change &Topic..."), 0, this, TQT_SLOT(setTopic()), this, "actionTopic"); actionModeMenu = new TDEActionMenu(i18n("Channel Modes"), 0, this, "actionModeMenu"); if( !property(m_protocol->propHomepage).value().isNull() ) { actionHomePage = new TDEAction( i18n("Visit &Homepage"), 0, this, TQT_SLOT(slotHomepage()), this, "actionHomepage"); } else if( actionHomePage ) { delete actionHomePage; } actionModeMenu->insert( actionModeT ); actionModeMenu->insert( actionModeN ); actionModeMenu->insert( actionModeS ); actionModeMenu->insert( actionModeM ); actionModeMenu->insert( actionModeI ); actionModeMenu->setEnabled( true ); codecAction = new KCodecAction( i18n("&Encoding"), 0, this, "selectcharset" ); connect( codecAction, TQT_SIGNAL( activated( const TQTextCodec * ) ), this, TQT_SLOT( setCodec( const TQTextCodec *) ) ); codecAction->setCodec( codec() ); } mCustomActions->append( actionJoin ); mCustomActions->append( actionPart ); mCustomActions->append( actionTopic ); mCustomActions->append( actionModeMenu ); mCustomActions->append( codecAction ); if( actionHomePage ) mCustomActions->append( actionHomePage ); bool isOperator = manager(Kopete::Contact::CannotCreate) && (manager()->contactOnlineStatus(ircAccount()->myself()).internalStatus() & IRCProtocol::Operator); actionJoin->setEnabled( !manager(Kopete::Contact::CannotCreate) ); actionPart->setEnabled( manager(Kopete::Contact::CannotCreate) ); actionTopic->setEnabled( manager(Kopete::Contact::CannotCreate) && ( !modeEnabled('t') || isOperator ) ); toggleOperatorActions(isOperator); return mCustomActions; } void IRCChannelContact::slotHomepage() { TQString homePage = property(m_protocol->propHomepage).value().toString(); if( !homePage.isEmpty() ) { new KRun( KURL( homePage ), 0, false); } } const TQString IRCChannelContact::caption() const { TQString cap = TQString::fromLatin1("%1 @ %2").arg(m_nickName).arg(kircEngine()->currentHost()); if(!mTopic.isEmpty()) cap.append( TQString::fromLatin1(" - %1").arg(Kopete::Message::unescape(mTopic)) ); return cap; } void IRCChannelContact::privateMessage(IRCContact *from, IRCContact *to, const TQString &message) { if(to == this) { Kopete::Message msg(from, manager()->members(), message, Kopete::Message::Inbound, Kopete::Message::RichText, CHAT_VIEW); appendMessage(msg); } } void IRCChannelContact::newAction(const TQString &from, const TQString &action) { IRCAccount *account = ircAccount(); IRCUserContact *f = account->contactManager()->findUser(from); Kopete::Message::MessageDirection dir = (f == account->mySelf()) ? Kopete::Message::Outbound : Kopete::Message::Inbound; Kopete::Message msg(f, manager()->members(), action, dir, Kopete::Message::RichText, CHAT_VIEW, Kopete::Message::TypeAction); appendMessage(msg); } #include "ircchannelcontact.moc"