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

Last change on this file since 14 was 2, checked in by dmik, 19 years ago

Imported original Psi 0.10 sources from Affinix

File size: 26.3 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 && !option.useTabs)
362 close();
363//#ifdef Q_WS_MAC //this is a standard on other platforms too
364 else if(e->key() == Key_W && e->state() & ControlButton && !option.useTabs)
365 close();
366//#endif
367 else if(e->key() == Key_Return || e->key() == Key_Enter || (e->key() == Key_S && (e->state() & AltButton)))
368 doSend();
369 else if(e->key() == Key_PageUp && (e->state() & ShiftButton))
370 d->log->setContentsPos(d->log->contentsX(), d->log->contentsY() - d->log->visibleHeight()/2);
371 else if(e->key() == Key_PageDown && (e->state() & ShiftButton))
372 d->log->setContentsPos(d->log->contentsX(), d->log->contentsY() + d->log->visibleHeight()/2);
373 else
374 e->ignore();
375}
376
377void ChatDlg::resizeEvent(QResizeEvent *e)
378{
379 if(option.keepSizes)
380 option.sizeChatDlg = e->size();
381}
382
383void ChatDlg::closeEvent(QCloseEvent *e)
384{
385 // really lame way of checking if we are encrypting
386 if(!d->mle->isEnabled())
387 return;
388
389 if(d->keepOpen) {
390 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"));
391 if(n != 0)
392 return;
393 }
394
395 // destroy the dialog if delChats is dcClose
396 if(option.delChats == dcClose)
397 setWFlags(getWFlags() | WDestructiveClose);
398 else {
399 if(option.delChats == dcHour)
400 setSelfDestruct(60);
401 else if(option.delChats == dcDay)
402 setSelfDestruct(60 * 24);
403 }
404
405 // Reset 'contact is composing' & cancel own composing event
406 updateContactIsComposing(false);
407 if (d->composingTimer) {
408 delete d->composingTimer;
409 d->composingTimer = 0;
410 d->isComposing = false;
411 updateIsComposing(false);
412 }
413
414
415 if(d->pending > 0) {
416 d->pending = 0;
417 messagesRead(d->jid);
418 updateCaption();
419 }
420 doFlash(false);
421
422 d->mle->setFocus();
423 e->accept();
424}
425
426void ChatDlg::showEvent(QShowEvent *)
427{
428 setSelfDestruct(0);
429}
430
431void ChatDlg::windowActivationChange(bool oldstate)
432{
433 QWidget::windowActivationChange(oldstate);
434
435 // if we're bringing it to the front, get rid of the '*' if necessary
436 if( isActiveWindow() && !isHidden() ) { //this is a tab hack
437 activated();
438 }
439}
440
441void ChatDlg::logSelectionChanged()
442{
443#ifdef Q_WS_MAC
444 // A hack to only give the message log focus when text is selected
445 if (d->log->hasSelectedText())
446 d->log->setFocus();
447 else
448 d->mle->setFocus();
449#endif
450}
451
452void ChatDlg::activated()
453{
454 if(d->pending > 0) {
455 d->pending = 0;
456 messagesRead(d->jid);
457 updateCaption();
458 }
459 doFlash(false);
460
461 if(option.showCounter && !d->smallChat)
462 d->lcd_count->show();
463 else
464 d->lcd_count->hide();
465
466 d->mle->setFocus();
467}
468
469
470void ChatDlg::dropEvent(QDropEvent* event)
471{
472 QStringList l;
473 if (d->pa->loggedIn() && QUriDrag::decodeLocalFiles(event,l) && !l.isEmpty())
474 d->pa->actionSendFiles(d->jid,l);
475}
476
477void ChatDlg::dragEnterEvent(QDragEnterEvent* event)
478{
479 QStringList l;
480 event->accept(d->pa->loggedIn() && QUriDrag::canDecode(event) && QUriDrag::decodeLocalFiles(event,l) && !l.isEmpty());
481}
482
483
484const Jid & ChatDlg::jid() const
485{
486 return d->jid;
487}
488
489void ChatDlg::setJid(const Jid &jid)
490{
491 if(!jid.compare(d->jid)) {
492 d->pa->dialogUnregister(this);
493 d->jid = jid;
494 d->pa->dialogRegister(this, d->jid);
495 updateContact(d->jid, false);
496 }
497}
498
499const QString& ChatDlg::getDisplayNick()
500{
501 return d->dispNick;
502}
503
504QSize ChatDlg::defaultSize()
505{
506 return QSize(320, 280);
507}
508
509void ChatDlg::updateContact(const Jid &jid, bool fromPresence)
510{
511 // if groupchat, only update if the resource matches
512 if(d->pa->findGCContact(jid) && !d->jid.compare(jid))
513 return;
514
515 if(d->jid.compare(jid, false)) {
516 QString rname = d->jid.resource();
517 QPtrList<UserListItem> ul = d->pa->findRelavent(jid);
518 UserListItem *u = 0;
519 int status = -1;
520 QString statusString, key;
521 if(!ul.isEmpty()) {
522 u = ul.first();
523 if(rname.isEmpty()) {
524 // use priority
525 if(!u->isAvailable())
526 status = STATUS_OFFLINE;
527 else {
528 const UserResource &r = *u->userResourceList().priority();
529 status = makeSTATUS(r.status());
530 statusString = r.status().status();
531 key = r.publicKeyID();
532 }
533 }
534 else {
535 // use specific
536 UserResourceList::ConstIterator rit = u->userResourceList().find(rname);
537 if(rit != u->userResourceList().end()) {
538 status = makeSTATUS((*rit).status());
539 statusString = (*rit).status().status();
540 key = (*rit).publicKeyID();
541 }
542 else {
543 status = STATUS_OFFLINE;
544 statusString = u->lastUnavailableStatus().status();
545 key = "";
546 }
547 }
548 }
549
550 bool statusChanged = false;
551 if(d->status != status || d->statusString != statusString) {
552 statusChanged = true;
553 d->status = status;
554 d->statusString = statusString;
555 }
556
557 if(statusChanged) {
558 if(status == -1 || !u)
559 d->lb_status->setIcon(IconsetFactory::iconPtr("status/noauth"));
560 else
561 d->lb_status->setIcon(is->statusPtr(jid, status));
562 }
563
564 if(u)
565 QToolTip::add(d->lb_status, u->makeTip(true, false));
566 else
567 QToolTip::remove(d->lb_status);
568
569 if(u) {
570 QString name;
571 QString j;
572 if(rname.isEmpty())
573 j = u->jid().full();
574 else
575 j = u->jid().userHost() + '/' + rname;
576
577 if(!u->name().isEmpty())
578 name = u->name() + QString(" <%1>").arg(j);
579 else
580 name = j;
581
582 d->le_jid->setText(name);
583 d->le_jid->setCursorPosition(0);
584 QToolTip::add(d->le_jid, name);
585
586 d->dispNick = jidnick(u->jid().full(), u->name());
587 updateCaption();
588
589 d->key = key;
590 updatePGP();
591
592 if(fromPresence && statusChanged) {
593 QString msg = tr("%1 is %2").arg(expandEntities(d->dispNick)).arg(status2txt(d->status));
594 if(!statusString.isEmpty()) {
595 QString ss = linkify(plain2rich(statusString));
596 if(option.useEmoticons)
597 ss = emoticonify(ss);
598
599 msg += QString(" [%1]").arg(ss);
600 }
601 appendSysMsg(msg);
602 }
603 }
604
605 // Reset 'is composing' event if the status changed
606 if (statusChanged) {
607 updateContactIsComposing(false);
608 }
609 }
610}
611
612
613void ChatDlg::updateAvatar(const Jid& j)
614{
615 if (j.compare(d->jid,false))
616 updateAvatar();
617}
618
619void ChatDlg::updateAvatar()
620{
621#ifdef AVATARS
622 QString res;
623 QString client;
624
625 if (option.chatLineEdit || !option.avatarsEnabled || !option.avatarsChatdlgEnabled) {
626 d->avatar->hide();
627 return;
628 }
629
630 UserListItem *ul = d->pa->findFirstRelavent(d->jid);
631 if (ul && !ul->userResourceList().isEmpty()) {
632 UserResourceList::Iterator it = ul->userResourceList().find(d->jid.resource());
633 if(it == ul->userResourceList().end())
634 it = ul->userResourceList().priority();
635
636 res = (*it).name();
637 client = (*it).clientName();
638 }
639 QPixmap p = d->pa->avatarFactory()->getAvatar(d->jid.withResource(res),client);
640 if (p.isNull()) {
641 d->avatar->hide();
642 }
643 else {
644 d->avatar->setPixmap(p);
645 d->avatar->show();
646 }
647#endif
648}
649
650
651void ChatDlg::setLooks()
652{
653 // update the font
654 QFont f;
655 f.fromString(option.font[fChat]);
656 d->log->setFont(f);
657 d->mle->setFont(f);
658
659 if (d->smallChat) {
660 d->lb_ident->hide();
661 d->lb_status->hide();
662 d->le_jid->hide();
663 d->toolbar->hide();
664 }
665 else {
666 d->lb_ident->show();
667 d->lb_status->show();
668 d->le_jid->show();
669 d->toolbar->show();
670 }
671
672 if ( option.showCounter && !d->smallChat )
673 d->lcd_count->show();
674 else
675 d->lcd_count->hide();
676
677 // update contact info
678 d->status = -2; // sick way of making it redraw the status
679 updateContact(d->jid, false);
680
681 // toolbuttons
682 QIconSet i;
683 i.setPixmap(IconsetFactory::icon("psi/cryptoNo"), QIconSet::Automatic, QIconSet::Normal, QIconSet::Off);
684 i.setPixmap(IconsetFactory::icon("psi/cryptoYes"), QIconSet::Automatic, QIconSet::Normal, QIconSet::On);
685 d->act_pgp->setIcon( 0 );
686 d->act_pgp->setIconSet( i );
687
688 // update the widget icon
689#ifndef Q_WS_MAC
690 setIcon(IconsetFactory::icon("psi/start-chat"));
691#endif
692
693 /*QBrush brush;
694 brush.setPixmap( QPixmap( option.chatBgImage ) );
695 d->log->setPaper(brush);
696 d->log->setStaticBackground(true);*/
697
698#if QT_VERSION >= 0x030300
699 setWindowOpacity(double(option.chatOpacity)/100);
700#endif
701}
702
703void ChatDlg::optionsUpdate()
704{
705 if (option.oldSmallChats!=option.smallChats)
706 {
707 d->smallChat=option.smallChats;
708 }
709
710 setLooks();
711
712 if(isHidden()) {
713 if(option.delChats == dcClose) {
714 deleteLater();
715 return;
716 }
717 else {
718 if(option.delChats == dcHour)
719 setSelfDestruct(60);
720 else if(option.delChats == dcDay)
721 setSelfDestruct(60 * 24);
722 else
723 setSelfDestruct(0);
724 }
725 }
726}
727
728void ChatDlg::updatePGP()
729{
730 if(!d->pa->pgpKey().isEmpty()) {
731 d->act_pgp->setEnabled(true);
732 }
733 else {
734 d->act_pgp->setOn(false);
735 d->act_pgp->setEnabled(false);
736 }
737}
738
739void ChatDlg::doInfo()
740{
741 aInfo(d->jid);
742}
743
744void ChatDlg::doHistory()
745{
746 aHistory(d->jid);
747}
748
749void ChatDlg::doFile()
750{
751 aFile(d->jid);
752}
753
754void ChatDlg::doClearButton()
755{
756 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"));
757 if(n == 0)
758 doClear();
759}
760
761void ChatDlg::doClear()
762{
763 d->log->setText("");
764}
765
766void ChatDlg::setKeepOpenFalse()
767{
768 d->keepOpen = false;
769}
770
771void ChatDlg::setWarnSendFalse()
772{
773 d->warnSend = false;
774}
775
776void ChatDlg::setSelfDestruct(int minutes)
777{
778 if(minutes <= 0) {
779 if(d->selfDestruct) {
780 delete d->selfDestruct;
781 d->selfDestruct = 0;
782 }
783 return;
784 }
785
786 if(!d->selfDestruct) {
787 d->selfDestruct = new QTimer(this);
788 connect(d->selfDestruct, SIGNAL(timeout()), SLOT(deleteLater()));
789 }
790
791 d->selfDestruct->start(minutes * 60000);
792}
793
794void ChatDlg::updateCaption()
795{
796 QString cap = "";
797
798 if(d->pending > 0) {
799 cap += "* ";
800 if(d->pending > 1)
801 cap += QString("[%1] ").arg(d->pending);
802 }
803 cap += d->dispNick;
804
805 if (d->contactIsComposing)
806 cap = tr("%1 (Composing ...)").arg(cap);
807
808 setCaption(cap);
809
810 emit captionChanged(this);
811 emit unreadMessageUpdate(this, d->pending);
812}
813
814void ChatDlg::doSend()
815{
816 if(!d->mle->isEnabled())
817 return;
818
819 if(d->mle->text().isEmpty())
820 return;
821
822 if(d->mle->text() == "/clear") {
823 d->mle->setText("");
824 doClear();
825 return;
826 }
827
828 if(!d->pa->loggedIn())
829 return;
830
831 if(d->warnSend) {
832 d->warnSend = false;
833 int n = QMessageBox::information(this, tr("Warning"), tr(
834 "<p>Encryption was recently disabled by the remote contact. "
835 "Are you sure you want to send this message without encryption?</p>"
836 ), tr("&Yes"), tr("&No"));
837 if(n != 0)
838 return;
839 }
840
841 Message m(d->jid);
842 m.setType("chat");
843 m.setBody(d->mle->text());
844 m.setTimeStamp(QDateTime::currentDateTime());
845 if(d->act_pgp->isOn())
846 m.setWasEncrypted(true);
847 d->m = m;
848
849 // Request events
850 if (option.messageEvents) {
851 //m.addEvent(OfflineEvent);
852 //m.addEvent(DeliveredEvent);
853 //m.addEvent(DisplayedEvent);
854 m.addEvent(ComposingEvent);
855 }
856
857 if(d->act_pgp->isOn()) {
858 d->mle->setEnabled(false);
859 d->transid = d->pa->sendMessageEncrypted(m);
860 if(d->transid == -1) {
861 d->mle->setEnabled(true);
862 d->mle->setFocus();
863 return;
864 }
865 }
866 else {
867 aSend(m);
868 doneSend();
869 }
870}
871
872void ChatDlg::doneSend()
873{
874 appendMessage(d->m, true);
875 disconnect(d->mle, SIGNAL(textChanged()), d, SLOT(setComposing()));
876 d->mle->setText("");
877
878 // Reset composing timer
879 connect(d->mle, SIGNAL(textChanged()), d, SLOT(setComposing()));
880 if (d->composingTimer) {
881 delete d->composingTimer;
882 d->composingTimer = 0;
883 d->isComposing = false;
884 }
885}
886
887void ChatDlg::encryptedMessageSent(int x, bool b)
888{
889 if(d->transid == -1)
890 return;
891 if(d->transid != x)
892 return;
893 d->transid = -1;
894 if(b)
895 doneSend();
896 else
897 QMessageBox::information(this, tr("Error"), tr("There was an error trying to send the message encrypted.\nCheck your OpenPGP application/settings."));
898 d->mle->setEnabled(true);
899 d->mle->setFocus();
900}
901
902void ChatDlg::incomingMessage(const Message &m)
903{
904 if (m.body().isEmpty()) {
905 /* Event message */
906 if (m.containsEvent(CancelEvent))
907 updateContactIsComposing(false);
908 if (m.containsEvent(ComposingEvent))
909 updateContactIsComposing(true);
910 }
911 else {
912 // Normal message
913 // Check if user requests event messages
914 d->sendComposingEvents = m.containsEvent(ComposingEvent);
915 if (!m.eventId().isEmpty())
916 d->eventId = m.eventId();
917 updateContactIsComposing(false);
918 appendMessage(m);
919 }
920}
921
922void ChatDlg::appendSysMsg(const QString &str)
923{
924 QString timestr;
925 QDateTime time = QDateTime::currentDateTime();
926 //timestr.sprintf("%02d:%02d:%02d", time.time().hour(), time.time().minute(), time.time().second());
927 timestr = time.time().toString(LocalDate);
928
929 int y = d->log->contentsHeight() - d->log->visibleHeight();
930 if(y < 0)
931 y = 0;
932 bool atBottom = (d->log->contentsY() < y - 32) ? false: true;
933
934 d->log->append(QString("<font color=\"#00A000\">[%1]").arg(timestr) + QString(" *** %1</font>").arg(str));
935 if(atBottom)
936 deferredScroll();
937}
938
939void ChatDlg::appendMessage(const Message &m, bool local)
940{
941 QString who, color;
942
943 if(local) {
944 who = d->pa->nick();
945 color = "#FF0000";
946 }
947 else {
948 who = d->dispNick;
949 color = "#0000FF";
950 }
951 if(m.spooled())
952 color = "#008000";
953
954 // figure out the encryption state
955 bool encChanged = false;
956 bool encEnabled = false;
957 if(d->lastWasEncrypted != m.wasEncrypted())
958 encChanged = true;
959 d->lastWasEncrypted = m.wasEncrypted();
960 encEnabled = d->lastWasEncrypted;
961
962 if(encChanged) {
963 if(encEnabled) {
964 appendSysMsg(QString("<icon name=\"psi/cryptoYes\"> ") + tr("Encryption Enabled"));
965 if(!local)
966 d->act_pgp->setOn(true);
967 }
968 else {
969 appendSysMsg(QString("<icon name=\"psi/cryptoNo\"> ") + tr("Encryption Disabled"));
970 if(!local) {
971 d->act_pgp->setOn(false);
972
973 // enable warning
974 d->warnSend = true;
975 QTimer::singleShot(3000, this, SLOT(setWarnSendFalse()));
976 }
977 }
978 }
979
980 QString timestr;
981 QDateTime time = m.timeStamp();
982 timestr = time.time().toString(LocalDate);
983
984 int y = d->log->contentsHeight() - d->log->visibleHeight();
985 if(y < 0)
986 y = 0;
987 bool atBottom = (d->log->contentsY() < y - 32) ? false: true;
988
989 bool emote = false;
990 if(m.body().left(4) == "/me ")
991 emote = true;
992
993 QString txt;
994 if(emote)
995 txt = plain2rich(m.body().mid(4));
996 else
997 txt = plain2rich(m.body());
998
999 txt = linkify(txt);
1000
1001 if(option.useEmoticons)
1002 txt = emoticonify(txt);
1003
1004 who = expandEntities(who);
1005
1006 if(emote) {
1007 d->log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1]").arg(timestr) + QString(" *%1 ").arg(who) + txt + "</font>");
1008 }
1009 else {
1010 if(option.chatSays)
1011 d->log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1] ").arg(timestr) + tr("%1 says:").arg(who) + "</font><br>" + txt);
1012 else
1013 d->log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1] &lt;").arg(timestr) + who + QString("&gt;</font> ") + txt);
1014 }
1015 if(!m.subject().isEmpty()) {
1016 d->log->append(QString("<b>") + tr("Subject:") + "</b> " + QString("%1").arg(expandEntities(m.subject())));
1017 }
1018 if(!m.urlList().isEmpty()) {
1019 UrlList urls = m.urlList();
1020 d->log->append(QString("<i>") + tr("-- Attached URL(s) --") + "</i>");
1021 for(QValueList<Url>::ConstIterator it = urls.begin(); it != urls.end(); ++it) {
1022 const Url &u = *it;
1023 d->log->append(QString("<b>") + tr("URL:") + "</b> " + QString("%1").arg( linkify(expandEntities(u.url())) ));
1024 d->log->append(QString("<b>") + tr("Desc:") + "</b> " + QString("%1").arg(u.desc()));
1025 }
1026 }
1027
1028 if(local || atBottom)
1029 deferredScroll();
1030
1031 // if we're not active, notify the user by changing the title
1032 if(!isActiveWindow() || isHidden()) { //isHidden==tab hack
1033 ++d->pending;
1034 updateCaption();
1035 doFlash(true);
1036 if(option.raiseChatWindow)
1037 bringToFront(this, false);
1038 }
1039 //else {
1040 // messagesRead(d->jid);
1041 //}
1042
1043 if(!local) {
1044 d->keepOpen = true;
1045 QTimer::singleShot(1000, this, SLOT(setKeepOpenFalse()));
1046 }
1047}
1048
1049void ChatDlg::deferredScroll()
1050{
1051 QTimer::singleShot(250, this, SLOT(slotScroll()));
1052}
1053
1054void ChatDlg::slotScroll()
1055{
1056 d->log->scrollToBottom();
1057}
1058
1059void ChatDlg::updateIsComposing(bool c)
1060{
1061 if (option.messageEvents && d->sendComposingEvents) {
1062 // Don't send to offline resource
1063 QPtrList<UserListItem> ul = d->pa->findRelavent(d->jid);
1064 if(ul.isEmpty()) {
1065 d->sendComposingEvents = false;
1066 return;
1067 }
1068
1069 QString rname = d->jid.resource();
1070 UserListItem *u = ul.first();
1071 if(rname.isEmpty() && !u->isAvailable() || u->userResourceList().find(rname) == u->userResourceList().end()) {
1072 d->sendComposingEvents = false;
1073 return;
1074 }
1075
1076 // Send event message
1077 Message m(d->jid);
1078 m.setEventId(d->eventId);
1079 if (c) {
1080 m.addEvent(ComposingEvent);
1081 }
1082 else {
1083 m.addEvent(CancelEvent);
1084 }
1085 d->pa->dj_sendMessage(m, false);
1086 }
1087}
1088
1089void ChatDlg::updateContactIsComposing(bool c)
1090{
1091 d->contactIsComposing = c;
1092 emit contactIsComposing(this, c);
1093 updateCaption();
1094 /*if (c)
1095 {
1096 d->lb_composing->setText(QString(d->dispNick+" "+tr("is composing a reply.")));
1097 d->lb_composing->show();
1098 }
1099 else
1100 {
1101 d->lb_composing->hide();
1102 }*/
1103}
1104
1105void ChatDlg::toggleSmallChat()
1106{
1107 d->smallChat = !d->smallChat;
1108 setLooks();
1109}
1110
1111void ChatDlg::toggleEncryption()
1112{
1113 d->act_pgp->setOn( !d->act_pgp->isOn() );
1114}
1115
1116void ChatDlg::buildMenu()
1117{
1118 // Dialog menu
1119 d->pm_settings->clear();
1120 d->act_compact->addTo( d->pm_settings );
1121 d->act_clear->addTo( d->pm_settings );
1122 d->pm_settings->insertSeparator();
1123
1124 d->act_icon->addTo( d->pm_settings );
1125 d->act_file->addTo( d->pm_settings );
1126 d->act_pgp->addTo( d->pm_settings );
1127 d->pm_settings->insertSeparator();
1128
1129 d->act_info->addTo( d->pm_settings );
1130 d->act_history->addTo( d->pm_settings );
1131}
1132
1133#include "chatdlg.moc"
Note: See TracBrowser for help on using the repository browser.