/* * chatdlg.cpp - dialog for handling chats * Copyright (C) 2001, 2002 Justin Karneges * * 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. * * This program 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 this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include"chatdlg.h" #include #include #include #include #include #include #include #include #include #include #include #include"profiles.h" #include"psiaccount.h" #include"common.h" #include"userlist.h" #include"iconwidget.h" #include"fancylabel.h" #include"msgmle.h" #include"iconselect.h" #include"psicon.h" #include"psitoolbar.h" #include"iconaction.h" #include"avatars.h" #include"tabdlg.h" #ifdef Q_WS_WIN #include #endif //---------------------------------------------------------------------------- // ChatDlg //---------------------------------------------------------------------------- class ChatDlg::Private : public QObject { Q_OBJECT public: Private(ChatDlg *d) { dlg = d; } ChatDlg *dlg; Jid jid; PsiAccount *pa; QString dispNick; int status; QString statusString; ChatView *log; ChatEdit *mle; #ifdef AVATARS QLabel *avatar; #endif QLabel *lb_ident; IconLabel *lb_status; QLineEdit *le_jid; QLCDNumber *lcd_count; QPopupMenu *pm_settings; QLabel *lb_composing; PsiToolBar *toolbar; IconAction *act_send, *act_clear, *act_history, *act_info, *act_pgp, *act_icon, *act_file, *act_compact; int pending; bool keepOpen, warnSend; QTimer *selfDestruct; QString key; int transid; Message m; bool lastWasEncrypted; bool smallChat; // Message Events QTimer *composingTimer; bool isComposing; bool contactIsComposing; bool sendComposingEvents; QString eventId; signals: // Signals if user (re)started/stopped composing void composing(bool); public slots: void addEmoticon(const Icon *icon) { if ( !dlg->isActiveWindow() ) return; QString text; QDict itext = icon->text(); QDictIterator it ( itext ); for ( ; it.current(); ++it) { if ( it.current() && !it.current()->isEmpty() ) { text = *(it.current()) + " "; break; } } if ( !text.isEmpty() ) mle->insert( text ); } void addEmoticon(QString text) { if ( !pa->psi()->isChatActiveWindow(dlg) ) return; mle->insert( text + " " ); } void updateCounter() { lcd_count->display((int)mle->text().length()); } // Records that the user is composing void setComposing() { if (!composingTimer) { /* User (re)starts composing */ composingTimer = new QTimer(this); connect(composingTimer, SIGNAL(timeout()), SLOT(checkComposing())); composingTimer->start(2000); // FIXME: magic number emit composing(true); } isComposing = true; } // Checks if the user is still composing void checkComposing() { if (!isComposing) { // User stopped composing delete composingTimer; composingTimer = 0; emit composing(false); } isComposing = false; // Reset composing } }; ChatDlg::ChatDlg(const Jid &jid, PsiAccount *pa) { d = new Private(this); d->jid = jid; d->pa = pa; d->pending = 0; d->keepOpen = false; d->warnSend = false; d->selfDestruct = 0; d->transid = -1; d->key = ""; d->lastWasEncrypted = false; setAcceptDrops(TRUE); QVBoxLayout *vb1 = new QVBoxLayout(this, 4); QWidget *sp; if ( !option.chatLineEdit ) { sp = new QSplitter(Vertical, this); vb1->addWidget(sp); } else sp = this; QWidget *sp_top = new QWidget(sp); sp_top->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ); if ( option.chatLineEdit ) vb1->addWidget( sp_top ); // first row QVBoxLayout *vb2 = new QVBoxLayout(sp_top, 0, 4); d->lb_ident = d->pa->accountLabel(sp_top, true); d->lb_ident->setSizePolicy(QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum )); // second row QHBoxLayout *hb2 = new QHBoxLayout(vb2); d->lb_status = new IconLabel(sp_top); d->lb_status->setIcon(IconsetFactory::iconPtr("status/noauth")); hb2->addWidget(d->lb_status); d->le_jid = new QLineEdit(sp_top); d->le_jid->setReadOnly(true); d->lcd_count = new QLCDNumber(sp_top); d->le_jid->setFocusPolicy(QWidget::NoFocus); QToolTip::add(d->lcd_count, tr("Message length")); d->lcd_count->setFixedWidth(50); hb2->addWidget(d->le_jid); hb2->addWidget(d->lcd_count); hb2->addWidget(d->lb_ident); // mid area d->log = new ChatView(sp_top); vb2->addWidget(d->log); #ifdef Q_WS_MAC connect(d->log,SIGNAL(selectionChanged()),SLOT(logSelectionChanged())); #endif QWidget *sp_bottom = new QWidget(sp); sp_bottom->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Maximum ); // tool area QVBoxLayout *vb3 = new QVBoxLayout(sp_bottom); QHBoxLayout *hb3 = new QHBoxLayout(vb3); d->toolbar = new PsiToolBar(tr("Chat toolbar"), 0, sp_bottom); d->toolbar->setCustomizeable( false ); // it isn't ready now, and we don't want segfaults d->toolbar->setFrameShape( QFrame::NoFrame ); hb3->addWidget( d->toolbar ); d->act_clear = new IconAction (tr("Clear chat window"), "psi/clearChat", tr("Clear chat window"), 0, this); connect( d->act_clear, SIGNAL( activated() ), SLOT( doClearButton() ) ); connect(pa->psi()->iconSelectPopup(), SIGNAL(textSelected(QString)), d, SLOT(addEmoticon(QString))); d->act_icon = new IconAction( tr( "Select icon" ), "psi/smile", tr( "Select icon" ), 0, this ); d->act_icon->setPopup( pa->psi()->iconSelectPopup() ); d->act_file = new IconAction( tr( "Send file" ), "psi/upload", tr( "Send file" ), 0, this ); connect( d->act_file, SIGNAL( activated() ), SLOT( doFile() ) ); d->act_pgp = new IconAction( tr( "Toggle encryption" ), "psi/cryptoNo", tr( "Toggle encryption" ), 0, this, 0, true ); d->act_info = new IconAction( tr( "User info" ), "psi/vCard", tr( "User info" ), CTRL+Key_I, this ); connect( d->act_info, SIGNAL( activated() ), SLOT( doInfo() ) ); d->act_history = new IconAction( tr( "Message history" ), "psi/history", tr( "Message history" ), CTRL+Key_H, this ); connect( d->act_history, SIGNAL( activated() ), SLOT( doHistory() ) ); d->act_compact = new IconAction( tr( "Toggle Compact/Full size" ), "psi/compact", tr( "Toggle Compact/Full size" ), 0, this ); connect( d->act_compact, SIGNAL( activated() ), SLOT( toggleSmallChat() ) ); d->act_clear->addTo( d->toolbar ); d->toolbar->setStretchableWidget(new StretchWidget(d->toolbar)); d->act_icon->addTo( d->toolbar ); d->act_file->addTo( d->toolbar ); d->act_pgp->addTo( d->toolbar ); d->act_info->addTo( d->toolbar ); d->act_history->addTo( d->toolbar ); // Bottom but one row (composing notification) QHBoxLayout *hb_comp = new QHBoxLayout(vb3); d->lb_composing = new QLabel(sp_bottom,"composing"); d->lb_composing->setPaletteBackgroundColor(Qt::yellow.light(190)); hb_comp->addWidget(d->lb_composing); d->lb_composing->hide(); // Bottom row QHBoxLayout *hb4 = new QHBoxLayout(vb3); // Text input if ( !option.chatLineEdit ) { d->mle = new ChatEdit(sp_bottom); hb4->addWidget(d->mle); } else { vb1->addWidget( sp_bottom ); QHBoxLayout *hb5 = new QHBoxLayout( vb1 ); d->mle = new LineEdit(sp); #ifdef Q_WS_MAC hb5->addSpacing( 16 ); #endif hb5->addWidget( d->mle ); #ifdef Q_WS_MAC hb5->addSpacing( 16 ); #endif } connect(d->mle, SIGNAL(textChanged()), d, SLOT(updateCounter())); // Avatars #ifdef AVATARS d->avatar = new QLabel(sp_bottom); hb4->addWidget(d->avatar); updateAvatar(); // FIXME: Maybe the psiaccount should do this, to avoid a signal to each // open window when an avatar changes. connect(d->pa->avatarFactory(),SIGNAL(avatarChanged(const Jid&)), this, SLOT(updateAvatar(const Jid&))); connect(d->pa->psi(),SIGNAL(emitOptionsUpdate()), this, SLOT(updateAvatar())); #endif d->pm_settings = new QPopupMenu(this); connect(d->pm_settings, SIGNAL(aboutToShow()), SLOT(buildMenu())); QValueList list; list << 324; list << 96; if ( !option.chatLineEdit ) ((QSplitter *)sp)->setSizes(list); d->status = -1; d->pa->dialogRegister(this, d->jid); // Message events d->contactIsComposing = false; d->sendComposingEvents = false; d->isComposing = false; d->composingTimer = 0; connect(d->mle, SIGNAL(textChanged()), d, SLOT(setComposing())); connect(d, SIGNAL(composing(bool)), SLOT(updateIsComposing(bool))); updateContact(d->jid, true); d->smallChat = option.smallChats; X11WM_CLASS("chat"); setLooks(); updatePGP(); connect(d->pa, SIGNAL(pgpKeyChanged()), SLOT(updatePGP())); connect(d->pa, SIGNAL(encryptedMessageSent(int, bool)), SLOT(encryptedMessageSent(int, bool))); #ifdef Q_WS_X11 d->le_jid->setFocusPolicy(QWidget::NoFocus); d->log->setFocusPolicy(QWidget::NoFocus); #elif defined(Q_WS_MAC) d->log->setFocusPolicy(QWidget::NoFocus); #endif d->mle->setFocus(); resize(option.sizeChatDlg); UserListItem *u = d->pa->findFirstRelavent(d->jid); if(u && u->isSecure(d->jid.resource())) d->act_pgp->setOn(true); } ChatDlg::~ChatDlg() { d->pa->dialogUnregister(this); delete d; } void ChatDlg::contextMenuEvent(QContextMenuEvent *) { d->pm_settings->exec(QCursor::pos()); } void ChatDlg::keyPressEvent(QKeyEvent *e) { if(e->key() == Key_Escape && e->state() == 0 && !option.useTabs) showMinimized(); else if(e->key() == Key_W && e->state() & ControlButton && !option.useTabs) close(); else if(e->key() == Key_Return || e->key() == Key_Enter || (e->key() == Key_S && (e->state() & AltButton))) doSend(); else if(e->key() == Key_PageUp && (e->state() & ShiftButton)) d->log->setContentsPos(d->log->contentsX(), d->log->contentsY() - d->log->visibleHeight()/2); else if(e->key() == Key_PageDown && (e->state() & ShiftButton)) d->log->setContentsPos(d->log->contentsX(), d->log->contentsY() + d->log->visibleHeight()/2); else e->ignore(); } void ChatDlg::resizeEvent(QResizeEvent *e) { if(option.keepSizes) option.sizeChatDlg = e->size(); } void ChatDlg::closeEvent(QCloseEvent *e) { // really lame way of checking if we are encrypting if(!d->mle->isEnabled()) return; if(d->keepOpen) { int n = QMessageBox::information(this, tr("Warning"), tr("A new chat message was just received.\nDo you still want to close the window?"), tr("&Yes"), tr("&No")); if(n != 0) return; } // destroy the dialog if delChats is dcClose if(option.delChats == dcClose) setWFlags(getWFlags() | WDestructiveClose); else { if(option.delChats == dcHour) setSelfDestruct(60); else if(option.delChats == dcDay) setSelfDestruct(60 * 24); } // Reset 'contact is composing' & cancel own composing event updateContactIsComposing(false); if (d->composingTimer) { delete d->composingTimer; d->composingTimer = 0; d->isComposing = false; updateIsComposing(false); } if(d->pending > 0) { d->pending = 0; messagesRead(d->jid); updateCaption(); } doFlash(false); d->mle->setFocus(); e->accept(); } void ChatDlg::showEvent(QShowEvent *) { setSelfDestruct(0); } void ChatDlg::windowActivationChange(bool oldstate) { QWidget::windowActivationChange(oldstate); // if we're bringing it to the front, get rid of the '*' if necessary if( isActiveWindow() && !isHidden() ) { //this is a tab hack activated(); } } void ChatDlg::logSelectionChanged() { #ifdef Q_WS_MAC // A hack to only give the message log focus when text is selected if (d->log->hasSelectedText()) d->log->setFocus(); else d->mle->setFocus(); #endif } void ChatDlg::activated() { if(d->pending > 0) { d->pending = 0; messagesRead(d->jid); updateCaption(); } doFlash(false); if(option.showCounter && !d->smallChat) d->lcd_count->show(); else d->lcd_count->hide(); d->mle->setFocus(); } void ChatDlg::dropEvent(QDropEvent* event) { QStringList l; if (d->pa->loggedIn() && QUriDrag::decodeLocalFiles(event,l) && !l.isEmpty()) d->pa->actionSendFiles(d->jid,l); } void ChatDlg::dragEnterEvent(QDragEnterEvent* event) { QStringList l; event->accept(d->pa->loggedIn() && QUriDrag::canDecode(event) && QUriDrag::decodeLocalFiles(event,l) && !l.isEmpty()); } const Jid & ChatDlg::jid() const { return d->jid; } void ChatDlg::setJid(const Jid &jid) { if(!jid.compare(d->jid)) { d->pa->dialogUnregister(this); d->jid = jid; d->pa->dialogRegister(this, d->jid); updateContact(d->jid, false); } } const QString& ChatDlg::getDisplayNick() { return d->dispNick; } QSize ChatDlg::defaultSize() { return QSize(320, 280); } void ChatDlg::updateContact(const Jid &jid, bool fromPresence) { // if groupchat, only update if the resource matches if(d->pa->findGCContact(jid) && !d->jid.compare(jid)) return; if(d->jid.compare(jid, false)) { QString rname = d->jid.resource(); QPtrList ul = d->pa->findRelavent(jid); UserListItem *u = 0; int status = -1; QString statusString, key; if(!ul.isEmpty()) { u = ul.first(); if(rname.isEmpty()) { // use priority if(!u->isAvailable()) status = STATUS_OFFLINE; else { const UserResource &r = *u->userResourceList().priority(); status = makeSTATUS(r.status()); statusString = r.status().status(); key = r.publicKeyID(); } } else { // use specific UserResourceList::ConstIterator rit = u->userResourceList().find(rname); if(rit != u->userResourceList().end()) { status = makeSTATUS((*rit).status()); statusString = (*rit).status().status(); key = (*rit).publicKeyID(); } else { status = STATUS_OFFLINE; statusString = u->lastUnavailableStatus().status(); key = ""; } } } bool statusChanged = false; if(d->status != status || d->statusString != statusString) { statusChanged = true; d->status = status; d->statusString = statusString; } if(statusChanged) { if(status == -1 || !u) d->lb_status->setIcon(IconsetFactory::iconPtr("status/noauth")); else d->lb_status->setIcon(is->statusPtr(jid, status)); } if(u) QToolTip::add(d->lb_status, u->makeTip(true, false)); else QToolTip::remove(d->lb_status); if(u) { QString name; QString j; if(rname.isEmpty()) j = u->jid().full(); else j = u->jid().userHost() + '/' + rname; if(!u->name().isEmpty()) name = u->name() + QString(" <%1>").arg(j); else name = j; d->le_jid->setText(name); d->le_jid->setCursorPosition(0); QToolTip::add(d->le_jid, name); d->dispNick = jidnick(u->jid().full(), u->name()); updateCaption(); d->key = key; updatePGP(); if(fromPresence && statusChanged) { QString msg = tr("%1 is %2").arg(expandEntities(d->dispNick)).arg(status2txt(d->status)); if(!statusString.isEmpty()) { QString ss = linkify(plain2rich(statusString)); if(option.useEmoticons) ss = emoticonify(ss); msg += QString(" [%1]").arg(ss); } appendSysMsg(msg); } } // Reset 'is composing' event if the status changed if (statusChanged) { updateContactIsComposing(false); } } } void ChatDlg::updateAvatar(const Jid& j) { if (j.compare(d->jid,false)) updateAvatar(); } void ChatDlg::updateAvatar() { #ifdef AVATARS QString res; QString client; if (option.chatLineEdit || !option.avatarsEnabled || !option.avatarsChatdlgEnabled) { d->avatar->hide(); return; } UserListItem *ul = d->pa->findFirstRelavent(d->jid); if (ul && !ul->userResourceList().isEmpty()) { UserResourceList::Iterator it = ul->userResourceList().find(d->jid.resource()); if(it == ul->userResourceList().end()) it = ul->userResourceList().priority(); res = (*it).name(); client = (*it).clientName(); } QPixmap p = d->pa->avatarFactory()->getAvatar(d->jid.withResource(res),client); if (p.isNull()) { d->avatar->hide(); } else { d->avatar->setPixmap(p); d->avatar->show(); } #endif } void ChatDlg::setLooks() { // update the font QFont f; f.fromString(option.font[fChat]); d->log->setFont(f); d->mle->setFont(f); if (d->smallChat) { d->lb_ident->hide(); d->lb_status->hide(); d->le_jid->hide(); d->toolbar->hide(); } else { d->lb_ident->show(); d->lb_status->show(); d->le_jid->show(); d->toolbar->show(); } if ( option.showCounter && !d->smallChat ) d->lcd_count->show(); else d->lcd_count->hide(); // update contact info d->status = -2; // sick way of making it redraw the status updateContact(d->jid, false); // toolbuttons QIconSet i; i.setPixmap(IconsetFactory::icon("psi/cryptoNo"), QIconSet::Automatic, QIconSet::Normal, QIconSet::Off); i.setPixmap(IconsetFactory::icon("psi/cryptoYes"), QIconSet::Automatic, QIconSet::Normal, QIconSet::On); d->act_pgp->setIcon( 0 ); d->act_pgp->setIconSet( i ); // update the widget icon #ifndef Q_WS_MAC setIcon(IconsetFactory::icon("psi/start-chat")); #endif /*QBrush brush; brush.setPixmap( QPixmap( option.chatBgImage ) ); d->log->setPaper(brush); d->log->setStaticBackground(true);*/ #if QT_VERSION >= 0x030300 setWindowOpacity(double(option.chatOpacity)/100); #endif } void ChatDlg::optionsUpdate() { if (option.oldSmallChats!=option.smallChats) { d->smallChat=option.smallChats; } setLooks(); if(isHidden()) { if(option.delChats == dcClose) { deleteLater(); return; } else { if(option.delChats == dcHour) setSelfDestruct(60); else if(option.delChats == dcDay) setSelfDestruct(60 * 24); else setSelfDestruct(0); } } } void ChatDlg::updatePGP() { if(!d->pa->pgpKey().isEmpty()) { d->act_pgp->setEnabled(true); } else { d->act_pgp->setOn(false); d->act_pgp->setEnabled(false); } } void ChatDlg::doInfo() { aInfo(d->jid); } void ChatDlg::doHistory() { aHistory(d->jid); } void ChatDlg::doFile() { aFile(d->jid); } void ChatDlg::doClearButton() { int n = QMessageBox::information(this, tr("Warning"), tr("Are you sure you want to clear the chat window?\n(note: does not affect saved history)"), tr("&Yes"), tr("&No")); if(n == 0) doClear(); } void ChatDlg::doClear() { d->log->setText(""); } void ChatDlg::setKeepOpenFalse() { d->keepOpen = false; } void ChatDlg::setWarnSendFalse() { d->warnSend = false; } void ChatDlg::setSelfDestruct(int minutes) { if(minutes <= 0) { if(d->selfDestruct) { delete d->selfDestruct; d->selfDestruct = 0; } return; } if(!d->selfDestruct) { d->selfDestruct = new QTimer(this); connect(d->selfDestruct, SIGNAL(timeout()), SLOT(deleteLater())); } d->selfDestruct->start(minutes * 60000); } void ChatDlg::updateCaption() { QString cap = ""; if(d->pending > 0) { cap += "* "; if(d->pending > 1) cap += QString("[%1] ").arg(d->pending); } cap += d->dispNick; if (d->contactIsComposing) cap = tr("%1 (Composing ...)").arg(cap); setCaption(cap); emit captionChanged(this); emit unreadMessageUpdate(this, d->pending); } void ChatDlg::doSend() { if(!d->mle->isEnabled()) return; if(d->mle->text().isEmpty()) return; if(d->mle->text() == "/clear") { d->mle->setText(""); doClear(); return; } if(!d->pa->loggedIn()) return; if(d->warnSend) { d->warnSend = false; int n = QMessageBox::information(this, tr("Warning"), tr( "

Encryption was recently disabled by the remote contact. " "Are you sure you want to send this message without encryption?

" ), tr("&Yes"), tr("&No")); if(n != 0) return; } Message m(d->jid); m.setType("chat"); m.setBody(d->mle->text()); m.setTimeStamp(QDateTime::currentDateTime()); if(d->act_pgp->isOn()) m.setWasEncrypted(true); d->m = m; // Request events if (option.messageEvents) { //m.addEvent(OfflineEvent); //m.addEvent(DeliveredEvent); //m.addEvent(DisplayedEvent); m.addEvent(ComposingEvent); } if(d->act_pgp->isOn()) { d->mle->setEnabled(false); d->transid = d->pa->sendMessageEncrypted(m); if(d->transid == -1) { d->mle->setEnabled(true); d->mle->setFocus(); return; } } else { aSend(m); doneSend(); } } void ChatDlg::doneSend() { appendMessage(d->m, true); disconnect(d->mle, SIGNAL(textChanged()), d, SLOT(setComposing())); d->mle->setText(""); // Reset composing timer connect(d->mle, SIGNAL(textChanged()), d, SLOT(setComposing())); if (d->composingTimer) { delete d->composingTimer; d->composingTimer = 0; d->isComposing = false; } } void ChatDlg::encryptedMessageSent(int x, bool b) { if(d->transid == -1) return; if(d->transid != x) return; d->transid = -1; if(b) doneSend(); else QMessageBox::information(this, tr("Error"), tr("There was an error trying to send the message encrypted.\nCheck your OpenPGP application/settings.")); d->mle->setEnabled(true); d->mle->setFocus(); } void ChatDlg::incomingMessage(const Message &m) { if (m.body().isEmpty()) { /* Event message */ if (m.containsEvent(CancelEvent)) updateContactIsComposing(false); if (m.containsEvent(ComposingEvent)) updateContactIsComposing(true); } else { // Normal message // Check if user requests event messages d->sendComposingEvents = m.containsEvent(ComposingEvent); if (!m.eventId().isEmpty()) d->eventId = m.eventId(); updateContactIsComposing(false); appendMessage(m); } } void ChatDlg::appendSysMsg(const QString &str) { QString timestr; QDateTime time = QDateTime::currentDateTime(); //timestr.sprintf("%02d:%02d:%02d", time.time().hour(), time.time().minute(), time.time().second()); timestr = time.time().toString(LocalDate); int y = d->log->contentsHeight() - d->log->visibleHeight(); if(y < 0) y = 0; bool atBottom = (d->log->contentsY() < y - 32) ? false: true; d->log->append(QString("[%1]").arg(timestr) + QString(" *** %1").arg(str)); if(atBottom) deferredScroll(); } void ChatDlg::appendMessage(const Message &m, bool local) { QString who, color; if(local) { who = d->pa->nick(); color = "#FF0000"; } else { who = d->dispNick; color = "#0000FF"; } if(m.spooled()) color = "#008000"; // figure out the encryption state bool encChanged = false; bool encEnabled = false; if(d->lastWasEncrypted != m.wasEncrypted()) encChanged = true; d->lastWasEncrypted = m.wasEncrypted(); encEnabled = d->lastWasEncrypted; if(encChanged) { if(encEnabled) { appendSysMsg(QString(" ") + tr("Encryption Enabled")); if(!local) d->act_pgp->setOn(true); } else { appendSysMsg(QString(" ") + tr("Encryption Disabled")); if(!local) { d->act_pgp->setOn(false); // enable warning d->warnSend = true; QTimer::singleShot(3000, this, SLOT(setWarnSendFalse())); } } } QString timestr; QDateTime time = m.timeStamp(); timestr = time.time().toString(LocalDate); int y = d->log->contentsHeight() - d->log->visibleHeight(); if(y < 0) y = 0; bool atBottom = (d->log->contentsY() < y - 32) ? false: true; bool emote = false; if(m.body().left(4) == "/me ") emote = true; QString txt; if(emote) txt = plain2rich(m.body().mid(4)); else txt = plain2rich(m.body()); txt = linkify(txt); if(option.useEmoticons) txt = emoticonify(txt); who = expandEntities(who); if(emote) { d->log->append(QString("").arg(color) + QString("[%1]").arg(timestr) + QString(" *%1 ").arg(who) + txt + ""); } else { if(option.chatSays) d->log->append(QString("").arg(color) + QString("[%1] ").arg(timestr) + tr("%1 says:").arg(who) + "
" + txt); else d->log->append(QString("").arg(color) + QString("[%1] <").arg(timestr) + who + QString("> ") + txt); } if(!m.subject().isEmpty()) { d->log->append(QString("") + tr("Subject:") + " " + QString("%1").arg(expandEntities(m.subject()))); } if(!m.urlList().isEmpty()) { UrlList urls = m.urlList(); d->log->append(QString("") + tr("-- Attached URL(s) --") + ""); for(QValueList::ConstIterator it = urls.begin(); it != urls.end(); ++it) { const Url &u = *it; d->log->append(QString("") + tr("URL:") + " " + QString("%1").arg( linkify(expandEntities(u.url())) )); d->log->append(QString("") + tr("Desc:") + " " + QString("%1").arg(u.desc())); } } if(local || atBottom) deferredScroll(); TabDlg *dlg = NULL; AdvancedWidget *atlw = this; if (d->pa->psi()->isChatTabbed(this)) { dlg = d->pa->psi()->getManagingTabs(this); atlw = dlg; } // if we're not active, notify the user by changing the title if(!atlw->isActiveWindow() || (dlg && !dlg->chatOnTop(this))) { // select the chat tab only if the Raise option is set but not when // the tab dialog is already active (so that the user is typing to // some other chat tab). Note that we do that before updateCaption() // because selectTab() will remove any title emphasis (asterisk, etc) if(option.raiseChatWindow && dlg && !dlg->isActiveWindow()) dlg->selectTab(this); ++d->pending; updateCaption(); if(option.raiseChatWindow) bringToFront(atlw, false); else atlw->doFlash(true); } //else { // messagesRead(d->jid); //} if(!local) { d->keepOpen = true; QTimer::singleShot(1000, this, SLOT(setKeepOpenFalse())); } } void ChatDlg::deferredScroll() { QTimer::singleShot(250, this, SLOT(slotScroll())); } void ChatDlg::slotScroll() { d->log->scrollToBottom(); } void ChatDlg::updateIsComposing(bool c) { if (option.messageEvents && d->sendComposingEvents) { // Don't send to offline resource QPtrList ul = d->pa->findRelavent(d->jid); if(ul.isEmpty()) { d->sendComposingEvents = false; return; } QString rname = d->jid.resource(); UserListItem *u = ul.first(); if(rname.isEmpty() && !u->isAvailable() || u->userResourceList().find(rname) == u->userResourceList().end()) { d->sendComposingEvents = false; return; } // Send event message Message m(d->jid); m.setEventId(d->eventId); if (c) { m.addEvent(ComposingEvent); } else { m.addEvent(CancelEvent); } d->pa->dj_sendMessage(m, false); } } void ChatDlg::updateContactIsComposing(bool c) { d->contactIsComposing = c; emit contactIsComposing(this, c); updateCaption(); /*if (c) { d->lb_composing->setText(QString(d->dispNick+" "+tr("is composing a reply."))); d->lb_composing->show(); } else { d->lb_composing->hide(); }*/ } void ChatDlg::toggleSmallChat() { d->smallChat = !d->smallChat; setLooks(); } void ChatDlg::toggleEncryption() { d->act_pgp->setOn( !d->act_pgp->isOn() ); } void ChatDlg::buildMenu() { // Dialog menu d->pm_settings->clear(); d->act_compact->addTo( d->pm_settings ); d->act_clear->addTo( d->pm_settings ); d->pm_settings->insertSeparator(); d->act_icon->addTo( d->pm_settings ); d->act_file->addTo( d->pm_settings ); d->act_pgp->addTo( d->pm_settings ); d->pm_settings->insertSeparator(); d->act_info->addTo( d->pm_settings ); d->act_history->addTo( d->pm_settings ); } #include "chatdlg.moc"