source: psi/trunk/src/chatdlg.cpp@ 139

Last change on this file since 139 was 49, checked in by dmik, 19 years ago

Psi: Improved: Pressing ESC in a chat or a group chat window minimizes it.

File size: 26.8 KB
Line 
1/*
2 * chatdlg.cpp - dialog for handling chats
3 * Copyright (C) 2001, 2002 Justin Karneges
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this library; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 *
19 */
20
21#include"chatdlg.h"
22
23#include<qlabel.h>
24#include<qcursor.h>
25#include<qdragobject.h>
26#include<qlineedit.h>
27#include<qtoolbutton.h>
28#include<qlayout.h>
29#include<qsplitter.h>
30#include<qtooltip.h>
31#include<qtimer.h>
32#include<qdatetime.h>
33#include<qlcdnumber.h>
34#include"profiles.h"
35#include"psiaccount.h"
36#include"common.h"
37#include"userlist.h"
38#include"iconwidget.h"
39#include"fancylabel.h"
40#include"msgmle.h"
41#include"iconselect.h"
42#include"psicon.h"
43#include"psitoolbar.h"
44#include"iconaction.h"
45#include"avatars.h"
46#include"tabdlg.h"
47
48#ifdef Q_WS_WIN
49#include<windows.h>
50#endif
51
52//----------------------------------------------------------------------------
53// ChatDlg
54//----------------------------------------------------------------------------
55class ChatDlg::Private : public QObject
56{
57 Q_OBJECT
58public:
59 Private(ChatDlg *d) {
60 dlg = d;
61 }
62
63 ChatDlg *dlg;
64 Jid jid;
65 PsiAccount *pa;
66 QString dispNick;
67 int status;
68 QString statusString;
69
70 ChatView *log;
71 ChatEdit *mle;
72
73#ifdef AVATARS
74 QLabel *avatar;
75#endif
76
77 QLabel *lb_ident;
78 IconLabel *lb_status;
79 QLineEdit *le_jid;
80 QLCDNumber *lcd_count;
81 QPopupMenu *pm_settings;
82
83 QLabel *lb_composing;
84
85 PsiToolBar *toolbar;
86 IconAction *act_send, *act_clear, *act_history, *act_info, *act_pgp, *act_icon, *act_file, *act_compact;
87
88 int pending;
89 bool keepOpen, warnSend;
90
91 QTimer *selfDestruct;
92
93 QString key;
94 int transid;
95 Message m;
96 bool lastWasEncrypted;
97 bool smallChat;
98
99 // Message Events
100 QTimer *composingTimer;
101 bool isComposing;
102 bool contactIsComposing;
103 bool sendComposingEvents;
104 QString eventId;
105
106signals:
107 // Signals if user (re)started/stopped composing
108 void composing(bool);
109
110public slots:
111 void addEmoticon(const Icon *icon) {
112 if ( !dlg->isActiveWindow() )
113 return;
114
115 QString text;
116
117 QDict<QString> itext = icon->text();
118 QDictIterator<QString> it ( itext );
119 for ( ; it.current(); ++it) {
120 if ( it.current() && !it.current()->isEmpty() ) {
121 text = *(it.current()) + " ";
122 break;
123 }
124 }
125
126 if ( !text.isEmpty() )
127 mle->insert( text );
128 }
129
130 void addEmoticon(QString text) {
131 if ( !pa->psi()->isChatActiveWindow(dlg) )
132 return;
133
134 mle->insert( text + " " );
135 }
136
137 void updateCounter() {
138 lcd_count->display((int)mle->text().length());
139 }
140
141 // Records that the user is composing
142 void setComposing() {
143 if (!composingTimer) {
144 /* User (re)starts composing */
145 composingTimer = new QTimer(this);
146 connect(composingTimer, SIGNAL(timeout()), SLOT(checkComposing()));
147 composingTimer->start(2000); // FIXME: magic number
148 emit composing(true);
149 }
150 isComposing = true;
151 }
152
153 // Checks if the user is still composing
154 void checkComposing() {
155 if (!isComposing) {
156 // User stopped composing
157 delete composingTimer;
158 composingTimer = 0;
159 emit composing(false);
160 }
161 isComposing = false; // Reset composing
162 }
163};
164
165ChatDlg::ChatDlg(const Jid &jid, PsiAccount *pa)
166{
167 d = new Private(this);
168 d->jid = jid;
169 d->pa = pa;
170
171 d->pending = 0;
172 d->keepOpen = false;
173 d->warnSend = false;
174 d->selfDestruct = 0;
175 d->transid = -1;
176 d->key = "";
177 d->lastWasEncrypted = false;
178
179 setAcceptDrops(TRUE);
180
181 QVBoxLayout *vb1 = new QVBoxLayout(this, 4);
182
183 QWidget *sp;
184 if ( !option.chatLineEdit ) {
185 sp = new QSplitter(Vertical, this);
186 vb1->addWidget(sp);
187 }
188 else
189 sp = this;
190
191 QWidget *sp_top = new QWidget(sp);
192 sp_top->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
193 if ( option.chatLineEdit )
194 vb1->addWidget( sp_top );
195 // first row
196 QVBoxLayout *vb2 = new QVBoxLayout(sp_top, 0, 4);
197 d->lb_ident = d->pa->accountLabel(sp_top, true);
198 d->lb_ident->setSizePolicy(QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ));
199
200 // second row
201 QHBoxLayout *hb2 = new QHBoxLayout(vb2);
202 d->lb_status = new IconLabel(sp_top);
203 d->lb_status->setIcon(IconsetFactory::iconPtr("status/noauth"));
204 hb2->addWidget(d->lb_status);
205 d->le_jid = new QLineEdit(sp_top);
206 d->le_jid->setReadOnly(true);
207 d->lcd_count = new QLCDNumber(sp_top);
208 d->le_jid->setFocusPolicy(QWidget::NoFocus);
209 QToolTip::add(d->lcd_count, tr("Message length"));
210 d->lcd_count->setFixedWidth(50);
211 hb2->addWidget(d->le_jid);
212 hb2->addWidget(d->lcd_count);
213 hb2->addWidget(d->lb_ident);
214
215 // mid area
216 d->log = new ChatView(sp_top);
217 vb2->addWidget(d->log);
218#ifdef Q_WS_MAC
219 connect(d->log,SIGNAL(selectionChanged()),SLOT(logSelectionChanged()));
220#endif
221
222 QWidget *sp_bottom = new QWidget(sp);
223 sp_bottom->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Maximum );
224 // tool area
225 QVBoxLayout *vb3 = new QVBoxLayout(sp_bottom);
226 QHBoxLayout *hb3 = new QHBoxLayout(vb3);
227
228 d->toolbar = new PsiToolBar(tr("Chat toolbar"), 0, sp_bottom);
229 d->toolbar->setCustomizeable( false ); // it isn't ready now, and we don't want segfaults
230 d->toolbar->setFrameShape( QFrame::NoFrame );
231 hb3->addWidget( d->toolbar );
232
233 d->act_clear = new IconAction (tr("Clear chat window"), "psi/clearChat", tr("Clear chat window"), 0, this);
234 connect( d->act_clear, SIGNAL( activated() ), SLOT( doClearButton() ) );
235
236 connect(pa->psi()->iconSelectPopup(), SIGNAL(textSelected(QString)), d, SLOT(addEmoticon(QString)));
237 d->act_icon = new IconAction( tr( "Select icon" ), "psi/smile", tr( "Select icon" ), 0, this );
238 d->act_icon->setPopup( pa->psi()->iconSelectPopup() );
239
240 d->act_file = new IconAction( tr( "Send file" ), "psi/upload", tr( "Send file" ), 0, this );
241 connect( d->act_file, SIGNAL( activated() ), SLOT( doFile() ) );
242
243 d->act_pgp = new IconAction( tr( "Toggle encryption" ), "psi/cryptoNo", tr( "Toggle encryption" ), 0, this, 0, true );
244
245 d->act_info = new IconAction( tr( "User info" ), "psi/vCard", tr( "User info" ), CTRL+Key_I, this );
246 connect( d->act_info, SIGNAL( activated() ), SLOT( doInfo() ) );
247
248 d->act_history = new IconAction( tr( "Message history" ), "psi/history", tr( "Message history" ), CTRL+Key_H, this );
249 connect( d->act_history, SIGNAL( activated() ), SLOT( doHistory() ) );
250
251 d->act_compact = new IconAction( tr( "Toggle Compact/Full size" ), "psi/compact", tr( "Toggle Compact/Full size" ), 0, this );
252 connect( d->act_compact, SIGNAL( activated() ), SLOT( toggleSmallChat() ) );
253
254 d->act_clear->addTo( d->toolbar );
255 d->toolbar->setStretchableWidget(new StretchWidget(d->toolbar));
256
257 d->act_icon->addTo( d->toolbar );
258 d->act_file->addTo( d->toolbar );
259 d->act_pgp->addTo( d->toolbar );
260 d->act_info->addTo( d->toolbar );
261 d->act_history->addTo( d->toolbar );
262
263 // Bottom but one row (composing notification)
264 QHBoxLayout *hb_comp = new QHBoxLayout(vb3);
265 d->lb_composing = new QLabel(sp_bottom,"composing");
266 d->lb_composing->setPaletteBackgroundColor(Qt::yellow.light(190));
267 hb_comp->addWidget(d->lb_composing);
268 d->lb_composing->hide();
269
270 // Bottom row
271 QHBoxLayout *hb4 = new QHBoxLayout(vb3);
272
273 // Text input
274 if ( !option.chatLineEdit ) {
275 d->mle = new ChatEdit(sp_bottom);
276 hb4->addWidget(d->mle);
277 }
278 else {
279 vb1->addWidget( sp_bottom );
280 QHBoxLayout *hb5 = new QHBoxLayout( vb1 );
281 d->mle = new LineEdit(sp);
282#ifdef Q_WS_MAC
283 hb5->addSpacing( 16 );
284#endif
285 hb5->addWidget( d->mle );
286#ifdef Q_WS_MAC
287 hb5->addSpacing( 16 );
288#endif
289 }
290
291 connect(d->mle, SIGNAL(textChanged()), d, SLOT(updateCounter()));
292
293 // Avatars
294#ifdef AVATARS
295 d->avatar = new QLabel(sp_bottom);
296 hb4->addWidget(d->avatar);
297 updateAvatar();
298 // FIXME: Maybe the psiaccount should do this, to avoid a signal to each
299 // open window when an avatar changes.
300 connect(d->pa->avatarFactory(),SIGNAL(avatarChanged(const Jid&)), this, SLOT(updateAvatar(const Jid&)));
301 connect(d->pa->psi(),SIGNAL(emitOptionsUpdate()), this, SLOT(updateAvatar()));
302#endif
303
304 d->pm_settings = new QPopupMenu(this);
305 connect(d->pm_settings, SIGNAL(aboutToShow()), SLOT(buildMenu()));
306
307 QValueList<int> list;
308 list << 324;
309 list << 96;
310 if ( !option.chatLineEdit )
311 ((QSplitter *)sp)->setSizes(list);
312
313 d->status = -1;
314 d->pa->dialogRegister(this, d->jid);
315
316 // Message events
317 d->contactIsComposing = false;
318 d->sendComposingEvents = false;
319 d->isComposing = false;
320 d->composingTimer = 0;
321 connect(d->mle, SIGNAL(textChanged()), d, SLOT(setComposing()));
322 connect(d, SIGNAL(composing(bool)), SLOT(updateIsComposing(bool)));
323
324 updateContact(d->jid, true);
325
326 d->smallChat = option.smallChats;
327 X11WM_CLASS("chat");
328 setLooks();
329
330 updatePGP();
331 connect(d->pa, SIGNAL(pgpKeyChanged()), SLOT(updatePGP()));
332 connect(d->pa, SIGNAL(encryptedMessageSent(int, bool)), SLOT(encryptedMessageSent(int, bool)));
333#ifdef Q_WS_X11
334 d->le_jid->setFocusPolicy(QWidget::NoFocus);
335 d->log->setFocusPolicy(QWidget::NoFocus);
336#elif defined(Q_WS_MAC)
337 d->log->setFocusPolicy(QWidget::NoFocus);
338#endif
339 d->mle->setFocus();
340 resize(option.sizeChatDlg);
341
342 UserListItem *u = d->pa->findFirstRelavent(d->jid);
343 if(u && u->isSecure(d->jid.resource()))
344 d->act_pgp->setOn(true);
345}
346
347ChatDlg::~ChatDlg()
348{
349 d->pa->dialogUnregister(this);
350
351 delete d;
352}
353
354void ChatDlg::contextMenuEvent(QContextMenuEvent *)
355{
356 d->pm_settings->exec(QCursor::pos());
357}
358
359void ChatDlg::keyPressEvent(QKeyEvent *e)
360{
361 if(e->key() == Key_Escape && e->state() == 0 && !option.useTabs)
362 showMinimized();
363 else if(e->key() == Key_W && e->state() & ControlButton && !option.useTabs)
364 close();
365 else if(e->key() == Key_Return || e->key() == Key_Enter || (e->key() == Key_S && (e->state() & AltButton)))
366 doSend();
367 else if(e->key() == Key_PageUp && (e->state() & ShiftButton))
368 d->log->setContentsPos(d->log->contentsX(), d->log->contentsY() - d->log->visibleHeight()/2);
369 else if(e->key() == Key_PageDown && (e->state() & ShiftButton))
370 d->log->setContentsPos(d->log->contentsX(), d->log->contentsY() + d->log->visibleHeight()/2);
371 else
372 e->ignore();
373}
374
375void ChatDlg::resizeEvent(QResizeEvent *e)
376{
377 if(option.keepSizes)
378 option.sizeChatDlg = e->size();
379}
380
381void ChatDlg::closeEvent(QCloseEvent *e)
382{
383 // really lame way of checking if we are encrypting
384 if(!d->mle->isEnabled())
385 return;
386
387 if(d->keepOpen) {
388 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"));
389 if(n != 0)
390 return;
391 }
392
393 // destroy the dialog if delChats is dcClose
394 if(option.delChats == dcClose)
395 setWFlags(getWFlags() | WDestructiveClose);
396 else {
397 if(option.delChats == dcHour)
398 setSelfDestruct(60);
399 else if(option.delChats == dcDay)
400 setSelfDestruct(60 * 24);
401 }
402
403 // Reset 'contact is composing' & cancel own composing event
404 updateContactIsComposing(false);
405 if (d->composingTimer) {
406 delete d->composingTimer;
407 d->composingTimer = 0;
408 d->isComposing = false;
409 updateIsComposing(false);
410 }
411
412
413 if(d->pending > 0) {
414 d->pending = 0;
415 messagesRead(d->jid);
416 updateCaption();
417 }
418 doFlash(false);
419
420 d->mle->setFocus();
421 e->accept();
422}
423
424void ChatDlg::showEvent(QShowEvent *)
425{
426 setSelfDestruct(0);
427}
428
429void ChatDlg::windowActivationChange(bool oldstate)
430{
431 QWidget::windowActivationChange(oldstate);
432
433 // if we're bringing it to the front, get rid of the '*' if necessary
434 if( isActiveWindow() && !isHidden() ) { //this is a tab hack
435 activated();
436 }
437}
438
439void ChatDlg::logSelectionChanged()
440{
441#ifdef Q_WS_MAC
442 // A hack to only give the message log focus when text is selected
443 if (d->log->hasSelectedText())
444 d->log->setFocus();
445 else
446 d->mle->setFocus();
447#endif
448}
449
450void ChatDlg::activated()
451{
452 if(d->pending > 0) {
453 d->pending = 0;
454 messagesRead(d->jid);
455 updateCaption();
456 }
457 doFlash(false);
458
459 if(option.showCounter && !d->smallChat)
460 d->lcd_count->show();
461 else
462 d->lcd_count->hide();
463
464 d->mle->setFocus();
465}
466
467
468void ChatDlg::dropEvent(QDropEvent* event)
469{
470 QStringList l;
471 if (d->pa->loggedIn() && QUriDrag::decodeLocalFiles(event,l) && !l.isEmpty())
472 d->pa->actionSendFiles(d->jid,l);
473}
474
475void ChatDlg::dragEnterEvent(QDragEnterEvent* event)
476{
477 QStringList l;
478 event->accept(d->pa->loggedIn() && QUriDrag::canDecode(event) && QUriDrag::decodeLocalFiles(event,l) && !l.isEmpty());
479}
480
481
482const Jid & ChatDlg::jid() const
483{
484 return d->jid;
485}
486
487void ChatDlg::setJid(const Jid &jid)
488{
489 if(!jid.compare(d->jid)) {
490 d->pa->dialogUnregister(this);
491 d->jid = jid;
492 d->pa->dialogRegister(this, d->jid);
493 updateContact(d->jid, false);
494 }
495}
496
497const QString& ChatDlg::getDisplayNick()
498{
499 return d->dispNick;
500}
501
502QSize ChatDlg::defaultSize()
503{
504 return QSize(320, 280);
505}
506
507void ChatDlg::updateContact(const Jid &jid, bool fromPresence)
508{
509 // if groupchat, only update if the resource matches
510 if(d->pa->findGCContact(jid) && !d->jid.compare(jid))
511 return;
512
513 if(d->jid.compare(jid, false)) {
514 QString rname = d->jid.resource();
515 QPtrList<UserListItem> ul = d->pa->findRelavent(jid);
516 UserListItem *u = 0;
517 int status = -1;
518 QString statusString, key;
519 if(!ul.isEmpty()) {
520 u = ul.first();
521 if(rname.isEmpty()) {
522 // use priority
523 if(!u->isAvailable())
524 status = STATUS_OFFLINE;
525 else {
526 const UserResource &r = *u->userResourceList().priority();
527 status = makeSTATUS(r.status());
528 statusString = r.status().status();
529 key = r.publicKeyID();
530 }
531 }
532 else {
533 // use specific
534 UserResourceList::ConstIterator rit = u->userResourceList().find(rname);
535 if(rit != u->userResourceList().end()) {
536 status = makeSTATUS((*rit).status());
537 statusString = (*rit).status().status();
538 key = (*rit).publicKeyID();
539 }
540 else {
541 status = STATUS_OFFLINE;
542 statusString = u->lastUnavailableStatus().status();
543 key = "";
544 }
545 }
546 }
547
548 bool statusChanged = false;
549 if(d->status != status || d->statusString != statusString) {
550 statusChanged = true;
551 d->status = status;
552 d->statusString = statusString;
553 }
554
555 if(statusChanged) {
556 if(status == -1 || !u)
557 d->lb_status->setIcon(IconsetFactory::iconPtr("status/noauth"));
558 else
559 d->lb_status->setIcon(is->statusPtr(jid, status));
560 }
561
562 if(u)
563 QToolTip::add(d->lb_status, u->makeTip(true, false));
564 else
565 QToolTip::remove(d->lb_status);
566
567 if(u) {
568 QString name;
569 QString j;
570 if(rname.isEmpty())
571 j = u->jid().full();
572 else
573 j = u->jid().userHost() + '/' + rname;
574
575 if(!u->name().isEmpty())
576 name = u->name() + QString(" <%1>").arg(j);
577 else
578 name = j;
579
580 d->le_jid->setText(name);
581 d->le_jid->setCursorPosition(0);
582 QToolTip::add(d->le_jid, name);
583
584 d->dispNick = jidnick(u->jid().full(), u->name());
585 updateCaption();
586
587 d->key = key;
588 updatePGP();
589
590 if(fromPresence && statusChanged) {
591 QString msg = tr("%1 is %2").arg(expandEntities(d->dispNick)).arg(status2txt(d->status));
592 if(!statusString.isEmpty()) {
593 QString ss = linkify(plain2rich(statusString));
594 if(option.useEmoticons)
595 ss = emoticonify(ss);
596
597 msg += QString(" [%1]").arg(ss);
598 }
599 appendSysMsg(msg);
600 }
601 }
602
603 // Reset 'is composing' event if the status changed
604 if (statusChanged) {
605 updateContactIsComposing(false);
606 }
607 }
608}
609
610
611void ChatDlg::updateAvatar(const Jid& j)
612{
613 if (j.compare(d->jid,false))
614 updateAvatar();
615}
616
617void ChatDlg::updateAvatar()
618{
619#ifdef AVATARS
620 QString res;
621 QString client;
622
623 if (option.chatLineEdit || !option.avatarsEnabled || !option.avatarsChatdlgEnabled) {
624 d->avatar->hide();
625 return;
626 }
627
628 UserListItem *ul = d->pa->findFirstRelavent(d->jid);
629 if (ul && !ul->userResourceList().isEmpty()) {
630 UserResourceList::Iterator it = ul->userResourceList().find(d->jid.resource());
631 if(it == ul->userResourceList().end())
632 it = ul->userResourceList().priority();
633
634 res = (*it).name();
635 client = (*it).clientName();
636 }
637 QPixmap p = d->pa->avatarFactory()->getAvatar(d->jid.withResource(res),client);
638 if (p.isNull()) {
639 d->avatar->hide();
640 }
641 else {
642 d->avatar->setPixmap(p);
643 d->avatar->show();
644 }
645#endif
646}
647
648
649void ChatDlg::setLooks()
650{
651 // update the font
652 QFont f;
653 f.fromString(option.font[fChat]);
654 d->log->setFont(f);
655 d->mle->setFont(f);
656
657 if (d->smallChat) {
658 d->lb_ident->hide();
659 d->lb_status->hide();
660 d->le_jid->hide();
661 d->toolbar->hide();
662 }
663 else {
664 d->lb_ident->show();
665 d->lb_status->show();
666 d->le_jid->show();
667 d->toolbar->show();
668 }
669
670 if ( option.showCounter && !d->smallChat )
671 d->lcd_count->show();
672 else
673 d->lcd_count->hide();
674
675 // update contact info
676 d->status = -2; // sick way of making it redraw the status
677 updateContact(d->jid, false);
678
679 // toolbuttons
680 QIconSet i;
681 i.setPixmap(IconsetFactory::icon("psi/cryptoNo"), QIconSet::Automatic, QIconSet::Normal, QIconSet::Off);
682 i.setPixmap(IconsetFactory::icon("psi/cryptoYes"), QIconSet::Automatic, QIconSet::Normal, QIconSet::On);
683 d->act_pgp->setIcon( 0 );
684 d->act_pgp->setIconSet( i );
685
686 // update the widget icon
687#ifndef Q_WS_MAC
688 setIcon(IconsetFactory::icon("psi/start-chat"));
689#endif
690
691 /*QBrush brush;
692 brush.setPixmap( QPixmap( option.chatBgImage ) );
693 d->log->setPaper(brush);
694 d->log->setStaticBackground(true);*/
695
696#if QT_VERSION >= 0x030300
697 setWindowOpacity(double(option.chatOpacity)/100);
698#endif
699}
700
701void ChatDlg::optionsUpdate()
702{
703 if (option.oldSmallChats!=option.smallChats)
704 {
705 d->smallChat=option.smallChats;
706 }
707
708 setLooks();
709
710 if(isHidden()) {
711 if(option.delChats == dcClose) {
712 deleteLater();
713 return;
714 }
715 else {
716 if(option.delChats == dcHour)
717 setSelfDestruct(60);
718 else if(option.delChats == dcDay)
719 setSelfDestruct(60 * 24);
720 else
721 setSelfDestruct(0);
722 }
723 }
724}
725
726void ChatDlg::updatePGP()
727{
728 if(!d->pa->pgpKey().isEmpty()) {
729 d->act_pgp->setEnabled(true);
730 }
731 else {
732 d->act_pgp->setOn(false);
733 d->act_pgp->setEnabled(false);
734 }
735}
736
737void ChatDlg::doInfo()
738{
739 aInfo(d->jid);
740}
741
742void ChatDlg::doHistory()
743{
744 aHistory(d->jid);
745}
746
747void ChatDlg::doFile()
748{
749 aFile(d->jid);
750}
751
752void ChatDlg::doClearButton()
753{
754 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"));
755 if(n == 0)
756 doClear();
757}
758
759void ChatDlg::doClear()
760{
761 d->log->setText("");
762}
763
764void ChatDlg::setKeepOpenFalse()
765{
766 d->keepOpen = false;
767}
768
769void ChatDlg::setWarnSendFalse()
770{
771 d->warnSend = false;
772}
773
774void ChatDlg::setSelfDestruct(int minutes)
775{
776 if(minutes <= 0) {
777 if(d->selfDestruct) {
778 delete d->selfDestruct;
779 d->selfDestruct = 0;
780 }
781 return;
782 }
783
784 if(!d->selfDestruct) {
785 d->selfDestruct = new QTimer(this);
786 connect(d->selfDestruct, SIGNAL(timeout()), SLOT(deleteLater()));
787 }
788
789 d->selfDestruct->start(minutes * 60000);
790}
791
792void ChatDlg::updateCaption()
793{
794 QString cap = "";
795
796 if(d->pending > 0) {
797 cap += "* ";
798 if(d->pending > 1)
799 cap += QString("[%1] ").arg(d->pending);
800 }
801 cap += d->dispNick;
802
803 if (d->contactIsComposing)
804 cap = tr("%1 (Composing ...)").arg(cap);
805
806 setCaption(cap);
807
808 emit captionChanged(this);
809 emit unreadMessageUpdate(this, d->pending);
810}
811
812void ChatDlg::doSend()
813{
814 if(!d->mle->isEnabled())
815 return;
816
817 if(d->mle->text().isEmpty())
818 return;
819
820 if(d->mle->text() == "/clear") {
821 d->mle->setText("");
822 doClear();
823 return;
824 }
825
826 if(!d->pa->loggedIn())
827 return;
828
829 if(d->warnSend) {
830 d->warnSend = false;
831 int n = QMessageBox::information(this, tr("Warning"), tr(
832 "<p>Encryption was recently disabled by the remote contact. "
833 "Are you sure you want to send this message without encryption?</p>"
834 ), tr("&Yes"), tr("&No"));
835 if(n != 0)
836 return;
837 }
838
839 Message m(d->jid);
840 m.setType("chat");
841 m.setBody(d->mle->text());
842 m.setTimeStamp(QDateTime::currentDateTime());
843 if(d->act_pgp->isOn())
844 m.setWasEncrypted(true);
845 d->m = m;
846
847 // Request events
848 if (option.messageEvents) {
849 //m.addEvent(OfflineEvent);
850 //m.addEvent(DeliveredEvent);
851 //m.addEvent(DisplayedEvent);
852 m.addEvent(ComposingEvent);
853 }
854
855 if(d->act_pgp->isOn()) {
856 d->mle->setEnabled(false);
857 d->transid = d->pa->sendMessageEncrypted(m);
858 if(d->transid == -1) {
859 d->mle->setEnabled(true);
860 d->mle->setFocus();
861 return;
862 }
863 }
864 else {
865 aSend(m);
866 doneSend();
867 }
868}
869
870void ChatDlg::doneSend()
871{
872 appendMessage(d->m, true);
873 disconnect(d->mle, SIGNAL(textChanged()), d, SLOT(setComposing()));
874 d->mle->setText("");
875
876 // Reset composing timer
877 connect(d->mle, SIGNAL(textChanged()), d, SLOT(setComposing()));
878 if (d->composingTimer) {
879 delete d->composingTimer;
880 d->composingTimer = 0;
881 d->isComposing = false;
882 }
883}
884
885void ChatDlg::encryptedMessageSent(int x, bool b)
886{
887 if(d->transid == -1)
888 return;
889 if(d->transid != x)
890 return;
891 d->transid = -1;
892 if(b)
893 doneSend();
894 else
895 QMessageBox::information(this, tr("Error"), tr("There was an error trying to send the message encrypted.\nCheck your OpenPGP application/settings."));
896 d->mle->setEnabled(true);
897 d->mle->setFocus();
898}
899
900void ChatDlg::incomingMessage(const Message &m)
901{
902 if (m.body().isEmpty()) {
903 /* Event message */
904 if (m.containsEvent(CancelEvent))
905 updateContactIsComposing(false);
906 if (m.containsEvent(ComposingEvent))
907 updateContactIsComposing(true);
908 }
909 else {
910 // Normal message
911 // Check if user requests event messages
912 d->sendComposingEvents = m.containsEvent(ComposingEvent);
913 if (!m.eventId().isEmpty())
914 d->eventId = m.eventId();
915 updateContactIsComposing(false);
916 appendMessage(m);
917 }
918}
919
920void ChatDlg::appendSysMsg(const QString &str)
921{
922 QString timestr;
923 QDateTime time = QDateTime::currentDateTime();
924 //timestr.sprintf("%02d:%02d:%02d", time.time().hour(), time.time().minute(), time.time().second());
925 timestr = time.time().toString(LocalDate);
926
927 int y = d->log->contentsHeight() - d->log->visibleHeight();
928 if(y < 0)
929 y = 0;
930 bool atBottom = (d->log->contentsY() < y - 32) ? false: true;
931
932 d->log->append(QString("<font color=\"#00A000\">[%1]").arg(timestr) + QString(" *** %1</font>").arg(str));
933 if(atBottom)
934 deferredScroll();
935}
936
937void ChatDlg::appendMessage(const Message &m, bool local)
938{
939 QString who, color;
940
941 if(local) {
942 who = d->pa->nick();
943 color = "#FF0000";
944 }
945 else {
946 who = d->dispNick;
947 color = "#0000FF";
948 }
949 if(m.spooled())
950 color = "#008000";
951
952 // figure out the encryption state
953 bool encChanged = false;
954 bool encEnabled = false;
955 if(d->lastWasEncrypted != m.wasEncrypted())
956 encChanged = true;
957 d->lastWasEncrypted = m.wasEncrypted();
958 encEnabled = d->lastWasEncrypted;
959
960 if(encChanged) {
961 if(encEnabled) {
962 appendSysMsg(QString("<icon name=\"psi/cryptoYes\"> ") + tr("Encryption Enabled"));
963 if(!local)
964 d->act_pgp->setOn(true);
965 }
966 else {
967 appendSysMsg(QString("<icon name=\"psi/cryptoNo\"> ") + tr("Encryption Disabled"));
968 if(!local) {
969 d->act_pgp->setOn(false);
970
971 // enable warning
972 d->warnSend = true;
973 QTimer::singleShot(3000, this, SLOT(setWarnSendFalse()));
974 }
975 }
976 }
977
978 QString timestr;
979 QDateTime time = m.timeStamp();
980 timestr = time.time().toString(LocalDate);
981
982 int y = d->log->contentsHeight() - d->log->visibleHeight();
983 if(y < 0)
984 y = 0;
985 bool atBottom = (d->log->contentsY() < y - 32) ? false: true;
986
987 bool emote = false;
988 if(m.body().left(4) == "/me ")
989 emote = true;
990
991 QString txt;
992 if(emote)
993 txt = plain2rich(m.body().mid(4));
994 else
995 txt = plain2rich(m.body());
996
997 txt = linkify(txt);
998
999 if(option.useEmoticons)
1000 txt = emoticonify(txt);
1001
1002 who = expandEntities(who);
1003
1004 if(emote) {
1005 d->log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1]").arg(timestr) + QString(" *%1 ").arg(who) + txt + "</font>");
1006 }
1007 else {
1008 if(option.chatSays)
1009 d->log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1] ").arg(timestr) + tr("%1 says:").arg(who) + "</font><br>" + txt);
1010 else
1011 d->log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1] &lt;").arg(timestr) + who + QString("&gt;</font> ") + txt);
1012 }
1013 if(!m.subject().isEmpty()) {
1014 d->log->append(QString("<b>") + tr("Subject:") + "</b> " + QString("%1").arg(expandEntities(m.subject())));
1015 }
1016 if(!m.urlList().isEmpty()) {
1017 UrlList urls = m.urlList();
1018 d->log->append(QString("<i>") + tr("-- Attached URL(s) --") + "</i>");
1019 for(QValueList<Url>::ConstIterator it = urls.begin(); it != urls.end(); ++it) {
1020 const Url &u = *it;
1021 d->log->append(QString("<b>") + tr("URL:") + "</b> " + QString("%1").arg( linkify(expandEntities(u.url())) ));
1022 d->log->append(QString("<b>") + tr("Desc:") + "</b> " + QString("%1").arg(u.desc()));
1023 }
1024 }
1025
1026 if(local || atBottom)
1027 deferredScroll();
1028
1029 TabDlg *dlg = NULL;
1030 AdvancedWidget<QWidget> *atlw = this;
1031 if (d->pa->psi()->isChatTabbed(this)) {
1032 dlg = d->pa->psi()->getManagingTabs(this);
1033 atlw = dlg;
1034 }
1035
1036 // if we're not active, notify the user by changing the title
1037 if(!atlw->isActiveWindow() || (dlg && !dlg->chatOnTop(this))) {
1038 // select the chat tab only if the Raise option is set but not when
1039 // the tab dialog is already active (so that the user is typing to
1040 // some other chat tab). Note that we do that before updateCaption()
1041 // because selectTab() will remove any title emphasis (asterisk, etc)
1042 if(option.raiseChatWindow && dlg && !dlg->isActiveWindow())
1043 dlg->selectTab(this);
1044 ++d->pending;
1045 updateCaption();
1046 if(option.raiseChatWindow)
1047 bringToFront(atlw, false);
1048 else
1049 atlw->doFlash(true);
1050 }
1051 //else {
1052 // messagesRead(d->jid);
1053 //}
1054
1055 if(!local) {
1056 d->keepOpen = true;
1057 QTimer::singleShot(1000, this, SLOT(setKeepOpenFalse()));
1058 }
1059}
1060
1061void ChatDlg::deferredScroll()
1062{
1063 QTimer::singleShot(250, this, SLOT(slotScroll()));
1064}
1065
1066void ChatDlg::slotScroll()
1067{
1068 d->log->scrollToBottom();
1069}
1070
1071void ChatDlg::updateIsComposing(bool c)
1072{
1073 if (option.messageEvents && d->sendComposingEvents) {
1074 // Don't send to offline resource
1075 QPtrList<UserListItem> ul = d->pa->findRelavent(d->jid);
1076 if(ul.isEmpty()) {
1077 d->sendComposingEvents = false;
1078 return;
1079 }
1080
1081 QString rname = d->jid.resource();
1082 UserListItem *u = ul.first();
1083 if(rname.isEmpty() && !u->isAvailable() || u->userResourceList().find(rname) == u->userResourceList().end()) {
1084 d->sendComposingEvents = false;
1085 return;
1086 }
1087
1088 // Send event message
1089 Message m(d->jid);
1090 m.setEventId(d->eventId);
1091 if (c) {
1092 m.addEvent(ComposingEvent);
1093 }
1094 else {
1095 m.addEvent(CancelEvent);
1096 }
1097 d->pa->dj_sendMessage(m, false);
1098 }
1099}
1100
1101void ChatDlg::updateContactIsComposing(bool c)
1102{
1103 d->contactIsComposing = c;
1104 emit contactIsComposing(this, c);
1105 updateCaption();
1106 /*if (c)
1107 {
1108 d->lb_composing->setText(QString(d->dispNick+" "+tr("is composing a reply.")));
1109 d->lb_composing->show();
1110 }
1111 else
1112 {
1113 d->lb_composing->hide();
1114 }*/
1115}
1116
1117void ChatDlg::toggleSmallChat()
1118{
1119 d->smallChat = !d->smallChat;
1120 setLooks();
1121}
1122
1123void ChatDlg::toggleEncryption()
1124{
1125 d->act_pgp->setOn( !d->act_pgp->isOn() );
1126}
1127
1128void ChatDlg::buildMenu()
1129{
1130 // Dialog menu
1131 d->pm_settings->clear();
1132 d->act_compact->addTo( d->pm_settings );
1133 d->act_clear->addTo( d->pm_settings );
1134 d->pm_settings->insertSeparator();
1135
1136 d->act_icon->addTo( d->pm_settings );
1137 d->act_file->addTo( d->pm_settings );
1138 d->act_pgp->addTo( d->pm_settings );
1139 d->pm_settings->insertSeparator();
1140
1141 d->act_info->addTo( d->pm_settings );
1142 d->act_history->addTo( d->pm_settings );
1143}
1144
1145#include "chatdlg.moc"
Note: See TracBrowser for help on using the repository browser.