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>&lt;";
761 					szMsg += nick;
762 					szMsg += "&gt;</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("&nbsp;");
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