1 //=============================================================================
2 //
3 // File : KviConsoleWindow.cpp
4 // Creation date : Sun Jun 25 2000 15:01:34 by Szymon Stefanek
5 //
6 // This file is part of the KVIrc IRC client distribution
7 // Copyright (C) 2000-2010 Szymon Stefanek (pragma at kvirc dot net)
8 //
9 // This program is FREE software. You can redistribute it and/or
10 // modify it under the terms of the GNU General Public License
11 // as published by the Free Software Foundation; either version 2
12 // of the License, or (at your option) any later version.
13 //
14 // This program is distributed in the HOPE that it will be USEFUL,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 // See the GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with this program. If not, write to the Free Software Foundation,
21 // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 //
23 //=============================================================================
24
25 #include "kvi_defaults.h"
26 #include "kvi_out.h"
27 #include "KviIrcUrl.h"
28 #include "KviApplication.h"
29 #include "KviConsoleWindow.h"
30 #include "KviMainWindow.h"
31 #include "KviIconManager.h"
32 #include "KviOptions.h"
33 #include "KviLocale.h"
34 #include "KviIrcView.h"
35 #include "KviControlCodes.h"
36 #include "KviInput.h"
37 #include "KviError.h"
38 #include "KviProxyDataBase.h"
39 #include "KviNetUtils.h"
40 #include "KviIrcNetwork.h"
41 #include "KviIrcServer.h"
42 #include "KviIrcServerDataBase.h"
43 #include "KviDnsResolver.h"
44 #include "KviIrcUserDataBase.h"
45 #include "KviChannelWindow.h"
46 #include "KviQueryWindow.h"
47 #include "KviRegisteredUserDataBase.h"
48 #include "KviUserListView.h"
49 #include "KviConfigurationFile.h"
50 #include "KviIrcToolBar.h"
51 #include "KviInternalCommand.h"
52 #include "KviIrcServerParser.h"
53 #include "KviModuleManager.h"
54 #include "KviFileUtils.h"
55 #include "KviTimeUtils.h"
56 #include "KviMexLinkFilter.h"
57 #include "KviAvatarCache.h"
58 #include "KviIrcConnection.h"
59 #include "KviIrcConnectionUserInfo.h"
60 #include "KviIrcConnectionServerInfo.h"
61 #include "KviIrcConnectionStateData.h"
62 #include "KviIrcConnectionTarget.h"
63 #include "KviIrcConnectionStatistics.h"
64 #include "KviAsynchronousConnectionData.h"
65 #include "KviIrcDataStreamMonitor.h"
66 #include "KviWindowToolWidget.h"
67 #include "KviMessageBox.h"
68 #include "KviKvsScript.h"
69 #include "KviKvsEventTriggers.h"
70 #include "KviTalHBox.h"
71 #include "KviNickColors.h"
72
73 #ifdef COMPILE_SSL_SUPPORT
74 #include "KviSSLMaster.h"
75 #endif
76
77 #include <QToolBar>
78 #include <QTimer>
79 #include <QMessageBox>
80 #include <QStringList>
81 #include <QCloseEvent>
82 #include <QRegExp>
83 #include <QMenu>
84
85 #include "kvi_debug.h"
86
87 extern KVIRC_API KviIrcServerDataBase * g_pServerDataBase;
88 extern KVIRC_API KviProxyDataBase * g_pProxyDataBase;
89
KviConsoleWindow(int iFlags)90 KviConsoleWindow::KviConsoleWindow(int iFlags) : KviWindow(KviWindow::Console, __tr2qs("CONSOLE"), this)
91 {
92 m_pContext = new KviIrcContext(this);
93
94 m_iFlags = iFlags;
95 if(m_pContext->id() == 1)
96 {
97 m_iFlags |= KVI_CONSOLE_FLAG_FIRSTINAPP;
98 }
99
100 m_pButtonBox = new KviTalHBox(this);
101 m_pButtonBox->setSpacing(0);
102 m_pButtonBox->setMargin(0);
103 new QLabel(__tr2qs("Address:"), m_pButtonBox);
104 m_pAddressEdit = new KviThemedComboBox(m_pButtonBox, this, "url_editor");
105 m_pAddressEdit->setAutoCompletion(true);
106 m_pAddressEdit->setDuplicatesEnabled(false);
107 m_pAddressEdit->setEditable(true);
108 m_pAddressEdit->addItem(QIcon(*(g_pIconManager->getSmallIcon(KviIconManager::Url))), "");
109 recentUrlsChanged();
110 m_pAddressEdit->setInsertPolicy(QComboBox::NoInsert);
111 m_pAddressEdit->setMinimumHeight(24); //icon is 16px, + margins
112 m_pButtonBox->setStretchFactor(m_pAddressEdit, 1);
113 m_pButtonBox->setObjectName(QLatin1String("kvi_window_button_box"));
114
115 KviTalToolTip::add(m_pAddressEdit, __tr2qs("Current IRC URI"));
116 connect(m_pAddressEdit, SIGNAL(activated(const QString &)), this, SLOT(ircUriChanged(const QString &)));
117 connect(m_pAddressEdit, SIGNAL(returnPressed(const QString &)), this, SLOT(ircUriChanged(const QString &)));
118 connect(g_pApp, SIGNAL(recentUrlsChanged()), this, SLOT(recentUrlsChanged()));
119
120 m_pSplitter = new KviTalSplitter(Qt::Horizontal, this);
121 m_pSplitter->setObjectName("console_splitter");
122 m_pSplitter->setChildrenCollapsible(false);
123
124 m_pIrcView = new KviIrcView(m_pSplitter, this);
125 connect(m_pIrcView, SIGNAL(rightClicked()), this, SLOT(textViewRightClicked()));
126
127 // FIXME: #warning "If notify list is disabled avoid to show this"
128 // FIXME: #warning "Button to show/hide the notifyListView (NOT DELETE RE_CREATE!)"
129 // The userlist on the right
130 //m_pEditorsContainer= new KviToolWindowsContainer(m_pSplitter);
131 m_pNotifyViewButton = new KviWindowToolPageButton(KviIconManager::HideListView, KviIconManager::ShowListView, __tr2qs("Notify list"), buttonContainer(), true);
132 connect(m_pNotifyViewButton, SIGNAL(clicked()), this, SLOT(toggleNotifyView()));
133
134 m_pNotifyListView = new KviUserListView(m_pSplitter, m_pNotifyViewButton, nullptr, this, 19, __tr2qs("Notify list"), "notify_list_view");
135
136 m_pInput = new KviInput(this, m_pNotifyListView);
137
138 m_pTmpHighLightedChannels = new QStringList;
139
140 applyOptions();
141 }
142
recentUrlsChanged()143 void KviConsoleWindow::recentUrlsChanged()
144 {
145 QString cur = m_pAddressEdit->currentText();
146 m_pAddressEdit->clear();
147 for(auto & it : KVI_OPTION_STRINGLIST(KviOption_stringlistRecentIrcUrls))
148 {
149 m_pAddressEdit->addItem(*(g_pIconManager->getSmallIcon(KviIconManager::Url)), it);
150 }
151
152 int i = m_pAddressEdit->findText(cur);
153 if(i != -1)
154 {
155 m_pAddressEdit->setCurrentIndex(i);
156 }
157 else
158 {
159 if(m_pAddressEdit->isEditable())
160 m_pAddressEdit->setEditText(cur);
161 else
162 m_pAddressEdit->setItemText(m_pAddressEdit->currentIndex(), cur);
163 }
164 }
165
connectionInProgress()166 bool KviConsoleWindow::connectionInProgress()
167 {
168 if(context()->asynchronousConnectionData() != nullptr)
169 return true;
170 if(context()->state() != KviIrcContext::Idle)
171 return true;
172 return false;
173 }
174
~KviConsoleWindow()175 KviConsoleWindow::~KviConsoleWindow()
176 {
177 // FIXME: #warning "WARNING : THIS SHOULD BECOME A COMMAND /QUIT $option() so the idents are parsed!"
178
179 // Force connection close: it will just return if no connection is present
180 context()->terminateConnectionRequest(true);
181
182 KVS_TRIGGER_EVENT_0(KviEvent_OnIrcContextDestroyed, this);
183
184 //if(m_pLastIrcServer)delete m_pLastIrcServer;
185
186 delete m_pContext;
187 m_pContext = nullptr;
188
189 delete m_pTmpHighLightedChannels;
190 }
191
triggerCreationEvents()192 void KviConsoleWindow::triggerCreationEvents()
193 {
194 if(m_iFlags & KVI_CONSOLE_FLAG_FIRSTINAPP) // this is the first context in the application
195 {
196 KVS_TRIGGER_EVENT_0(KviEvent_OnKVIrcStartup, this);
197
198 if(KVI_OPTION_BOOL(KviOption_boolShowTipAtStartup))
199 g_pMainWindow->executeInternalCommand(KVI_INTERNALCOMMAND_TIP_OPEN);
200 }
201
202 if(m_iFlags & KVI_CONSOLE_FLAG_FIRSTINFRAME)
203 {
204 KVS_TRIGGER_EVENT_0(KviEvent_OnFrameWindowCreated, this);
205 }
206
207 KVS_TRIGGER_EVENT_0(KviEvent_OnIrcContextCreated, this);
208 }
209
completeChannel(const QString & word,std::vector<QString> & matches)210 void KviConsoleWindow::completeChannel(const QString & word, std::vector<QString> & matches)
211 {
212 // FIXME: first look in our context ?
213 /*
214 if(!connection())return;
215 for(auto & c : connection()->channelList())
216 {
217 if(kvi_strEqualCIN(c.windowName(),word.ptr(),word.len()))matches->append(new KviCString((*it)
218 }
219 */
220 QStringList * pList = g_pApp->recentChannelsForNetwork(currentNetworkName());
221 if(pList)
222 {
223 for(auto & it : *pList)
224 {
225 if(KviQString::equalCIN(it, word, word.length()))
226 matches.push_back(it);
227 }
228 }
229 }
230
completeServer(const QString & word,std::vector<QString> & matches)231 void KviConsoleWindow::completeServer(const QString & word, std::vector<QString> & matches)
232 {
233 for(auto srv : KVI_OPTION_STRINGLIST(KviOption_stringlistRecentServers))
234 {
235 KviQString::cutToFirst(srv, '/');
236 while(srv.startsWith("/"))
237 srv.remove(0, 1);
238 KviQString::cutFromLast(srv, ':');
239 //We should have a full server name here, without the irc:// and without the port
240 if(KviQString::equalCIN(srv, word, word.length()))
241 matches.push_back(srv);
242 }
243 }
244
getUserTipText(const QString & nick,KviIrcUserEntry * e,QString & buffer)245 void KviConsoleWindow::getUserTipText(const QString & nick, KviIrcUserEntry * e, QString & buffer)
246 {
247 static QString br("<br>");
248 static QString bb("<b>");
249 static QString be("</b>");
250 static QString cln(":");
251 static QString space(" ");
252 static QString tds = "<tr><td style=\"background-color: rgb(48,48,48); white-space: pre; font-weight: bold; color: rgb(255,255,255); padding-left: 5px; padding-right: 5px;\">";
253 static QString nrs = "<tr><td>";
254 static QString enr = "</td></tr>";
255
256 KviRegisteredUserMask * u = g_pRegisteredUserDataBase->findMatchingMask(nick, e->user(), e->host());
257
258 buffer = "<table>";
259 buffer += tds;
260
261 buffer += KviQString::toHtmlEscaped(nick);
262 buffer += "!";
263 buffer += KviQString::toHtmlEscaped(e->user().isEmpty() ? QString("*") : e->user());
264 buffer += "@";
265 buffer += KviQString::toHtmlEscaped(e->host().isEmpty() ? QString("*") : e->host());
266 buffer += "</font>" + enr;
267
268 if(u)
269 {
270 QString szComment = u->user()->getProperty("comment");
271 if(!szComment.isEmpty())
272 {
273 buffer += "<tr bgcolor=\"#E0E0E0\"><td>";
274 buffer += __tr2qs("Comment");
275 buffer += cln + space + "(" + bb;
276 buffer += KviQString::toHtmlEscaped(szComment);
277 buffer += be + ")" + enr;
278 }
279 }
280
281 if(e->avatar())
282 {
283 buffer += QString(nrs + R"(<center><img src="%1" width="%2"></center>)" + enr).arg(e->avatar()->localPath()).arg(e->avatar()->size().width());
284 }
285
286 if(e->hasRealName())
287 {
288 buffer += "<tr><td style=\"white-space: pre\">";
289 buffer += __tr2qs("Real name");
290 buffer += cln + space + bb;
291 buffer += KviQString::toHtmlEscaped(KviControlCodes::stripControlBytes(e->realName()));
292 buffer += be + enr;
293 }
294
295 if(e->gender() != KviIrcUserEntry::Unknown)
296 {
297 buffer += nrs;
298 buffer += __tr2qs("Gender");
299 buffer += cln + space + bb;
300 buffer += (e->gender() == KviIrcUserEntry::Male) ? __tr2qs("Male") : __tr2qs("Female");
301 buffer += be + enr;
302 }
303
304 if(u)
305 {
306 QString mask;
307 u->mask()->mask(mask);
308 buffer += nrs;
309 buffer += __tr2qs("Registered as");
310 buffer += cln + space + bb;
311 buffer += KviQString::toHtmlEscaped(u->user()->name());
312 buffer += be + br;
313 buffer += "Group";
314 buffer += cln + space + bb;
315 buffer += KviQString::toHtmlEscaped(u->user()->group());
316 buffer += be + enr + nrs;
317 buffer += __tr2qs("Matched by");
318 buffer += cln + space + bb;
319 buffer += KviQString::toHtmlEscaped(mask);
320 buffer += be + enr;
321 }
322
323 if(connection())
324 {
325 QString chans;
326 if(connection()->getCommonChannels(nick, chans, false))
327 {
328 buffer += nrs;
329 buffer += __tr2qs("On");
330 buffer += cln + space + bb;
331 buffer += KviQString::toHtmlEscaped(chans);
332 buffer += be + enr;
333 }
334 }
335
336 if(e->hasServer())
337 {
338 buffer += "<tr><td style=\"white-space: pre\">";
339 buffer += __tr2qs("Using server: <b>%1</b>").arg(KviQString::toHtmlEscaped(e->server()));
340
341 if(e->hasHops())
342 {
343 buffer += R"(<tr><td bgcolor="#E0E0E0"><font color="#000000">)";
344 buffer += __tr2qs("Hops: <b>%1</b>").arg(e->hops());
345 buffer += "</font>" + enr;
346 }
347 else
348 {
349 buffer += enr;
350 buffer += "</table>";
351 }
352 }
353
354 if(e->hasAccountName())
355 {
356 buffer += R"(<tr><td bgcolor="#E0E0E0"><font color="#000000">)";
357 buffer += __tr2qs("Identified to account: <b>%1</b>").arg(e->accountName());
358 buffer += "</font>" + enr;
359 }
360
361 if(e->isAway())
362 {
363 buffer += R"(<tr><td width="100%" bgcolor="#E0E0E0"><font color="#000000">)";
364 buffer += __tr2qs("Probably away");
365 buffer += "</font>" + enr;
366 }
367 }
368
toggleNotifyView()369 void KviConsoleWindow::toggleNotifyView()
370 {
371 showNotifyList(!m_pNotifyListView->isVisible());
372 }
373
executeInternalCommand(int index)374 void KviConsoleWindow::executeInternalCommand(int index)
375 {
376 KviKvsScript::run(kvi_getInternalCommandBuffer(index), this);
377 }
378
saveProperties(KviConfigurationFile * cfg)379 void KviConsoleWindow::saveProperties(KviConfigurationFile * cfg)
380 {
381 KviWindow::saveProperties(cfg);
382 cfg->writeEntry("Splitter", m_pNotifyViewButton->isChecked() ? m_pSplitter->sizes() : m_SplitterSizesList);
383 cfg->writeEntry("NotifyListViewVisible", m_pNotifyViewButton->isChecked());
384 }
385
getBaseLogFileName(QString & buffer)386 void KviConsoleWindow::getBaseLogFileName(QString & buffer)
387 {
388 if(context()->connection())
389 buffer = context()->connection()->target()->network()->name();
390 else
391 buffer = context()->id();
392 }
393
showNotifyList(bool bShow,bool bIgnoreSizeChange)394 void KviConsoleWindow::showNotifyList(bool bShow, bool bIgnoreSizeChange)
395 {
396 if(bShow)
397 {
398 m_pNotifyListView->show();
399 if(!(m_pNotifyViewButton->isChecked()))
400 m_pNotifyViewButton->setChecked(true);
401
402 m_pSplitter->setSizes(m_SplitterSizesList);
403 }
404 else
405 {
406 if(!bIgnoreSizeChange)
407 m_SplitterSizesList = m_pSplitter->sizes();
408
409 m_pNotifyListView->hide();
410 if(m_pNotifyViewButton->isChecked())
411 m_pNotifyViewButton->setChecked(false);
412 }
413 }
414
loadProperties(KviConfigurationFile * cfg)415 void KviConsoleWindow::loadProperties(KviConfigurationFile * cfg)
416 {
417 int iWidth = width();
418 QList<int> def;
419 def.append((iWidth * 75) / 100);
420 def.append((iWidth * 25) / 100);
421 m_SplitterSizesList = cfg->readIntListEntry("Splitter", def);
422 m_pSplitter->setStretchFactor(0, 1);
423
424 KviWindow::loadProperties(cfg);
425 showNotifyList(cfg->readBoolEntry("NotifyListViewVisible", false), true);
426 }
427
textViewRightClicked()428 void KviConsoleWindow::textViewRightClicked()
429 {
430 KVS_TRIGGER_EVENT_0(KviEvent_OnConsolePopupRequest, this);
431 }
432
activeWindow()433 KviWindow * KviConsoleWindow::activeWindow()
434 {
435 if(!g_pActiveWindow)
436 return this;
437 if(g_pActiveWindow->console() == this)
438 return g_pActiveWindow;
439 return this;
440 }
441
ircUriChanged(const QString & text)442 void KviConsoleWindow::ircUriChanged(const QString & text)
443 {
444 int iStatus = KviIrcUrl::run(text, KviIrcUrl::CurrentContext, this);
445 if(iStatus & KviIrcUrl::InvalidProtocol || iStatus & KviIrcUrl::InvalidUrl)
446 {
447 KviMessageBox::warning(__tr2qs("KVIrc can only recognize irc://, irc6://, ircs:// or ircs6:// URL's\n"
448 "The URL you provided is invalid. Check spelling and try again"));
449 }
450 m_pInput->setFocus();
451 }
452
updateUri()453 void KviConsoleWindow::updateUri()
454 {
455 QString uri;
456 if(connection())
457 {
458 KviIrcServer * server = connection()->target()->server();
459 if(server)
460 {
461 KviIrcUrl::join(uri, server);
462 if(connection()->channelList().size())
463 {
464 KviChannelWindow * last = connection()->channelList().back();
465 for(auto & c : connection()->channelList())
466 {
467 uri.append(c->target());
468 if(c->hasChannelMode('k'))
469 {
470 uri.append("?");
471 uri.append(c->channelModeParam('k'));
472 }
473 if(c != last)
474 uri.append(",");
475 }
476 }
477 }
478 }
479
480 int i = m_pAddressEdit->findText(uri);
481 if(i != -1)
482 {
483 m_pAddressEdit->setCurrentIndex(i);
484 }
485 else
486 {
487 if(m_pAddressEdit->isEditable())
488 m_pAddressEdit->setEditText(uri);
489 else
490 m_pAddressEdit->setItemText(m_pAddressEdit->currentIndex(), uri);
491 }
492
493 m_pAddressEdit->lineEdit()->setCursorPosition(0);
494 }
495
connectionAttached()496 void KviConsoleWindow::connectionAttached()
497 {
498 //need to update URI
499 connect(m_pContext->connection(), SIGNAL(chanListChanged()), this, SLOT(updateUri()));
500 updateUri();
501 m_pNotifyListView->setUserDataBase(connection()->userDataBase());
502
503 // Update log file name
504 if(KVI_OPTION_BOOL(KviOption_boolAutoLogConsole))
505 {
506 if (m_pIrcView->isLogging())
507 m_pIrcView->stopLogging();
508 m_pIrcView->startLogging();
509 }
510 }
511
connectionDetached()512 void KviConsoleWindow::connectionDetached()
513 {
514 //need to update URI?
515 m_pNotifyListView->partAll();
516 m_pNotifyListView->setUserDataBase(nullptr); // this is rather for crash tests
517 }
518
closeEvent(QCloseEvent * e)519 void KviConsoleWindow::closeEvent(QCloseEvent * e)
520 {
521 if(g_pMainWindow->consoleCount() > 1)
522 {
523 // there are other consoles beside this one
524 if(context()->state() == KviIrcContext::Connected)
525 {
526 if(!KVI_OPTION_BOOL(KviOption_boolAlwaysDisconnectClosingConnectedConsole))
527 {
528 switch(QMessageBox::warning(this,
529 __tr2qs("Confirm Close - KVIrc"),
530 __tr2qs("You have just attempted to close a console window with an active connection inside.\n"
531 "Are you sure you wish to terminate the connection?"),
532 __tr2qs("&Yes"),
533 __tr2qs("&Always"),
534 __tr2qs("&No"),
535 2, 2))
536 {
537 case 0:
538 // nothing here
539 break;
540 case 1:
541 KVI_OPTION_BOOL(KviOption_boolAlwaysDisconnectClosingConnectedConsole) = true;
542 break;
543 default: // 2 = no
544 e->ignore();
545 return;
546 break;
547 }
548 }
549 // ask the context to terminate the connection gracefully
550 context()->terminateConnectionRequest(false);
551 // the close event will recall terminateConnectionRequest()
552 // to brutally interrupt it in a while
553 }
554
555 // just close
556 KviWindow::closeEvent(e);
557 return;
558 }
559
560 // this is the only console... ask if the user really wants to quit KVirc
561 if(!KVI_OPTION_BOOL(KviOption_boolAlwaysQuitKVIrcClosingLastConsole))
562 {
563 switch(QMessageBox::warning(this,
564 __tr2qs("Confirm Close - KVIrc"),
565 __tr2qs("You have just attempted to close the last console window.\nAre you sure you wish to quit KVIrc?"),
566 __tr2qs("&Always"),
567 __tr2qs("&Yes"),
568 __tr2qs("&No"),
569 2, 2))
570 {
571 case 0:
572 KVI_OPTION_BOOL(KviOption_boolAlwaysQuitKVIrcClosingLastConsole) = true;
573 break;
574 case 1:
575 // nothing here
576 break;
577 default: // 2 = no
578 e->ignore();
579 return;
580 break;
581 }
582 }
583
584 g_pApp->quit();
585 }
586
587 // internal helper for applyHighlighting
triggerOnHighlight(KviWindow * pWnd,int iType,const QString & szNick,const QString & szUser,const QString & szHost,const QString & szMsg,const QString & szTrigger)588 int KviConsoleWindow::triggerOnHighlight(KviWindow * pWnd, int iType, const QString & szNick, const QString & szUser, const QString & szHost, const QString & szMsg, const QString & szTrigger)
589 {
590 KviRegisteredUser * u = connection()->userDataBase()->registeredUser(szNick, szUser, szHost);
591 if(u)
592 {
593 if(u->isIgnoreEnabledFor(KviRegisteredUser::Highlight))
594 return iType;
595 }
596 if(!KVI_OPTION_STRING(KviOption_stringOnHighlightedMessageSound).isEmpty() && pWnd && !pWnd->hasAttention())
597 {
598 KviKvsVariantList soundParams{new KviKvsVariant{KVI_OPTION_STRING(KviOption_stringOnHighlightedMessageSound)}};
599 KviKvsScript::run("snd.play $0", nullptr, &soundParams);
600 }
601
602 QString szMessageType = QString("%1").arg(iType);
603
604 if(KVS_TRIGGER_EVENT_7_HALTED(KviEvent_OnHighlight,
605 pWnd, szNick, szUser, szHost,
606 szMsg, szTrigger,
607 szMessageType, (iType == KVI_OUT_ACTION || iType == KVI_OUT_ACTIONCRYPTED)))
608 return -1;
609 return KVI_OUT_HIGHLIGHT;
610 }
611
addHighlightedChannel(const QString & szChan)612 void KviConsoleWindow::addHighlightedChannel(const QString & szChan)
613 {
614 if(m_pTmpHighLightedChannels->contains(szChan, Qt::CaseInsensitive))
615 return;
616 else
617 m_pTmpHighLightedChannels->append(szChan);
618 }
619
removeHighlightedChannel(const QString & szChan)620 void KviConsoleWindow::removeHighlightedChannel(const QString & szChan)
621 {
622 m_pTmpHighLightedChannels->removeOne(szChan);
623 }
624
625 // if it returns -1 you should just return and not display the message
applyHighlighting(KviWindow * wnd,int type,const QString & nick,const QString & user,const QString & host,const QString & szMsg)626 int KviConsoleWindow::applyHighlighting(KviWindow * wnd, int type, const QString & nick, const QString & user, const QString & host, const QString & szMsg)
627 {
628 QString szPattern = KVI_OPTION_STRING(KviOption_stringWordSplitters);
629 QString szSource;
630 QString szStripMsg = KviControlCodes::stripControlBytes(szMsg);
631 QRegExp rgxHlite;
632 Qt::CaseSensitivity cs = KVI_OPTION_BOOL(KviOption_boolCaseSensitiveHighlighting) ? Qt::CaseSensitive : Qt::CaseInsensitive;
633
634 if(KVI_OPTION_BOOL(KviOption_boolAlwaysHighlightNick) && connection())
635 {
636 if(KVI_OPTION_BOOL(KviOption_boolUseFullWordHighlighting))
637 {
638 if(szStripMsg.contains(connection()->userInfo()->nickName(), cs))
639 return triggerOnHighlight(wnd, type, nick, user, host, szMsg, connection()->userInfo()->nickName());
640 }
641 else
642 {
643 if(!szPattern.isEmpty())
644 rgxHlite.setPattern(
645 QString("(?:[%1]|\\s|^)%2(?:[%1]|\\s|$)").arg(QRegExp::escape(szPattern), QRegExp::escape(connection()->userInfo()->nickName())));
646 else
647 rgxHlite.setPattern(
648 QString("(?:\\s|^)%1(?:\\s|$)").arg(QRegExp::escape(connection()->userInfo()->nickName())));
649 rgxHlite.setCaseSensitivity(cs);
650 if(szStripMsg.contains(rgxHlite))
651 return triggerOnHighlight(wnd, type, nick, user, host, szMsg, connection()->userInfo()->nickName());
652 }
653 }
654
655 if(KVI_OPTION_BOOL(KviOption_boolUseWordHighlighting))
656 {
657 for(auto & it : KVI_OPTION_STRINGLIST(KviOption_stringlistHighlightWords))
658 {
659 if(it.isEmpty())
660 continue;
661
662 if(KVI_OPTION_BOOL(KviOption_boolUseFullWordHighlighting))
663 {
664 if(szStripMsg.contains(it, cs))
665 return triggerOnHighlight(wnd, type, nick, user, host, szMsg, it);
666 }
667 else
668 {
669 if(!szPattern.isEmpty())
670 rgxHlite.setPattern(
671 QString("(?:[%1]|\\s|^)%2(?:[%1]|\\s|$)").arg(QRegExp::escape(szPattern), QRegExp::escape(it)));
672 else
673 rgxHlite.setPattern(
674 QString("(?:\\s|^)%1(?:\\s|$)").arg(QRegExp::escape(it)));
675 rgxHlite.setCaseSensitivity(cs);
676 if(szStripMsg.contains(rgxHlite))
677 return triggerOnHighlight(wnd, type, nick, user, host, szMsg, it);
678 }
679 }
680 }
681
682 if(wnd->type() == KviWindow::Channel)
683 {
684 if(((KviChannelWindow *)wnd)->isHighlightedUser(nick) || isHighlightedChannel(wnd->windowName()))
685 return triggerOnHighlight(wnd, type, nick, user, host, szMsg, nick);
686
687 // FIXME: this is for userhighlighing
688 // maybe mark the users as highlighted in the console user database
689 // and then lookup them there ? this would be potentially a lot faster
690 KviRegisteredUser * u = connection()->userDataBase()->registeredUser(nick, user, host);
691
692 // note that we're highlighting users only in channels since
693 // in a query (or DCC) highlighting the remote end is senseless.
694 if(u)
695 {
696 if(u->getBoolProperty("highlight"))
697 return triggerOnHighlight(wnd, type, nick, user, host, szMsg, nick);
698 }
699 }
700
701 return type;
702 }
703
outputPrivmsg(KviWindow * wnd,int type,const QString & daNick,const QString & daUser,const QString & daHost,const QString & msg,int iFlags,const QString & prefix,const QString & suffix,const QDateTime & datetime)704 void KviConsoleWindow::outputPrivmsg(KviWindow * wnd,
705 int type,
706 const QString & daNick,
707 const QString & daUser,
708 const QString & daHost,
709 const QString & msg,
710 int iFlags,
711 const QString & prefix,
712 const QString & suffix,
713 const QDateTime & datetime)
714 {
715 // FIXME: #warning "THIS IS USED BY WINDOWS THAT ARE NOT BOUND TO THIS IRC CONTEXT"
716 // FIXME: #warning "REMEMBER IT IN ESCAPE COMMANDS"
717 // KVI_ASSERT(wnd);
718
719 bool bIsChan = (wnd->type() == KviWindow::Channel);
720 bool bIsNotice = ((type == KVI_OUT_CHANNELNOTICE) || (type == KVI_OUT_CHANNELNOTICECRYPTED)
721 || (type == KVI_OUT_QUERYNOTICE) || (type == KVI_OUT_QUERYNOTICECRYPTED));
722
723 QString nick = daNick; // not that beautiful.. :/
724 QString user = daUser;
725 QString host = daHost;
726
727 if(connection())
728 {
729 if(nick.isEmpty())
730 nick = connection()->userInfo()->nickName();
731 if(user.isEmpty())
732 user = connection()->userInfo()->userName();
733 if(host.isEmpty())
734 host = connection()->userInfo()->hostName();
735 }
736
737 QString szDecodedMessage = msg; // shallow copy
738
739 if(KVI_OPTION_BOOL(KviOption_boolStripMircColorsInUserMessages))
740 szDecodedMessage = KviControlCodes::stripControlBytes(szDecodedMessage);
741
742 if(!(iFlags & NoHighlighting))
743 {
744 // HIGHLIGHTING BLOCK
745 int iSaveType = type;
746 type = applyHighlighting(wnd, type, nick, user, host, szDecodedMessage);
747 if(type < 0)
748 return; // event stopped the message!
749
750 if(type == KVI_OUT_HIGHLIGHT)
751 {
752 if(!wnd->hasAttention(KviWindow::MainWindowIsVisible))
753 {
754 if(KVI_OPTION_BOOL(KviOption_boolFlashWindowOnHighlightedMessages) && (!(iFlags & NoWindowFlashing)))
755 {
756 wnd->demandAttention();
757 }
758 if(KVI_OPTION_BOOL(KviOption_boolPopupNotifierOnHighlightedMessages) && (!(iFlags & NoNotifier)))
759 {
760 QString szMsg = "<b><";
761 szMsg += nick;
762 szMsg += "></b> ";
763 szMsg += KviQString::toHtmlEscaped(szDecodedMessage);
764 //qDebug("KviConsoleWindow.cpp:629 debug: %s",szMsg.data());
765 g_pApp->notifierMessage(wnd, KVI_OPTION_MSGTYPE(iSaveType).pixId(), szMsg, KVI_OPTION_UINT(KviOption_uintNotifierAutoHideTime));
766 }
767 }
768 }
769 }
770
771 // <PREFIX>nick[!user@host]<POSTFIX>This is a test message
772
773 QString szNick = QString("\r!nc\r%1\r").arg(nick);
774
775 if(KVI_OPTION_BOOL(KviOption_boolShowUserAndHostInPrivmsgView))
776 KviQString::appendFormatted(szNick, "!%Q@\r!h\r%Q\r", &user, &host);
777
778 if(bIsChan && KVI_OPTION_BOOL(KviOption_boolShowChannelUserFlagInPrivmsgView))
779 ((KviChannelWindow *)wnd)->prependUserFlag(nick, szNick);
780
781 if(KVI_OPTION_BOOL(KviOption_boolColorNicks) && connection())
782 {
783 if(KVI_OPTION_BOOL(KviOption_boolUseSpecifiedSmartColorForOwnNick) && QString::compare(nick, connection()->userInfo()->nickName(), Qt::CaseSensitive) == 0)
784 {
785 // it's me
786 szNick.prepend(m_szOwnSmartColor);
787 }
788 else
789 {
790 //search for a cached entry
791 KviIrcUserEntry * pUserEntry = connection()->userDataBase()->find(nick);
792 if(pUserEntry)
793 {
794 int sum = pUserEntry->smartNickColor();
795 if(sum < 0)
796 {
797 // cache miss, create entry
798 sum = KviNickColors::getSmartColorForNick(&nick);
799 if(KVI_OPTION_BOOL(KviOption_boolUseSpecifiedSmartColorForOwnNick))
800 {
801 //avoid the use of the color specifier for own nickname
802 if(m_szOwnSmartColor == KviNickColors::getSmartColor(sum, KVI_OPTION_BOOL(KviOption_boolColorNicksWithBackground)))
803 sum++;
804 }
805 pUserEntry->setSmartNickColor(sum);
806 }
807
808 szNick.prepend(KviNickColors::getSmartColor(sum, KVI_OPTION_BOOL(KviOption_boolColorNicksWithBackground)));
809 }
810 else
811 {
812 /*
813 * Received a message from a user not in userDataBase: how can this happen?
814 * - services replaying some log
815 * - user non joined msging a channel -n
816 * anyway, better fallback than wrong
817 */
818 int sum = KviNickColors::getSmartColorForNick(&nick);
819 if(KVI_OPTION_BOOL(KviOption_boolUseSpecifiedSmartColorForOwnNick))
820 {
821 //avoid the use of the color specifier for own nickname
822 if(m_szOwnSmartColor == KviNickColors::getSmartColor(sum, KVI_OPTION_BOOL(KviOption_boolColorNicksWithBackground)))
823 sum++;
824 }
825 szNick.prepend(KviNickColors::getSmartColor(sum, KVI_OPTION_BOOL(KviOption_boolColorNicksWithBackground)));
826 }
827 }
828 szNick.prepend(KviControlCodes::Color);
829 szNick.append(KviControlCodes::Color);
830 }
831
832 if(KVI_OPTION_BOOL(KviOption_boolBoldedNicks))
833 {
834 szNick.prepend(KviControlCodes::Bold);
835 szNick.append(KviControlCodes::Bold);
836 }
837
838 QString szMessage;
839
840 if(KVI_OPTION_BOOL(KviOption_boolUseExtendedPrivmsgView))
841 {
842 szMessage = prefix.isEmpty() ? KVI_OPTION_STRING(KviOption_stringExtendedPrivmsgPrefix) : prefix;
843 szMessage += szNick;
844 szMessage += suffix.isEmpty() ? KVI_OPTION_STRING(KviOption_stringExtendedPrivmsgPostfix) : suffix;
845 }
846 else
847 {
848 if(bIsNotice)
849 {
850 static QString pre1("*");
851 static QString suf1("* ");
852 szMessage = prefix.isEmpty() ? pre1 : prefix;
853 szMessage += szNick;
854 szMessage += suffix.isEmpty() ? suf1 : suffix;
855 }
856 else
857 {
858 static QString pre2("<");
859 static QString suf2("> ");
860 szMessage = prefix.isEmpty() ? pre2 : prefix;
861 szMessage += szNick;
862 szMessage += suffix.isEmpty() ? suf2 : suffix;
863 }
864 }
865
866 szMessage += szDecodedMessage;
867
868 if(bIsChan)
869 ((KviChannelWindow *)wnd)->outputMessage(type, szMessage, datetime);
870 else
871 wnd->outputNoFmt(type, szMessage, 0, datetime);
872 }
873
avatarChangedUpdateWindows(const QString & nick,const QString & textLine)874 void KviConsoleWindow::avatarChangedUpdateWindows(const QString & nick, const QString & textLine)
875 {
876 if(!connection())
877 return; //ops...
878
879 // in quiet mode avoid bugging the user about avatar changes
880 bool bOut = ((!textLine.isEmpty()) && (!(_OUTPUT_QUIET)));
881
882 for(auto & c : connection()->channelList())
883 {
884 if(c->avatarChanged(nick))
885 {
886 if(bOut)
887 c->outputNoFmt(KVI_OUT_AVATAR, textLine);
888 }
889 }
890 for(auto & q : connection()->queryList())
891 {
892 if(q->avatarChanged(nick))
893 {
894 if(bOut)
895 q->outputNoFmt(KVI_OUT_AVATAR, textLine);
896 }
897 }
898 m_pNotifyListView->avatarChanged(nick); // recalc the item height here too!
899 }
900
avatarChanged(KviAvatar * avatar,const QString & nick,const QString & user,const QString & host,const QString & textLine)901 void KviConsoleWindow::avatarChanged(KviAvatar * avatar, const QString & nick, const QString & user, const QString & host, const QString & textLine)
902 {
903 if(!connection())
904 return; //ops...
905
906 bool bRegisteredStuff = false;
907
908 if(KVI_OPTION_BOOL(KviOption_boolSetLastAvatarAsDefaultForRegisteredUsers))
909 {
910 // Don't even try to do it for myself
911 if(!KviQString::equalCI(nick, connection()->userInfo()->nickName()))
912 {
913 KviRegisteredUser * u = connection()->userDataBase()->registeredUser(nick, user, host);
914 if(u)
915 {
916 if(avatar)
917 u->setProperty("avatar", avatar->identificationString());
918 else
919 u->setProperty("avatar", QString());
920 bRegisteredStuff = true;
921 }
922 }
923 }
924
925 if(!bRegisteredStuff)
926 {
927 // cache it
928 if(avatar)
929 KviAvatarCache::instance()->replace(avatar->identificationString(), KviIrcMask(nick, user, host), currentNetworkName().toUtf8().data());
930 else
931 KviAvatarCache::instance()->remove(KviIrcMask(nick, user, host), currentNetworkName().toUtf8().data());
932 }
933
934 avatarChangedUpdateWindows(nick, textLine);
935 }
936
checkDefaultAvatar(KviIrcUserEntry * e,const QString & nick,const QString & user,const QString & host)937 void KviConsoleWindow::checkDefaultAvatar(KviIrcUserEntry * e, const QString & nick, const QString & user, const QString & host)
938 {
939 // look it up in the cache
940 QString szAvatar = KviAvatarCache::instance()->lookup(KviIrcMask(nick, user, host), currentNetworkName());
941 if(!szAvatar.isEmpty())
942 {
943 // got a cache hit... is it on disk ?
944 KviAvatar * avatar = g_pIconManager->getAvatar(QString(), szAvatar);
945 if(avatar)
946 {
947 // cached image on disk
948 e->setAvatar(avatar);
949 avatarChangedUpdateWindows(nick, QString());
950 return;
951 }
952 // no cached image on disk.. will need to requery it anyway
953 // remove from cache
954 KviAvatarCache::instance()->remove(KviIrcMask(nick, user, host), currentNetworkName());
955 }
956
957 // registered ?
958 KviRegisteredUser * u = connection()->userDataBase()->registeredUser(nick, user, host);
959 if(u)
960 {
961 // the user is registered...
962 QString szAvatar;
963 if(u->getProperty("avatar", szAvatar))
964 {
965 // the user has a default avatar...
966 KviAvatar * avatar = g_pIconManager->getAvatar(szAvatar, KviFileUtils::extractFileName(szAvatar));
967 if(avatar)
968 {
969 e->setAvatar(avatar);
970 avatarChangedUpdateWindows(nick, QString());
971 return;
972 }
973 }
974 }
975 }
976
resetAvatarForMatchingUsers(KviRegisteredUser * u)977 void KviConsoleWindow::resetAvatarForMatchingUsers(KviRegisteredUser * u)
978 {
979 if(!connection())
980 return;
981
982 QString szAvatar;
983 if(!u->getProperty("avatar", szAvatar))
984 return;
985
986 KviPointerHashTableIterator<QString, KviIrcUserEntry> it(*(connection()->userDataBase()->dict()));
987 while(KviIrcUserEntry * e = it.current())
988 {
989 if(e->hasHost())
990 {
991 if(u->matchesFixed(it.currentKey(), e->user(), e->host()))
992 {
993 KviAvatar * a = g_pIconManager->getAvatar(QString(), szAvatar);
994 e->setAvatar(a);
995 avatarChangedUpdateWindows(it.currentKey(), QString());
996 }
997 }
998 ++it;
999 }
1000 }
1001
setAvatar(const QString & nick,const QString & user,const QString & host,const QString & szLocalPath,const QString & szName)1002 KviAvatar * KviConsoleWindow::setAvatar(const QString & nick, const QString & user, const QString & host, const QString & szLocalPath, const QString & szName)
1003 {
1004 if(!connection())
1005 return nullptr;
1006 KviIrcUserEntry * e = connection()->userDataBase()->find(nick);
1007 if(e)
1008 {
1009 // User and host must match
1010 if((!user.isEmpty()) && e->hasUser())
1011 {
1012 if(!KviQString::equalCI(user, e->user()))
1013 return nullptr;
1014 }
1015
1016 if((!host.isEmpty()) && e->hasHost())
1017 {
1018 if(!KviQString::equalCI(host, e->host()))
1019 return nullptr;
1020 }
1021
1022 // Ok...got it
1023 KviAvatar * avatar = g_pIconManager->getAvatar(szLocalPath, szName);
1024 if(avatar)
1025 {
1026 e->setAvatar(avatar);
1027 avatarChanged(avatar, nick, user, host, QString());
1028 return avatar;
1029 }
1030 else
1031 {
1032 if(_OUTPUT_PARANOIC)
1033 output(KVI_OUT_VERBOSE, __tr2qs("Failed to load avatar with name \"%Q\" and local path \"%Q\""), &szName, &szLocalPath);
1034 }
1035 }
1036 return nullptr;
1037 }
1038
defaultAvatarFromOptions()1039 KviAvatar * KviConsoleWindow::defaultAvatarFromOptions()
1040 {
1041 QPixmap * avatar = KVI_OPTION_PIXMAP(KviOption_pixmapMyAvatar).pixmap();
1042
1043 if(!avatar)
1044 return nullptr;
1045
1046 if(avatar->isNull())
1047 return nullptr;
1048
1049 if(KVI_OPTION_STRING(KviOption_stringMyAvatar).isEmpty())
1050 return nullptr;
1051
1052 KviAvatar * loadedAvatar = new KviAvatar(KVI_OPTION_PIXMAP(KviOption_pixmapMyAvatar).path(), KVI_OPTION_STRING(KviOption_stringMyAvatar));
1053
1054 if(loadedAvatar->isValid())
1055 return loadedAvatar;
1056
1057 delete loadedAvatar;
1058 return nullptr;
1059 }
1060
currentAvatar()1061 KviAvatar * KviConsoleWindow::currentAvatar()
1062 {
1063 if(!connection())
1064 return nullptr;
1065
1066 KviIrcUserEntry * e = connection()->userDataBase()->find(connection()->userInfo()->nickName());
1067
1068 if(!e)
1069 return nullptr;
1070
1071 KviAvatar * a = e->avatar();
1072
1073 if(!a)
1074 {
1075 a = defaultAvatarFromOptions();
1076 if(a)
1077 {
1078 e->setAvatar(a);
1079 avatarChanged(a, connection()->userInfo()->nickName(), QString(), QString(), QString());
1080 }
1081 }
1082
1083 return a;
1084 }
1085
setAvatarFromOptions()1086 void KviConsoleWindow::setAvatarFromOptions()
1087 {
1088 if(!connection())
1089 return;
1090 KviIrcUserEntry * e = connection()->userDataBase()->find(connection()->userInfo()->nickName());
1091
1092 if(!e)
1093 return;
1094
1095 // a=0 => avatar unset
1096 KviAvatar * a = defaultAvatarFromOptions();
1097 e->setAvatar(a);
1098 avatarChanged(a, connection()->userInfo()->nickName(), QString(), QString(), QString());
1099 }
1100
applyOptions()1101 void KviConsoleWindow::applyOptions()
1102 {
1103 m_pAddressEdit->applyOptions();
1104 m_pNotifyListView->applyOptions();
1105 m_pInput->applyOptions();
1106 m_pIrcView->applyOptions();
1107
1108 KviWindow::applyOptions();
1109
1110 // trick
1111 resize(width() - 1, height() - 1);
1112 resize(width() + 1, height() + 1);
1113
1114 if(KVI_OPTION_BOOL(KviOption_boolUseSpecifiedSmartColorForOwnNick))
1115 {
1116 int iBack = KVI_OPTION_UINT(KviOption_uintUserIrcViewOwnBackground);
1117 if(iBack == KviControlCodes::Transparent)
1118 {
1119 m_szOwnSmartColor = QString("%1").arg(KVI_OPTION_UINT(KviOption_uintUserIrcViewOwnForeground));
1120 }
1121 else
1122 {
1123 m_szOwnSmartColor = QString("%1,%2").arg(KVI_OPTION_UINT(KviOption_uintUserIrcViewOwnForeground)).arg(iBack);
1124 }
1125 }
1126 }
1127
resizeEvent(QResizeEvent *)1128 void KviConsoleWindow::resizeEvent(QResizeEvent *)
1129 {
1130 //qDebug("Console window resize event %d,%d",width(),height());
1131 int hght = m_pInput->heightHint();
1132 int hght2 = m_pButtonBox->sizeHint().height();
1133 m_pButtonBox->setGeometry(0, 0, width(), hght2);
1134 m_pSplitter->setGeometry(0, hght2, width(), height() - (hght + hght2));
1135 m_pInput->setGeometry(0, height() - hght, width(), hght);
1136 }
1137
sizeHint() const1138 QSize KviConsoleWindow::sizeHint() const
1139 {
1140 QSize ret(m_pIrcView->sizeHint().height(), m_pIrcView->sizeHint().height() + m_pInput->heightHint());
1141 return ret;
1142 }
1143
fillStatusString()1144 void KviConsoleWindow::fillStatusString()
1145 {
1146 switch(context()->state())
1147 {
1148 case KviIrcContext::Idle:
1149 m_szStatusString = __tr2qs("No connection");
1150 break;
1151 case KviIrcContext::PendingReconnection:
1152 m_szStatusString = __tr2qs("Waiting to reconnect...");
1153 break;
1154 case KviIrcContext::Connecting:
1155 m_szStatusString = __tr2qs("Connection in progress...");
1156 break;
1157 case KviIrcContext::LoggingIn:
1158 m_szStatusString = __tr2qs("Login in progress...");
1159 break;
1160 case KviIrcContext::Connected:
1161 m_szStatusString = connection()->userInfo()->nickName();
1162 if(!connection()->userInfo()->userMode().isEmpty())
1163 {
1164 m_szStatusString += " (+";
1165 m_szStatusString += connection()->userInfo()->userMode();
1166
1167 if(connection()->userInfo()->isAway())
1168 {
1169 m_szStatusString += QChar(' ');
1170 m_szStatusString += __tr2qs("away");
1171 }
1172 m_szStatusString += QChar(')');
1173 }
1174 else
1175 {
1176 if(connection()->userInfo()->isAway())
1177 {
1178 m_szStatusString += " (";
1179 m_szStatusString += __tr2qs("away");
1180 m_szStatusString += QChar(')');
1181 }
1182 }
1183
1184 m_szStatusString += __tr2qs(" on ");
1185 m_szStatusString += connection()->serverInfo()->name();
1186 break;
1187 }
1188 }
1189
fillCaptionBuffers()1190 void KviConsoleWindow::fillCaptionBuffers()
1191 {
1192 fillStatusString();
1193
1194 m_szPlainTextCaption = windowName();
1195 m_szPlainTextCaption += " [";
1196 m_szPlainTextCaption += m_szStatusString;
1197 m_szPlainTextCaption += QChar(']');
1198 }
1199
myIconPtr()1200 QPixmap * KviConsoleWindow::myIconPtr()
1201 {
1202 return g_pIconManager->getSmallIcon(isConnected() ? KviIconManager::Links : KviIconManager::Console);
1203 }
1204
getWindowListTipText(QString & buffer)1205 void KviConsoleWindow::getWindowListTipText(QString & buffer)
1206 {
1207 fillStatusString();
1208
1209 static QString html_channel(__tr2qs("channel"));
1210 static QString html_channels(__tr2qs("channels"));
1211 static QString html_query(__tr2qs("query"));
1212 static QString html_queries(__tr2qs("queries"));
1213 static QString html_bold("<b>");
1214 static QString html_tab(" ");
1215 static QString html_eofbold("</b>");
1216 static QString html_hrbr("<br><hr>");
1217 static QString html_cln(":");
1218 static QString html_space(" ");
1219 static QString html_commaspace(", ");
1220 static QString html_br("<br>");
1221 static QString html_spaceparopen(" (");
1222 static QString html_spaceparclosed(")");
1223 static QString nrs = "<tr><td>";
1224 static QString enr = "</td></tr>";
1225
1226 // no wrapping contents
1227 buffer = "<table style=\"white-space: pre\">" START_TABLE_BOLD_ROW;
1228 buffer += m_szStatusString;
1229 buffer += END_TABLE_BOLD_ROW;
1230 if((context()->state() == KviIrcContext::Connected) && connection())
1231 {
1232 QString num;
1233 unsigned int uD, uH;
1234
1235 uD = connection()->channelList().size();
1236 uH = connection()->queryList().size();
1237
1238 if(uD || uH > 0)
1239 {
1240 buffer += nrs;
1241 buffer += html_tab;
1242
1243 if(uD > 0)
1244 {
1245 num.setNum(uD);
1246
1247 buffer += html_bold;
1248 buffer += num;
1249 buffer += html_eofbold;
1250 buffer += html_space;
1251 buffer += uD > 1 ? html_channels : html_channel;
1252 if(uH > 0)
1253 buffer += html_br;
1254 }
1255
1256 if(uH > 0)
1257 {
1258 num.setNum(uH);
1259
1260 buffer += html_tab;
1261 buffer += html_bold;
1262 buffer += num;
1263 buffer += html_eofbold;
1264 buffer += html_space;
1265 buffer += uH > 1 ? html_queries : html_query;
1266 }
1267
1268 buffer += enr;
1269 }
1270
1271 QString szTmp;
1272 QDateTime date;
1273 date.setTime_t(connection()->statistics()->connectionStartTime());
1274 switch(KVI_OPTION_UINT(KviOption_uintOutputDatetimeFormat))
1275 {
1276 case 0:
1277 // this is the equivalent to an empty date.toString() call, but it's needed
1278 // to ensure qt4 will use the default() locale and not the system() one
1279 szTmp = QLocale().toString(date, "ddd MMM d hh:mm:ss yyyy");
1280 break;
1281 case 1:
1282 szTmp = date.toString(Qt::ISODate);
1283 break;
1284 case 2:
1285 szTmp = date.toString(Qt::SystemLocaleShortDate);
1286 break;
1287 }
1288
1289 buffer += nrs;
1290
1291 buffer += __tr2qs("Connected since");
1292 buffer += html_cln;
1293 buffer += html_space;
1294 buffer += html_bold;
1295 buffer += szTmp;
1296 buffer += html_eofbold;
1297 buffer += html_br;
1298
1299 QString tspan = KviTimeUtils::formatTimeInterval((unsigned int)(kvi_secondsSince(connection()->statistics()->connectionStartTime())),
1300 KviTimeUtils::NoLeadingEmptyIntervals | KviTimeUtils::NoLeadingZeroes);
1301
1302 buffer += __tr2qs("Online for");
1303 buffer += html_cln;
1304 buffer += html_space;
1305 buffer += html_bold;
1306 buffer += tspan;
1307 buffer += html_eofbold;
1308
1309 buffer += enr + R"(<tr><td bgcolor="#E0E0E0"><font color="#000000">)";
1310
1311 tspan = KviTimeUtils::formatTimeInterval((unsigned int)(kvi_secondsSince(connection()->statistics()->lastMessageTime())),
1312 KviTimeUtils::NoLeadingEmptyIntervals | KviTimeUtils::NoLeadingZeroes);
1313
1314 buffer += __tr2qs("Server idle for");
1315 buffer += html_cln;
1316 buffer += html_space;
1317 buffer += html_bold;
1318 buffer += tspan;
1319 buffer += html_eofbold;
1320 buffer += "</font>" + enr;
1321 }
1322
1323 buffer += "</table>";
1324 }
1325