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