ircdocktabcontents.cpp
1 //------------------------------------------------------------------------------
2 // ircdocktabcontents.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library 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 GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301 USA
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2010 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "ircdocktabcontents.h"
24 #include "ui_ircdocktabcontents.h"
25 #include "gui/irc/ircignoresmanager.h"
26 #include "gui/irc/ircsounds.h"
27 #include "gui/irc/ircuserlistmodel.h"
28 #include "gui/commongui.h"
29 #include "irc/configuration/chatlogscfg.h"
30 #include "irc/configuration/ircconfig.h"
31 #include "irc/entities/ircnetworkentity.h"
32 #include "irc/ops/ircdelayedoperationignore.h"
33 #include "irc/chatlogrotate.h"
34 #include "irc/chatlogs.h"
35 #include "irc/ircchanneladapter.h"
36 #include "irc/ircdock.h"
37 #include "irc/ircglobal.h"
38 #include "irc/ircmessageclass.h"
39 #include "irc/ircnetworkadapter.h"
40 #include "irc/ircnicknamecompleter.h"
41 #include "irc/ircuserinfo.h"
42 #include "irc/ircuserlist.h"
43 #include "application.h"
44 #include "log.h"
45 #include <QDateTime>
46 #include <QFile>
47 #include <QKeyEvent>
48 #include <QMenu>
49 #include <QScrollBar>
50 #include <QStandardItemModel>
51 #include <QTimer>
52 #include <cassert>
53 
54 
55 const int IRCDockTabContents::BLINK_TIMER_DELAY_MS = 650;
56 
57 DClass<IRCDockTabContents> : public Ui::IRCDockTabContents
58 {
59 public:
60  QFile log;
61  QDateTime lastMessageDate;
62 
70  bool bBlinkTitle;
71  bool bIsDestroying;
72 
73  QTimer blinkTimer;
74 
75  IRCMessageClass* lastMessageClass;
76  IRCNicknameCompleter *nicknameCompleter;
81  QStringList textOutputContents;
82  ::IRCDockTabContents::UserListMenu* userListContextMenu;
83 };
84 
85 DPointeredNoCopy(IRCDockTabContents)
86 
87 class IRCDockTabContents::UserListMenu : public QMenu
88 {
89  public:
90  UserListMenu();
91 
92  QAction* ban;
93  QAction *whois;
94  QAction *ctcpTime;
95  QAction *ctcpPing;
96  QAction *ctcpVersion;
97  QAction* dehalfOp;
98  QAction* deop;
99  QAction* devoice;
100  QAction* halfOp;
101  QAction* kick;
102  QAction *ignore;
103  QAction* op;
104  QAction* openChatWindow;
105  QAction* voice;
106 
107  private:
108  bool bIsOperator;
109 
110 };
111 
112 IRCDockTabContents::IRCDockTabContents(IRCDock* pParentIRCDock)
113 {
114  d->setupUi(this);
115  d->lastMessageDate = QDateTime::currentDateTime();
116 
117  d->bBlinkTitle = false;
118  d->bIsDestroying = false;
119  d->lastMessageClass = NULL;
120  d->userListContextMenu = NULL;
121  this->pIrcAdapter = NULL;
122 
123  this->pParentIRCDock = pParentIRCDock;
124  d->nicknameCompleter = new IRCNicknameCompleter();
125 
126  d->txtOutputWidget->setContextMenuPolicy(Qt::CustomContextMenu);
127  setupNewUserListModel();
128 
129  // There is only one case in which we want this to be visible:
130  // if we are in a channel.
131  d->lvUserList->setVisible(false);
132 
133  this->connect(d->btnSend, SIGNAL(clicked()), SLOT(sendMessage()));
134  this->connect(d->leCommandLine, SIGNAL(returnPressed()), SLOT(sendMessage()));
135  this->connect(gApp, SIGNAL(focusChanged(QWidget*, QWidget*)),
136  SLOT(onFocusChanged(QWidget*, QWidget*)));
137 
139 
140  d->blinkTimer.setSingleShot(false);
141  this->connect(&d->blinkTimer, SIGNAL( timeout() ),
142  SLOT( blinkTimerSlot() ) );
143 
144  // Performance check line, keep commented for non-testing builds:
145  //receiveMessage(Strings::createRandomAlphaNumericStringWithNewLines(80, 5000));
146 }
147 
148 IRCDockTabContents::~IRCDockTabContents()
149 {
150  d->bIsDestroying = true;
151 
152  if (d->lastMessageClass != NULL)
153  {
154  delete d->lastMessageClass;
155  }
156 
157  if (d->userListContextMenu != NULL)
158  {
159  delete d->userListContextMenu;
160  }
161 
162  if (pIrcAdapter != NULL)
163  {
164  disconnect(pIrcAdapter, 0, 0, 0);
165  IRCAdapterBase* pTmpAdapter = pIrcAdapter;
166  pIrcAdapter = NULL;
167  delete pTmpAdapter;
168  }
169 }
170 
171 void IRCDockTabContents::adapterFocusRequest()
172 {
173  emit focusRequest(this);
174 }
175 
176 void IRCDockTabContents::adapterTerminating()
177 {
178  if (pIrcAdapter != NULL && !d->bIsDestroying)
179  {
180  // Disconnect the adapter from this tab.
181  disconnect(pIrcAdapter, 0, 0, 0);
182  pIrcAdapter = NULL;
183 
184  emit chatWindowCloseRequest(this);
185  }
186 }
187 
188 void IRCDockTabContents::alertIfConfigured()
189 {
190  if (gIRCConfig.appearance.windowAlertOnImportantChatEvent)
191  {
192  QApplication::alert(gApp->mainWindowAsQWidget());
193  }
194 }
195 
197 {
198  const static QString STYLE_SHEET_BASE_TEMPLATE =
199  "QListView, QTextEdit, QLineEdit { background: %1; color: %2; } ";
200 
201  const IRCConfig::AppearanceCfg& appearance = gIRCConfig.appearance;
202 
203  QString qtStyleSheet = STYLE_SHEET_BASE_TEMPLATE
204  .arg(appearance.backgroundColor)
205  .arg(appearance.defaultTextColor);
206 
207  QColor colorSelectedText(appearance.userListSelectedTextColor);
208  QColor colorSelectedBackground(appearance.userListSelectedBackgroundColor);
209  qtStyleSheet += QString("QListView::item:selected { color: %1; background: %2; } ").arg(colorSelectedText.name(), colorSelectedBackground.name());
210 
211  QColor colorHoverText = colorSelectedText.lighter();
212  QColor colorHoverBackground = colorSelectedBackground.lighter();
213  qtStyleSheet += QString("QListView::item:hover { color: %1; background: %2; } ").arg(colorHoverText.name(), colorHoverBackground.name());
214 
215  QString channelActionClassName = IRCMessageClass::toStyleSheetClassName(IRCMessageClass::ChannelAction);
216  QString ctcpClassName = IRCMessageClass::toStyleSheetClassName(IRCMessageClass::Ctcp);
217  QString errorClassName = IRCMessageClass::toStyleSheetClassName(IRCMessageClass::Error);
218  QString networkActionClassName = IRCMessageClass::toStyleSheetClassName(IRCMessageClass::NetworkAction);
219 
220  QString htmlStyleSheetMessageArea = "";
221  htmlStyleSheetMessageArea += "span { white-space: pre; }";
222  htmlStyleSheetMessageArea += QString("a { color: %1; white-space: pre; } ").arg(appearance.urlColor);
223  htmlStyleSheetMessageArea += QString("." + channelActionClassName + " { color: %1; } ").arg(appearance.channelActionColor);
224  htmlStyleSheetMessageArea += QString("." + ctcpClassName + " { color: %1; } ").arg(appearance.ctcpColor);
225  htmlStyleSheetMessageArea += QString("." + errorClassName + " { color: %1; } ").arg(appearance.errorColor);
226  htmlStyleSheetMessageArea += QString("." + networkActionClassName + " { color: %1; } ").arg(appearance.networkActionColor);
227 
228  d->lvUserList->setStyleSheet(qtStyleSheet);
229  d->lvUserList->setFont(appearance.userListFont);
230 
231  d->leCommandLine->installEventFilter(this);
232  d->leCommandLine->setStyleSheet(qtStyleSheet);
233  d->leCommandLine->setFont(appearance.mainFont);
234 
235  d->txtOutputWidget->setStyleSheet(qtStyleSheet);
236  d->txtOutputWidget->setFont(appearance.mainFont);
237 
238  d->txtOutputWidget->document()->setDefaultStyleSheet(htmlStyleSheetMessageArea);
239  d->txtOutputWidget->clear();
240  d->txtOutputWidget->insertHtml(d->textOutputContents.join(""));
241  d->txtOutputWidget->moveCursor(QTextCursor::End);
242 }
243 
244 void IRCDockTabContents::blinkTimerSlot()
245 {
246  setBlinkTitle(!d->bBlinkTitle);
247 }
248 
249 void IRCDockTabContents::completeNickname()
250 {
251  IRCCompletionResult result;
252  if (d->nicknameCompleter->isReset())
253  {
254  result = d->nicknameCompleter->complete(d->leCommandLine->text(), d->leCommandLine->cursorPosition());
255  }
256  else
257  {
258  result = d->nicknameCompleter->cycleNext();
259  }
260  if (result.isValid())
261  {
262  // Prevent reset due to cursor position change.
263  d->leCommandLine->blockSignals(true);
264  d->leCommandLine->setText(result.textLine);
265  d->leCommandLine->setCursorPosition(result.cursorPos);
266  d->leCommandLine->blockSignals(false);
267  }
268 }
269 
270 bool IRCDockTabContents::eventFilter(QObject *watched, QEvent *event)
271 {
272  if (watched == d->leCommandLine && event->type() == QEvent::KeyPress)
273  {
274  QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
275  if (keyEvent->key() == Qt::Key_Tab)
276  {
277  completeNickname();
278  return true;
279  }
280  }
281  return false;
282 }
283 
284 QStandardItem* IRCDockTabContents::findUserListItem(const QString& nickname)
285 {
286  QStandardItemModel* pModel = (QStandardItemModel*)d->lvUserList->model();
287  IRCUserInfo userInfo(nickname, network());
288 
289  for (int i = 0; i < pModel->rowCount(); ++i)
290  {
291  QStandardItem* pItem = pModel->item(i);
292  if (userInfo == IRCUserInfo(pItem->text(), network()))
293  {
294  return pItem;
295  }
296  }
297 
298  return NULL;
299 }
300 
301 IRCDockTabContents::UserListMenu& IRCDockTabContents::getUserListContextMenu()
302 {
303  if (d->userListContextMenu == NULL)
304  {
305  d->userListContextMenu = new UserListMenu();
306  }
307 
308  return *d->userListContextMenu;
309 }
310 
312 {
313  // Make sure the tab title is not "d->blinkTimered out" anymore.
314  d->blinkTimer.stop();
315  setBlinkTitle(false);
316 
317  d->leCommandLine->setFocus();
318 }
319 
320 bool IRCDockTabContents::hasTabFocus() const
321 {
322  return this->pParentIRCDock->hasTabFocus(this);
323 }
324 
325 QIcon IRCDockTabContents::icon() const
326 {
327  if (pIrcAdapter == NULL)
328  {
329  return QIcon();
330  }
331 
332  switch (pIrcAdapter->adapterType())
333  {
334  case IRCAdapterBase::ChannelAdapter:
335  return QIcon(":/icons/irc_channel.png");
336 
337  case IRCAdapterBase::NetworkAdapter:
338  return QIcon(":/flags/lan-small");
339 
340  case IRCAdapterBase::PrivAdapter:
341  return QIcon(":/icons/person.png");
342 
343  default:
344  return QIcon();
345  }
346 }
347 
348 void IRCDockTabContents::insertMessage(const IRCMessageClass& messageClass, const QString& htmlString)
349 {
350  if (d->lastMessageClass == NULL)
351  {
352  d->lastMessageClass = new IRCMessageClass();
353  }
354  *d->lastMessageClass = messageClass;
355 
356  d->textOutputContents << htmlString;
357 
358  // Text insertion must be done this way to allow proper
359  // handling of "Pause" button. Note that the cursor
360  // in the widget is not affected as textCursor() creates a copy
361  // of cursor object.
362  QTextCursor cursor = d->txtOutputWidget->textCursor();
363  cursor.movePosition(QTextCursor::End);
364  cursor.insertHtml(htmlString);
365 
366  if (!d->btnPauseTextArea->isChecked())
367  {
368  d->txtOutputWidget->moveCursor(QTextCursor::End);
369  }
370 
371  emit newMessagePrinted();
372 }
373 
374 void IRCDockTabContents::markDate()
375 {
376  QDateTime previousMessageDate = d->lastMessageDate;
377  QDateTime nowDate = QDateTime::currentDateTime();
378  d->lastMessageDate = nowDate;
379  if (previousMessageDate.daysTo(nowDate) != 0)
380  {
381  receiveMessageWithClass(tr("<<<DATE>>> Date on this computer changes to %1").arg(
382  nowDate.toString()), IRCMessageClass::NetworkAction);
383  }
384 }
385 
386 void IRCDockTabContents::myNicknameUsedSlot()
387 {
388  alertIfConfigured();
389  pParentIRCDock->sounds().playIfAvailable(IRCSounds::NicknameUsed);
390  if (!hasTabFocus())
391  {
392  d->blinkTimer.start(BLINK_TIMER_DELAY_MS);
393  }
394 }
395 
396 void IRCDockTabContents::nameAdded(const IRCUserInfo& userInfo)
397 {
398  QStandardItemModel* pModel = (QStandardItemModel*)d->lvUserList->model();
399  QStandardItem* pItem = new QStandardItem(userInfo.prefixedName());
400  pItem->setData(userInfo.cleanNickname(), IRCUserListModel::RoleCleanNickname);
401 
402  // Try to append the nickname at the proper place in the list.
403  for (int i = 0; i < pModel->rowCount(); ++i)
404  {
405  QStandardItem* pExistingItem = pModel->item(i);
406  QString existingNickname = pExistingItem->text();
407 
408  if (userInfo <= IRCUserInfo(existingNickname, network()))
409  {
410  pModel->insertRow(i, pItem);
411  return;
412  }
413  }
414 
415  // If above code didn't return then
416  // this nickname should be appended to the end of the list.
417  pModel->appendRow(pItem);
418 }
419 
420 void IRCDockTabContents::nameListUpdated(const IRCUserList& userList)
421 {
422  setupNewUserListModel();
423 
424  for (unsigned i = 0; i < userList.size(); ++i)
425  {
426  nameAdded(*userList[i]);
427  }
428 }
429 
430 void IRCDockTabContents::nameRemoved(const IRCUserInfo& userInfo)
431 {
432  QStandardItemModel* pModel = (QStandardItemModel*)d->lvUserList->model();
433  for (int i = 0; i < pModel->rowCount(); ++i)
434  {
435  QStandardItem* pItem = pModel->item(i);
436  if (userInfo.isSameNickname(pItem->text()))
437  {
438  pModel->removeRow(i);
439  break;
440  }
441  }
442 }
443 
444 void IRCDockTabContents::nameUpdated(const IRCUserInfo& userInfo)
445 {
446  nameRemoved(userInfo);
447  nameAdded(userInfo);
448 }
449 
450 IRCNetworkAdapter* IRCDockTabContents::network()
451 {
452  return ircAdapter()->network();
453 }
454 
455 const IRCNetworkEntity &IRCDockTabContents::networkEntity() const
456 {
457  return ircAdapter()->networkEntity();
458 }
459 
461 {
462  // Once a new chat adapter is opened we need to add it to the master
463  // dock widget.
464  pParentIRCDock->addIRCAdapter(pAdapter);
465 }
466 
467 void IRCDockTabContents::onFocusChanged(QWidget *old, QWidget *now)
468 {
469  if (old == d->lvUserList && now != d->userListContextMenu)
470  {
471  d->lvUserList->clearSelection();
472  }
473 }
474 
475 bool IRCDockTabContents::openLog()
476 {
477  rotateOldLog();
478  ChatLogs logs;
479  if (!logs.mkLogDir(networkEntity()))
480  {
481  receiveMessageWithClass(tr("Failed to create chat log directory:\n'%1'").arg(
482  logs.networkDirPath(networkEntity())), IRCMessageClass::Error);
483  return false;
484  }
485  d->log.setFileName(ChatLogs().logFilePath(networkEntity(), recipient()));
486  d->log.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text);
487  d->log.write(tr("<<<DATE>>> Chat log started on %1\n\n").arg(QDateTime::currentDateTime().toString()).toUtf8());
488  return true;
489 }
490 
491 void IRCDockTabContents::rotateOldLog()
492 {
493  assert(!d->log.isOpen());
494  ChatLogsCfg cfg;
495 
496  ChatLogRotate logRotate;
497  logRotate.setRemovalAgeDaysThreshold(
498  cfg.isRestoreChatFromLogs() ? cfg.oldLogsRemovalDaysThreshold() : -1);
499  logRotate.rotate(networkEntity(), recipient());
500 }
501 
502 void IRCDockTabContents::printToSendersNetworksCurrentChatBox(const QString &text, const IRCMessageClass &msgClass)
503 {
504  IRCAdapterBase *adapter = static_cast<IRCAdapterBase*>(sender());
505  IRCDockTabContents *tab = pParentIRCDock->tabWithFocus();
506  if (tab != NULL && tab->ircAdapter()->network()->isAdapterRelated(adapter))
507  {
508  tab->ircAdapter()->emitMessageWithClass(text, msgClass);
509  }
510  else
511  {
512  adapter->emitMessageWithClass(text, msgClass);
513  }
514 }
515 
516 void IRCDockTabContents::receiveError(const QString& error)
517 {
518  receiveMessageWithClass(tr("Error: %1").arg(error), IRCMessageClass::Error);
519 }
520 
521 void IRCDockTabContents::receiveMessage(const QString& message)
522 {
523  receiveMessageWithClass(message, IRCMessageClass::Normal);
524 }
525 
526 void IRCDockTabContents::receiveMessageWithClass(const QString& message, const IRCMessageClass& messageClass)
527 {
528  markDate();
529 
530  QString messageHtmlEscaped = message;
531 
532  if (gIRCConfig.appearance.timestamps)
533  {
534  QString timestamp = Strings::timestamp("[hh:mm:ss] ") ;
535 
536  messageHtmlEscaped = timestamp + messageHtmlEscaped;
537 
538  // It is also required to replace all '\n' characters with timestamp
539  // markers to ensure that timestamp is in every line of text.
540  messageHtmlEscaped = messageHtmlEscaped.replace("\n", "\n" + timestamp);
541  }
542 
543  // As the new-line character is stripped by the lower levels we should
544  // assume that each message ends with a new-line char, as specified
545  // by RFC 1459.
546  messageHtmlEscaped += "\n";
547 
548  writeLog(messageHtmlEscaped);
549 
550  messageHtmlEscaped = wrapTextWithMetaTags(messageHtmlEscaped, messageClass);
551 
552  // Play sound if this is Priv adapter.
553  if (pIrcAdapter->adapterType() == IRCAdapterBase::PrivAdapter)
554  {
555  alertIfConfigured();
556  pParentIRCDock->sounds().playIfAvailable(IRCSounds::PrivateMessageReceived);
557 
558  // If this tab doesn't have focus, also start d->blinkTimering the title.
559  if (!hasTabFocus())
560  {
561  d->blinkTimer.start(BLINK_TIMER_DELAY_MS);
562  }
563  }
564 
565  this->insertMessage(messageClass, messageHtmlEscaped);
566 }
567 
568 QString IRCDockTabContents::recipient() const
569 {
570  return pIrcAdapter->recipient();
571 }
572 
573 void IRCDockTabContents::resetNicknameCompletion()
574 {
575  d->nicknameCompleter->reset();
576 }
577 
578 bool IRCDockTabContents::restoreLog()
579 {
580  ChatLogs logs;
581  QFile file(logs.logFilePath(networkEntity(), recipient()));
582  if (file.open(QIODevice::ReadOnly | QIODevice::Text))
583  {
584  QByteArray contents = file.readAll();
585  QStringList lines = QString::fromUtf8(contents, contents.size()).split("\n");
586  int line = lines.size() - 1000;
587  lines = lines.mid((line > 0) ? line : 0);
588 
589  insertMessage(IRCMessageClass::Normal,
590  wrapTextWithMetaTags(lines.join("\n"), IRCMessageClass::Normal));
591 
592  receiveMessageWithClass(tr("---- All lines above were loaded from log ----"),
593  IRCMessageClass::NetworkAction);
594  return true;
595  }
596  return false;
597 }
598 
599 QString IRCDockTabContents::selectedNickname()
600 {
601  QModelIndexList selectedIndexes = d->lvUserList->selectionModel()->selectedRows();
602  // There can be only one.
603  if (!selectedIndexes.isEmpty())
604  {
605  int row = selectedIndexes[0].row();
606  QStandardItemModel* pModel = (QStandardItemModel*)d->lvUserList->model();
607  QStandardItem* pItem = pModel->item(row);
608 
609  return pItem->text();
610  }
611 
612  return "";
613 }
614 
615 void IRCDockTabContents::sendCtcpPing(const QString &nickname)
616 {
617  network()->sendCtcp(nickname, QString("PING %1").arg(QDateTime::currentMSecsSinceEpoch()));
618 }
619 
620 void IRCDockTabContents::sendCtcpTime(const QString &nickname)
621 {
622  network()->sendCtcp(nickname, QString("TIME"));
623 }
624 
625 void IRCDockTabContents::sendCtcpVersion(const QString &nickname)
626 {
627  network()->sendCtcp(nickname, QString("VERSION"));
628 }
629 
630 void IRCDockTabContents::sendMessage()
631 {
632  QString message = d->leCommandLine->text();
633  d->leCommandLine->setText("");
634 
635  if (!message.trimmed().isEmpty())
636  {
637  pIrcAdapter->sendMessage(message);
638  }
639 }
640 
641 void IRCDockTabContents::sendWhois(const QString &nickname)
642 {
643  network()->sendMessage(QString("/WHOIS %1").arg(nickname));
644 }
645 
646 void IRCDockTabContents::setBlinkTitle(bool b)
647 {
648  bool bEmit = false;
649  if (d->bBlinkTitle != b)
650  {
651  // Delay signal emit until after we change the variable.
652  bEmit = true;
653  }
654 
655  d->bBlinkTitle = b;
656 
657  if (bEmit)
658  {
659  emit titleBlinkRequested();
660  }
661 }
662 
664 {
665  assert(pIrcAdapter == NULL);
666  pIrcAdapter = pAdapter;
667 
668  ChatLogsCfg cfg;
669  if (cfg.isRestoreChatFromLogs())
670  {
671  restoreLog();
672  }
673  if (cfg.isStoreLogs())
674  {
675  openLog();
676  }
677 
678  connect(pIrcAdapter, SIGNAL( error(const QString&) ), SLOT( receiveError(const QString& ) ));
679  connect(pIrcAdapter, SIGNAL( focusRequest() ), SLOT( adapterFocusRequest() ));
680  connect(pIrcAdapter, SIGNAL( message(const QString&) ), SLOT( receiveMessage(const QString& ) ));
681  connect(pIrcAdapter, SIGNAL( messageWithClass(const QString&, const IRCMessageClass&) ), SLOT( receiveMessageWithClass(const QString&, const IRCMessageClass&) ));
682  connect(pIrcAdapter, SIGNAL( terminating() ), SLOT( adapterTerminating() ) );
683  connect(pIrcAdapter, SIGNAL( titleChange() ), SLOT( adapterTitleChange() ) );
684  connect(pIrcAdapter, SIGNAL( messageToNetworksCurrentChatBox(QString, IRCMessageClass) ),
685  SLOT( printToSendersNetworksCurrentChatBox(QString, IRCMessageClass) ) );
686 
687  switch (pIrcAdapter->adapterType())
688  {
689  case IRCAdapterBase::NetworkAdapter:
690  {
691  IRCNetworkAdapter* pNetworkAdapter = (IRCNetworkAdapter*)pAdapter;
692  connect(pNetworkAdapter, SIGNAL( newChatWindowIsOpened(IRCChatAdapter*) ), SLOT( newChatWindowIsOpened(IRCChatAdapter*) ) );
693  break;
694  }
695 
696  case IRCAdapterBase::ChannelAdapter:
697  {
698  IRCChannelAdapter* pChannelAdapter = (IRCChannelAdapter*)pAdapter;
699  connect(pChannelAdapter, SIGNAL( myNicknameUsed() ), SLOT( myNicknameUsedSlot() ) );
700  connect(pChannelAdapter, SIGNAL( nameAdded(const IRCUserInfo&) ), SLOT( nameAdded(const IRCUserInfo&) ) );
701  connect(pChannelAdapter, SIGNAL( nameListUpdated(const IRCUserList&) ), SLOT( nameListUpdated(const IRCUserList&) ) );
702  connect(pChannelAdapter, SIGNAL( nameRemoved(const IRCUserInfo&) ), SLOT( nameRemoved(const IRCUserInfo&) ) );
703  connect(pChannelAdapter, SIGNAL( nameUpdated(const IRCUserInfo&) ), SLOT( nameUpdated(const IRCUserInfo&) ) );
704 
705  d->lvUserList->setVisible(true);
706  connect(d->lvUserList, SIGNAL( customContextMenuRequested(const QPoint&) ),
707  SLOT( userListCustomContextMenuRequested(const QPoint&) ) );
708 
709  connect(d->lvUserList, SIGNAL( doubleClicked(const QModelIndex&) ),
710  SLOT( userListDoubleClicked(const QModelIndex&) ) );
711 
712  d->lvUserList->setContextMenuPolicy(Qt::CustomContextMenu);
713 
714  break;
715  }
716 
717  case IRCAdapterBase::PrivAdapter:
718  {
719  break;
720  }
721 
722  default:
723  {
724  receiveError("Doomseeker error: Unknown IRCAdapterBase*");
725  break;
726  }
727  }
728 }
729 
730 void IRCDockTabContents::setupNewUserListModel()
731 {
732  d->lvUserList->setModel(new QStandardItemModel(d->lvUserList));
733  d->nicknameCompleter->setModel(d->lvUserList->model());
734 }
735 
736 void IRCDockTabContents::showChatContextMenu(const QPoint &pos)
737 {
738  QMenu *menu = d->txtOutputWidget->createStandardContextMenu(pos);
739  if (ircAdapter()->adapterType() == IRCAdapterBase::PrivAdapter)
740  {
741  menu->addSeparator();
742  appendPrivChatContextMenuOptions(menu);
743  }
744  menu->addSeparator();
745  appendGeneralChatContextMenuOptions(menu);
746  menu->exec(d->txtOutputWidget->mapToGlobal(pos));
747  delete menu;
748 }
749 
750 void IRCDockTabContents::appendGeneralChatContextMenuOptions(QMenu *menu)
751 {
752  QAction *manageIgnores = menu->addAction(tr("Manage ignores"));
753  this->connect(manageIgnores, SIGNAL(triggered()), SLOT(showIgnoresManager()));
754 }
755 
756 void IRCDockTabContents::appendPrivChatContextMenuOptions(QMenu *menu)
757 {
758  appendPrivChatContextMenuAction(menu, tr("Whois"), PrivWhois);
759  appendPrivChatContextMenuAction(menu, tr("CTCP Ping"), PrivCtcpPing);
760  appendPrivChatContextMenuAction(menu, tr("CTCP Time"), PrivCtcpTime);
761  appendPrivChatContextMenuAction(menu, tr("CTCP Version"), PrivCtcpVersion);
762  appendPrivChatContextMenuAction(menu, tr("Ignore"), PrivIgnore);
763 }
764 
765 void IRCDockTabContents::appendPrivChatContextMenuAction(QMenu *menu,
766  const QString &text, PrivChatMenu type)
767 {
768  QAction *action = menu->addAction(text);
769  action->setData(type);
770  this->connect(action, SIGNAL(triggered()), SLOT(onPrivChatActionTriggered()));
771 }
772 
773 void IRCDockTabContents::onPrivChatActionTriggered()
774 {
775  QString nickname = ircAdapter()->recipient();
776  QString cleanNickname = IRCUserInfo(nickname, network()).cleanNickname();
777  QAction *action = static_cast<QAction*>(sender());
778  switch (action->data().toInt())
779  {
780  case PrivWhois:
781  sendWhois(cleanNickname);
782  break;
783  case PrivCtcpPing:
784  sendCtcpPing(cleanNickname);
785  break;
786  case PrivCtcpTime:
787  sendCtcpTime(cleanNickname);
788  break;
789  case PrivCtcpVersion:
790  sendCtcpVersion(cleanNickname);
791  break;
792  case PrivIgnore:
793  startIgnoreOperation(cleanNickname);
794  break;
795  default:
796  assert(0 && "Unsupported priv chat action");
797  qDebug() << "Unsupported priv chat action: " << action->data();
798  break;
799  }
800 }
801 
802 void IRCDockTabContents::showIgnoresManager()
803 {
804  IRCIgnoresManager *dialog = new IRCIgnoresManager(this, networkEntity().description());
805  connect(dialog, SIGNAL(accepted()), network(), SLOT(reloadNetworkEntityFromConfig()));
806  dialog->setAttribute(Qt::WA_DeleteOnClose);
807  dialog->show();
808 }
809 
810 void IRCDockTabContents::startIgnoreOperation(const QString &nickname)
811 {
812  IRCDelayedOperationIgnore *op = new IRCDelayedOperationIgnore(this, network(), nickname);
813  op->setShowPatternPopup(true);
814  op->start();
815 }
816 
817 QString IRCDockTabContents::title() const
818 {
819  return pIrcAdapter->title();
820 }
821 
822 QString IRCDockTabContents::titleColor() const
823 {
824  if (d->lastMessageClass != NULL && !this->hasTabFocus())
825  {
826  QString color;
827 
828  if (*d->lastMessageClass == IRCMessageClass::Normal)
829  {
830  color = "#ff0000";
831  }
832  else
833  {
834  color = d->lastMessageClass->colorFromConfig();
835  }
836 
837  if (d->bBlinkTitle)
838  {
839  QColor c(color);
840 
841  int rInverted = 0xff - c.red();
842  int gInverted = 0xff - c.green();
843  int bInverted = 0xff - c.blue();
844 
845  QColor inverted(rInverted, gInverted, bInverted);
846 
847  return inverted.name();
848  }
849  else
850  {
851  return color;
852  }
853  }
854 
855  return "";
856 }
857 
858 void IRCDockTabContents::userListCustomContextMenuRequested(const QPoint& pos)
859 {
860  if (this->pIrcAdapter->adapterType() != IRCAdapterBase::ChannelAdapter)
861  {
862  // Prevent illegal calls.
863  return;
864  }
865 
866  QString nickname = this->selectedNickname();
867  if (nickname.isEmpty())
868  {
869  // Prevent calls if there is no one selected.
870  return;
871  }
872  QString cleanNickname = IRCUserInfo(nickname, network()).cleanNickname();
873 
874  IRCChannelAdapter* pAdapter = (IRCChannelAdapter*) this->pIrcAdapter;
875  const QString& channel = pAdapter->recipient();
876 
877  UserListMenu& menu = this->getUserListContextMenu();
878  QPoint posGlobal = d->lvUserList->mapToGlobal(pos);
879 
880  QAction* pAction = menu.exec(posGlobal);
881 
882  if (pAction == NULL)
883  {
884  return;
885  }
886 
887  if (pAction == menu.ban)
888  {
889  bool bOk = false;
890 
891  QString reason = CommonGUI::askString(tr("Ban user"), tr("Input reason for banning user %1 from channel %2").arg(nickname, channel), &bOk);
892  if (bOk)
893  {
894  pAdapter->banUser(cleanNickname, reason);
895  }
896  }
897  else if (pAction == menu.ctcpTime)
898  {
899  sendCtcpTime(cleanNickname);
900  }
901  else if (pAction == menu.ctcpPing)
902  {
903  sendCtcpPing(cleanNickname);
904  }
905  else if (pAction == menu.ctcpVersion)
906  {
907  sendCtcpVersion(cleanNickname);
908  }
909  else if (pAction == menu.deop)
910  {
911  pAdapter->setOp(cleanNickname, false);
912  }
913  else if (pAction == menu.dehalfOp)
914  {
915  pAdapter->setHalfOp(cleanNickname, false);
916  }
917  else if (pAction == menu.devoice)
918  {
919  pAdapter->setVoiced(cleanNickname, false);
920  }
921  else if (pAction == menu.halfOp)
922  {
923  pAdapter->setHalfOp(cleanNickname, true);
924  }
925  else if (pAction == menu.ignore)
926  {
927  startIgnoreOperation(cleanNickname);
928  }
929  else if (pAction == menu.kick)
930  {
931  bool bOk = false;
932 
933  QString reason = CommonGUI::askString(tr("Kick user"), tr("Input reason for kicking user %1 from channel %2").arg(nickname, channel), &bOk);
934  if (bOk)
935  {
936  pAdapter->kickUser(cleanNickname, reason);
937  }
938  }
939  else if (pAction == menu.op)
940  {
941  pAdapter->setOp(cleanNickname, true);
942  }
943  else if (pAction == menu.openChatWindow)
944  {
945  pAdapter->network()->openNewAdapter(cleanNickname);
946  }
947  else if (pAction == menu.voice)
948  {
949  pAdapter->setVoiced(cleanNickname, true);
950  }
951  else if (pAction == menu.whois)
952  {
953  sendWhois(cleanNickname);
954  }
955 }
956 
957 void IRCDockTabContents::userListDoubleClicked(const QModelIndex& index)
958 {
959  if (this->pIrcAdapter->adapterType() != IRCAdapterBase::ChannelAdapter)
960  {
961  // Prevent illegal calls.
962  return;
963  }
964 
965  QString nickname = this->selectedNickname();
966  if (nickname.isEmpty())
967  {
968  // Prevent calls if there is no one selected.
969  return;
970  }
971  QString cleanNickname = IRCUserInfo(nickname, network()).cleanNickname();
972 
973  this->pIrcAdapter->network()->openNewAdapter(cleanNickname);
974 }
975 
976 QString IRCDockTabContents::wrapTextWithMetaTags(const QString &text,
977  const IRCMessageClass &messageClass) const
978 {
979  QString result = text;
980  result.replace("<", "&lt;").replace(">", "&gt;");
981  result = Strings::wrapUrlsWithHtmlATags(result);
982 
983  QString className = messageClass.toStyleSheetClassName();
984  if (className.isEmpty())
985  {
986  result = "<span>" + result + "</span>";
987  }
988  else
989  {
990  result = ("<span class='" + className + "'>" + result + "</span>");
991  }
992  return result;
993 }
994 
995 bool IRCDockTabContents::writeLog(const QString &text)
996 {
997  ChatLogsCfg cfg;
998  if (d->log.isOpen() && cfg.isStoreLogs())
999  {
1000  d->log.write(text.toUtf8());
1001  d->log.flush();
1002  return true;
1003  }
1004  return false;
1005 }
1006 
1008 IRCDockTabContents::UserListMenu::UserListMenu()
1009 {
1010  this->openChatWindow = this->addAction(tr("Open chat window"));
1011  this->addSeparator();
1012  this->whois = this->addAction(tr("Whois"));
1013  this->ctcpTime = this->addAction(tr("CTCP Time"));
1014  this->ctcpPing = this->addAction(tr("CTCP Ping"));
1015  this->ctcpVersion = this->addAction(tr("CTCP Version"));
1016  this->addSeparator();
1017  this->op = this->addAction(tr("Op"));
1018  this->deop = this->addAction(tr("Deop"));
1019  this->halfOp = this->addAction(tr("Half op"));
1020  this->dehalfOp = this->addAction(tr("De half op"));
1021  this->voice = this->addAction(tr("Voice"));
1022  this->devoice = this->addAction(tr("Devoice"));
1023  this->addSeparator();
1024  this->ignore = this->addAction(tr("Ignore"));
1025  this->kick = this->addAction(tr("Kick"));
1026  this->ban = this->addAction(tr("Ban"));
1027 
1028  this->bIsOperator = false;
1029 }
Interprets communication between the client and the IRC server.
Allows to perform operation on a list of users.
Definition: ircuserlist.h:42
void setIRCAdapter(IRCAdapterBase *pAdapter)
Calling this multiple times on the same object will cause memory leaks.
Class type that is used for conversations within a channel.
Dockable widget designed for IRC communication.
Definition: ircdock.h:39
void openNewAdapter(const QString &recipientName)
Opens a new chat adapter for specified recipient.
void kickUser(const QString &nickname, const QString &reason)
Kicks user from the channel.
void setHalfOp(const QString &nickname, bool bSet)
Sets half op mode for given user.
void grabFocus()
Called when tab becomes active.
Provides an unified communication interface between a client and IRC network entities.
QString cleanNickname() const
Returns nickname with no prefixes, contrary to the prefixedName() .
Definition: ircuserinfo.cpp:50
virtual AdapterType adapterType() const =0
Gets adapter type for this adapter instance.
static QString wrapUrlsWithHtmlATags(const QString &str)
Detects all links within a given string and wraps them in <a href> tags.
Definition: strings.cpp:447
QString prefixedName() const
Will generate prefix based on the user flags.
void chatWindowCloseRequest(IRCDockTabContents *)
Emitted when the IRCAdapterBase that is associated with this widget is no longer valid - possibly eve...
Dockable widget designed for IRC communication.
Handles chatting through IRC.
void banUser(const QString &nickname, const QString &reason)
Bans and kicks user from the channel.
virtual QString title() const =0
Gets title for this adapter.
IRCNetworkAdapter * network()
The idea of the adapter system is that each adapter is either a network or is a child of a network...
void newChatWindowIsOpened(IRCChatAdapter *pAdapter)
Captures signals from IRC Networks which indicate that a new chat window is being opened...
bool isSameNickname(const IRCUserInfo &otherUser) const
Check if this user and user specified as parameter are the same user.
Definition: ircuserinfo.cpp:79
void setRemovalAgeDaysThreshold(int age)
void titleChange(IRCDockTabContents *pCaller)
Emitted when the variable returned by IRCAdapterBase::title() might have changed and the application ...
Normal has no representation in string, ie. it represents a global style for the widget.
void setVoiced(const QString &nickname, bool bSet)
Sets voice mode for given user.
void focusRequest(IRCDockTabContents *pCaller)
Emitted when network adapter for this dock emits its focusRequest() signal.
Holds information flags about given nickname.
Definition: ircuserinfo.h:35
void setOp(const QString &nickname, bool bSet)
Sets op mode for given user.
virtual IRCNetworkAdapter * network()=0
The idea of the adapter system is that each adapter is either a network or is a child of a network...
Data structure that describes and defines a connection to an IRC network or server.
void applyAppearanceSettings()
Applies current appearance settings from the IRC config.
static QString askString(const QString &title, const QString &label, bool *ok=NULL, const QString &defaultString="")
Calls QInputDialog::getText().
Definition: commongui.cpp:31
bool isAdapterRelated(const IRCAdapterBase *pAdapter) const
Checks if pAdapter equals this or is one of chat windows of this network.
void playIfAvailable(SoundType sound)
Plays given sound.
Definition: ircsounds.cpp:58