1 //=============================================================================
2 //
3 //   File : KviWindow.cpp
4 //   Creation date : Tue Jul 6 1999 14:52:11 by Szymon Stefanek
5 //
6 //   This file is part of the KVIrc IRC client distribution
7 //   Copyright (C) 1999-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 /**
26 * \file KviWindow.cpp
27 * \brief Contains the KviWindow class
28 */
29 
30 #define KVI_WINDOW_MIN_WIDTH 100
31 #define KVI_WINDOW_MIN_HEIGHT 100
32 
33 #define _KVI_WINDOW_CPP_
34 #define _KVI_DEBUG_CHECK_RANGE_
35 
36 #include "kvi_debug.h"
37 #include "kvi_out.h"
38 #include "KviApplication.h"
39 #include "KviWindow.h"
40 #include "KviMainWindow.h"
41 #include "KviWindowListBase.h"
42 #include "KviLocale.h"
43 #include "KviIrcView.h"
44 #include "KviMemory.h"
45 #include "KviInput.h"
46 #include "KviFileUtils.h"
47 #include "KviOptions.h"
48 #include "KviConfigurationFile.h"
49 #include "KviIrcContext.h"
50 #include "KviConsoleWindow.h"
51 #include "KviIrcConnectionServerInfo.h"
52 #include "KviControlCodes.h"
53 #include "KviWindowToolWidget.h"
54 #include "KviKvsScript.h"
55 #include "KviTalToolTip.h"
56 #include "KviKvsEventTriggers.h"
57 
58 #include <QPixmap>
59 #include <QCursor>
60 #include <QTimer>
61 #include <QSplitter>
62 #include <QMetaObject>
63 #include <QDateTime>
64 #include <QTextCodec>
65 #include <QPushButton>
66 #include <QDesktopWidget>
67 #include <QVariant>
68 #include <QMessageBox>
69 #include <QEvent>
70 #include <QCloseEvent>
71 #include <QIcon>
72 #include <QActionGroup>
73 #include <QMenu>
74 #include <QInputMethodEvent>
75 #include <QFileInfo>
76 
77 #include <array>
78 #include <tuple>
79 #include <vector>
80 
81 #ifdef COMPILE_ZLIB_SUPPORT
82 #include <zlib.h>
83 #endif
84 
85 #ifdef COMPILE_CRYPT_SUPPORT
86 #include "KviCryptEngine.h"
87 #include "KviCryptController.h"
88 #endif
89 
90 #ifdef COMPILE_KDE_SUPPORT
91 #include <KWindowSystem>
92 #endif
93 
94 KVIRC_API KviWindow * g_pActiveWindow = nullptr;
95 
96 static QMenu * g_pMdiWindowSystemMainPopup = nullptr;
97 static QMenu * g_pMdiWindowSystemTextEncodingPopup = nullptr;
98 static QMenu * g_pMdiWindowSystemTextEncodingPopupStandard = nullptr;
99 static QMenu * g_pMdiWindowSystemTextEncodingPopupSmart = nullptr;
100 static QMenu * g_pMdiWindowSystemTextEncodingPopupSmartUtf8 = nullptr;
101 static QActionGroup * g_pMdiWindowSystemTextEncodingActionGroup = nullptr;
102 static QAction * g_pMdiWindowSystemTextEncodingCurrentAction = nullptr;
103 static QAction * g_pMdiWindowSystemTextEncodingDefaultAction = nullptr;
104 
105 unsigned long int g_uUniqueWindowId = 1;
106 
KviWindow(Type eType,const QString & szName,KviConsoleWindow * lpConsole)107 KviWindow::KviWindow(Type eType, const QString & szName, KviConsoleWindow * lpConsole)
108     : QWidget(nullptr)
109 {
110 	m_uId = g_uUniqueWindowId;
111 	g_uUniqueWindowId++;
112 
113 	m_szName = szName;
114 	setObjectName(szName);
115 
116 	g_pApp->registerWindow(this);
117 
118 	m_eType = eType;
119 	m_pFocusHandler = nullptr;
120 	m_pIrcView = nullptr;
121 	m_pInput = nullptr;
122 	m_pSplitter = nullptr;
123 	m_pButtonBox = nullptr;
124 	m_pConsole = lpConsole;
125 	m_pLastFocusedChild = nullptr;
126 	m_pTextCodec = nullptr; // will be set by loadProperties
127 	m_pTextEncodingButton = nullptr;
128 	m_pHideToolsButton = nullptr;
129 	//m_pEditorsContainer       = nullptr;
130 	m_bIsDocked = false;
131 	m_pWindowListItem = nullptr;
132 	m_bProcessingInputEvent = false;
133 #ifdef COMPILE_CRYPT_SUPPORT
134 	m_pCryptControllerButton = nullptr;
135 	m_pCryptController = nullptr;
136 	m_pCryptSessionInfo = nullptr;
137 #endif
138 
139 	setMinimumSize(KVI_WINDOW_MIN_WIDTH, KVI_WINDOW_MIN_HEIGHT);
140 	//setAutoFillBackground(false);
141 	setFocusPolicy(Qt::StrongFocus);
142 	connect(g_pApp, SIGNAL(reloadImages()), this, SLOT(reloadImages()));
143 
144 	setAttribute(Qt::WA_InputMethodEnabled, true);
145 }
146 
~KviWindow()147 KviWindow::~KviWindow()
148 {
149 	destroyWindowListItem();
150 	g_pApp->unregisterWindow(this);
151 	if(g_pApp->windowCount() == 0)
152 	{
153 		// this is the last window!
154 		delete g_pMdiWindowSystemMainPopup;
155 		delete g_pMdiWindowSystemTextEncodingPopup;
156 		delete g_pMdiWindowSystemTextEncodingPopupStandard;
157 		delete g_pMdiWindowSystemTextEncodingPopupSmart;
158 		delete g_pMdiWindowSystemTextEncodingPopupSmartUtf8;
159 	}
160 #ifdef COMPILE_CRYPT_SUPPORT
161 	if(m_pCryptSessionInfo)
162 		KviCryptController::destroyCryptSessionInfo(&m_pCryptSessionInfo);
163 #endif
164 }
165 
setWindowName(const QString & szName)166 void KviWindow::setWindowName(const QString & szName)
167 {
168 	m_szName = szName;
169 	setObjectName(szName);
170 	emit windowNameChanged();
171 }
172 
toggleButtonContainer()173 void KviWindow::toggleButtonContainer()
174 {
175 	QFrame * pContainer = buttonContainer();
176 	if(pContainer)
177 		pContainer->setHidden(!pContainer->isHidden());
178 }
179 
reloadImages()180 void KviWindow::reloadImages()
181 {
182 	updateIcon();
183 }
184 
hasAttention(AttentionLevel eLevel)185 bool KviWindow::hasAttention(AttentionLevel eLevel)
186 {
187 	if(isDocked())
188 	{
189 		switch(eLevel)
190 		{
191 			case VisibleAndActive:
192 				return (g_pMainWindow->isActiveWindow() && g_pActiveWindow == this);
193 				break;
194 			case MainWindowIsVisible:
195 				return g_pMainWindow->isActiveWindow();
196 				break;
197 			default:
198 				return false;
199 				break;
200 		}
201 	}
202 	else
203 	{
204 		// undocked window
205 		if(isActiveWindow())
206 			return true;
207 	}
208 
209 	return false;
210 }
211 
demandAttention()212 void KviWindow::demandAttention()
213 {
214 	[[maybe_unused]] WId windowId = isDocked() ? g_pMainWindow->winId() : winId();
215 
216 #if defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW)
217 	FLASHWINFO fwi;
218 	fwi.cbSize = sizeof(fwi);
219 	fwi.hwnd = (HWND)windowId;
220 	fwi.dwFlags = FLASHW_TRAY | FLASHW_TIMERNOFG;
221 	fwi.uCount = 20;
222 	fwi.dwTimeout = 500;
223 	FlashWindowEx(&fwi);
224 #elif defined(COMPILE_KDE_SUPPORT)
225 	KWindowSystem::demandAttention(windowId, true);
226 #endif
227 }
228 
focusNextPrevChild(bool bNext)229 bool KviWindow::focusNextPrevChild(bool bNext)
230 {
231 	QWidget * pWidget = focusWidget();
232 	if(pWidget)
233 	{
234 		if(pWidget->focusPolicy() == Qt::StrongFocus)
235 			return false;
236 		if(pWidget->parent())
237 		{
238 			if(pWidget->parent()->metaObject()->indexOfProperty("KviProperty_ChildFocusOwner") == -1)
239 				return false; // Do NOT change the focus widget!
240 		}
241 	}
242 
243 	return QWidget::focusNextPrevChild(bNext);
244 }
245 
forceTextCodec(QTextCodec * pCodec)246 void KviWindow::forceTextCodec(QTextCodec * pCodec)
247 {
248 	if(!pCodec)
249 		return;
250 	m_pTextCodec = pCodec;
251 	QTextCodec * pDefault = defaultTextCodec();
252 	if(pDefault != pCodec)
253 		m_szTextEncoding = pCodec->name();
254 	else
255 		m_szTextEncoding = ""; // this is the default anyway
256 }
257 
setTextEncoding(const QString & szTextEncoding)258 bool KviWindow::setTextEncoding(const QString & szTextEncoding)
259 {
260 	if(!szTextEncoding.isEmpty())
261 	{
262 		m_pTextCodec = KviLocale::instance()->codecForName(szTextEncoding.toLatin1());
263 		if(m_pTextCodec)
264 		{
265 			m_szTextEncoding = szTextEncoding;
266 			return true;
267 		}
268 		// this is an error because we specified an encoding
269 		// and we couldn't find a codec for this
270 	} // else it is empty : this means : guess from locale
271 	  // either empty or not found...
272 	m_pTextCodec = nullptr;
273 	m_szTextEncoding = ""; // empty: we're using the default
274 	return false;
275 }
276 
makeEncoder()277 QTextEncoder * KviWindow::makeEncoder()
278 {
279 	if(!m_pTextCodec)
280 		return defaultTextCodec()->makeEncoder();
281 	return m_pTextCodec->makeEncoder();
282 }
283 
activityMeter(unsigned int *,unsigned int *)284 bool KviWindow::activityMeter(unsigned int *, unsigned int *)
285 {
286 	return false;
287 }
288 
highlightMeter(unsigned int * puValue)289 bool KviWindow::highlightMeter(unsigned int * puValue)
290 {
291 	if(!m_pWindowListItem)
292 	{
293 		*puValue = 0;
294 		return false;
295 	}
296 	*puValue = m_pWindowListItem->highlightLevel();
297 	return true;
298 }
299 
highlightMe(unsigned int uValue)300 bool KviWindow::highlightMe(unsigned int uValue)
301 {
302 	if(uValue > 5)
303 		uValue = 5;
304 	if(m_pWindowListItem)
305 		m_pWindowListItem->highlight(uValue);
306 	return true;
307 }
308 
309 const char * KviWindow::m_typeTable[TypeCount] = {
310 	"console",      // 0
311 	"channel",      // 1
312 	"query",        // 2
313 	"deadchannel",  // 3
314 	"deadquery",    // 4
315 	"editor",       // 5
316 	"help",         // 6
317 	"terminal",     // 7
318 	"socketspy",    // 8
319 	"links",        // 9
320 	"list",         // 10
321 	"dccchat",      // 11
322 	"dcctransfer",  // 12
323 	"dcccanvas",    // 13
324 	"dccvoice",     // 14
325 	"dccvideo",     // 15
326 	"userwindow",   // 16
327 	"tool",         // 17
328 	"iograph",      // 18
329 	"dirbrowser",   // 19 /!\ no longer exists please reuse entry
330 	"scripteditor", // 20
331 	"scriptobject", // 21
332 	"logview",      // 22
333 	"offer",        // 23
334 	"debug",        // 24
335 	// <------ NEW TYPES GO HERE!
336 	"unknown"       // 25
337 };
338 
typeString()339 const char * KviWindow::typeString()
340 {
341 	if(m_eType < TypeCount)
342 		return m_typeTable[m_eType];
343 	return m_typeTable[Unknown];
344 }
345 
createWindowListItem()346 void KviWindow::createWindowListItem()
347 {
348 	if(m_pWindowListItem)
349 		return;
350 	m_pWindowListItem = g_pMainWindow->m_pWindowList->addItem(this);
351 }
352 
destroyWindowListItem()353 void KviWindow::destroyWindowListItem()
354 {
355 	if(m_pWindowListItem)
356 	{
357 		g_pMainWindow->m_pWindowList->removeItem(m_pWindowListItem);
358 		m_pWindowListItem = nullptr;
359 	}
360 }
361 
createToolButton(QWidget * pPar,const char * pcName,KviIconManager::SmallIcon eIcon,const QString & szToolTip,bool bOn)362 QToolButton * KviWindow::createToolButton(QWidget * pPar, const char * pcName, KviIconManager::SmallIcon eIcon, const QString & szToolTip, bool bOn)
363 {
364 	QToolButton * pTool = new QToolButton(pPar);
365 	pTool->setObjectName(pcName);
366 	pTool->setIcon(QIcon(*(g_pIconManager->getSmallIcon(eIcon))));
367 	pTool->setAutoRaise(true);
368 	pTool->setChecked(bOn);
369 	KviTalToolTip::add(pTool, szToolTip);
370 	return pTool;
371 }
372 
373 // This is always defined...
createCryptControllerButton(QWidget *)374 void KviWindow::createCryptControllerButton(QWidget *)
375 {
376 #ifdef COMPILE_CRYPT_SUPPORT
377 	m_pCryptControllerButton = new KviWindowToolPageButton(KviIconManager::UnLockedOff, KviIconManager::UnLocked, __tr2qs("Encryption"), buttonContainer(), false);
378 	m_pCryptControllerButton->setObjectName("crypt_controller_button");
379 	connect(m_pCryptControllerButton, SIGNAL(clicked()), this, SLOT(toggleCryptController()));
380 #endif // COMPILE_CRYPT_SUPPORT
381 }
382 
createTextEncodingButton(QWidget * pPar)383 void KviWindow::createTextEncodingButton(QWidget * pPar)
384 {
385 	delete m_pTextEncodingButton;
386 	m_pTextEncodingButton = createToolButton(pPar, "text_encoding_button", KviIconManager::TextEncoding, __tr2qs("Text encoding"), false);
387 	connect(m_pTextEncodingButton, SIGNAL(clicked()), this, SLOT(textEncodingButtonClicked()));
388 }
389 
textEncodingButtonClicked()390 void KviWindow::textEncodingButtonClicked()
391 {
392 	createSystemTextEncodingPopup();
393 	g_pMdiWindowSystemTextEncodingPopup->popup(m_pTextEncodingButton->mapToGlobal(QPoint(0, m_pTextEncodingButton->height())));
394 	m_pTextEncodingButton->setChecked(false);
395 }
396 
lastLineOfText()397 const QString & KviWindow::lastLineOfText()
398 {
399 	if(m_pIrcView)
400 		return m_pIrcView->lastLineOfText();
401 	return KviQString::Empty;
402 }
403 
lastMessageText()404 const QString & KviWindow::lastMessageText()
405 {
406 	if(m_pIrcView)
407 		return m_pIrcView->lastMessageText();
408 	return KviQString::Empty;
409 }
410 
411 // The following three have to be here even if the crypt support is disabled...moc does not support conditional compilations
toggleCryptController()412 void KviWindow::toggleCryptController()
413 {
414 #ifdef COMPILE_CRYPT_SUPPORT
415 	if(!m_pCryptControllerButton->isChecked())
416 	{
417 		if(m_pCryptController)
418 		{
419 			delete m_pCryptController;
420 			m_pCryptController = nullptr;
421 			if(!m_pCryptControllerButton)
422 				return;
423 			if(m_pCryptControllerButton->isChecked())
424 				m_pCryptControllerButton->setChecked(false);
425 		}
426 	}
427 	else
428 	{
429 		if(m_pSplitter && m_pInput)
430 		{
431 			m_pCryptController = new KviCryptController(m_pSplitter, m_pCryptControllerButton, this, m_pCryptSessionInfo);
432 			connect(m_pCryptController, SIGNAL(done()), this, SLOT(cryptControllerFinished()));
433 			//setFocusHandlerNoClass(m_pInput,m_pCryptController,"QLineEdit"); //link it!
434 			m_pCryptController->show();
435 			if(!m_pCryptControllerButton)
436 				return;
437 			if(!(m_pCryptControllerButton->isChecked()))
438 				m_pCryptControllerButton->setChecked(true);
439 		}
440 	}
441 #endif // COMPILE_CRYPT_SUPPORT
442 }
443 
444 #ifdef COMPILE_CRYPT_SUPPORT
setCryptSessionInfo(KviCryptSessionInfo * pInfo)445 void KviWindow::setCryptSessionInfo(KviCryptSessionInfo * pInfo)
446 {
447 	if(m_pCryptSessionInfo)
448 		KviCryptController::destroyCryptSessionInfo(&m_pCryptSessionInfo);
449 	m_pCryptSessionInfo = pInfo;
450 	if(m_pCryptSessionInfo)
451 	{
452 		connect(m_pCryptSessionInfo->m_pEngine, SIGNAL(destroyed()), this, SLOT(cryptSessionInfoDestroyed()));
453 	}
454 	if(m_pCryptControllerButton)
455 	{
456 		QIcon is;
457 		is.addPixmap(*(g_pIconManager->getSmallIcon(m_pCryptSessionInfo ? KviIconManager::LockedOff : KviIconManager::UnLockedOff)), QIcon::Normal, QIcon::Off);
458 		is.addPixmap(*(g_pIconManager->getSmallIcon(m_pCryptSessionInfo ? KviIconManager::Locked : KviIconManager::UnLocked)), QIcon::Active, QIcon::On);
459 		is.addPixmap(*(g_pIconManager->getSmallIcon(m_pCryptSessionInfo ? KviIconManager::Locked : KviIconManager::UnLocked)), QIcon::Active);
460 		m_pCryptControllerButton->setIcon(is);
461 
462 		if(m_pCryptControllerButton->isChecked())
463 			m_pCryptControllerButton->setChecked(false);
464 	}
465 }
466 #endif // COMPILE_CRYPT_SUPPORT
467 
cryptControllerFinished()468 void KviWindow::cryptControllerFinished()
469 {
470 #ifdef COMPILE_CRYPT_SUPPORT
471 	KviCryptSessionInfo * pInfo = m_pCryptController->getNewSessionInfo();
472 	setCryptSessionInfo(pInfo);
473 	delete m_pCryptController;
474 	m_pCryptController = nullptr;
475 #endif
476 }
477 
cryptSessionInfoDestroyed()478 void KviWindow::cryptSessionInfoDestroyed()
479 {
480 #ifdef COMPILE_CRYPT_SUPPORT
481 	output(KVI_OUT_SYSTEMERROR, __tr2qs("Oops! I've accidentally lost the encryption engine."));
482 	m_pCryptSessionInfo->m_pEngine = nullptr;
483 	delete m_pCryptSessionInfo;
484 	m_pCryptSessionInfo = nullptr;
485 #endif
486 }
487 
setProgress(int iProgress)488 void KviWindow::setProgress(int iProgress)
489 {
490 	m_pWindowListItem->setProgress(iProgress);
491 }
492 
listWindowTypes()493 void KviWindow::listWindowTypes()
494 {
495 	outputNoFmt(KVI_OUT_SYSTEMMESSAGE, __tr2qs("List of window types available in this release of KVIrc:"));
496 	for(auto & i : m_typeTable)
497 		outputNoFmt(KVI_OUT_SYSTEMMESSAGE, i);
498 }
499 
getConfigGroupName(QString & szBuffer)500 void KviWindow::getConfigGroupName(QString & szBuffer)
501 {
502 	szBuffer = typeString();
503 }
504 
getDefaultLogFileName(QString & szBuffer)505 void KviWindow::getDefaultLogFileName(QString & szBuffer)
506 {
507 	return getDefaultLogFileName(szBuffer, QDate::currentDate(), KVI_OPTION_BOOL(KviOption_boolGzipLogs),
508 		KVI_OPTION_UINT(KviOption_uintOutputDatetimeFormat));
509 }
510 
getDefaultLogFileName(QString & szBuffer,QDate date,bool bGzip,unsigned int uDatetimeFormat)511 void KviWindow::getDefaultLogFileName(QString & szBuffer, QDate date, bool bGzip, unsigned int uDatetimeFormat)
512 {
513 	QString szLog;
514 
515 	// dynamic log path
516 	QString szDynamicPath = KVI_OPTION_STRING(KviOption_stringLogsDynamicPath).trimmed();
517 	if(!szDynamicPath.isEmpty())
518 	{
519 		KviQString::escapeKvs(&szDynamicPath, KviQString::PermitVariables | KviQString::PermitFunctions);
520 
521 		KviKvsVariant vRet;
522 		if(KviKvsScript::evaluate(szDynamicPath, this, nullptr, &vRet))
523 			vRet.asString(szDynamicPath);
524 	}
525 
526 	g_pApp->getLocalKvircDirectory(szLog, KviApplication::Log, szDynamicPath);
527 	KviQString::ensureLastCharIs(szLog, KVI_PATH_SEPARATOR_CHAR);
528 
529 	//ensure the directory exists
530 	KviFileUtils::makeDir(szLog);
531 
532 	QString szDate;
533 
534 	switch(uDatetimeFormat)
535 	{
536 		case 1:
537 			szDate = date.toString(Qt::ISODate);
538 			break;
539 		case 2:
540 			szDate = date.toString(Qt::SystemLocaleShortDate);
541 			break;
542 		case 0:
543 		default:
544 			szDate = date.toString("yyyy.MM.dd");
545 			break;
546 	}
547 	szDate.replace('_', '-'); // this would confuse the log viewer
548 	KviFileUtils::cleanFileName(szDate);
549 
550 	QString szBase;
551 	getBaseLogFileName(szBase);
552 	KviFileUtils::encodeFileName(szBase);
553 	szBase = szBase.toLower();
554 	szBase.replace("%%2e", "%2e");
555 
556 	QString szTmp;
557 	if(bGzip)
558 		szTmp = "%1_%2_%3.log.gz";
559 	else
560 		szTmp = "%1_%2_%3.log";
561 
562 	szLog.append(QString(szTmp).arg(typeString(), szBase, szDate));
563 
564 	szBuffer = szLog;
565 }
566 
getBaseLogFileName(QString & szBuffer)567 void KviWindow::getBaseLogFileName(QString & szBuffer)
568 {
569 	szBuffer = m_szName;
570 }
571 
saveProperties(KviConfigurationFile * pCfg)572 void KviWindow::saveProperties(KviConfigurationFile * pCfg)
573 {
574 	// store only the non-default text encoding.
575 	QString szCodec = m_szTextEncoding;
576 	QTextCodec * pCodec = defaultTextCodec();
577 	if(pCodec && m_pTextCodec)
578 	{
579 		if(KviQString::equalCI(szCodec, pCodec->name().data()))
580 			szCodec = KviQString::Empty; // store "default"
581 	}
582 
583 	if(!szCodec.isEmpty())
584 		pCfg->writeEntry("TextEncoding", szCodec);
585 	if(m_pInput)
586 	{
587 		pCfg->writeEntry("inputToolButtonsHidden", m_pInput->isButtonsHidden());
588 		pCfg->writeEntry("commandLineIsUserFriendly", m_pInput->isUserFriendly());
589 	}
590 
591 	/*
592 	if(m_pIrcView && m_eType == KviWindow::Channel)
593 	if(m_pIrcView->isLogging())
594 		pCfg->writeEntry("LoggingEnabled",m_pIrcView->isLogging());
595 	*/
596 }
597 
loadProperties(KviConfigurationFile * pCfg)598 void KviWindow::loadProperties(KviConfigurationFile * pCfg)
599 {
600 	QString szCodec = pCfg->readEntry("TextEncoding", KviQString::Empty);
601 	if(szCodec.isEmpty())
602 	{
603 		// try to load kvirc 4.0's entry
604 		QString szKey = "TextEncoding_";
605 		szKey += m_szName;
606 		szCodec = pCfg->readEntry(szKey, KviQString::Empty);
607 	}
608 
609 	setTextEncoding(szCodec.toUtf8().data());
610 	if(m_pInput)
611 	{
612 		m_pInput->setButtonsHidden(pCfg->readBoolEntry("inputToolButtonsHidden", KVI_OPTION_BOOL(KviOption_boolHideInputToolButtons)));
613 		m_pInput->setUserFriendly(pCfg->readBoolEntry("commandLineIsUserFriendly", KVI_OPTION_BOOL(KviOption_boolCommandlineInUserFriendlyModeByDefault)));
614 	}
615 	/*
616 	if(m_pIrcView && m_eType == KviWindow::Channel)
617 	{
618 		bool bEnableLogs = pCfg->readBoolEntry("LoggingEnabled",0);
619 		if(!m_pIrcView->isLogging() && bEnableLogs)
620 		{
621 			QString szTmp;
622 			getBaseLogFileName(szTmp);
623 			m_pIrcView->startLogging();
624 		}
625 	}
626 	*/
627 }
628 
myIconPtr()629 QPixmap * KviWindow::myIconPtr()
630 {
631 	return g_pIconManager->getSmallIcon(KviIconManager::DefaultIcon);
632 }
633 
fillCaptionBuffers()634 void KviWindow::fillCaptionBuffers()
635 {
636 	QString szCaption = m_szPlainTextCaption;
637 	if(szCaption.isEmpty())
638 		szCaption = m_szName;
639 
640 	fillSingleColorCaptionBuffers(szCaption);
641 }
642 
updateCaption()643 void KviWindow::updateCaption()
644 {
645 	fillCaptionBuffers();
646 	bool bHaltOutput = false;
647 	bHaltOutput = KVS_TRIGGER_EVENT_2_HALTED(KviEvent_OnWindowTitleRequest, this, id(), m_szPlainTextCaption);
648 
649 	if(!bHaltOutput)
650 		setWindowTitle(m_szPlainTextCaption);
651 
652 	if(m_pWindowListItem)
653 		m_pWindowListItem->captionChanged();
654 }
655 
setWindowTitle(QString & szTitle)656 void KviWindow::setWindowTitle(QString & szTitle)
657 {
658 	QWidget::setWindowTitle(szTitle);
659 	g_pMainWindow->updateWindowTitle(this);
660 }
661 
createSystemTextEncodingPopup()662 void KviWindow::createSystemTextEncodingPopup()
663 {
664 	if(!g_pMdiWindowSystemTextEncodingPopup)
665 	{
666 		// first time called, create the menu
667 		g_pMdiWindowSystemTextEncodingPopup = new QMenu();
668 		g_pMdiWindowSystemTextEncodingPopupStandard = new QMenu();
669 		g_pMdiWindowSystemTextEncodingPopupSmart = new QMenu();
670 		g_pMdiWindowSystemTextEncodingPopupSmartUtf8 = new QMenu();
671 		g_pMdiWindowSystemTextEncodingActionGroup = new QActionGroup(g_pMdiWindowSystemTextEncodingPopup);
672 
673 		//default action
674 		QTextCodec * pCodec = defaultTextCodec();
675 		QString szTmp = __tr2qs("Use Default Encoding");
676 		if(pCodec)
677 		{
678 			szTmp += " (";
679 			szTmp += pCodec->name();
680 			szTmp += ")";
681 		}
682 
683 		g_pMdiWindowSystemTextEncodingDefaultAction = g_pMdiWindowSystemTextEncodingPopup->addAction(szTmp);
684 		g_pMdiWindowSystemTextEncodingActionGroup->addAction(g_pMdiWindowSystemTextEncodingDefaultAction);
685 		g_pMdiWindowSystemTextEncodingDefaultAction->setData(-1);
686 		if(m_szTextEncoding.isEmpty())
687 		{
688 			g_pMdiWindowSystemTextEncodingDefaultAction->setCheckable(true);
689 			g_pMdiWindowSystemTextEncodingDefaultAction->setChecked(true);
690 		}
691 
692 		//current action
693 		g_pMdiWindowSystemTextEncodingCurrentAction = g_pMdiWindowSystemTextEncodingPopup->addAction(__tr2qs("Current: "));
694 		g_pMdiWindowSystemTextEncodingActionGroup->addAction(g_pMdiWindowSystemTextEncodingCurrentAction);
695 		g_pMdiWindowSystemTextEncodingCurrentAction->setVisible(false);
696 
697 		// other first level menus
698 		g_pMdiWindowSystemTextEncodingPopup->addSeparator();
699 
700 		QAction * pAction = g_pMdiWindowSystemTextEncodingPopup->addAction(__tr2qs("Standard"));
701 		pAction->setMenu(g_pMdiWindowSystemTextEncodingPopupStandard);
702 		pAction = g_pMdiWindowSystemTextEncodingPopup->addAction(__tr2qs("Smart (Send Local)"));
703 		pAction->setMenu(g_pMdiWindowSystemTextEncodingPopupSmart);
704 		pAction = g_pMdiWindowSystemTextEncodingPopup->addAction(__tr2qs("Smart (Send UTF-8)"));
705 		pAction->setMenu(g_pMdiWindowSystemTextEncodingPopupSmartUtf8);
706 
707 		// second level menus (encoding groups)
708 		std::array<QMenu *, KVI_NUM_ENCODING_GROUPS> pPopupStandard = {};
709 		std::array<QMenu *, KVI_NUM_ENCODING_GROUPS> pPopupSmart = {};
710 		std::array<QMenu *, KVI_NUM_ENCODING_GROUPS> pPopupSmartUtf8 = {};
711 
712 		uint u = 0;
713 		const char * pcEncodingGroup = KviLocale::instance()->encodingGroup(u);
714 
715 		while(pcEncodingGroup)
716 		{
717 			pPopupStandard[u] = g_pMdiWindowSystemTextEncodingPopupStandard->addMenu(pcEncodingGroup);
718 			if(u) //only standard popup contains unicode menu
719 			{
720 				pPopupSmart[u] = g_pMdiWindowSystemTextEncodingPopupSmart->addMenu(pcEncodingGroup);
721 				pPopupSmartUtf8[u] = g_pMdiWindowSystemTextEncodingPopupSmartUtf8->addMenu(pcEncodingGroup);
722 			}
723 
724 			pcEncodingGroup = KviLocale::instance()->encodingGroup(++u);
725 		}
726 
727 		// third level menus (encodings)
728 		uint i = 0;
729 		KviLocale::EncodingDescription * pDesc = KviLocale::instance()->encodingDescription(i);
730 		while(pDesc->pcName)
731 		{
732 			szTmp = QString("%1 (%2)").arg(pDesc->pcName, pDesc->pcDescription);
733 			if(KviQString::equalCI(m_szTextEncoding, pDesc->pcName))
734 			{
735 				g_pMdiWindowSystemTextEncodingCurrentAction->setText(__tr2qs("Current: ") + szTmp);
736 				g_pMdiWindowSystemTextEncodingCurrentAction->setCheckable(true);
737 				g_pMdiWindowSystemTextEncodingCurrentAction->setChecked(true);
738 				g_pMdiWindowSystemTextEncodingCurrentAction->setVisible(true);
739 				g_pMdiWindowSystemTextEncodingCurrentAction->setData(i);
740 			}
741 
742 			QMenu * pMenu = pDesc->bSmart ? (pDesc->bSendUtf8 ? pPopupSmartUtf8[pDesc->uGroup] : pPopupSmart[pDesc->uGroup]) : pPopupStandard[pDesc->uGroup];
743 
744 			QAction * pAction = pMenu->addAction(szTmp);
745 			pAction->setData(i);
746 			g_pMdiWindowSystemTextEncodingActionGroup->addAction(pAction);
747 
748 			pDesc = KviLocale::instance()->encodingDescription(++i);
749 		}
750 	}
751 	else
752 	{
753 		//default action: refresh the name
754 		QTextCodec * pCodec = defaultTextCodec();
755 		QString szTmp = __tr2qs("Use Default Encoding");
756 		if(pCodec)
757 		{
758 			szTmp += " (";
759 			szTmp += pCodec->name();
760 			szTmp += ")";
761 		}
762 
763 		disconnect(g_pMdiWindowSystemTextEncodingActionGroup, SIGNAL(triggered(QAction *)), nullptr, nullptr);
764 		connect(g_pMdiWindowSystemTextEncodingActionGroup, SIGNAL(triggered(QAction *)), this, SLOT(systemTextEncodingPopupActivated(QAction *)));
765 		g_pMdiWindowSystemTextEncodingDefaultAction->setText(szTmp);
766 
767 		//menu already exists, choose the right item
768 		if(m_szTextEncoding.isEmpty())
769 		{
770 			//default action; hide the current action
771 			g_pMdiWindowSystemTextEncodingCurrentAction->setVisible(false);
772 			g_pMdiWindowSystemTextEncodingDefaultAction->setCheckable(true);
773 			g_pMdiWindowSystemTextEncodingDefaultAction->setChecked(true);
774 		}
775 		else
776 		{
777 			int i = 0;
778 			KviLocale::EncodingDescription * pDesc = KviLocale::instance()->encodingDescription(i);
779 			while(pDesc->pcName)
780 			{
781 				if(KviQString::equalCI(m_szTextEncoding, pDesc->pcName))
782 				{
783 					szTmp = QString("%1 (%2)").arg(pDesc->pcName, pDesc->pcDescription);
784 					g_pMdiWindowSystemTextEncodingCurrentAction->setText(__tr2qs("Current: ") + szTmp);
785 					g_pMdiWindowSystemTextEncodingCurrentAction->setCheckable(true);
786 					g_pMdiWindowSystemTextEncodingCurrentAction->setChecked(true);
787 					g_pMdiWindowSystemTextEncodingCurrentAction->setVisible(true);
788 					g_pMdiWindowSystemTextEncodingCurrentAction->setData(i);
789 					break;
790 				}
791 
792 				pDesc = KviLocale::instance()->encodingDescription(++i);
793 			}
794 		}
795 	}
796 
797 	disconnect(g_pMdiWindowSystemTextEncodingActionGroup, SIGNAL(triggered(QAction *)), nullptr, nullptr);
798 	connect(g_pMdiWindowSystemTextEncodingActionGroup, SIGNAL(triggered(QAction *)), this, SLOT(systemTextEncodingPopupActivated(QAction *)));
799 }
800 
systemTextEncodingPopupActivated(QAction * pAction)801 void KviWindow::systemTextEncodingPopupActivated(QAction * pAction)
802 {
803 	if(!pAction || pAction == g_pMdiWindowSystemTextEncodingCurrentAction)
804 		return;
805 	if(pAction == g_pMdiWindowSystemTextEncodingDefaultAction)
806 	{
807 		setTextEncoding("");
808 	}
809 	else
810 	{
811 		QString szTmp = pAction->text();
812 		KviQString::cutFromFirst(szTmp, " (");
813 		setTextEncoding(szTmp);
814 	}
815 }
816 
savePropertiesAsDefault()817 void KviWindow::savePropertiesAsDefault()
818 {
819 	QString szGroup;
820 	getConfigGroupName(szGroup);
821 
822 	// save also the settings for THIS specialized window
823 	if(!KviQString::equalCI(szGroup, typeString()))
824 		g_pMainWindow->saveWindowProperties(this, szGroup);
825 
826 	g_pMainWindow->saveWindowProperties(this, typeString());
827 }
828 
contextPopup()829 void KviWindow::contextPopup()
830 {
831 	KVS_TRIGGER_EVENT_0(KviEvent_OnWindowPopupRequest, this);
832 }
833 
undock()834 void KviWindow::undock()
835 {
836 	g_pMainWindow->undockWindow(this);
837 }
838 
dock()839 void KviWindow::dock()
840 {
841 	g_pMainWindow->dockWindow(this);
842 	g_pMainWindow->setActiveWindow(this);
843 }
844 
delayedAutoRaise()845 void KviWindow::delayedAutoRaise()
846 {
847 	QTimer::singleShot(0, this, SLOT(autoRaise()));
848 }
849 
autoRaise()850 void KviWindow::autoRaise()
851 {
852 	if(!isDocked())
853 		raise();
854 
855 	g_pMainWindow->setActiveWindow(this);
856 
857 	if(m_pFocusHandler)
858 		m_pFocusHandler->setFocus();
859 	else
860 		setFocus();
861 }
862 
delayedClose()863 void KviWindow::delayedClose()
864 {
865 	QTimer::singleShot(0, this, SLOT(close()));
866 }
867 
closeEvent(QCloseEvent * pEvent)868 void KviWindow::closeEvent(QCloseEvent * pEvent)
869 {
870 	pEvent->ignore();
871 	if(g_pMainWindow)
872 	{
873 		g_pMainWindow->childWindowCloseRequest(this);
874 	}
875 	else
876 	{
877 		/* In kvi_app destructor, g_pMainWindow gets deleted before modules gets unloaded.
878 		 * So if a module tries to destroy a kviwindow while it gets unloaded, we end up here,
879 		 * having to delete this window without the help of g_pMainWindow.
880 		 * So we have 3 choices:
881 		 * 1) delete this => will just print a qt warning "don't delete things on their event handler"
882 		 * 2) deleteLater() => will tipically create an infinite recursion in the module unload routine
883 		 * 3) do nothing => same as #2
884 		 */
885 		delete this;
886 	}
887 }
888 
updateIcon()889 void KviWindow::updateIcon()
890 {
891 	setWindowIcon(QIcon(*myIconPtr()));
892 }
893 
youAreDocked()894 void KviWindow::youAreDocked()
895 {
896 	m_bIsDocked = true;
897 	updateCaption();
898 }
899 
youAreUndocked()900 void KviWindow::youAreUndocked()
901 {
902 	m_bIsDocked = false;
903 	setWindowIcon(QIcon(*myIconPtr()));
904 	updateCaption();
905 
906 	QPoint pPos = g_pMainWindow->pos();
907 	move(pPos.x() + 50, pPos.y() + 50);
908 }
909 
inputMethodEvent(QInputMethodEvent * e)910 void KviWindow::inputMethodEvent(QInputMethodEvent * e)
911 {
912 	// 2016.01.04: Qt 5 seems to be unable to properly follow our focus changes.
913 	// It keeps this class as input method target after we have set focus to our
914 	// focus handler in focusInEvent(). Relay it to our focus handler.
915 
916 	e->accept(); // we always accept the input method events
917 
918 	// recursion detected
919 	if(m_bProcessingInputEvent)
920 		return;
921 
922 	m_bProcessingInputEvent = true;
923 
924 	if(
925 	    m_pLastFocusedChild && m_pLastFocusedChild->hasFocus() && m_pLastFocusedChild->isVisible())
926 	{
927 		KviApplication::sendEvent(m_pLastFocusedChild, e);
928 		m_bProcessingInputEvent = false;
929 		return;
930 	}
931 
932 	if(
933 	    m_pFocusHandler && (m_pFocusHandler != m_pLastFocusedChild) && m_pFocusHandler->hasFocus() && m_pFocusHandler->isVisible())
934 	{
935 		KviApplication::sendEvent(m_pFocusHandler, e);
936 		m_bProcessingInputEvent = false;
937 		return;
938 	}
939 
940 	m_bProcessingInputEvent = false;
941 }
942 
943 #ifdef FocusIn
944 // Hack for X.h
945 #undef FocusIn
946 #endif
947 
focusInEvent(QFocusEvent *)948 void KviWindow::focusInEvent(QFocusEvent *)
949 {
950 	if(m_pLastFocusedChild)
951 	{
952 		if(m_pLastFocusedChild->hasFocus() && m_pLastFocusedChild->isVisible())
953 		{
954 			// focus is still in this window.
955 			// just make sure that we're the active one.
956 			if(g_pActiveWindow != this)
957 				g_pMainWindow->windowActivated(this);
958 			return;
959 		}
960 	}
961 
962 	// focus doesn't seem to be in this window
963 	if(!m_pFocusHandler)
964 	{
965 		// figure out a child to give focus to.
966 		// we probably have no KviInput since it would have been grabbed anyway
967 
968 		if(m_pIrcView)
969 			m_pFocusHandler = m_pIrcView;
970 		else
971 		{
972 			for(auto & it : children())
973 			{
974 				QObject * pObj = it;
975 				if(pObj->isWidgetType())
976 				{
977 					m_pFocusHandler = (QWidget *)pObj;
978 					break;
979 				}
980 			}
981 		}
982 
983 		if(m_pFocusHandler)
984 			m_pFocusHandler->setFocus();
985 		else
986 		{
987 			// else too bad :/
988 			qDebug("No widget able to handle focus for window %s", objectName().toUtf8().data());
989 			updateCaption(); // do it anyway
990 			return;
991 		}
992 	}
993 	else
994 	{
995 		m_pFocusHandler->setFocus();
996 	}
997 
998 	// Setting the focus to the focus handler usually
999 	// triggers our filter for the children's focusInEvent
1000 	// which in turn should invoke our filter and make this window the active one.
1001 	// So we should be already the active window at this point.
1002 	// If we're not, then fix this.
1003 	if(g_pActiveWindow != this)
1004 		g_pMainWindow->windowActivated(this);
1005 
1006 	updateCaption();
1007 }
1008 
eventFilter(QObject * pObject,QEvent * pEvent)1009 bool KviWindow::eventFilter(QObject * pObject, QEvent * pEvent)
1010 {
1011 	switch(pEvent->type())
1012 	{
1013 		case QEvent::FocusIn:
1014 			// a child got focused
1015 			m_pLastFocusedChild = (QWidget *)pObject;
1016 			if(g_pActiveWindow != this)
1017 				g_pMainWindow->windowActivated(this);
1018 			break;
1019 		case QEvent::Enter:
1020 			// this is a handler moved here from KviMdiChild::eventFilter
1021 			if(QApplication::overrideCursor())
1022 				QApplication::restoreOverrideCursor();
1023 			break;
1024 		case QEvent::ChildAdded:
1025 			if(((QChildEvent *)pEvent)->child()->isWidgetType())
1026 				childInserted((QWidget *)((QChildEvent *)pEvent)->child());
1027 			break;
1028 		case QEvent::ChildRemoved:
1029 			if(((QChildEvent *)pEvent)->child()->isWidgetType())
1030 				childRemoved((QWidget *)((QChildEvent *)pEvent)->child());
1031 			break;
1032 		default: /* make gcc happy */
1033 			break;
1034 	}
1035 	return false;
1036 }
1037 
childInserted(QWidget * pObject)1038 void KviWindow::childInserted(QWidget * pObject)
1039 {
1040 	pObject->removeEventFilter(this);  // ensure that we don't filter twice
1041 	pObject->installEventFilter(this); // we filter its events
1042 
1043 	connect(pObject, SIGNAL(destroyed()), this, SLOT(childDestroyed()));
1044 
1045 	// attempt to grab a decent focus handler
1046 
1047 	if(pObject->inherits("KviInput"))
1048 	{
1049 		// KviInput is our preferential focus handler
1050 		m_pFocusHandler = pObject;
1051 	}
1052 	else
1053 	{
1054 		// not a KviInput
1055 		if(!m_pFocusHandler && (pObject->focusPolicy() == Qt::StrongFocus))
1056 		{
1057 			// still without a focus handler: take this widget (possibly only temporarily)
1058 			m_pFocusHandler = pObject;
1059 		}
1060 	}
1061 
1062 	for(auto & it : pObject->children())
1063 	{
1064 		QObject * pObj = it;
1065 		if(pObj->isWidgetType())
1066 			childInserted((QWidget *)pObj);
1067 	}
1068 }
1069 
childDestroyed()1070 void KviWindow::childDestroyed()
1071 {
1072 	QWidget * pWidget = (QWidget *)sender();
1073 	childRemoved(pWidget);
1074 }
1075 
childRemoved(QWidget * pObject)1076 void KviWindow::childRemoved(QWidget * pObject)
1077 {
1078 	pObject->removeEventFilter(this);
1079 
1080 	if(pObject == m_pFocusHandler)
1081 		m_pFocusHandler = nullptr;
1082 	if(pObject == m_pLastFocusedChild)
1083 		m_pLastFocusedChild = nullptr;
1084 
1085 	for(auto & it : pObject->children())
1086 	{
1087 		QObject * pObj = it;
1088 		if(pObj->isWidgetType())
1089 			childRemoved((QWidget *)pObj);
1090 	}
1091 }
1092 
childEvent(QChildEvent * pEvent)1093 void KviWindow::childEvent(QChildEvent * pEvent)
1094 {
1095 	if(pEvent->child()->isWidgetType())
1096 	{
1097 		if(pEvent->removed())
1098 			childRemoved((QWidget *)(pEvent->child()));
1099 		else
1100 			childInserted((QWidget *)(pEvent->child()));
1101 	}
1102 	QWidget::childEvent(pEvent);
1103 }
1104 
childrenTreeChanged(QWidget *)1105 void KviWindow::childrenTreeChanged(QWidget *)
1106 {
1107 	//	if(widgetAdded && m_pFocusHandler)setFocusHandler(m_pFocusHandler,widgetAdded);
1108 	// FIXME: This might be useless
1109 	QResizeEvent * pEvent = new QResizeEvent(size(), size());
1110 	resizeEvent(pEvent);
1111 	delete pEvent;
1112 }
1113 
updateBackgrounds(QObject * pObject)1114 void KviWindow::updateBackgrounds(QObject * pObject)
1115 {
1116 	if(!pObject)
1117 		pObject = this;
1118 	QList<QObject *> list = pObject->children();
1119 	if(list.count())
1120 	{
1121 		for(auto & it : list)
1122 		{
1123 			QObject * pChild = it;
1124 			if(pChild->metaObject()->indexOfProperty("TransparencyCapable") != -1)
1125 				((QWidget *)pChild)->update();
1126 			updateBackgrounds(pChild);
1127 		}
1128 	}
1129 }
1130 
moveEvent(QMoveEvent * pEvent)1131 void KviWindow::moveEvent(QMoveEvent * pEvent)
1132 {
1133 #ifdef COMPILE_PSEUDO_TRANSPARENCY
1134 	updateBackgrounds();
1135 #endif
1136 	QWidget::moveEvent(pEvent);
1137 }
1138 
applyOptions()1139 void KviWindow::applyOptions()
1140 {
1141 	updateCaption();
1142 
1143 	if(m_pIrcView)
1144 		m_pIrcView->applyOptions();
1145 
1146 	if(m_pInput)
1147 		m_pInput->applyOptions();
1148 
1149 	// trick: relayout
1150 	resize(width() - 1, height() - 1);
1151 	resize(width() + 1, height() + 1);
1152 }
1153 
outputProxy()1154 KviWindow * KviWindow::outputProxy()
1155 {
1156 	return nullptr;
1157 }
1158 
lostUserFocus()1159 void KviWindow::lostUserFocus()
1160 {
1161 	if(!m_pIrcView)
1162 		return;
1163 	if(m_pIrcView->hasLineMark())
1164 		m_pIrcView->clearLineMark(true);
1165 }
1166 
internalOutput(KviIrcView * pView,int iMsgType,const kvi_wchar_t * pwText,int iFlags,const QDateTime & datetime)1167 void KviWindow::internalOutput(KviIrcView * pView, int iMsgType, const kvi_wchar_t * pwText, int iFlags, const QDateTime & datetime)
1168 {
1169 	// all roads lead to Rome :)
1170 
1171 	if(pView)
1172 	{
1173 		if(!hasAttention())
1174 		{
1175 			iFlags |= KviIrcView::TriggersNotification;
1176 			if(!pView->hasLineMark())
1177 			{
1178 				iFlags |= KviIrcView::SetLineMark;
1179 			}
1180 		}
1181 		pView->appendText(iMsgType, pwText, iFlags, datetime);
1182 	}
1183 	else
1184 	{
1185 		// Redirect to the output proxy
1186 		KviWindow * pWnd = outputProxy();
1187 		if(pWnd)
1188 			pWnd->outputNoFmt(iMsgType, pwText, iFlags, datetime);
1189 	}
1190 
1191 	if(!m_pWindowListItem)
1192 		return;
1193 
1194 	// if this option is checked we don't highlight other than channel msg
1195 	if(KVI_OPTION_BOOL(KviOption_boolHighlightOnlyNormalMsg))
1196 	{
1197 		if((iMsgType != KVI_OUT_CHANPRIVMSG) && (iMsgType != KVI_OUT_CHANPRIVMSGCRYPTED))
1198 		{
1199 			if(!(
1200 			       (
1201 			           KVI_OPTION_BOOL(KviOption_boolHighlightOnlyNormalMsgQueryToo) && ((iMsgType == KVI_OUT_QUERYPRIVMSG) || (iMsgType == KVI_OUT_QUERYTRACE) || (iMsgType == KVI_OUT_QUERYPRIVMSGCRYPTED) || (iMsgType == KVI_OUT_QUERYNOTICE) || (iMsgType == KVI_OUT_QUERYNOTICECRYPTED)))
1202 			       || (KVI_OPTION_BOOL(KviOption_boolHighlightOnlyNormalMsgHighlightInChanToo) && (iMsgType == KVI_OUT_HIGHLIGHT))))
1203 				return;
1204 		}
1205 	}
1206 
1207 	if(KVI_OPTION_BOOL(KviOption_boolHighlightOnlyAtCostumHighlightLevel) && (KVI_OPTION_MSGTYPE(iMsgType).level() < ((int)(KVI_OPTION_UINT(KviOption_uintMinHighlightLevel)))))
1208 	{
1209 		return;
1210 	}
1211 
1212 	m_pWindowListItem->highlight(KVI_OPTION_MSGTYPE(iMsgType).level());
1213 }
1214 
output(int iMsgType,const char * pcFormat,...)1215 void KviWindow::output(int iMsgType, const char * pcFormat, ...)
1216 {
1217 	QString szFmt(pcFormat);
1218 	kvi_va_list l;
1219 	kvi_va_start(l, pcFormat);
1220 	QString szBuf;
1221 	KviQString::vsprintf(szBuf, szFmt, l);
1222 	kvi_va_end(l);
1223 	preprocessMessage(szBuf);
1224 	const QChar * pC = szBuf.constData();
1225 	if(!pC)
1226 		return;
1227 	internalOutput(m_pIrcView, iMsgType, (kvi_wchar_t *)pC);
1228 }
1229 
output(int iMsgType,QString szFmt,...)1230 void KviWindow::output(int iMsgType, QString szFmt, ...)
1231 {
1232 	kvi_va_list l;
1233 	kvi_va_start(l, szFmt);
1234 	QString szBuf;
1235 	KviQString::vsprintf(szBuf, szFmt, l);
1236 	kvi_va_end(l);
1237 	preprocessMessage(szBuf);
1238 	const QChar * pC = szBuf.constData();
1239 	if(!pC)
1240 		return;
1241 	internalOutput(m_pIrcView, iMsgType, (kvi_wchar_t *)pC);
1242 }
1243 
output(int iMsgType,const kvi_wchar_t * pwFormat,...)1244 void KviWindow::output(int iMsgType, const kvi_wchar_t * pwFormat, ...)
1245 {
1246 	QString szFmt = QString::fromUtf8(KviCString(pwFormat).ptr());
1247 	kvi_va_list l;
1248 	kvi_va_start(l, pwFormat);
1249 	QString szBuf;
1250 	KviQString::vsprintf(szBuf, szFmt, l);
1251 	kvi_va_end(l);
1252 	preprocessMessage(szBuf);
1253 	const QChar * pC = szBuf.constData();
1254 	if(!pC)
1255 		return;
1256 	internalOutput(m_pIrcView, iMsgType, (kvi_wchar_t *)pC);
1257 }
1258 
output(int iMsgType,const QDateTime & datetime,const char * pcFormat,...)1259 void KviWindow::output(int iMsgType, const QDateTime & datetime, const char * pcFormat, ...)
1260 {
1261 	QString szFmt(pcFormat);
1262 	kvi_va_list l;
1263 	kvi_va_start(l, pcFormat);
1264 	QString szBuf;
1265 	KviQString::vsprintf(szBuf, szFmt, l);
1266 	kvi_va_end(l);
1267 	preprocessMessage(szBuf);
1268 	const QChar * pC = szBuf.constData();
1269 	if(!pC)
1270 		return;
1271 	internalOutput(m_pIrcView, iMsgType, (kvi_wchar_t *)pC, 0, datetime);
1272 }
1273 
output(int iMsgType,const QDateTime & datetime,QString szFmt,...)1274 void KviWindow::output(int iMsgType, const QDateTime & datetime, QString szFmt, ...)
1275 {
1276 	kvi_va_list l;
1277 	kvi_va_start(l, szFmt);
1278 	QString szBuf;
1279 	KviQString::vsprintf(szBuf, szFmt, l);
1280 	kvi_va_end(l);
1281 	preprocessMessage(szBuf);
1282 	const QChar * pC = szBuf.constData();
1283 	if(!pC)
1284 		return;
1285 	internalOutput(m_pIrcView, iMsgType, (kvi_wchar_t *)pC, 0, datetime);
1286 }
1287 
output(int iMsgType,const QDateTime & datetime,const kvi_wchar_t * pwFormat,...)1288 void KviWindow::output(int iMsgType, const QDateTime & datetime, const kvi_wchar_t * pwFormat, ...)
1289 {
1290 	QString szFmt = QString::fromUtf8(KviCString(pwFormat).ptr());
1291 	kvi_va_list l;
1292 	kvi_va_start(l, pwFormat);
1293 	QString szBuf;
1294 	KviQString::vsprintf(szBuf, szFmt, l);
1295 	kvi_va_end(l);
1296 	preprocessMessage(szBuf);
1297 	const QChar * pC = szBuf.constData();
1298 	if(!pC)
1299 		return;
1300 	internalOutput(m_pIrcView, iMsgType, (kvi_wchar_t *)pC, 0, datetime);
1301 }
outputNoFmt(int iMsgType,const char * pcText,int iFlags,const QDateTime & datetime)1302 void KviWindow::outputNoFmt(int iMsgType, const char * pcText, int iFlags, const QDateTime & datetime)
1303 {
1304 	QString szText(pcText);
1305 	preprocessMessage(szText);
1306 	const QChar * pC = szText.constData();
1307 	if(!pC)
1308 		return;
1309 	internalOutput(m_pIrcView, iMsgType, (kvi_wchar_t *)pC, iFlags, datetime);
1310 }
1311 
outputNoFmt(int iMsgType,const QString & szText,int iFlags,const QDateTime & datetime)1312 void KviWindow::outputNoFmt(int iMsgType, const QString & szText, int iFlags, const QDateTime & datetime)
1313 {
1314 	QString szBuf(szText);
1315 	preprocessMessage(szBuf);
1316 	const QChar * pC = szBuf.constData();
1317 	if(!pC)
1318 		return;
1319 	internalOutput(m_pIrcView, iMsgType, (kvi_wchar_t *)pC, iFlags, datetime);
1320 }
1321 
unhighlight()1322 void KviWindow::unhighlight()
1323 {
1324 	if(!m_pWindowListItem)
1325 		return;
1326 	m_pWindowListItem->unhighlight();
1327 }
1328 
preprocessMessage(QString & szMessage)1329 void KviWindow::preprocessMessage(QString & szMessage)
1330 {
1331 	// FIXME: slow
1332 
1333 	if(!m_pConsole || !m_pConsole->connection())
1334 		return;
1335 
1336 	static QString szNonStandardLinkPrefix = QString::fromLatin1("\r![");
1337 
1338 	if(szMessage.contains(szNonStandardLinkPrefix))
1339 		return; // contains a non standard link that may contain spaces, do not break it.
1340 
1341 	// FIXME: This STILL breaks $fmtlink() in certain configurations
1342 
1343 	QStringList strings = szMessage.split(" ");
1344 	for(auto & it : strings)
1345 	{
1346 		if(it.contains('\r'))
1347 			continue;
1348 		QString szTmp(it);
1349 		szTmp = KviControlCodes::stripControlBytes(szTmp).trimmed();
1350 		if(szTmp.length() < 1)
1351 			continue;
1352 		if(m_pConsole->connection()->serverInfo()->supportedChannelTypes().contains(szTmp[0]))
1353 		{
1354 			if(it == szTmp)
1355 				it = QString("\r!c\r%1\r").arg(it);
1356 			else
1357 				it = QString("\r!c%1\r%2\r").arg(szTmp, it);
1358 		}
1359 	}
1360 	szMessage = strings.join(" ");
1361 }
1362 
defaultTextCodec()1363 QTextCodec * KviWindow::defaultTextCodec()
1364 {
1365 	// if we have a connection try to inherit from there...
1366 	if(connection())
1367 	{
1368 		QTextCodec * pCodec = connection()->textCodec();
1369 		if(pCodec)
1370 			return pCodec;
1371 	}
1372 	return KviApplication::defaultTextCodec();
1373 }
1374 
connection()1375 KviIrcConnection * KviWindow::connection()
1376 {
1377 	if(console() && console()->context())
1378 		return console()->context()->connection();
1379 	return nullptr;
1380 }
1381 
context()1382 KviIrcContext * KviWindow::context()
1383 {
1384 	if(console())
1385 	{
1386 		if(console() == this)
1387 			return ((KviConsoleWindow *)this)->context();
1388 		else
1389 			return console()->context();
1390 	}
1391 	return nullptr;
1392 }
1393 
pasteLastLog()1394 void KviWindow::pasteLastLog()
1395 {
1396 	bool bChannel = type() == KviWindow::Channel || type() == KviWindow::DeadChannel;
1397 	QDate date = QDate::currentDate();
1398 	int iInterval = -(int)KVI_OPTION_UINT(bChannel ? KviOption_uintDaysIntervalToPasteOnChannelJoin : KviOption_uintDaysIntervalToPasteOnQueryJoin);
1399 	QDate checkDate = date.addDays(iInterval);
1400 
1401 	unsigned int uMaxLines = KVI_OPTION_UINT(bChannel ? KviOption_uintLinesToPasteOnChannelJoin : KviOption_uintLinesToPasteOnQueryJoin);
1402 	if (!uMaxLines)
1403 		return;
1404 
1405 	std::vector<std::tuple<QString, QDate, int>> vLines;
1406 
1407 	for (; date >= checkDate; date = date.addDays(-1))
1408 		for (int iGzip = 0; iGzip <= 1; iGzip++)
1409 			for (unsigned int uDatetimeFormat = 0; uDatetimeFormat < 3; uDatetimeFormat++)
1410 			{
1411 				bool bGzip = !!iGzip;
1412 
1413 				QString szFileName;
1414 				getDefaultLogFileName(szFileName, date, bGzip, uDatetimeFormat);
1415 
1416 				QFileInfo fi(szFileName);
1417 				if (!fi.exists() || !fi.isFile())
1418 					continue;
1419 
1420 				// Load the log
1421 				QByteArray log = loadLogFile(szFileName, bGzip);
1422 
1423 				if(log.size() == 0)
1424 					continue;
1425 
1426 				QList<QByteArray> list = log.split('\n');
1427 				unsigned int uCount = list.size();
1428 
1429 				while (uCount)
1430 				{
1431 					vLines.emplace_back(QString(list.at(--uCount)), date, uDatetimeFormat);
1432 
1433 					if (vLines.size() == uMaxLines)
1434 						goto enough;
1435 				}
1436 			}
1437 
1438 	if (vLines.empty())
1439 		return;
1440 
1441 enough:
1442 	QString szDummy = __tr2qs("Starting last log");
1443 	output(KVI_OUT_LOG, szDummy);
1444 
1445 	for (auto logIter = vLines.rbegin(); logIter != vLines.rend(); ++logIter)
1446 	{
1447 		QString & szLine      = std::get<0>(*logIter);
1448 		const QDate & logDate = std::get<1>(*logIter);
1449 		int uDatetimeFormat   = std::get<2>(*logIter);
1450 
1451 		bool ok;
1452 		int msgType = szLine.section(' ', 0, 0).toInt(&ok);
1453 		if (ok)
1454 			szLine = szLine.section(' ', 1);
1455 		else
1456 			msgType = KVI_OUT_LOG;
1457 
1458 		QDateTime date;
1459 		switch(uDatetimeFormat)
1460 		{
1461 			case 0:
1462 			{
1463 				QTime time = QTime::fromString(szLine.section(' ', 0, 0), "[hh:mm:ss]");
1464 				if (time.isValid())
1465 				{
1466 					date = QDateTime(logDate, time);
1467 					szLine = szLine.section(' ', 1);
1468 				}
1469 				break;
1470 			}
1471 			case 1:
1472 				date = QDateTime::fromString(szLine.section(' ', 0, 0), Qt::ISODate);
1473 				if (date.isValid())
1474 					szLine = szLine.section(' ', 1);
1475 				break;
1476 			case 2:
1477 			{
1478 				// The system-locale format is hairy, because it has no clear delimiter.
1479 				// Count how many spaces a typical time format has,
1480 				// and assume that that number is not going to change.
1481 				static int iSpaceCount = -1;
1482 				if (iSpaceCount == -1)
1483 				{
1484 					QString szTypicalDate = QDateTime::currentDateTime().toString(Qt::SystemLocaleShortDate);
1485 					iSpaceCount = szTypicalDate.count(' ');
1486 				}
1487 				date = QDateTime::fromString(szLine.section(' ', 0, iSpaceCount), Qt::SystemLocaleShortDate);
1488 				if (date.isValid())
1489 				{
1490 					szLine = szLine.section(' ', iSpaceCount+1);
1491 
1492 					// Work around Qt bug:
1493 					// if the date string contains a two-digit year, it may be
1494 					// parsed in the wrong century (i.e. 1916 instead of 2016).
1495 					if (logDate.year() == date.date().year() + 100)
1496 						date = date.addYears(100);
1497 				}
1498 				break;
1499 			}
1500 		}
1501 
1502 		if (szLine.isEmpty())
1503 			continue;
1504 
1505 		// Print the line in the channel buffer
1506 		output(msgType, date, "%Q", &szLine);
1507 	}
1508 
1509 	szDummy = __tr2qs("End of log");
1510 	output(KVI_OUT_LOG, szDummy);
1511 }
1512 
loadLogFile(const QString & szFileName,bool bGzip)1513 QByteArray KviWindow::loadLogFile(const QString & szFileName, bool bGzip)
1514 {
1515 	QByteArray data;
1516 
1517 #ifdef COMPILE_ZLIB_SUPPORT
1518 	if(bGzip)
1519 	{
1520 		gzFile logFile = gzopen(szFileName.toLocal8Bit().data(), "rb");
1521 		if(logFile)
1522 		{
1523 			char cBuff[1025];
1524 			int iLen;
1525 
1526 			iLen = gzread(logFile, cBuff, 1024);
1527 			while(iLen > 0)
1528 			{
1529 				cBuff[iLen] = 0;
1530 				data.append(cBuff);
1531 				iLen = gzread(logFile, cBuff, 1024);
1532 			}
1533 
1534 			gzclose(logFile);
1535 		}
1536 		else
1537 		{
1538 			qDebug("Can't open compressed file %s", szFileName.toUtf8().data());
1539 		}
1540 	}
1541 	else
1542 	{
1543 #endif
1544 		QFile logFile(szFileName);
1545 		if(!logFile.open(QIODevice::ReadOnly))
1546 			return QByteArray();
1547 
1548 		data = logFile.readAll();
1549 		logFile.close();
1550 #ifdef COMPILE_ZLIB_SUPPORT
1551 	}
1552 #endif
1553 
1554 	return data;
1555 }
1556