1 /*
2 * This file is part of Licq, an instant messaging client for UNIX.
3 * Copyright (C) 1999-2013 Licq developers <licq-dev@googlegroups.com>
4 *
5 * Licq is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * Licq 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 Licq; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 // written by Graham Roff <graham@licq.org>
21 // Contributions by Dirk A. Mueller <dirk@licq.org>
22
23 // -----------------------------------------------------------------------------
24 #include "chatdlg.h"
25
26 #include "config.h"
27
28 #include <cctype>
29 #include <cstdlib>
30 #include <unistd.h>
31
32 #include <QActionGroup>
33 #include <QApplication>
34 #include <QByteArray>
35 #include <QClipboard>
36 #include <QCloseEvent>
37 #include <QComboBox>
38 #include <QDateTime>
39 #include <QFontDatabase>
40 #include <QGridLayout>
41 #include <QGroupBox>
42 #include <QKeyEvent>
43 #include <QLabel>
44 #include <QList>
45 #include <QListWidget>
46 #include <QMenu>
47 #include <QMenuBar>
48 #include <QMouseEvent>
49 #include <QPainter>
50 #include <QPixmap>
51 #include <QSocketNotifier>
52 #include <QTextStream>
53 #include <QToolBar>
54 #include <QToolButton>
55
56 #ifdef USE_KDE
57 #include <kfiledialog.h>
58 #else
59 #include <QFileDialog>
60 #endif
61
62 #include <licq/icq/chat.h>
63 #include <licq/icq/icq.h>
64 #include <licq/logging/log.h>
65 #include <licq/plugin/pluginmanager.h>
66
67 #include "config/chat.h"
68 #include "config/general.h"
69 #include "config/iconmanager.h"
70
71 #include "core/messagebox.h"
72
73 #include "helpers/support.h"
74
75 using namespace LicqQtGui;
76 /* TRANSLATOR LicqQtGui::ChatDlg */
77
78 ChatDlgList ChatDlg::chatDlgs;
79
80 static const int col_array[] =
81 {
82 0x00, 0x00, 0x00,
83 0x80, 0x00, 0x00,
84 0x00, 0x80, 0x00,
85 0x80, 0x80, 0x00,
86 0x00, 0x00, 0x80,
87 0x80, 0x00, 0x80,
88 0x00, 0x80, 0x80,
89 0x80, 0x80, 0x80,
90 0xC0, 0xC0, 0xC0,
91 0xFF, 0x00, 0x00,
92 0x00, 0xFF, 0x00,
93 0xFF, 0xFF, 0x00,
94 0x00, 0x00, 0xFF,
95 0xFF, 0x00, 0xFF,
96 0x00, 0xFF, 0xFF,
97 0xFF, 0xFF, 0xFF
98 };
99
100 #define NUM_COLORS sizeof(col_array)/sizeof(int)/3
101
102
103 // ---------------------------------------------------------------------------
ChatDlg(const Licq::UserId & userId,QWidget * parent)104 ChatDlg::ChatDlg(const Licq::UserId& userId, QWidget* parent)
105 : QDialog(parent),
106 myAudio(true)
107 {
108 Support::setWidgetProps(this, "ChatDialog");
109 setAttribute(Qt::WA_DeleteOnClose, true);
110
111 myId = userId.accountId().c_str();
112 myPpid = userId.protocolId();
113
114 sn = NULL;
115
116 m_nMode = CHAT_PANE;
117
118 setWindowTitle(tr("Licq - Chat"));
119
120 // Pane mode setup
121 boxPane = new QGroupBox();
122 paneLayout = new QGridLayout(boxPane);
123 remoteLayout = new QGridLayout();
124 paneLayout->addLayout(remoteLayout, 0, 0);
125 lblRemote = new QLabel(tr("Remote - Not connected"), boxPane);
126 remoteLayout->addWidget(lblRemote, 0, 0);
127 remoteLayout->setRowStretch(1, 1);
128
129 paneLayout->setRowMinimumHeight(1, 15);
130
131 QGridLayout* llay = new QGridLayout();
132 paneLayout->addLayout(llay, 2, 0);
133 lblLocal = new QLabel(boxPane);
134 mlePaneLocal = new ChatWindow(boxPane);
135 mlePaneLocal->setMinimumHeight(100);
136 mlePaneLocal->setMinimumWidth(150);
137 mlePaneLocal->setEnabled(false);
138 llay->addWidget(lblLocal, 0, 0);
139 llay->addWidget(mlePaneLocal, 1, 0);
140 llay->setRowStretch(1, 1);
141
142 // IRC mode setup
143 boxIRC = new QGroupBox();
144 QGridLayout* lay = new QGridLayout(boxIRC);
145 mleIRCRemote = new ChatWindow(boxIRC);
146 mleIRCRemote->setReadOnly(true);
147 mleIRCRemote->setMinimumHeight(100);
148 mleIRCRemote->setMinimumWidth(150);
149 lay->addWidget(mleIRCRemote, 0, 0);
150 lstUsers = new QListWidget(boxIRC);
151 lay->addWidget(lstUsers, 0, 1, 2, 1);
152 mleIRCLocal = new ChatWindow(boxIRC);
153 mleIRCLocal->setEnabled(false);
154 mleIRCLocal->setFixedHeight(mleIRCLocal->fontMetrics().lineSpacing() * 4);
155 lay->addWidget(mleIRCLocal, 1, 0);
156 lay->setRowStretch(0, 1);
157 lay->setColumnStretch(0, 1);
158
159 // Generic setup
160 QMenuBar* menuBar = new QMenuBar(this);
161
162 mnuMain = new QMenu(tr("Chat"), menuBar);
163 menuBar->addMenu(mnuMain);
164 QAction* chatAudio = mnuMain->addAction(tr("&Audio"), this, SLOT(slot_audio(bool)), Qt::ALT + Qt::Key_A);
165 chatAudio->setCheckable(true);
166 chatAudio->setChecked(myAudio);
167 mnuMain->addAction(tr("&Save Chat"), this, SLOT(slot_save()), Qt::ALT + Qt::Key_S);
168 mnuMain->addSeparator();
169 mnuMain->addAction(tr("&Close Chat"), this, SLOT(close()), Qt::ALT + Qt::Key_Q);
170
171 mnuMode = new QMenu(tr("Mode"), menuBar);
172 menuBar->addMenu(mnuMode);
173 QActionGroup* modeGroup = new QActionGroup(this);
174 QAction* paneModeAction = modeGroup->addAction(tr("&Pane Mode"));
175 connect(paneModeAction, SIGNAL(triggered()), SLOT(SwitchToPaneMode()));
176 paneModeAction->setCheckable(true);
177 paneModeAction->setChecked(true);
178 QAction* ircModeAction = modeGroup->addAction(tr("&IRC Mode"));
179 connect(ircModeAction, SIGNAL(triggered()), SLOT(SwitchToIRCMode()));
180 ircModeAction->setCheckable(true);
181 mnuMode->addActions(modeGroup->actions());
182
183 // Toolbar
184 QToolBar* barChat = new QToolBar("label", this);
185 barChat->setIconSize(QSize(16, 16));
186
187 // ### FIXME: implement laughing
188 // tbtLaugh = new QToolButton(LeftArrow, barChat);
189
190 tbtIgnore = barChat->addAction(IconManager::instance()->getIcon(IconManager::IgnoreIcon), tr("Ignore user settings"));
191 tbtIgnore->setToolTip(tr("Ignores user color settings"));
192 connect(tbtIgnore, SIGNAL(triggered()), SLOT(updateRemoteStyle()));
193 tbtIgnore->setCheckable(true);
194
195 tbtBeep = barChat->addAction(IconManager::instance()->getIcon(IconManager::BeepIcon), tr("Beep"));
196 tbtBeep->setToolTip(tr("Sends a Beep to all recipients"));
197 connect(tbtBeep, SIGNAL(triggered()), SLOT(chatSendBeep()));
198
199 barChat->addSeparator();
200
201 tbtFg = barChat->addAction(IconManager::instance()->getIcon(IconManager::TextColorIcon), tr("Foreground color"));
202 tbtFg->setToolTip(tr("Changes the foreground color"));
203 mnuFg = new QMenu();
204 connect(mnuFg, SIGNAL(triggered(QAction*)), SLOT(changeFrontColor(QAction*)));
205 tbtFg->setMenu(mnuFg);
206 dynamic_cast<QToolButton*>(barChat->widgetForAction(tbtFg))->setPopupMode(QToolButton::InstantPopup);
207
208 tbtBg = barChat->addAction(IconManager::instance()->getIcon(IconManager::BackColorIcon), tr("Background color"));
209 tbtBg->setToolTip(tr("Changes the background color"));
210 mnuBg = new QMenu();
211 connect(mnuBg, SIGNAL(triggered(QAction*)), SLOT(changeBackColor(QAction*)));
212 tbtBg->setMenu(mnuBg);
213 dynamic_cast<QToolButton*>(barChat->widgetForAction(tbtBg))->setPopupMode(QToolButton::InstantPopup);
214
215 for(unsigned int i = 0; i < NUM_COLORS; i++)
216 {
217 QPixmap pix(48, 14);
218 QPainter p(&pix);
219 QColor c (col_array[i*3+0], col_array[i*3+1], col_array[i*3+2]);
220
221 pix.fill(c);
222 p.drawRect(0, 0, 48, 14);
223
224 mnuBg->addAction(pix, QString())->setData(i);
225 QPixmap pixf(48, 14);
226 pixf.fill(palette().color(QPalette::Background));
227 QPainter pf(&pixf);
228 pf.setPen(c);
229 pf.drawText(5, 12, QString("Abc"));
230 mnuFg->addAction(pixf, QString())->setData(i);
231 }
232 barChat->addSeparator();
233
234 tbtBold = barChat->addAction(IconManager::instance()->getIcon(IconManager::BoldIcon), tr("Bold"));
235 tbtBold->setToolTip(tr("Toggles Bold font"));
236 connect(tbtBold, SIGNAL(triggered()), SLOT(fontStyleChanged()));
237 tbtBold->setCheckable(true);
238
239 tbtItalic = barChat->addAction(IconManager::instance()->getIcon(IconManager::ItalicIcon), tr("Italic"));
240 tbtItalic->setToolTip(tr("Toggles Italic font"));
241 connect(tbtItalic, SIGNAL(triggered()), SLOT(fontStyleChanged()));
242 tbtItalic->setCheckable(true);
243
244 tbtUnderline = barChat->addAction(IconManager::instance()->getIcon(IconManager::UnderlineIcon), tr("Underline"));
245 tbtUnderline->setToolTip(tr("Toggles Underline font"));
246 connect(tbtUnderline, SIGNAL(triggered()), SLOT(fontStyleChanged()));
247 tbtUnderline->setCheckable(true);
248
249 tbtStrikeOut = barChat->addAction(IconManager::instance()->getIcon(IconManager::StrikethroughIcon), tr("StrikeOut"));
250 tbtStrikeOut->setToolTip(tr("Toggles StrikeOut font"));
251 connect(tbtStrikeOut, SIGNAL(triggered()), SLOT(fontStyleChanged()));
252 tbtStrikeOut->setCheckable(true);
253
254 barChat->addSeparator();
255
256 cmbFontSize = new QComboBox();
257 barChat->addWidget(cmbFontSize);
258 cmbFontSize->setInsertPolicy(QComboBox::NoInsert);
259 //windows font size limit seems to be 1638 (tested 98, 2000)
260 cmbFontSize->setValidator(new QIntValidator(1, 1638, cmbFontSize));
261 connect(cmbFontSize, SIGNAL(activated(const QString&)), SLOT(fontSizeChanged(const QString&)));
262 cmbFontSize->addItem(QString::number(font().pointSize()));
263
264 QList<int> sizes = QFontDatabase::standardSizes();
265 for(int i = 0; i < sizes.count(); i++)
266 if(sizes[i] != font().pointSize())
267 cmbFontSize->addItem(QString::number(sizes[i]));
268
269 QFontDatabase fb;
270 cmbFontName = new QComboBox();
271 #if 0
272 cmbFontName->setSizeLimit(15);
273 QStringList sl = fb.families();
274 while(sl.at(55) != sl.end()) sl.remove(sl.at(55));
275 #endif
276 // cmbFontName->setFixedSize(cmbFontName->sizeHint());
277 cmbFontName->addItems(fb.families());
278 barChat->addWidget(cmbFontName);
279 connect(cmbFontName, SIGNAL(activated(const QString&)), SLOT(fontNameChanged(const QString&)));
280
281 barChat->addSeparator();
282
283 QMenu* popupEncoding = new QMenu;
284 QActionGroup* encodingsGroup = new QActionGroup(this);
285 connect(encodingsGroup, SIGNAL(triggered(QAction*)), SLOT(setEncoding(QAction*)));
286
287 QAction* a;
288 #define ADD_ENCODING(id, name) \
289 a = new QAction(name, encodingsGroup); \
290 a->setData(id);
291
292 ADD_ENCODING(Licq::ENCODING_DEFAULT, tr("Default (UTF-8)"))
293 a->setChecked(true);
294 ADD_ENCODING(Licq::ENCODING_ANSI, tr("Western Europe (CP 1252)"))
295 ADD_ENCODING(Licq::ENCODING_SHIFTJIS, tr("Shift-JIS"))
296 ADD_ENCODING(Licq::ENCODING_GB2312, tr("Chinese (GBK)"))
297 ADD_ENCODING(Licq::ENCODING_CHINESEBIG5, tr("Chinese Traditional (Big5)"))
298 ADD_ENCODING(Licq::ENCODING_GREEK, tr("Greek (CP 1253)"))
299 ADD_ENCODING(Licq::ENCODING_TURKISH, tr("Turkish (CP 1254)"))
300 ADD_ENCODING(Licq::ENCODING_HEBREW, tr("Hebrew (CP 1255)"))
301 ADD_ENCODING(Licq::ENCODING_ARABIC, tr("Arabic (CP 1256)"))
302 ADD_ENCODING(Licq::ENCODING_BALTIC, tr("Baltic (CP 1257)"))
303 ADD_ENCODING(Licq::ENCODING_RUSSIAN, tr("Russian (CP 1251)"))
304 ADD_ENCODING(Licq::ENCODING_THAI, tr("Thai (TIS-620)"))
305 ADD_ENCODING(Licq::ENCODING_EASTEUROPE, tr("Central European (CP 1250)"))
306 #undef ADD_ENCODING
307 myChatEncoding = Licq::ENCODING_DEFAULT;
308
309 tbtEncoding = barChat->addAction(IconManager::instance()->getIcon(IconManager::EncodingIcon), tr("Set Encoding"));
310 tbtEncoding->setMenu(popupEncoding);
311 dynamic_cast<QToolButton*>(barChat->widgetForAction(tbtEncoding))->setPopupMode(QToolButton::InstantPopup);
312
313 // QWidget* dummy = new QWidget(barChat);
314 // barChat->setStretchableWidget(dummy);
315
316 QGridLayout* g = new QGridLayout(this);
317 int ml, mt, mr, mb;
318 g->getContentsMargins(&ml, &mt, &mr, &mb);
319 g->setContentsMargins(ml, menuBar->height(), mr, mb);
320 g->addWidget(barChat, 0, 0);
321 g->addWidget(boxPane, 1, 0);
322 g->addWidget(boxIRC, 1, 0);
323
324 SwitchToPaneMode();
325
326 // Add ourselves to the list
327 chatDlgs.push_back(this);
328
329 // Create the chat manager using our font
330 QFontInfo fi(mlePaneLocal->font());
331 QFontDatabase fd; //QFontInfo.fixedPitch returns incorrect info???
332 unsigned char style = Licq::STYLE_DONTCARE;
333
334 if (fd.isFixedPitch(fi.family(), fd.styleString(mlePaneLocal->font())))
335 style |= Licq::STYLE_FIXEDxPITCH;
336 else
337 style |= Licq::STYLE_VARIABLExPITCH;
338
339 Licq::IcqProtocol::Ptr icq = plugin_internal_cast<Licq::IcqProtocol>(
340 Licq::gPluginManager.getProtocolInstance(userId.ownerId()));
341 if (!icq)
342 {
343 close();
344 return;
345 }
346
347 //TODO in daemon
348 chatman = icq->createChatManager(userId);
349 chatman->init(fi.family().toUtf8().constData(), myChatEncoding, style,
350 fi.pointSize(), fi.bold(), fi.italic(), fi.underline(), fi.strikeOut());
351
352 sn = new QSocketNotifier(chatman->Pipe(), QSocketNotifier::Read);
353 connect(sn, SIGNAL(activated(int)), SLOT(slot_chat()));
354
355 // But use the chat manager default colors
356 mlePaneLocal->setForeground(QColor(chatman->ColorFg()[0],
357 chatman->ColorFg()[1], chatman->ColorFg()[2]));
358 mlePaneLocal->setBackground(QColor(chatman->ColorBg()[0],
359 chatman->ColorBg()[1], chatman->ColorBg()[2]));
360 mleIRCLocal->setForeground(QColor(chatman->ColorFg()[0],
361 chatman->ColorFg()[1], chatman->ColorFg()[2]));
362 mleIRCLocal->setBackground(QColor(chatman->ColorBg()[0],
363 chatman->ColorBg()[1], chatman->ColorBg()[2]));
364 mleIRCRemote->setForeground(QColor(chatman->ColorFg()[0],
365 chatman->ColorFg()[1], chatman->ColorFg()[2]));
366 mleIRCRemote->setBackground(QColor(chatman->ColorBg()[0],
367 chatman->ColorBg()[1], chatman->ColorBg()[2]));
368 chatname = QString::fromLocal8Bit(chatman->name().c_str());
369 lstUsers->addItem(chatname);
370 lblLocal->setText(tr("Local - %1").arg(chatname));
371
372 setMinimumSize(400, 300);
373 resize(550, 475);
374 show();
375 }
376
377 // -----------------------------------------------------------------------------
378
~ChatDlg()379 ChatDlg::~ChatDlg()
380 {
381 delete chatman;
382
383 if (sn != NULL) delete sn;
384 sn = NULL;
385
386 ChatDlgList::iterator iter;
387 for (iter = chatDlgs.begin(); iter != chatDlgs.end(); iter++)
388 {
389 if (this == *iter)
390 {
391 chatDlgs.erase(iter);
392 break;
393 }
394 }
395 }
396
397
398 // -----------------------------------------------------------------------------
399
fontSizeChanged(const QString & txt)400 void ChatDlg::fontSizeChanged(const QString& txt)
401 {
402 QFont f(mlePaneLocal->font());
403
404 int nNewSize = txt.toInt();
405 if (nNewSize > 24)
406 nNewSize = 24;
407
408 f.setPointSize(nNewSize);
409
410 mlePaneLocal->setFont(f);
411 mleIRCLocal->setFont(f);
412 mleIRCRemote->setFont(f);
413
414 // if ignoring style change the remote panes too
415 updateRemoteStyle();
416
417 // transmit to remote
418 QFontInfo fi(f);
419 chatman->ChangeFontSize(fi.pointSize());
420 }
421
422
423 // -----------------------------------------------------------------------------
424
fontNameChanged(const QString & txt)425 void ChatDlg::fontNameChanged(const QString& txt)
426 {
427 QFont f(mlePaneLocal->font());
428
429 f.setFamily(txt);
430
431 mlePaneLocal->setFont(f);
432 mleIRCLocal->setFont(f);
433 mleIRCRemote->setFont(f);
434
435 // if ignoring style change the remote panes too
436 updateRemoteStyle();
437
438 // transmit to remote
439 sendFontInfo();
440 }
441
442 // -----------------------------------------------------------------------------
443
sendFontInfo()444 void ChatDlg::sendFontInfo()
445 {
446 //FIXME can we get more precise style???
447 QFontInfo fi(mlePaneLocal->font());
448 QFontDatabase fd; //QFontInfo.fixedPitch returns incorrect info???
449 unsigned char style = Licq::STYLE_DONTCARE;
450
451 if (fd.isFixedPitch(fi.family(), fd.styleString(mlePaneLocal->font())))
452 style |= Licq::STYLE_FIXEDxPITCH;
453 else
454 style |= Licq::STYLE_VARIABLExPITCH;
455
456 chatman->changeFontFamily(fi.family().toUtf8().constData(), myChatEncoding, style);
457 }
458
459 // -----------------------------------------------------------------------------
460
fontStyleChanged()461 void ChatDlg::fontStyleChanged()
462 {
463 QFont f(mlePaneLocal->font());
464
465 f.setBold(tbtBold->isChecked());
466 f.setItalic(tbtItalic->isChecked());
467 f.setUnderline(tbtUnderline->isChecked());
468 f.setStrikeOut(tbtStrikeOut->isChecked());
469
470 mlePaneLocal->setFont(f);
471 mleIRCLocal->setFont(f);
472 mleIRCRemote->setFont(f);
473
474 // if ignoring style change the remote panes too
475 updateRemoteStyle();
476
477 // transmit to remote
478 QFontInfo fi(f);
479 chatman->ChangeFontFace(fi.bold(), fi.italic(), fi.underline(), fi.strikeOut());
480 }
481
482
483 // -----------------------------------------------------------------------------
484
chatSendBeep()485 void ChatDlg::chatSendBeep()
486 {
487 chatman->SendBeep();
488 QApplication::beep();
489 }
490
491 // -----------------------------------------------------------------------------
492
changeFrontColor(QAction * action)493 void ChatDlg::changeFrontColor(QAction* action)
494 {
495 int i = action->data().toInt();
496 if (i < 0) return;
497
498 QColor color (col_array[i*3+0], col_array[i*3+1], col_array[i*3+2]);
499
500 mlePaneLocal->setForeground(color);
501 mleIRCLocal->setForeground(color);
502 mleIRCRemote->setForeground(color);
503
504 // if ignoring style change the remote panes too
505 updateRemoteStyle();
506
507 // sent to remote
508 chatman->ChangeColorFg(color.red(), color.green(), color.blue());
509 }
510
511
512 // -----------------------------------------------------------------------------
513
changeBackColor(QAction * action)514 void ChatDlg::changeBackColor(QAction* action)
515 {
516 int i = action->data().toInt();
517 if (i < 0) return;
518
519 QColor color (col_array[i*3+0], col_array[i*3+1], col_array[i*3+2]);
520
521 mlePaneLocal->setBackground(color);
522 mleIRCLocal->setBackground(color);
523 mleIRCRemote->setBackground(color);
524
525 // if ignoring style change the remote panes too
526 updateRemoteStyle();
527
528 // sent to remote
529 chatman->ChangeColorBg(color.red(), color.green(), color.blue());
530 }
531
532 // -----------------------------------------------------------------------------
533
updateRemoteStyle()534 void ChatDlg::updateRemoteStyle()
535 {
536 if(tbtIgnore->isChecked()) {
537 QColor fg(chatman->ColorFg()[0], chatman->ColorFg()[1],
538 chatman->ColorFg()[2]);
539 QColor bg(chatman->ColorBg()[0], chatman->ColorBg()[1],
540 chatman->ColorBg()[2]);
541 QFont f(mlePaneLocal->font());
542 ChatUserWindowsList::iterator iter;
543 for (iter = chatUserWindows.begin(); iter != chatUserWindows.end(); iter++)
544 {
545 iter->w->setForeground(fg);
546 iter->w->setBackground(bg);
547 iter->w->setFont(f);
548 }
549 }
550 else {
551 ChatUserWindowsList::iterator iter;
552 for (iter = chatUserWindows.begin(); iter != chatUserWindows.end(); iter++)
553 {
554 QColor fg(iter->u->ColorFg()[0], iter->u->ColorFg()[1],
555 iter->u->ColorFg()[2]);
556 QColor bg(iter->u->ColorBg()[0], iter->u->ColorBg()[1],
557 iter->u->ColorBg()[2]);
558 QFont f(iter->w->font());
559 f.setFixedPitch((iter->u->FontStyle() & 0x0F) == Licq::STYLE_FIXEDxPITCH);
560
561 switch (iter->u->FontStyle() & 0xF0)
562 {
563 case Licq::STYLE_ROMAN:
564 f.setStyleHint(QFont::Serif);
565 break;
566 case Licq::STYLE_SWISS:
567 f.setStyleHint(QFont::SansSerif);
568 break;
569 case Licq::STYLE_DECORATIVE:
570 f.setStyleHint(QFont::Decorative);
571 break;
572 case Licq::STYLE_DONTCARE:
573 case Licq::STYLE_MODERN:
574 case Licq::STYLE_SCRIPT:
575 default:
576 f.setStyleHint(QFont::AnyStyle);
577 break;
578 }
579 f.setFamily(QString::fromUtf8(iter->u->fontFamily().c_str()));
580 f.setPointSize(iter->u->FontSize());
581 f.setBold(iter->u->FontBold());
582 f.setItalic(iter->u->FontItalic());
583 f.setUnderline(iter->u->FontUnderline());
584 f.setStrikeOut(iter->u->FontStrikeOut());
585 iter->w->setForeground(fg);
586 iter->w->setBackground(bg);
587 iter->w->setFont(f);
588 }
589 }
590 }
591
592
593 //-----ChatDlg::StartAsServer------------------------------------------------
StartAsServer()594 bool ChatDlg::StartAsServer()
595 {
596 lblRemote->setText(tr("Remote - Waiting for joiners..."));
597 if (!chatman->StartAsServer()) return false;
598 return true;
599 }
600
601
602
603 //-----ChatDlg::StartAsClient------------------------------------------------
StartAsClient(unsigned short nPort)604 bool ChatDlg::StartAsClient(unsigned short nPort)
605 {
606 lblRemote->setText(tr("Remote - Connecting..."));
607 chatman->StartAsClient(nPort);
608 return true;
609 }
610
611
612
613 //-----chatSend-----------------------------------------------------------------
chatSend(QKeyEvent * e)614 void ChatDlg::chatSend(QKeyEvent* e)
615 {
616 switch (e->key())
617 {
618 case Qt::Key_Enter:
619 case Qt::Key_Return:
620 {
621 if (m_nMode == CHAT_IRC) {
622 QString text = mleIRCLocal->toPlainText();
623 if (text.right(1) == "\n") text.truncate(text.length()-1);
624 // send the data over the wire
625 chatman->sendText(text.toUtf8().constData());
626
627 // even if the pane didn't trigger the event,
628 // we need to keep it updated
629 mlePaneLocal->appendNoNewLine("\n");
630 // so you'll get some idea what your buddy sees (encoding-wise)
631 mleIRCRemote->append(chatname + "> " + text);
632 mleIRCRemote->GotoEnd();
633
634 mleIRCLocal->clear();
635 } else {
636 // keep IRC updated anyway (encoding should be already properly represented)
637 mleIRCRemote->append(chatname + "> " + mlePaneLocal->lastLine());
638 }
639
640 chatman->SendNewline();
641 break;
642 }
643 case Qt::Key_Backspace:
644 {
645 if (m_nMode == CHAT_IRC) {
646 mlePaneLocal->backspace(); // keep the pane updated
647 }
648
649 if (m_nMode == CHAT_PANE) {
650 chatman->SendBackspace();
651 }
652
653 break;
654 }
655 case Qt::Key_Tab:
656 case Qt::Key_Backtab:
657 break;
658
659 default:
660 {
661 // if in pane mode, send right away
662 if (m_nMode == CHAT_PANE) {
663 // for multibyte encodings
664 chatman->sendText(e->text().toUtf8().constData());
665 } else {
666 // if the pane is not what triggered the key press, it still needs
667 // to be updated
668 mlePaneLocal->appendNoNewLine(e->text());
669 }
670
671 break;
672 }
673 }
674 }
675
676
677
678
679
680 //-----ChatDlg::slot_chat----------------------------------------------------
slot_chat()681 void ChatDlg::slot_chat()
682 {
683 // Read out any pending events
684 char buf[32];
685 read(chatman->Pipe(), buf, 32);
686
687 Licq::IcqChatEvent* e = NULL;
688 while ( (e = chatman->PopChatEvent()) != NULL)
689 {
690 Licq::IcqChatUser* u = e->Client();
691
692 switch(e->Command())
693 {
694 case Licq::CHAT_ERRORxBIND:
695 {
696 WarnUser(this, tr("Unable to bind to a port.\nSee Network Window "
697 "for details."));
698 chatClose(u);
699 break;
700 }
701
702 case Licq::CHAT_ERRORxCONNECT:
703 {
704 WarnUser(this, tr("Unable to connect to the remote chat.\nSee Network "
705 "Window for details."));
706 chatClose(u);
707 break;
708 }
709
710 case Licq::CHAT_ERRORxRESOURCES:
711 {
712 WarnUser(this, tr("Unable to create new thread.\nSee Network Window "
713 "for details."));
714 chatClose(u);
715 break;
716 }
717
718 case Licq::CHAT_DISCONNECTION:
719 {
720 QString n = QString::fromUtf8(u->name().c_str());
721
722 if (n.isEmpty())
723 n = u->userId().toString().c_str();
724 chatClose(u);
725 InformUser(this, tr("%1 closed connection.").arg(n));
726 break;
727 }
728
729 case Licq::CHAT_CONNECTION:
730 {
731 QString n = QString::fromUtf8(u->name().c_str());
732
733 // Add the user to the listbox
734 lstUsers->addItem(n);
735 // If this is the first user, set up the remote mle
736 if (!mlePaneLocal->isEnabled())
737 {
738 delete lblRemote;
739 connect(mlePaneLocal, SIGNAL(keyPressed(QKeyEvent*)), SLOT(chatSend(QKeyEvent*)));
740 connect(mleIRCLocal, SIGNAL(keyPressed(QKeyEvent*)), SLOT(chatSend(QKeyEvent*)));
741 mlePaneLocal->setEnabled(true);
742 mleIRCLocal->setEnabled(true);
743 if (m_nMode == CHAT_PANE)
744 mlePaneLocal->setFocus();
745 else
746 mleIRCLocal->setFocus();
747 }
748
749 ChatWindow* lePaneRemote = new ChatWindow(boxPane);
750 lePaneRemote->setReadOnly(true);
751 QLabel* lbl = new QLabel(n, boxPane);
752 UserWindowPair uwp = { u, lePaneRemote, lbl};
753 chatUserWindows.push_back(uwp);
754
755 updateRemoteStyle();
756
757 UpdateRemotePane();
758
759 break;
760 }
761
762 case Licq::CHAT_NEWLINE:
763 {
764 QString n = QString::fromUtf8(u->name().c_str());
765
766 // add to IRC box
767 mleIRCRemote->append(n + QString::fromLatin1("> ") + QString::fromUtf8(e->data().c_str()));
768 mleIRCRemote->GotoEnd();
769 GetWindow(u)->appendNoNewLine("\n");
770 GetWindow(u)->GotoEnd();
771 break;
772 }
773
774 case Licq::CHAT_BEEP: // beep
775 {
776 if (myAudio)
777 QApplication::beep();
778 else
779 {
780 GetWindow(u)->append(tr("\n<--BEEP-->\n"));
781 mleIRCRemote->append(chatname + tr("> <--BEEP-->\n"));
782 }
783 break;
784 }
785
786 case Licq::CHAT_BACKSPACE: // backspace
787 {
788
789 GetWindow(u)->setReadOnly(false);
790
791 GetWindow(u)->backspace();
792
793 GetWindow(u)->setReadOnly(true);
794 GetWindow(u)->update();
795
796 break;
797 }
798
799 case Licq::CHAT_COLORxFG: // change foreground color
800 {
801 if (! tbtIgnore->isChecked())
802 GetWindow(u)->setForeground(QColor (u->ColorFg()[0],
803 u->ColorFg()[1], u->ColorFg()[2]));
804 break;
805 }
806
807 case Licq::CHAT_COLORxBG: // change background color
808 {
809 if (! tbtIgnore->isChecked())
810 GetWindow(u)->setBackground(QColor (u->ColorBg()[0],
811 u->ColorBg()[1], u->ColorBg()[2]));
812
813 break;
814 }
815
816 case Licq::CHAT_FONTxFAMILY: // change font type
817 {
818 if (! tbtIgnore->isChecked())
819 {
820 QFont f(GetWindow(u)->font());
821 f.setFixedPitch((u->FontStyle() & 0x0F) == Licq::STYLE_FIXEDxPITCH);
822
823 switch (u->FontStyle() & 0xF0)
824 {
825 case Licq::STYLE_ROMAN:
826 f.setStyleHint(QFont::Serif);
827 break;
828 case Licq::STYLE_SWISS:
829 f.setStyleHint(QFont::SansSerif);
830 break;
831 case Licq::STYLE_DECORATIVE:
832 f.setStyleHint(QFont::Decorative);
833 break;
834 case Licq::STYLE_DONTCARE:
835 case Licq::STYLE_MODERN:
836 case Licq::STYLE_SCRIPT:
837 default:
838 f.setStyleHint(QFont::AnyStyle);
839 break;
840 }
841
842 f.setFamily(QString::fromUtf8(u->fontFamily().c_str()));
843
844 GetWindow(u)->setFont(f);
845 }
846 break;
847 }
848
849 case Licq::CHAT_FONTxFACE: // change font style
850 {
851 if (! tbtIgnore->isChecked())
852 {
853 QFont f(GetWindow(u)->font());
854 f.setBold(u->FontBold());
855 f.setItalic(u->FontItalic());
856 f.setUnderline(u->FontUnderline());
857 f.setStrikeOut(u->FontStrikeOut());
858 GetWindow(u)->setFont(f);
859 }
860 break;
861 }
862
863 case Licq::CHAT_FONTxSIZE: // change font size
864 {
865 if (! tbtIgnore->isChecked())
866 {
867 QFont f(GetWindow(u)->font());
868 f.setPointSize(u->FontSize() > 24 ? 24 : u->FontSize());
869 GetWindow(u)->setFont(f);
870 }
871 break;
872 }
873
874 case Licq::CHAT_FOCUSxOUT:
875 case Licq::CHAT_FOCUSxIN:
876 case Licq::CHAT_SLEEPxON:
877 case Licq::CHAT_SLEEPxOFF:
878 {
879 // TODO add some visible indication of these
880 break;
881 }
882
883 case Licq::CHAT_CHARACTER:
884 {
885 GetWindow(u)->appendNoNewLine(QString::fromUtf8(e->data().c_str()));
886 break;
887 }
888
889 default:
890 {
891 Licq::gLog.warning("Internal Error: invalid command from chat manager (%d)",
892 e->Command());
893 break;
894 }
895 }
896
897 delete e;
898 }
899 }
900
901
SwitchToIRCMode()902 void ChatDlg::SwitchToIRCMode()
903 {
904 m_nMode = CHAT_IRC;
905 boxPane->hide();
906 mleIRCLocal->setText(mlePaneLocal->lastLine());
907 mleIRCLocal->GotoEnd();
908 boxIRC->show();
909 mleIRCLocal->setFocus();
910 }
911
912
SwitchToPaneMode()913 void ChatDlg::SwitchToPaneMode()
914 {
915 m_nMode = CHAT_PANE;
916 boxIRC->hide();
917 mlePaneLocal->GotoEnd();
918 boxPane->show();
919 mlePaneLocal->setFocus();
920 }
921
922
chatClose(Licq::IcqChatUser * u)923 void ChatDlg::chatClose(Licq::IcqChatUser* u)
924 {
925 if (u == NULL)
926 {
927 chatUserWindows.clear();
928 lstUsers->clear();
929 disconnect(sn, SIGNAL(activated(int)), this, SLOT(slot_chat()));
930 chatman->CloseChat();
931 }
932 else
933 {
934 // Remove the user from the list box
935 for (int i = 0; i < lstUsers->count(); i++)
936 {
937 if (lstUsers->item(i)->text() == QString::fromUtf8(u->name().c_str()))
938 {
939 lstUsers->removeItemWidget(lstUsers->item(i));
940 break;
941 }
942 }
943 ChatUserWindowsList::iterator iter;
944 for (iter = chatUserWindows.begin(); iter != chatUserWindows.end(); iter++)
945 {
946 if (iter->u == u)
947 {
948 delete iter->w;
949 delete iter->l;
950 chatUserWindows.erase(iter);
951 break;
952 }
953 }
954 UpdateRemotePane();
955 }
956
957 // Modify the dialogs
958 if (chatman->ConnectedUsers() == 0)
959 {
960 mleIRCLocal->setEnabled(false);
961 mlePaneLocal->setEnabled(false);
962 disconnect(mleIRCLocal, SIGNAL(keyPressed(QKeyEvent*)), this, SLOT(chatSend(QKeyEvent*)));
963 disconnect(mlePaneLocal, SIGNAL(keyPressed(QKeyEvent *)), this, SLOT(chatSend(QKeyEvent*)));
964
965 lblRemote = new QLabel(tr("Remote - Not connected"), boxPane);
966 remoteLayout->addWidget(lblRemote, 0, 0);
967 lblRemote->show();
968 }
969 }
970
971
closeEvent(QCloseEvent * e)972 void ChatDlg::closeEvent(QCloseEvent* e)
973 {
974 if (QueryYesNo(this, tr("Do you want to save the chat session?")))
975 {
976 if (!slot_save())
977 {
978 e->ignore();
979 return;
980 }
981 }
982
983 e->accept();
984 chatClose(NULL);
985 }
986
GetWindow(Licq::IcqChatUser * u)987 ChatWindow* ChatDlg::GetWindow(Licq::IcqChatUser* u)
988 {
989 ChatUserWindowsList::iterator iter;
990 for (iter = chatUserWindows.begin(); iter != chatUserWindows.end(); iter++)
991 if (iter->u == u)
992 return iter->w;
993 return NULL;
994 }
995
UpdateRemotePane()996 void ChatDlg::UpdateRemotePane()
997 {
998 delete remoteLayout;
999 remoteLayout = new QGridLayout();
1000 paneLayout->addLayout(remoteLayout, 0, 0);
1001
1002 setWindowTitle(tr("Licq - Chat %1").arg(ChatClients()));
1003
1004 ChatUserWindowsList::iterator iter;
1005 int i;
1006 for (i = 0, iter = chatUserWindows.begin(); iter != chatUserWindows.end();
1007 i++, iter++)
1008 {
1009 remoteLayout->addWidget(iter->l, 0, i);
1010 remoteLayout->addWidget(iter->w, 1, i);
1011 iter->l->show();
1012 iter->w->show();
1013 }
1014 remoteLayout->setRowStretch(1, 1);
1015 }
1016
1017
ChatClients()1018 QString ChatDlg::ChatClients()
1019 {
1020 return QString::fromUtf8(chatman->clientsString().c_str());
1021 }
1022
ChatName()1023 QString ChatDlg::ChatName()
1024 {
1025 return QString::fromUtf8(chatman->name().c_str());
1026 }
1027
1028
slot_save()1029 bool ChatDlg::slot_save()
1030 {
1031 QString t = QDateTime::currentDateTime().toString();
1032 for ( int l = 0; l < t.length(); ++l ) {
1033 if ( t[l] == ' ' ) t[l] = '-';
1034 if ( t[l] == ':' ) t[l] = '-';
1035 }
1036 QString n = tr("/%1.chat").arg(t);
1037
1038 #ifdef USE_KDE
1039 QString fn = KFileDialog::getSaveFileName(QDir::homePath() + n,
1040 QString::null, this);
1041 #else
1042 QString fn = QFileDialog::getSaveFileName(this, QString(), QDir::homePath() + n);
1043 #endif
1044
1045 if (!fn.isEmpty())
1046 {
1047 QFile f(fn);
1048 if (!f.open(QIODevice::WriteOnly))
1049 {
1050 WarnUser(this, tr("Failed to open file:\n%1").arg(fn));
1051 return false;
1052 }
1053 else
1054 {
1055 QTextStream t(&f);
1056 t << mleIRCRemote->toPlainText();
1057 f.close();
1058 }
1059 return true;
1060 }
1061 else
1062 {
1063 return false;
1064 }
1065 }
1066
1067
slot_audio(bool audio)1068 void ChatDlg::slot_audio(bool audio)
1069 {
1070 myAudio = audio;
1071 }
1072
1073
LocalPort()1074 unsigned short ChatDlg::LocalPort()
1075 {
1076 return chatman->LocalPort();
1077 }
1078
setEncoding(QAction * action)1079 void ChatDlg::setEncoding(QAction* action)
1080 {
1081 myChatEncoding = action->data().toUInt();
1082
1083 // transmit to remote
1084 sendFontInfo();
1085 }
1086
1087 // -----------------------------------------------------------------------------
1088 // -----------------------------------------------------------------------------
1089
ChatWindow(QWidget * parent)1090 ChatWindow::ChatWindow (QWidget* parent)
1091 : QTextEdit(parent)
1092 {
1093 setLineWrapMode(WidgetWidth);
1094 setWordWrapMode(QTextOption::WordWrap);
1095 setFont(Config::General::instance()->editFont());
1096 }
1097
1098
1099 // -----------------------------------------------------------------------------
1100
appendNoNewLine(const QString & s)1101 void ChatWindow::appendNoNewLine(const QString& s)
1102 {
1103 QTextCursor tc = textCursor();
1104 tc.movePosition(QTextCursor::End);
1105 tc.insertText(s);
1106 }
1107
1108
1109 // -----------------------------------------------------------------------------
1110
GotoEnd()1111 void ChatWindow::GotoEnd()
1112 {
1113 QTextCursor tc = textCursor();
1114 tc.movePosition(QTextCursor::End);
1115 setTextCursor(tc);
1116 }
1117
1118
1119 // -----------------------------------------------------------------------------
1120
insert(const QString & s)1121 void ChatWindow::insert(const QString& s)
1122 {
1123 QTextCursor tc = textCursor();
1124 tc.movePosition(QTextCursor::End);
1125 tc.insertText(s);
1126 }
1127
1128
1129 // -----------------------------------------------------------------------------
1130
keyPressEvent(QKeyEvent * e)1131 void ChatWindow::keyPressEvent(QKeyEvent* e)
1132 {
1133 if ( (e->text().length() == 0 ||
1134 e->modifiers() & Qt::ControlModifier ||
1135 e->modifiers() & Qt::AltModifier) &&
1136 (e->key() != Qt::Key_Tab &&
1137 e->key() != Qt::Key_Backtab &&
1138 e->key() != Qt::Key_Backspace &&
1139 e->key() != Qt::Key_Return &&
1140 e->key() != Qt::Key_Enter))
1141 return;
1142
1143 GotoEnd();
1144
1145 // the order of the two is important -- on Enter, first QMultiLineEdit adds
1146 // a line break, and later we clear the input line, and not vice versa
1147 QTextEdit::keyPressEvent(e);
1148 emit keyPressed(e);
1149 }
1150
1151
1152 // -----------------------------------------------------------------------------
1153
mousePressEvent(QMouseEvent *)1154 void ChatWindow::mousePressEvent(QMouseEvent*)
1155 {
1156 // a user might not change the cursor position
1157 // and marking / cutting away text is not allowed
1158
1159 // so ignore the event.
1160 }
1161
mouseMoveEvent(QMouseEvent *)1162 void ChatWindow::mouseMoveEvent(QMouseEvent*)
1163 {
1164 // ignore it
1165 }
1166
mouseReleaseEvent(QMouseEvent * e)1167 void ChatWindow::mouseReleaseEvent(QMouseEvent* e)
1168 {
1169 if (e->button() == Qt::MidButton && !isReadOnly())
1170 paste();
1171 }
1172
1173 // -----------------------------------------------------------------------------
1174
paste()1175 void ChatWindow::paste()
1176 {
1177 QString t = QApplication::clipboard()->text();
1178
1179 if ( !t.isEmpty() ) {
1180
1181 for (int i=0; i<t.length(); i++) {
1182 if ( t[i] < ' ' && t[i] != '\n' && t[i] != '\t' )
1183 t[i] = ' ';
1184 }
1185
1186 for(int i=0; i<t.length(); i++) {
1187 QKeyEvent press(QKeyEvent::KeyPress, t[i].toLatin1() == '\n' ? Qt::Key_Enter : 0, static_cast<Qt::KeyboardModifiers>(Qt::NoModifier), QString(t[i]), false, 1);
1188
1189 keyPressEvent(&press);
1190 }
1191 }
1192 }
1193
1194
1195 // -----------------------------------------------------------------------------
1196
setBackground(const QColor & c)1197 void ChatWindow::setBackground(const QColor& c)
1198 {
1199 QPalette pal = palette();
1200
1201 pal.setColor(QPalette::Active, QPalette::Base, c);
1202 pal.setColor(QPalette::Inactive, QPalette::Base, c);
1203
1204 setPalette(pal);
1205 }
1206
1207
1208 // -----------------------------------------------------------------------------
1209
1210
setForeground(const QColor & c)1211 void ChatWindow::setForeground(const QColor& c)
1212 {
1213 QPalette pal = palette();
1214
1215 pal.setColor(QPalette::Active, QPalette::Text, c);
1216 pal.setColor(QPalette::Inactive, QPalette::Text, c);
1217
1218 setPalette(pal);
1219 }
1220
backspace()1221 void ChatWindow::backspace()
1222 {
1223 QTextCursor tc = textCursor();
1224 tc.deletePreviousChar();
1225 }
1226
lastLine() const1227 QString ChatWindow::lastLine() const
1228 {
1229 QString text = toPlainText();
1230
1231 // Ignore the last empty line
1232 if (text.endsWith("\n"))
1233 text = text.left(text.size() - 1);
1234
1235 int pos = text.lastIndexOf("\n");
1236 if (pos == -1)
1237 return text;
1238 return text.mid(pos + 1);
1239 }
1240