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

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

Imported original Psi 0.10 sources from Affinix

File size: 35.2 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_PageUp && (e->state() & ShiftButton))
835 d->te_log->setContentsPos(d->te_log->contentsX(), d->te_log->contentsY() - d->te_log->visibleHeight()/2);
836 else if(e->key() == Key_PageDown && (e->state() & ShiftButton))
837 d->te_log->setContentsPos(d->te_log->contentsX(), d->te_log->contentsY() + d->te_log->visibleHeight()/2);
838 else
839 e->ignore();
840}
841
842void GCMainDlg::closeEvent(QCloseEvent *e)
843{
844 e->accept();
845}
846
847void GCMainDlg::windowActivationChange(bool oldstate)
848{
849 QWidget::windowActivationChange(oldstate);
850
851 // if we're bringing it to the front, get rid of the '*' if necessary
852 if(isActiveWindow()) {
853 if(d->pending > 0) {
854 d->pending = 0;
855 updateCaption();
856 }
857 doFlash(false);
858
859 d->mle->setFocus();
860 d->trackBar = false;
861 } else {
862 d->trackBar = true;
863 }
864}
865
866
867void GCMainDlg::logSelectionChanged()
868{
869#ifdef Q_WS_MAC
870 // A hack to only give the message log focus when text is selected
871 if (d->te_log->hasSelectedText())
872 d->te_log->setFocus();
873 else
874 d->mle->setFocus();
875#endif
876}
877
878void GCMainDlg::mle_returnPressed()
879{
880 if(d->mle->text().isEmpty())
881 return;
882
883 QString str = d->mle->text();
884 if(str == "/clear") {
885 doClear();
886
887 d->histAt = 0;
888 d->hist.prepend(str);
889 d->mle->setText("");
890 return;
891 }
892
893 if(str.lower().startsWith("/nick ")) {
894 QString nick = str.mid(6).stripWhiteSpace();
895 if ( !nick.isEmpty() ) {
896 d->self = nick;
897 d->pa->groupChatChangeNick(d->jid.host(), d->jid.user(), d->self, d->pa->status());
898 }
899 d->mle->setText("");
900 return;
901 }
902
903 if(d->state != Private::Connected)
904 return;
905
906 Message m(d->jid);
907 m.setType("groupchat");
908 m.setBody(str);
909 m.setTimeStamp(QDateTime::currentDateTime());
910
911 aSend(m);
912
913 d->histAt = 0;
914 d->hist.prepend(str);
915 d->mle->setText("");
916}
917
918/*void GCMainDlg::le_upPressed()
919{
920 if(d->histAt < (int)d->hist.count()) {
921 ++d->histAt;
922 d->le_input->setText(d->hist[d->histAt-1]);
923 }
924}
925
926void GCMainDlg::le_downPressed()
927{
928 if(d->histAt > 0) {
929 --d->histAt;
930 if(d->histAt == 0)
931 d->le_input->setText("");
932 else
933 d->le_input->setText(d->hist[d->histAt-1]);
934 }
935}*/
936
937void GCMainDlg::doTopic()
938{
939 bool ok = false;
940 QString str = QInputDialog::getText(
941 tr("Set Groupchat Topic"),
942 tr("Enter a topic:"),
943 QLineEdit::Normal, d->le_topic->text(), &ok, this);
944
945 if(ok) {
946 Message m(d->jid);
947 m.setType("groupchat");
948 m.setSubject(str);
949 m.setBody(QString("/me ") + tr("has set the topic to: %1").arg(str));
950 m.setTimeStamp(QDateTime::currentDateTime());
951 aSend(m);
952 }
953}
954
955void GCMainDlg::doClear()
956{
957 d->te_log->setText("");
958}
959
960void GCMainDlg::doClearButton()
961{
962 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"));
963 if(n == 0)
964 doClear();
965}
966
967void GCMainDlg::openFind()
968{
969 if(d->findDlg)
970 bringToFront(d->findDlg);
971 else {
972 d->findDlg = new GCFindDlg(0, 0, d->lastSearch, this);
973 connect(d->findDlg, SIGNAL(find(int, int, const QString &)), SLOT(doFind(int, int, const QString &)));
974 d->findDlg->show();
975 }
976}
977
978void GCMainDlg::doFind(int para, int index, const QString &str)
979{
980 d->lastSearch = str;
981
982 if(!d->te_log->find(str, false, false, true, &para, &index))
983 d->findDlg->error(str);
984 else {
985 // pass one character over
986 d->findDlg->found(para, index+1);
987 }
988}
989
990void GCMainDlg::goDisc()
991{
992 if(d->state != Private::Idle) {
993 d->state = Private::Idle;
994 d->pb_topic->setEnabled(false);
995 appendSysMsg(tr("Disconnected."), true);
996 }
997}
998
999void GCMainDlg::goConn()
1000{
1001 if(d->state == Private::Idle) {
1002 d->state = Private::Connecting;
1003 appendSysMsg(tr("Reconnecting..."), true);
1004
1005 QString host = d->jid.host();
1006 QString room = d->jid.user();
1007 QString nick = d->self;
1008
1009 if(!d->pa->groupChatJoin(host, room, nick)) {
1010 appendSysMsg(tr("Error: You are in or joining this room already!"), true);
1011 d->state = Private::Idle;
1012 }
1013 }
1014}
1015
1016void GCMainDlg::pa_updatedActivity()
1017{
1018 if(!d->pa->loggedIn())
1019 goDisc();
1020 else {
1021 if(d->state == Private::Idle)
1022 goConn();
1023 else if(d->state == Private::Connected)
1024 d->pa->groupChatSetStatus(d->jid.host(), d->jid.user(), d->pa->status());
1025 }
1026}
1027
1028Jid GCMainDlg::jid() const
1029{
1030 return d->jid;
1031}
1032
1033void GCMainDlg::error(int, const QString &str)
1034{
1035 d->pb_topic->setEnabled(false);
1036
1037 if(d->state == Private::Connecting)
1038 appendSysMsg(tr("Unable to join groupchat. Reason: %1").arg(str), true);
1039 else
1040 appendSysMsg(tr("Unexpected groupchat error: %1").arg(str), true);
1041
1042 d->state = Private::Idle;
1043}
1044
1045void GCMainDlg::presence(const QString &nick, const Status &s)
1046{
1047 if(s.isAvailable()) {
1048 /*if ((option.showJoins)&&(d->lv_users->findEntry(nick)==0)) {
1049 //contact joining
1050 QString m=nick+tr(" has joined the channel");
1051 appendSysMsg(m, false, QDateTime::currentDateTime());
1052 }*/
1053 d->lv_users->updateEntry(nick, s);
1054 } else {
1055 /*if (option.showJoins) {
1056 //contact leaving
1057 QString m=nick+tr(" has left the channel");
1058 appendSysMsg(m, false, QDateTime::currentDateTime());
1059 }*/
1060 d->lv_users->removeEntry(nick);
1061 }
1062}
1063
1064void GCMainDlg::message(const Message &m)
1065{
1066 QString from = m.from().resource();
1067 bool alert = false;
1068
1069 if(!m.subject().isEmpty()) {
1070 d->le_topic->setText(m.subject());
1071 d->le_topic->setCursorPosition(0);
1072 QToolTip::add(d->le_topic, QString("<qt><p>%1</p></qt>").arg(m.subject()));
1073 }
1074
1075 // code to determine if the speaker was addressing this client in chat
1076 if(m.body().contains(d->self) > 0)
1077 alert = true;
1078
1079 if (m.body().left(d->self.length()) == d->self)
1080 d->lastReferrer = m.from().resource();
1081
1082 if(option.gcHighlighting) {
1083 for(QStringList::Iterator it=option.gcHighlights.begin();it!=option.gcHighlights.end();it++) {
1084 if(m.body().contains((*it),false) > 0) {
1085 alert = true;
1086 }
1087 }
1088 }
1089
1090 // play sound?
1091 if(from == d->self) {
1092 if(!m.spooled())
1093 d->pa->playSound(option.onevent[eSend]);
1094 }
1095 else {
1096 if(alert || (!option.noGCSound && !m.spooled() && !from.isEmpty()) )
1097 d->pa->playSound(option.onevent[eChat2]);
1098 }
1099
1100 if(from.isEmpty())
1101 appendSysMsg(m.body(), alert, m.timeStamp());
1102 else
1103 appendMessage(m, alert);
1104}
1105
1106void GCMainDlg::joined()
1107{
1108 if(d->state == Private::Connecting) {
1109 d->lv_users->QListView::clear();
1110 d->state = Private::Connected;
1111 d->pb_topic->setEnabled(true);
1112 appendSysMsg(tr("Connected."), true);
1113 }
1114}
1115
1116void GCMainDlg::appendSysMsg(const QString &str, bool alert, const QDateTime &ts)
1117{
1118 bool atBottom = d->te_log->contentsY() >= d->te_log->contentsHeight() - d->te_log->visibleHeight();
1119
1120 QString hr ="";
1121 if (d->trackBar) {
1122 hr = QString("<hr>");
1123 d->trackBar = false;
1124
1125 if ( d->trackBarParagraph ) {
1126 d->te_log->setUpdatesEnabled( false );
1127 d->te_log->setSelection(d->trackBarParagraph, 0, d->trackBarParagraph, 1, 1);
1128 d->te_log->removeSelectedText(1);
1129 d->te_log->setUpdatesEnabled( true );
1130 }
1131 d->trackBarParagraph = d->te_log->paragraphs();
1132 }
1133
1134 QString timestr;
1135 if (!option.gcHighlighting)
1136 alert=false;
1137
1138 QDateTime time;
1139 if(!ts.isNull())
1140 time = ts;
1141 else
1142 time = QDateTime::currentDateTime();
1143
1144 //timestr.sprintf("%02d:%02d:%02d", time.time().hour(), time.time().minute(), time.time().second());
1145 timestr = time.time().toString(LocalDate);
1146
1147 /*int y = d->te_log->contentsHeight() - d->te_log->visibleHeight();
1148 if(y < 0)
1149 y = 0;
1150 bool atBottom = (d->te_log->contentsY() < y - 32) ? false: true;*/
1151
1152 d->te_log->append(hr + QString("<font color=\"#00A000\">[%1]").arg(timestr) + QString(" *** %1</font>").arg(expandEntities(str)));
1153
1154 if(atBottom)
1155 d->deferredScroll();
1156
1157 if(alert)
1158 doAlert();
1159}
1160
1161QString GCMainDlg::getNickColor(QString nick)
1162{
1163 int sender;
1164 if(nick == d->self||nick.isEmpty())
1165 sender = -1;
1166 else {
1167 if (!nicks.contains(nick)) {
1168 //not found in map
1169 nicks.insert(nick,nicknumber);
1170 nicknumber++;
1171 }
1172 sender=nicks[nick];
1173 }
1174
1175 if(!option.gcNickColoring || option.gcNickColors.empty()) {
1176 return "#000000";
1177 }
1178 else if(sender == -1 || option.gcNickColors.size() == 1) {
1179 return option.gcNickColors[option.gcNickColors.size()-1];
1180 }
1181 else {
1182 int n = sender % (option.gcNickColors.size()-1);
1183 return option.gcNickColors[n];
1184 }
1185}
1186
1187void GCMainDlg::appendMessage(const Message &m, bool alert)
1188{
1189 //QString who, color;
1190 if (!option.gcHighlighting)
1191 alert=false;
1192 QString who, textcolor, nickcolor,alerttagso,alerttagsc;
1193
1194 bool atBottom = d->te_log->contentsY() >= d->te_log->contentsHeight() - d->te_log->visibleHeight();
1195
1196 who = m.from().resource();
1197 QString hr = "";
1198 if (d->trackBar&&m.from().resource() != d->self&&!m.spooled()) {
1199 hr = QString("<hr>");
1200 d->trackBar = false;
1201
1202 if ( d->trackBarParagraph ) {
1203 d->te_log->setUpdatesEnabled( false );
1204 d->te_log->setSelection(d->trackBarParagraph, 0, d->trackBarParagraph, 1, 1);
1205 d->te_log->removeSelectedText(1);
1206 d->te_log->setUpdatesEnabled( true );
1207 }
1208 d->trackBarParagraph = d->te_log->paragraphs();
1209 }
1210 /*if(local) {
1211 color = "#FF0000";
1212 }
1213 else {
1214 color = "#0000FF";
1215 }*/
1216 nickcolor = getNickColor(who);
1217 textcolor = d->te_log->palette().active().text().name();
1218 if(alert) {
1219 textcolor = "#FF0000";
1220 alerttagso = "<b>";
1221 alerttagsc = "</b>";
1222 }
1223 if(m.spooled())
1224 nickcolor = "#008000"; //color = "#008000";
1225
1226 QString timestr;
1227 QDateTime time = m.timeStamp();
1228 //timestr.sprintf("%02d:%02d:%02d", time.time().hour(), time.time().minute(), time.time().second());
1229 timestr = time.time().toString(LocalDate);
1230
1231 /*int y = d->te_log->contentsHeight() - d->te_log->visibleHeight();
1232 if(y < 0)
1233 y = 0;
1234 bool atBottom = (d->te_log->contentsY() < y - 32) ? false: true;*/
1235
1236 bool emote = false;
1237 if(m.body().left(4) == "/me ")
1238 emote = true;
1239
1240 QString txt;
1241 if(emote)
1242 txt = plain2rich(m.body().mid(4));
1243 else
1244 txt = plain2rich(m.body());
1245
1246 txt = linkify(txt);
1247
1248 if(option.useEmoticons)
1249 txt = emoticonify(txt);
1250
1251 if(emote) {
1252 //d->te_log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1]").arg(timestr) + QString(" *%1 ").arg(expandEntities(who)) + txt + "</font>");
1253 d->te_log->append(hr + QString("<font color=\"%1\">").arg(nickcolor) + QString("[%1]").arg(timestr) + QString(" *%1 ").arg(expandEntities(who)) + alerttagso + txt + alerttagsc + "</font>");
1254 }
1255 else {
1256 if(option.chatSays) {
1257 //d->te_log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1] ").arg(timestr) + QString("%1 says:").arg(expandEntities(who)) + "</font><br>" + txt);
1258 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>");
1259 }
1260 else {
1261 //d->te_log->append(QString("<font color=\"%1\">").arg(color) + QString("[%1] &lt;").arg(timestr) + expandEntities(who) + QString("&gt;</font> ") + txt);
1262 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>");
1263 }
1264 }
1265
1266 //if(local || atBottom)
1267 if(m.from().resource() == d->self || atBottom)
1268 d->deferredScroll();
1269
1270 // if we're not active, notify the user by changing the title
1271 if(!isActiveWindow()) {
1272 ++d->pending;
1273 updateCaption();
1274 }
1275
1276 //if someone directed their comments to us, notify the user
1277 if(alert)
1278 doAlert();
1279
1280 //if the message spoke to us, alert the user before closing this window
1281 //except that keepopen doesn't seem to be implemented for this class yet.
1282 /*if(alert) {
1283 d->keepOpen = true;
1284 QTimer::singleShot(1000, this, SLOT(setKeepOpenFalse()));
1285 }*/
1286}
1287
1288void GCMainDlg::doAlert()
1289{
1290 if(!isActiveWindow())
1291 doFlash(true);
1292}
1293
1294void GCMainDlg::updateCaption()
1295{
1296 QString cap = "";
1297
1298 if(d->pending > 0) {
1299 cap += "* ";
1300 if(d->pending > 1)
1301 cap += QString("[%1] ").arg(d->pending);
1302 }
1303 cap += d->jid.full();
1304
1305 // if taskbar flash, then we need to erase first and redo
1306#ifdef Q_WS_WIN
1307 bool on = false;
1308 if(d->flashTimer)
1309 on = d->flashCount & 1;
1310 if(on)
1311 FlashWindow(winId(), true);
1312#endif
1313 setCaption(cap);
1314#ifdef Q_WS_WIN
1315 if(on)
1316 FlashWindow(winId(), true);
1317#endif
1318}
1319
1320#ifdef Q_WS_WIN
1321void GCMainDlg::doFlash(bool yes)
1322{
1323 if(yes) {
1324 if(d->flashTimer)
1325 return;
1326 d->flashTimer = new QTimer(this);
1327 connect(d->flashTimer, SIGNAL(timeout()), SLOT(flashAnimate()));
1328 d->flashCount = 0;
1329 flashAnimate(); // kick the first one immediately
1330 d->flashTimer->start(500);
1331 }
1332 else {
1333 if(d->flashTimer) {
1334 delete d->flashTimer;
1335 d->flashTimer = 0;
1336 FlashWindow(winId(), false);
1337 }
1338 }
1339}
1340#else
1341void GCMainDlg::doFlash(bool)
1342{
1343}
1344#endif
1345
1346void GCMainDlg::flashAnimate()
1347{
1348#ifdef Q_WS_WIN
1349 FlashWindow(winId(), true);
1350 ++d->flashCount;
1351 if(d->flashCount == 5)
1352 d->flashTimer->stop();
1353#endif
1354}
1355
1356void GCMainDlg::setLooks()
1357{
1358 // update the fonts
1359 QFont f;
1360 f.fromString(option.font[fChat]);
1361 d->te_log->setFont(f);
1362 d->mle->setFont(f);
1363
1364 f.fromString(option.font[fRoster]);
1365 d->lv_users->QListView::setFont(f);
1366
1367 if ( d->smallChat ) {
1368 d->toolbar->hide();
1369 }
1370 else {
1371 d->toolbar->show();
1372 }
1373
1374 // update the widget icon
1375#ifndef Q_WS_MAC
1376 setIcon(IconsetFactory::icon("psi/groupChat"));
1377#endif
1378}
1379
1380void GCMainDlg::optionsUpdate()
1381{
1382 /*QMimeSourceFactory *m = d->te_log->mimeSourceFactory();
1383 d->te_log->setMimeSourceFactory(is->emoticons.generateFactory());
1384 delete m;*/
1385
1386 setLooks();
1387
1388 // update status icons
1389 d->lv_users->updateAll();
1390}
1391
1392void GCMainDlg::lv_action(const QString &nick, const Status &s, int x)
1393{
1394 if(x == 0) {
1395 d->pa->invokeGCMessage(d->jid.withResource(nick));
1396 }
1397 else if(x == 1) {
1398 d->pa->invokeGCChat(d->jid.withResource(nick));
1399 }
1400 else if(x == 2) {
1401 UserListItem u;
1402 u.setJid(d->jid.withResource(nick));
1403 u.setName(nick);
1404
1405 // make a resource so the contact appears online
1406 UserResource ur;
1407 ur.setName(nick);
1408 ur.setStatus(s);
1409 u.userResourceList().append(ur);
1410
1411 StatusShowDlg *w = new StatusShowDlg(u);
1412 w->show();
1413 }
1414 else if(x == 3) {
1415 d->pa->invokeGCInfo(d->jid.withResource(nick));
1416 }
1417 else if(x == 4) {
1418 d->pa->invokeGCFile(d->jid.withResource(nick));
1419 }
1420}
1421
1422void GCMainDlg::contextMenuEvent(QContextMenuEvent *)
1423{
1424 d->pm_settings->exec(QCursor::pos());
1425}
1426
1427void GCMainDlg::buildMenu()
1428{
1429 // Dialog menu
1430 d->pm_settings->clear();
1431 d->pm_settings->insertItem(tr("Toggle Compact/Full Size"), this, SLOT(toggleSmallChat()));
1432
1433 d->act_clear->addTo( d->pm_settings );
1434 d->pm_settings->insertSeparator();
1435
1436 d->act_icon->addTo( d->pm_settings );
1437}
1438
1439void GCMainDlg::toggleSmallChat()
1440{
1441 d->smallChat = !d->smallChat;
1442 setLooks();
1443}
1444
1445//----------------------------------------------------------------------------
1446// GCFindDlg
1447//----------------------------------------------------------------------------
1448GCFindDlg::GCFindDlg(int startPara, int startIndex, const QString &str, QWidget *parent, const char *name)
1449:QDialog(parent, name, false, WDestructiveClose)
1450{
1451 para = startPara;
1452 index = startIndex;
1453
1454 setCaption(tr("Find"));
1455 QVBoxLayout *vb = new QVBoxLayout(this, 4);
1456 QHBoxLayout *hb = new QHBoxLayout(vb);
1457 QLabel *l = new QLabel(tr("Find:"), this);
1458 hb->addWidget(l);
1459 le_input = new QLineEdit(this);
1460 hb->addWidget(le_input);
1461 vb->addStretch(1);
1462
1463 QFrame *Line1 = new QFrame(this);
1464 Line1->setFrameShape( QFrame::HLine );
1465 Line1->setFrameShadow( QFrame::Sunken );
1466 Line1->setFrameShape( QFrame::HLine );
1467 vb->addWidget(Line1);
1468
1469 hb = new QHBoxLayout(vb);
1470 hb->addStretch(1);
1471 QPushButton *pb_close = new QPushButton(tr("&Close"), this);
1472 connect(pb_close, SIGNAL(clicked()), SLOT(close()));
1473 hb->addWidget(pb_close);
1474 QPushButton *pb_find = new QPushButton(tr("&Find"), this);
1475 pb_find->setDefault(true);
1476 connect(pb_find, SIGNAL(clicked()), SLOT(doFind()));
1477 hb->addWidget(pb_find);
1478 pb_find->setAutoDefault(true);
1479
1480 resize(200, minimumSize().height());
1481
1482 le_input->setText(str);
1483 le_input->setFocus();
1484}
1485
1486GCFindDlg::~GCFindDlg()
1487{
1488}
1489
1490void GCFindDlg::found(int _para, int _index)
1491{
1492 para = _para;
1493 index = _index;
1494}
1495
1496void GCFindDlg::error(const QString &str)
1497{
1498 QMessageBox::warning(this, tr("Find"), tr("Search string '%1' not found.").arg(str));
1499 le_input->setFocus();
1500}
1501
1502void GCFindDlg::doFind()
1503{
1504 emit find(para, index, le_input->text());
1505}
1506
1507#include "groupchatdlg.moc"
Note: See TracBrowser for help on using the repository browser.