source: psi/trunk/src/groupchatdlg.cpp@ 148

Last change on this file since 148 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: 35.3 KB
Line 
1/*
2 * groupchatdlg.cpp - dialogs for handling groupchat
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"groupchatdlg.h"
22
23#include<qlabel.h>
24#include<qlayout.h>
25#include<qpushbutton.h>
26#include<qgroupbox.h>
27#include<qmessagebox.h>
28#include<qtextedit.h>
29#include<qsplitter.h>
30#include<qtimer.h>
31#include<qheader.h>
32#include<qtoolbutton.h>
33#include<qinputdialog.h>
34#include<qguardedptr.h>
35#include<qaction.h>
36#include<qobjectlist.h>
37#include<qpopupmenu.h>
38#include<qcursor.h>
39#include"psicon.h"
40#include"psiaccount.h"
41#include"userlist.h"
42#include"statusdlg.h"
43#include"busywidget.h"
44#include"common.h"
45#include"msgmle.h"
46#include"iconwidget.h"
47#include"iconselect.h"
48#include"psitoolbar.h"
49#include"iconaction.h"
50
51#ifdef Q_WS_WIN
52#include<windows.h>
53#endif
54
55
56//----------------------------------------------------------------------------
57// GCJoinDlg
58//----------------------------------------------------------------------------
59class GCJoinDlg::Private
60{
61public:
62 Private() {}
63
64 PsiCon *psi;
65 PsiAccount *pa;
66 AccountsComboBox *cb_ident;
67 BusyWidget *busy;
68 QStringList rl;
69 Jid jid;
70};
71
72GCJoinDlg::GCJoinDlg(PsiCon *psi, PsiAccount *pa)
73:GCJoinUI(0, 0, false, WDestructiveClose)
74{
75 d = new Private;
76 d->psi = psi;
77 d->pa = 0;
78 d->psi->dialogRegister(this);
79 d->busy = busy;
80
81 updateIdentity(pa);
82
83 d->cb_ident = d->psi->accountsComboBox(this,true);
84 connect(d->cb_ident, SIGNAL(activated(PsiAccount *)), SLOT(updateIdentity(PsiAccount *)));
85 d->cb_ident->setAccount(pa);
86 replaceWidget(lb_ident, d->cb_ident);
87
88 d->rl = d->psi->recentGCList();
89 for(QStringList::ConstIterator it = d->rl.begin(); it != d->rl.end(); ++it) {
90 Jid j(*it);
91 QString s = tr("%1 on %2").arg(j.resource()).arg(j.userHost());
92 cb_recent->insertItem(s);
93 }
94
95 setCaption(CAP(caption()));
96 pb_join->setDefault(true);
97 connect(pb_close, SIGNAL(clicked()), SLOT(close()));
98 connect(pb_join, SIGNAL(clicked()), SLOT(doJoin()));
99 connect(cb_recent, SIGNAL(activated(int)), SLOT(recent_activated(int)));
100 if(d->rl.isEmpty()) {
101 cb_recent->setEnabled(false);
102 le_host->setFocus();
103 }
104 else
105 recent_activated(0);
106}
107
108GCJoinDlg::~GCJoinDlg()
109{
110 if(d->psi)
111 d->psi->dialogUnregister(this);
112 if(d->pa)
113 d->pa->dialogUnregister(this);
114 delete d;
115}
116
117/*void GCJoinDlg::closeEvent(QCloseEvent *e)
118{
119 e->ignore();
120 reject();
121}*/
122
123void GCJoinDlg::done(int r)
124{
125 if(d->busy->isActive()) {
126 int n = QMessageBox::information(this, tr("Warning"), tr("Are you sure you want to cancel joining groupchat?"), tr("&Yes"), tr("&No"));
127 if(n != 0)
128 return;
129 d->pa->groupChatLeave(d->jid.host(), d->jid.user());
130 }
131 QDialog::done(r);
132}
133
134void GCJoinDlg::updateIdentity(PsiAccount *pa)
135{
136 if(d->pa)
137 disconnect(d->pa, SIGNAL(disconnected()), this, SLOT(pa_disconnected()));
138
139 d->pa = pa;
140 pb_join->setEnabled(d->pa);
141
142 if(!d->pa) {
143 d->busy->stop();
144 return;
145 }
146
147 connect(d->pa, SIGNAL(disconnected()), this, SLOT(pa_disconnected()));
148}
149
150void GCJoinDlg::pa_disconnected()
151{
152 if(d->busy->isActive()) {
153 d->busy->stop();
154 }
155}
156
157void GCJoinDlg::recent_activated(int x)
158{
159 int n = 0;
160 bool found = false;
161 QString str;
162 for(QStringList::ConstIterator it = d->rl.begin(); it != d->rl.end(); ++it) {
163 if(n == x) {
164 found = true;
165 str = *it;
166 break;
167 }
168 ++n;
169 }
170 if(!found)
171 return;
172
173 Jid j(str);
174 le_host->setText(j.host());
175 le_room->setText(j.user());
176 le_nick->setText(j.resource());
177}
178
179void GCJoinDlg::doJoin()
180{
181 if(!d->pa->checkConnected(this))
182 return;
183
184 QString host = le_host->text();
185 QString room = le_room->text();
186 QString nick = le_nick->text();
187
188 if(host.isEmpty() || room.isEmpty() || nick.isEmpty()) {
189 QMessageBox::information(this, tr("Error"), tr("You must fill out the fields in order to join."));
190 return;
191 }
192
193 Jid j = room + '@' + host + '/' + nick;
194 if(!j.isValid()) {
195 QMessageBox::information(this, tr("Error"), tr("You entered an invalid room name."));
196 return;
197 }
198
199 if(!d->pa->groupChatJoin(host, room, nick)) {
200 QMessageBox::information(this, tr("Error"), tr("You are in or joining this room already!"));
201 return;
202 }
203
204 d->psi->dialogUnregister(this);
205 d->jid = room + '@' + host + '/' + nick;
206 d->pa->dialogRegister(this, d->jid);
207
208 disableWidgets();
209 d->busy->start();
210}
211
212void GCJoinDlg::disableWidgets()
213{
214 d->cb_ident->setEnabled(false);
215 cb_recent->setEnabled(false);
216 gb_info->setEnabled(false);
217 pb_join->setEnabled(false);
218}
219
220void GCJoinDlg::enableWidgets()
221{
222 d->cb_ident->setEnabled(true);
223 if(!d->rl.isEmpty())
224 cb_recent->setEnabled(true);
225 gb_info->setEnabled(true);
226 pb_join->setEnabled(true);
227}
228
229void GCJoinDlg::joined()
230{
231 d->psi->recentGCAdd(d->jid.full());
232 d->busy->stop();
233
234 closeDialogs(this);
235 deleteLater();
236}
237
238void GCJoinDlg::error(int, const QString &str)
239{
240 d->busy->stop();
241 enableWidgets();
242
243 pb_join->setFocus();
244
245 d->pa->dialogUnregister(this);
246 d->psi->dialogRegister(this);
247
248 QMessageBox::information(this, tr("Error"), tr("Unable to join groupchat.\nReason: %1").arg(str));
249}
250
251
252//----------------------------------------------------------------------------
253// GCUserView
254//----------------------------------------------------------------------------
255GCUserViewItem::GCUserViewItem(QListView *par)
256:QListViewItem(par)
257{
258}
259
260GCUserView::GCUserView(QWidget *parent, const char *name)
261:QListView(parent, name), QToolTip(viewport())
262{
263 setResizeMode(QListView::AllColumns);
264 setSorting(0);
265 header()->hide();
266 addColumn("");
267
268 connect(this, SIGNAL(doubleClicked(QListViewItem *)), SLOT(qlv_doubleClicked(QListViewItem *)));
269 connect(this, SIGNAL(contextMenuRequested(QListViewItem *, const QPoint &, int)), SLOT(qlv_contextMenuRequested(QListViewItem *, const QPoint &, int)));
270}
271
272GCUserView::~GCUserView()
273{
274}
275
276void GCUserView::updateAll()
277{
278 for(GCUserViewItem *i = (GCUserViewItem *)firstChild(); i; i = (GCUserViewItem *)i->nextSibling())
279 i->setPixmap(0, is->status(i->s));
280}
281
282QStringList GCUserView::nickList() const
283{
284 QStringList list;
285
286 for(QListViewItem *lvi = firstChild(); lvi; lvi = lvi->nextSibling())
287 list << lvi->text(0);
288
289 qstringlistisort(list); // caseless sorting
290 return list;
291}
292
293QListViewItem *GCUserView::findEntry(const QString &nick)
294{
295 for(QListViewItem *lvi = firstChild(); lvi; lvi = lvi->nextSibling()) {
296 if(lvi->text(0) == nick)
297 return lvi;
298 }
299 return 0;
300}
301
302void GCUserView::updateEntry(const QString &nick, const Status &s)
303{
304 GCUserViewItem *lvi = (GCUserViewItem *)findEntry(nick);
305 if(!lvi) {
306 lvi = new GCUserViewItem(this);
307 lvi->setText(0, nick);
308 }
309
310 lvi->s = s;
311 lvi->setPixmap(0, is->status(lvi->s));
312}
313
314void GCUserView::removeEntry(const QString &nick)
315{
316 QListViewItem *lvi = findEntry(nick);
317 if(lvi)
318 delete lvi;
319}
320
321void GCUserView::maybeTip(const QPoint &pos)
322{
323 GCUserViewItem *lvi = (GCUserViewItem *)itemAt(pos);
324 if(!lvi)
325 return;
326
327 QRect r(itemRect(lvi));
328
329 const QString &nick = lvi->text(0);
330 const Status &s = lvi->s;
331 UserListItem u;
332 // SICK SICK SICK SICK
333 u.setJid(((GCMainDlg *)topLevelWidget())->jid().withResource(nick));
334 u.setName(nick);
335
336 // make a resource so the contact appears online
337 UserResource ur;
338 ur.setName(nick);
339 ur.setStatus(s);
340 u.userResourceList().append(ur);
341
342 tip(r, u.makeTip());
343}
344
345void GCUserView::qlv_doubleClicked(QListViewItem *i)
346{
347 GCUserViewItem *lvi = (GCUserViewItem *)i;
348 if(!lvi)
349 return;
350
351 if(option.defaultAction == 0)
352 action(lvi->text(0), lvi->s, 0);
353 else
354 action(lvi->text(0), lvi->s, 1);
355}
356
357void GCUserView::qlv_contextMenuRequested(QListViewItem *i, const QPoint &pos, int)
358{
359 GCUserViewItem *lvi = (GCUserViewItem *)i;
360 if(!lvi)
361 return;
362
363 QPopupMenu *pm = new QPopupMenu;
364 pm->insertItem(IconsetFactory::icon("psi/sendMessage"), tr("Send &message"), 0);
365 pm->insertItem(IconsetFactory::icon("psi/start-chat"), tr("Open &chat window"), 1);
366 pm->insertSeparator();
367 //pm->insertItem(tr("Send &file"), 4);
368 //pm->insertSeparator();
369 pm->insertItem(tr("Check &Status"), 2);
370 pm->insertItem(IconsetFactory::icon("psi/vCard"), tr("User &Info"), 3);
371 int x = pm->exec(pos);
372 delete pm;
373
374 if(x == -1)
375 return;
376 action(lvi->text(0), lvi->s, x);
377}
378
379
380//----------------------------------------------------------------------------
381// GCMainDlg
382//----------------------------------------------------------------------------
383class GCMainDlg::Private : public QObject
384{
385 Q_OBJECT
386public:
387 enum { Connecting, Connected, Idle };
388 Private(GCMainDlg *d) {
389 dlg = d;
390 nickSeparator = ":";
391 typingStatus = Typing_Normal;
392 }
393
394 GCMainDlg *dlg;
395 int state;
396 PsiAccount *pa;
397 Jid jid;
398 QString self;
399 ChatView *te_log;
400 ChatEdit *mle;
401 QLineEdit *le_topic;
402 GCUserView *lv_users;
403 QPushButton *pb_topic;
404 PsiToolBar *toolbar;
405 IconAction *act_find, *act_clear, *act_icon;
406 QPopupMenu *pm_settings;
407 bool smallChat;
408 int pending;
409
410 bool trackBar;
411 int trackBarParagraph;
412
413 QTimer *flashTimer;
414 int flashCount;
415
416 QStringList hist;
417 int histAt;
418
419 QGuardedPtr<GCFindDlg> findDlg;
420 QString lastSearch;
421
422public slots:
423 void addEmoticon(const Icon *icon) {
424 if ( !dlg->isActiveWindow() )
425 return;
426
427 QString text;
428
429 QDict<QString> itext = icon->text();
430 QDictIterator<QString> it ( itext );
431 for ( ; it.current(); ++it) {
432 if ( it.current() && !it.current()->isEmpty() ) {
433 text = *(it.current()) + " ";
434 break;
435 }
436 }
437
438 if ( !text.isEmpty() )
439 mle->insert( text );
440 }
441
442 void addEmoticon(QString text) {
443 if ( !dlg->isActiveWindow() )
444 return;
445
446 mle->insert( text + " " );
447 }
448
449 void deferredScroll() {
450 //QTimer::singleShot(250, this, SLOT(slotScroll()));
451 te_log->scrollToBottom();
452 }
453
454protected slots:
455 void slotScroll() {
456 te_log->scrollToBottom();
457 }
458
459public:
460 // Nick auto-completion code follows...
461 enum TypingStatus {
462 Typing_Normal = 0,
463 Typing_TabPressed,
464 Typing_TabbingNicks,
465 Typing_MultipleSuggestions
466 };
467 TypingStatus typingStatus;
468 QString lastReferrer; // contains nick of last person, who have said "yourNick: ..."
469 QString nickSeparator; // in case of "nick: ...", it equals ":"
470 QStringList suggestedNicks;
471 int suggestedIndex;
472 bool suggestedFromStart;
473
474 QString beforeNickText(QString text) {
475 int i;
476 for (i = text.length() - 1; i >= 0; --i)
477 if ( text[i].isSpace() )
478 break;
479
480 QString beforeNick = text.left(i+1);
481 return beforeNick;
482 }
483
484 QStringList suggestNicks(QString text, bool fromStart) {
485 QString nickText = text;
486 QString beforeNick;
487 if ( !fromStart ) {
488 beforeNick = beforeNickText(text);
489 nickText = text.mid(beforeNick.length());
490 }
491
492 QStringList nicks = lv_users->nickList();
493 QStringList::Iterator it = nicks.begin();
494 QStringList suggestedNicks;
495 for ( ; it != nicks.end(); ++it) {
496 if ( (*it).left(nickText.length()).lower() == nickText.lower() ) {
497 if ( fromStart )
498 suggestedNicks << *it;
499 else
500 suggestedNicks << beforeNick + *it;
501 }
502 }
503
504 return suggestedNicks;
505 }
506
507 QString longestSuggestedString(QStringList suggestedNicks) {
508 QString testString = suggestedNicks.first();
509 while ( testString.length() > 0 ) {
510 bool found = true;
511 QStringList::Iterator it = suggestedNicks.begin();
512 for ( ; it != suggestedNicks.end(); ++it) {
513 if ( (*it).left(testString.length()).lower() != testString.lower() ) {
514 found = false;
515 break;
516 }
517 }
518
519 if ( found )
520 break;
521
522 testString = testString.left( testString.length() - 1 );
523 }
524
525 return testString;
526 }
527
528 QString insertNick(bool fromStart, QString beforeNick = "") {
529 typingStatus = Typing_MultipleSuggestions;
530 suggestedFromStart = fromStart;
531 suggestedNicks = lv_users->nickList();
532 QStringList::Iterator it = suggestedNicks.begin();
533 for ( ; it != suggestedNicks.end(); ++it)
534 *it = beforeNick + *it;
535
536 QString newText;
537 if ( !lastReferrer.isEmpty() ) {
538 newText = beforeNick + lastReferrer;
539 suggestedIndex = -1;
540 }
541 else {
542 newText = suggestedNicks.first();
543 suggestedIndex = 0;
544 }
545
546 if ( fromStart ) {
547 newText += nickSeparator;
548 newText += " ";
549 }
550
551 return newText;
552 }
553
554 QString suggestNick(bool fromStart, QString origText, bool *replaced) {
555 suggestedFromStart = fromStart;
556 suggestedNicks = suggestNicks(origText, fromStart);
557 suggestedIndex = -1;
558
559 QString newText;
560 if ( suggestedNicks.count() ) {
561 if ( suggestedNicks.count() == 1 ) {
562 newText = suggestedNicks.first();
563 if ( fromStart ) {
564 newText += nickSeparator;
565 newText += " ";
566 }
567 }
568 else {
569 newText = longestSuggestedString(suggestedNicks);
570 if ( !newText.length() )
571 return origText;
572
573 typingStatus = Typing_MultipleSuggestions;
574 // TODO: display a tooltip that will contain all suggestedNicks
575 }
576
577 *replaced = true;
578 }
579
580 return newText;
581 }
582
583 void doAutoNickInsertion() {
584 int para, index;
585 mle->getCursorPosition(&para, &index);
586 QString paraText = mle->text(para);
587 QString origText = paraText.left(index);
588 QString newText;
589
590 bool replaced = false;
591
592 if ( typingStatus == Typing_MultipleSuggestions ) {
593 suggestedIndex++;
594 if ( suggestedIndex >= (int)suggestedNicks.count() )
595 suggestedIndex = 0;
596
597 newText = suggestedNicks[suggestedIndex];
598 if ( suggestedFromStart ) {
599 newText += nickSeparator;
600 newText += " ";
601 }
602
603 replaced = true;
604 }
605
606 if ( !para && !replaced ) {
607 if ( !index && typingStatus == Typing_TabbingNicks ) {
608 newText = insertNick(true, "");
609 replaced = true;
610 }
611 else {
612 newText = suggestNick(true, origText, &replaced);
613 }
614 }
615
616 if ( !replaced ) {
617 if ( (!index || origText[index-1].isSpace()) && typingStatus == Typing_TabbingNicks ) {
618 newText = insertNick(false, beforeNickText(origText));
619 replaced = true;
620 }
621 else {
622 newText = suggestNick(false, origText, &replaced);
623 }
624 }
625
626 if ( replaced ) {
627 mle->setUpdatesEnabled( false );
628 QString newParaText = newText + paraText.mid(index, paraText.length() - index - 1);
629 mle->insertParagraph(newParaText, para);
630 mle->removeParagraph(para+1);
631 mle->setCursorPosition(para, newText.length());
632 mle->setUpdatesEnabled( true );
633 mle->viewport()->update();
634 }
635 }
636
637 bool eventFilter( QObject *obj, QEvent *ev ) {
638 if ( obj == mle && ev->type() == QEvent::KeyPress ) {
639 QKeyEvent *e = (QKeyEvent *)ev;
640
641 if ( e->key() == Key_Tab ) {
642 switch ( typingStatus ) {
643 case Typing_Normal:
644 typingStatus = Typing_TabPressed;
645 break;
646 case Typing_TabPressed:
647 typingStatus = Typing_TabbingNicks;
648 break;
649 default:
650 break;
651 }
652
653 doAutoNickInsertion();
654 return TRUE;
655 }
656
657 typingStatus = Typing_Normal;
658
659 return FALSE;
660 }
661
662 return QObject::eventFilter( obj, ev );
663 }
664};
665
666GCMainDlg::GCMainDlg(PsiAccount *pa, const Jid &j)
667:AdvancedWidget<QWidget>(0, 0, WDestructiveClose)
668{
669 nicknumber=0;
670 d = new Private(this);
671 d->pa = pa;
672 d->jid = j.userHost();
673 d->self = j.resource();
674 d->pa->dialogRegister(this, d->jid);
675 connect(d->pa, SIGNAL(updatedActivity()), SLOT(pa_updatedActivity()));
676
677 d->pending = 0;
678 d->flashTimer = 0;
679
680 d->histAt = 0;
681 d->findDlg = 0;
682
683 d->trackBar = false;
684 d->trackBarParagraph = 0;
685
686 d->state = Private::Connected;
687
688#ifndef Q_WS_MAC
689 setIcon(IconsetFactory::icon("psi/groupChat"));
690#endif
691
692 QVBoxLayout *dlg_layout = new QVBoxLayout(this, 4);
693
694 QWidget *vsplit;
695 if ( !option.chatLineEdit ) {
696 vsplit = new QSplitter(this);
697 ((QSplitter *)vsplit)->setOrientation( QSplitter::Vertical );
698 dlg_layout->addWidget(vsplit);
699 }
700 else
701 vsplit = this;
702
703 // --- top part ---
704 QWidget *sp_top = new QWidget(vsplit);
705 sp_top->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
706 if ( option.chatLineEdit )
707 dlg_layout->addWidget( sp_top );
708 QVBoxLayout *vb_top = new QVBoxLayout(sp_top, 0, 4);
709
710 // top row
711 QWidget *sp_top_top = new QWidget( sp_top );
712 vb_top->addWidget( sp_top_top );
713 QHBoxLayout *hb_top = new QHBoxLayout( sp_top_top, 0, 4 );
714
715 d->pb_topic = new QPushButton(tr("Topic:"), sp_top_top);
716 connect(d->pb_topic, SIGNAL(clicked()), SLOT(doTopic()));
717 hb_top->addWidget(d->pb_topic);
718
719 d->le_topic = new QLineEdit(sp_top_top);
720 d->le_topic->setReadOnly(true);
721 hb_top->addWidget(d->le_topic);
722
723 d->act_find = new IconAction(tr("Find"), "psi/search", tr("&Find"), CTRL+Key_F, this);
724 connect(d->act_find, SIGNAL(activated()), SLOT(openFind()));
725 d->act_find->addTo( sp_top_top );
726
727 QLabel *lb_ident = d->pa->accountLabel(sp_top_top, true);
728 lb_ident->setSizePolicy(QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ));
729 hb_top->addWidget(lb_ident);
730
731 // bottom row
732 QSplitter *hsp = new QSplitter(sp_top);
733 hsp->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
734 vb_top->addWidget(hsp);
735 hsp->setOrientation(QSplitter::Horizontal);
736
737 d->te_log = new ChatView(hsp);
738 d->te_log->setTextFormat( RichText );
739#ifdef Q_WS_MAC
740 connect(d->te_log,SIGNAL(selectionChanged()),SLOT(logSelectionChanged()));
741 d->te_log->setFocusPolicy(QWidget::NoFocus);
742#endif
743
744 d->lv_users = new GCUserView(hsp);
745 d->lv_users->setMinimumWidth(20);
746 connect(d->lv_users, SIGNAL(action(const QString &, const Status &, int)), SLOT(lv_action(const QString &, const Status &, int)));
747
748 // --- bottom part ---
749 QWidget *sp_bottom = new QWidget(vsplit);
750 sp_bottom->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Maximum );
751 if ( option.chatLineEdit )
752 dlg_layout->addWidget( sp_bottom );
753 QVBoxLayout *vb_bottom = new QVBoxLayout(sp_bottom);
754
755 // toolbar
756 d->act_clear = new IconAction (tr("Clear chat window"), "psi/clearChat", tr("Clear chat window"), 0, this);
757 connect( d->act_clear, SIGNAL( activated() ), SLOT( doClearButton() ) );
758
759 connect(pa->psi()->iconSelectPopup(), SIGNAL(textSelected(QString)), d, SLOT(addEmoticon(QString)));
760 d->act_icon = new IconAction( tr( "Select icon" ), "psi/smile", tr( "Select icon" ), 0, this );
761 d->act_icon->setPopup( pa->psi()->iconSelectPopup() );
762
763 d->toolbar = new PsiToolBar( tr("Groupchat toolbar"), 0, sp_bottom );
764 d->toolbar->setCustomizeable( false ); // it isn't ready now, and we don't want segfaults
765 d->toolbar->setFrameShape( QFrame::NoFrame );
766 vb_bottom->addWidget( d->toolbar );
767
768 d->act_clear->addTo( d->toolbar );
769 d->toolbar->setStretchableWidget(new StretchWidget(d->toolbar));
770 d->act_icon->addTo( d->toolbar );
771
772 // chat edit
773 if ( !option.chatLineEdit ) {
774 d->mle = new ChatEdit(sp_bottom);
775 vb_bottom->addWidget(d->mle);
776 }
777 else {
778 QHBoxLayout *hb5 = new QHBoxLayout( dlg_layout );
779 d->mle = new LineEdit( vsplit );
780#ifdef Q_WS_MAC
781 hb5->addSpacing( 16 );
782#endif
783 hb5->addWidget( d->mle );
784#ifdef Q_WS_MAC
785 hb5->addSpacing( 16 );
786#endif
787 }
788
789 d->mle->installEventFilter( d );
790
791 d->pm_settings = new QPopupMenu(this);
792 connect(d->pm_settings, SIGNAL(aboutToShow()), SLOT(buildMenu()));
793
794 // resize the horizontal splitter
795 QValueList<int> list;
796 list << 500;
797 list << 80;
798 hsp->setSizes(list);
799
800 list.clear();
801 list << 324;
802 list << 10;
803 if ( !option.chatLineEdit )
804 (( QSplitter *)vsplit)->setSizes(list);
805
806 resize(580,420);
807
808 d->smallChat = option.smallChats;
809 X11WM_CLASS("groupchat");
810
811 d->mle->setFocus();
812
813 setLooks();
814 updateCaption();
815}
816
817GCMainDlg::~GCMainDlg()
818{
819 if(d->state != Private::Idle)
820 d->pa->groupChatLeave(d->jid.host(), d->jid.user());
821
822 //QMimeSourceFactory *m = d->te_log->mimeSourceFactory();
823 //d->te_log->setMimeSourceFactory(0);
824 //delete m;
825
826 d->pa->dialogUnregister(this);
827 delete d;
828}
829
830void GCMainDlg::keyPressEvent(QKeyEvent *e)
831{
832 if(e->key() == Key_Return || e->key() == Key_Enter || (e->key() == Key_S && (e->state() & AltButton)))
833 mle_returnPressed();
834 else if(e->key() == Key_Escape && e->state() == 0)
835 showMinimized();
836 else if(e->key() == Key_PageUp && (e->state() & ShiftButton))
837 d->te_log->setContentsPos(d->te_log->contentsX(), d->te_log->contentsY() - d->te_log->visibleHeight()/2);
838 else if(e->key() == Key_PageDown && (e->state() & ShiftButton))
839 d->te_log->setContentsPos(d->te_log->contentsX(), d->te_log->contentsY() + d->te_log->visibleHeight()/2);
840 else
841 e->ignore();
842}
843
844void GCMainDlg::closeEvent(QCloseEvent *e)
845{
846 e->accept();
847}
848
849void GCMainDlg::windowActivationChange(bool oldstate)
850{
851 QWidget::windowActivationChange(oldstate);
852
853 // if we're bringing it to the front, get rid of the '*' if necessary
854 if(isActiveWindow()) {
855 if(d->pending > 0) {
856 d->pending = 0;
857 updateCaption();
858 }
859 doFlash(false);
860
861 d->mle->setFocus();
862 d->trackBar = false;
863 } else {
864 d->trackBar = true;
865 }
866}
867
868
869void GCMainDlg::logSelectionChanged()
870{
871#ifdef Q_WS_MAC
872 // A hack to only give the message log focus when text is selected
873 if (d->te_log->hasSelectedText())
874 d->te_log->setFocus();
875 else
876 d->mle->setFocus();
877#endif
878}
879
880void GCMainDlg::mle_returnPressed()
881{
882 if(d->mle->text().isEmpty())
883 return;
884
885 QString str = d->mle->text();
886 if(str == "/clear") {
887 doClear();
888
889 d->histAt = 0;
890 d->hist.prepend(str);
891 d->mle->setText("");
892 return;
893 }
894
895 if(str.lower().startsWith("/nick ")) {
896 QString nick = str.mid(6).stripWhiteSpace();
897 if ( !nick.isEmpty() ) {
898 d->self = nick;
899 d->pa->groupChatChangeNick(d->jid.host(), d->jid.user(), d->self, d->pa->status());
900 }
901 d->mle->setText("");
902 return;
903 }
904
905 if(d->state != Private::Connected)
906 return;
907
908 Message m(d->jid);
909 m.setType("groupchat");
910 m.setBody(str);
911 m.setTimeStamp(QDateTime::currentDateTime());
912
913 aSend(m);
914
915 d->histAt = 0;
916 d->hist.prepend(str);
917 d->mle->setText("");
918}
919
920/*void GCMainDlg::le_upPressed()
921{
922 if(d->histAt < (int)d->hist.count()) {
923 ++d->histAt;
924 d->le_input->setText(d->hist[d->histAt-1]);
925 }
926}
927
928void GCMainDlg::le_downPressed()
929{
930 if(d->histAt > 0) {
931 --d->histAt;
932 if(d->histAt == 0)
933 d->le_input->setText("");
934 else
935 d->le_input->setText(d->hist[d->histAt-1]);
936 }
937}*/
938
939void GCMainDlg::doTopic()
940{
941 bool ok = false;
942 QString str = QInputDialog::getText(
943 tr("Set Groupchat Topic"),
944 tr("Enter a topic:"),
945 QLineEdit::Normal, d->le_topic->text(), &ok, this);
946
947 if(ok) {
948 Message m(d->jid);
949 m.setType("groupchat");
950 m.setSubject(str);
951 m.setBody(QString("/me ") + tr("has set the topic to: %1").arg(str));
952 m.setTimeStamp(QDateTime::currentDateTime());
953 aSend(m);
954 }
955}
956
957void GCMainDlg::doClear()
958{
959 d->te_log->setText("");
960}
961
962void GCMainDlg::doClearButton()
963{
964 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"));
965 if(n == 0)
966 doClear();
967}
968
969void GCMainDlg::openFind()
970{
971 if(d->findDlg)
972 bringToFront(d->findDlg);
973 else {
974 d->findDlg = new GCFindDlg(0, 0, d->lastSearch, this);
975 connect(d->findDlg, SIGNAL(find(int, int, const QString &)), SLOT(doFind(int, int, const QString &)));
976 d->findDlg->show();
977 }
978}
979
980void GCMainDlg::doFind(int para, int index, const QString &str)
981{
982 d->lastSearch = str;
983
984 if(!d->te_log->find(str, false, false, true, &para, &index))
985 d->findDlg->error(str);
986 else {
987 // pass one character over
988 d->findDlg->found(para, index+1);
989 }
990}
991
992void GCMainDlg::goDisc()
993{
994 if(d->state != Private::Idle) {
995 d->state = Private::Idle;
996 d->pb_topic->setEnabled(false);
997 appendSysMsg(tr("Disconnected."), true);
998 }
999}
1000
1001void GCMainDlg::goConn()
1002{
1003 if(d->state == Private::Idle) {
1004 d->state = Private::Connecting;
1005 appendSysMsg(tr("Reconnecting..."), true);
1006
1007 QString host = d->jid.host();
1008 QString room = d->jid.user();
1009 QString nick = d->self;
1010
1011 if(!d->pa->groupChatJoin(host, room, nick)) {
1012 appendSysMsg(tr("Error: You are in or joining this room already!"), true);
1013 d->state = Private::Idle;
1014 }
1015 }
1016}
1017
1018void GCMainDlg::pa_updatedActivity()
1019{
1020 if(!d->pa->loggedIn())
1021 goDisc();
1022 else {
1023 if(d->state == Private::Idle)
1024 goConn();
1025 else if(d->state == Private::Connected)
1026 d->pa->groupChatSetStatus(d->jid.host(), d->jid.user(), d->pa->status());
1027 }
1028}
1029
1030Jid GCMainDlg::jid() const
1031{
1032 return d->jid;
1033}
1034
1035void GCMainDlg::error(int, const QString &str)
1036{
1037 d->pb_topic->setEnabled(false);
1038
1039 if(d->state == Private::Connecting)
1040 appendSysMsg(tr("Unable to join groupchat. Reason: %1").arg(str), true);
1041 else
1042 appendSysMsg(tr("Unexpected groupchat error: %1").arg(str), true);
1043
1044 d->state = Private::Idle;
1045}
1046
1047void GCMainDlg::presence(const QString &nick, const Status &s)
1048{
1049 if(s.isAvailable()) {
1050 /*if ((option.showJoins)&&(d->lv_users->findEntry(nick)==0)) {
1051 //contact joining
1052 QString m=nick+tr(" has joined the channel");
1053 appendSysMsg(m, false, QDateTime::currentDateTime());
1054 }*/
1055 d->lv_users->updateEntry(nick, s);
1056 } else {
1057 /*if (option.showJoins) {
1058 //contact leaving
1059 QString m=nick+tr(" has left the channel");
1060 appendSysMsg(m, false, QDateTime::currentDateTime());
1061 }*/
1062 d->lv_users->removeEntry(nick);
1063 }
1064}
1065
1066void GCMainDlg::message(const Message &m)
1067{
1068 QString from = m.from().resource();
1069 bool alert = false;
1070
1071 if(!m.subject().isEmpty()) {
1072 d->le_topic->setText(m.subject());
1073 d->le_topic->setCursorPosition(0);
1074 QToolTip::add(d->le_topic, QString("<qt><p>%1</p></qt>").arg(m.subject()));
1075 }
1076
1077 // code to determine if the speaker was addressing this client in chat
1078 if(m.body().contains(d->self) > 0)
1079 alert = true;
1080
1081 if (m.body().left(d->self.length()) == d->self)
1082 d->lastReferrer = m.from().resource();
1083
1084 if(option.gcHighlighting) {
1085 for(QStringList::Iterator it=option.gcHighlights.begin();it!=option.gcHighlights.end();it++) {
1086 if(m.body().contains((*it),false) > 0) {
1087 alert = true;
1088 }
1089 }
1090 }
1091
1092 // play sound?
1093 if(from == d->self) {
1094 if(!m.spooled())
1095 d->pa->playSound(option.onevent[eSend]);
1096 }
1097 else {
1098 if(alert || (!option.noGCSound && !m.spooled() && !from.isEmpty()) )
1099 d->pa->playSound(option.onevent[eChat2]);
1100 }
1101
1102 if(from.isEmpty())
1103 appendSysMsg(m.body(), alert, m.timeStamp());
1104 else
1105 appendMessage(m, alert);
1106}
1107
1108void GCMainDlg::joined()
1109{
1110 if(d->state == Private::Connecting) {
1111 d->lv_users->QListView::clear();
1112 d->state = Private::Connected;
1113 d->pb_topic->setEnabled(true);
1114 appendSysMsg(tr("Connected."), true);
1115 }
1116}
1117
1118void GCMainDlg::appendSysMsg(const QString &str, bool alert, const QDateTime &ts)
1119{
1120 bool atBottom = d->te_log->contentsY() >= d->te_log->contentsHeight() - d->te_log->visibleHeight();
1121
1122 QString hr ="";
1123 if (d->trackBar) {
1124 hr = QString("<hr>");
1125 d->trackBar = false;
1126
1127 if ( d->trackBarParagraph ) {
1128 d->te_log->setUpdatesEnabled( false );
1129 d->te_log->setSelection(d->trackBarParagraph, 0, d->trackBarParagraph, 1, 1);
1130 d->te_log->removeSelectedText(1);
1131 d->te_log->setUpdatesEnabled( true );
1132 }
1133 d->trackBarParagraph = d->te_log->paragraphs();
1134 }
1135
1136 QString timestr;
1137 if (!option.gcHighlighting)
1138 alert=false;
1139
1140 QDateTime time;
1141 if(!ts.isNull())
1142 time = ts;
1143 else
1144 time = QDateTime::currentDateTime();
1145
1146 //timestr.sprintf("%02d:%02d:%02d", time.time().hour(), time.time().minute(), time.time().second());
1147 timestr = time.time().toString(LocalDate);
1148
1149 /*int y = d->te_log->contentsHeight() - d->te_log->visibleHeight();
1150 if(y < 0)
1151 y = 0;
1152 bool atBottom = (d->te_log->contentsY() < y - 32) ? false: true;*/
1153
1154 d->te_log->append(hr + QString("<font color=\"#00A000\">[%1]").arg(timestr) + QString(" *** %1</font>").arg(expandEntities(str)));
1155
1156 if(atBottom)
1157 d->deferredScroll();
1158
1159 if(alert)
1160 doAlert();
1161}
1162
1163QString GCMainDlg::getNickColor(QString nick)
1164{
1165 int sender;
1166 if(nick == d->self||nick.isEmpty())
1167 sender = -1;
1168 else {
1169 if (!nicks.contains(nick)) {
1170 //not found in map
1171 nicks.insert(nick,nicknumber);
1172 nicknumber++;
1173 }
1174 sender=nicks[nick];
1175 }
1176
1177 if(!option.gcNickColoring || option.gcNickColors.empty()) {
1178 return "#000000";
1179 }
1180 else if(sender == -1 || option.gcNickColors.size() == 1) {
1181 return option.gcNickColors[option.gcNickColors.size()-1];
1182 }
1183 else {
1184 int n = sender % (option.gcNickColors.size()-1);
1185 return option.gcNickColors[n];
1186 }
1187}
1188
1189void GCMainDlg::appendMessage(const Message &m, bool alert)
1190{
1191 //QString who, color;
1192 if (!option.gcHighlighting)
1193 alert=false;
1194 QString who, textcolor, nickcolor,alerttagso,alerttagsc;
1195
1196 bool atBottom = d->te_log->contentsY() >= d->te_log->contentsHeight() - d->te_log->visibleHeight();
1197
1198 who = m.from().resource();
1199 QString hr = "";
1200 if (d->trackBar&&m.from().resource() != d->self&&!m.spooled()) {
1201 hr = QString("<hr>");
1202 d->trackBar = false;
1203
1204 if ( d->trackBarParagraph ) {
1205 d->te_log->setUpdatesEnabled( false );
1206 d->te_log->setSelection(d->trackBarParagraph, 0, d->trackBarParagraph, 1, 1);
1207 d->te_log->removeSelectedText(1);
1208 d->te_log->setUpdatesEnabled( true );
1209 }
1210 d->trackBarParagraph = d->te_log->paragraphs();
1211 }
1212 /*if(local) {
1213 color = "#FF0000";
1214 }
1215 else {
1216 color = "#0000FF";
1217 }*/
1218 nickcolor = getNickColor(who);
1219 textcolor = d->te_log->palette().active().text().name();
1220 if(alert) {
1221 textcolor = "#FF0000";
1222 alerttagso = "<b>";
1223 alerttagsc = "</b>";
1224 }
1225 if(m.spooled())
1226 nickcolor = "#008000"; //color = "#008000";
1227
1228 QString timestr;
1229 QDateTime time = m.timeStamp();
1230 //timestr.sprintf("%02d:%02d:%02d", time.time().hour(), time.time().minute(), time.time().second());
1231 timestr = time.time().toString(LocalDate);
1232
1233 /*int y = d->te_log->contentsHeight() - d->te_log->visibleHeight();
1234 if(y < 0)
1235 y = 0;
1236 bool atBottom = (d->te_log->contentsY() < y - 32) ? false: true;*/
1237
1238 bool emote = false;
1239 if(m.body().left(4) == "/me ")
1240 emote = true;
1241
1242 QString txt;
1243 if(emote)
1244 txt = plain2rich(m.body().mid(4));
1245 else
1246 txt = plain2rich(m.body());
1247
1248 txt = linkify(txt);
1249
1250 if(option.useEmoticons)
1251 txt = emoticonify(txt);
1252
1253 if(emote) {
1254 //d->te_log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1]").arg(timestr) + QString(" *%1 ").arg(expandEntities(who)) + txt + "</font>");
1255 d->te_log->append(hr + QString("<font color=\"%1\">").arg(nickcolor) + QString("[%1]").arg(timestr) + QString(" *%1 ").arg(expandEntities(who)) + alerttagso + txt + alerttagsc + "</font>");
1256 }
1257 else {
1258 if(option.chatSays) {
1259 //d->te_log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1] ").arg(timestr) + QString("%1 says:").arg(expandEntities(who)) + "</font><br>" + txt);
1260 d->te_log->append(hr + QString("<font color=\"%1\">").arg(nickcolor) + QString("[%1] ").arg(timestr) + QString("%1 says:").arg(expandEntities(who)) + "</font><br>" + QString("<font color=\"%1\">").arg(textcolor) + alerttagso + txt + alerttagsc + "</font>");
1261 }
1262 else {
1263 //d->te_log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1] &lt;").arg(timestr) + expandEntities(who) + QString("&gt;</font> ") + txt);
1264 d->te_log->append(hr + QString("<font color=\"%1\">").arg(nickcolor) + QString("[%1] &lt;").arg(timestr) + expandEntities(who) + QString("&gt;</font> ") + QString("<font color=\"%1\">").arg(textcolor) + alerttagso + txt + alerttagsc +"</font>");
1265 }
1266 }
1267
1268 //if(local || atBottom)
1269 if(m.from().resource() == d->self || atBottom)
1270 d->deferredScroll();
1271
1272 // if we're not active, notify the user by changing the title
1273 if(!isActiveWindow()) {
1274 ++d->pending;
1275 updateCaption();
1276 }
1277
1278 //if someone directed their comments to us, notify the user
1279 if(alert)
1280 doAlert();
1281
1282 //if the message spoke to us, alert the user before closing this window
1283 //except that keepopen doesn't seem to be implemented for this class yet.
1284 /*if(alert) {
1285 d->keepOpen = true;
1286 QTimer::singleShot(1000, this, SLOT(setKeepOpenFalse()));
1287 }*/
1288}
1289
1290void GCMainDlg::doAlert()
1291{
1292 if(!isActiveWindow())
1293 doFlash(true);
1294}
1295
1296void GCMainDlg::updateCaption()
1297{
1298 QString cap = "";
1299
1300 if(d->pending > 0) {
1301 cap += "* ";
1302 if(d->pending > 1)
1303 cap += QString("[%1] ").arg(d->pending);
1304 }
1305 cap += d->jid.full();
1306
1307 // if taskbar flash, then we need to erase first and redo
1308#ifdef Q_WS_WIN
1309 bool on = false;
1310 if(d->flashTimer)
1311 on = d->flashCount & 1;
1312 if(on)
1313 FlashWindow(winId(), true);
1314#endif
1315 setCaption(cap);
1316#ifdef Q_WS_WIN
1317 if(on)
1318 FlashWindow(winId(), true);
1319#endif
1320}
1321
1322#ifdef Q_WS_WIN
1323void GCMainDlg::doFlash(bool yes)
1324{
1325 if(yes) {
1326 if(d->flashTimer)
1327 return;
1328 d->flashTimer = new QTimer(this);
1329 connect(d->flashTimer, SIGNAL(timeout()), SLOT(flashAnimate()));
1330 d->flashCount = 0;
1331 flashAnimate(); // kick the first one immediately
1332 d->flashTimer->start(500);
1333 }
1334 else {
1335 if(d->flashTimer) {
1336 delete d->flashTimer;
1337 d->flashTimer = 0;
1338 FlashWindow(winId(), false);
1339 }
1340 }
1341}
1342#else
1343void GCMainDlg::doFlash(bool)
1344{
1345}
1346#endif
1347
1348void GCMainDlg::flashAnimate()
1349{
1350#ifdef Q_WS_WIN
1351 FlashWindow(winId(), true);
1352 ++d->flashCount;
1353 if(d->flashCount == 5)
1354 d->flashTimer->stop();
1355#endif
1356}
1357
1358void GCMainDlg::setLooks()
1359{
1360 // update the fonts
1361 QFont f;
1362 f.fromString(option.font[fChat]);
1363 d->te_log->setFont(f);
1364 d->mle->setFont(f);
1365
1366 f.fromString(option.font[fRoster]);
1367 d->lv_users->QListView::setFont(f);
1368
1369 if ( d->smallChat ) {
1370 d->toolbar->hide();
1371 }
1372 else {
1373 d->toolbar->show();
1374 }
1375
1376 // update the widget icon
1377#ifndef Q_WS_MAC
1378 setIcon(IconsetFactory::icon("psi/groupChat"));
1379#endif
1380}
1381
1382void GCMainDlg::optionsUpdate()
1383{
1384 /*QMimeSourceFactory *m = d->te_log->mimeSourceFactory();
1385 d->te_log->setMimeSourceFactory(is->emoticons.generateFactory());
1386 delete m;*/
1387
1388 setLooks();
1389
1390 // update status icons
1391 d->lv_users->updateAll();
1392}
1393
1394void GCMainDlg::lv_action(const QString &nick, const Status &s, int x)
1395{
1396 if(x == 0) {
1397 d->pa->invokeGCMessage(d->jid.withResource(nick));
1398 }
1399 else if(x == 1) {
1400 d->pa->invokeGCChat(d->jid.withResource(nick));
1401 }
1402 else if(x == 2) {
1403 UserListItem u;
1404 u.setJid(d->jid.withResource(nick));
1405 u.setName(nick);
1406
1407 // make a resource so the contact appears online
1408 UserResource ur;
1409 ur.setName(nick);
1410 ur.setStatus(s);
1411 u.userResourceList().append(ur);
1412
1413 StatusShowDlg *w = new StatusShowDlg(u);
1414 w->show();
1415 }
1416 else if(x == 3) {
1417 d->pa->invokeGCInfo(d->jid.withResource(nick));
1418 }
1419 else if(x == 4) {
1420 d->pa->invokeGCFile(d->jid.withResource(nick));
1421 }
1422}
1423
1424void GCMainDlg::contextMenuEvent(QContextMenuEvent *)
1425{
1426 d->pm_settings->exec(QCursor::pos());
1427}
1428
1429void GCMainDlg::buildMenu()
1430{
1431 // Dialog menu
1432 d->pm_settings->clear();
1433 d->pm_settings->insertItem(tr("Toggle Compact/Full Size"), this, SLOT(toggleSmallChat()));
1434
1435 d->act_clear->addTo( d->pm_settings );
1436 d->pm_settings->insertSeparator();
1437
1438 d->act_icon->addTo( d->pm_settings );
1439}
1440
1441void GCMainDlg::toggleSmallChat()
1442{
1443 d->smallChat = !d->smallChat;
1444 setLooks();
1445}
1446
1447//----------------------------------------------------------------------------
1448// GCFindDlg
1449//----------------------------------------------------------------------------
1450GCFindDlg::GCFindDlg(int startPara, int startIndex, const QString &str, QWidget *parent, const char *name)
1451:QDialog(parent, name, false, WDestructiveClose)
1452{
1453 para = startPara;
1454 index = startIndex;
1455
1456 setCaption(tr("Find"));
1457 QVBoxLayout *vb = new QVBoxLayout(this, 4);
1458 QHBoxLayout *hb = new QHBoxLayout(vb);
1459 QLabel *l = new QLabel(tr("Find:"), this);
1460 hb->addWidget(l);
1461 le_input = new QLineEdit(this);
1462 hb->addWidget(le_input);
1463 vb->addStretch(1);
1464
1465 QFrame *Line1 = new QFrame(this);
1466 Line1->setFrameShape( QFrame::HLine );
1467 Line1->setFrameShadow( QFrame::Sunken );
1468 Line1->setFrameShape( QFrame::HLine );
1469 vb->addWidget(Line1);
1470
1471 hb = new QHBoxLayout(vb);
1472 hb->addStretch(1);
1473 QPushButton *pb_close = new QPushButton(tr("&Close"), this);
1474 connect(pb_close, SIGNAL(clicked()), SLOT(close()));
1475 hb->addWidget(pb_close);
1476 QPushButton *pb_find = new QPushButton(tr("&Find"), this);
1477 pb_find->setDefault(true);
1478 connect(pb_find, SIGNAL(clicked()), SLOT(doFind()));
1479 hb->addWidget(pb_find);
1480 pb_find->setAutoDefault(true);
1481
1482 resize(200, minimumSize().height());
1483
1484 le_input->setText(str);
1485 le_input->setFocus();
1486}
1487
1488GCFindDlg::~GCFindDlg()
1489{
1490}
1491
1492void GCFindDlg::found(int _para, int _index)
1493{
1494 para = _para;
1495 index = _index;
1496}
1497
1498void GCFindDlg::error(const QString &str)
1499{
1500 QMessageBox::warning(this, tr("Find"), tr("Search string '%1' not found.").arg(str));
1501 le_input->setFocus();
1502}
1503
1504void GCFindDlg::doFind()
1505{
1506 emit find(para, index, le_input->text());
1507}
1508
1509#include "groupchatdlg.moc"
Note: See TracBrowser for help on using the repository browser.