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