1 //=============================================================================
2 //
3 //   File : LogViewWindow.cpp
4 //   Creation date : Tue Apr 23 2002 18:08:22 by Juanjo Alvarez
5 //
6 //   This file is part of the KVIrc IRC client distribution
7 //   Copyright (C) 2002 Juanjo Alvarez
8 //   Copyright (C) 2002-2010 Szymon Stefanek (pragma at kvirc dot net)
9 //   Copyright (C) 2011 Elvio Basello (hellvis69 at gmail dot com)
10 //   Copyright (C) 2014 OmegaPhil (OmegaPhil at startmail dot com)
11 //
12 //   This program is FREE software. You can redistribute it and/or
13 //   modify it under the terms of the GNU General Public License
14 //   as published by the Free Software Foundation; either version 2
15 //   of the License, or (at your option) any later version.
16 //
17 //   This program is distributed in the HOPE that it will be USEFUL,
18 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
20 //   See the GNU General Public License for more details.
21 //
22 //   You should have received a copy of the GNU General Public License
23 //   along with this program. If not, write to the Free Software Foundation,
24 //   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 //
26 //=============================================================================
27 
28 #include "LogViewWindow.h"
29 #include "LogViewWidget.h"
30 
31 #include "KviHtmlGenerator.h"
32 #include "KviIconManager.h"
33 #include "KviLocale.h"
34 #include "KviModule.h"
35 #include "KviOptions.h"
36 #include "KviMainWindow.h"
37 #include "KviIrcView.h"
38 #include "KviQString.h"
39 #include "KviApplication.h"
40 #include "KviFileUtils.h"
41 #include "KviFileDialog.h"
42 #include "KviControlCodes.h"
43 
44 #include <QList>
45 #include <QFileInfo>
46 #include <QDir>
47 #include <QCursor>
48 #include <QHeaderView>
49 #include <QPushButton>
50 #include <QDateEdit>
51 #include <QLineEdit>
52 #include <QLabel>
53 #include <QMouseEvent>
54 #include <QMessageBox>
55 #include <QProgressBar>
56 #include <QTextStream>
57 #include <QTabWidget>
58 #include <QCheckBox>
59 #include <QMenu>
60 
61 #include <climits> //for INT_MAX
62 
63 extern LogViewWindow * g_pLogViewWindow;
64 
LogViewListView(QWidget * pParent)65 LogViewListView::LogViewListView(QWidget * pParent)
66     : QTreeWidget(pParent)
67 {
68 	header()->setSortIndicatorShown(true);
69 	setColumnCount(1);
70 	setHeaderLabel(__tr2qs_ctx("Log File", "log"));
71 	setSelectionMode(QAbstractItemView::SingleSelection);
72 	setSortingEnabled(true);
73 	setRootIsDecorated(true);
74 	setAnimated(true);
75 }
76 
mousePressEvent(QMouseEvent * pEvent)77 void LogViewListView::mousePressEvent(QMouseEvent * pEvent)
78 {
79 	if(pEvent->button() == Qt::RightButton)
80 	{
81 		QTreeWidgetItem * pItem = itemAt(pEvent->pos());
82 		if(pItem)
83 			emit rightButtonPressed(pItem, QCursor::pos());
84 	}
85 	QTreeWidget::mousePressEvent(pEvent);
86 }
87 
LogViewWindow()88 LogViewWindow::LogViewWindow()
89     : KviWindow(KviWindow::LogView, "log")
90 {
91 	g_pLogViewWindow = this;
92 	//m_pLogViewWidget = new KviLogViewWidget(this);
93 
94 	m_pSplitter = new KviTalSplitter(Qt::Horizontal, this);
95 	m_pSplitter->setObjectName("main_splitter");
96 	m_pSplitter->setChildrenCollapsible(false);
97 
98 	m_pLeftLayout = new KviTalVBox(m_pSplitter);
99 	m_pTabWidget = new QTabWidget(m_pLeftLayout);
100 	m_pBottomLayout = new KviTalHBox(m_pLeftLayout);
101 	m_pProgressBar = new QProgressBar(m_pBottomLayout);
102 
103 	m_pCancelButton = new QPushButton(m_pBottomLayout);
104 	m_pCancelButton->setText(__tr2qs_ctx("Cancel", "log"));
105 	connect(m_pCancelButton, SIGNAL(clicked()), this, SLOT(abortFilter()));
106 	m_pBottomLayout->setVisible(false);
107 
108 	m_pIndexTab = new KviTalVBox(m_pTabWidget);
109 	m_pTabWidget->addTab(m_pIndexTab, __tr2qs_ctx("Index", "log"));
110 
111 	m_pListView = new LogViewListView(m_pIndexTab);
112 
113 	connect(m_pListView, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(itemSelected(QTreeWidgetItem *, QTreeWidgetItem *)));
114 	connect(m_pListView, SIGNAL(rightButtonPressed(QTreeWidgetItem *, QPoint)), this, SLOT(rightButtonClicked(QTreeWidgetItem *, QPoint)));
115 
116 	m_pSearchTab = new QWidget(m_pTabWidget);
117 	m_pTabWidget->addTab(m_pSearchTab, __tr2qs_ctx("Filter", "log"));
118 
119 	QGridLayout * pLayout = new QGridLayout(m_pSearchTab);
120 
121 	m_pShowChannelsCheck = new QCheckBox(__tr2qs_ctx("Show channel logs", "log"), m_pSearchTab);
122 	m_pShowChannelsCheck->setChecked(true);
123 	pLayout->addWidget(m_pShowChannelsCheck, 0, 0, 1, 2);
124 
125 	m_pShowQueryesCheck = new QCheckBox(__tr2qs_ctx("Show query logs", "log"), m_pSearchTab);
126 	m_pShowQueryesCheck->setChecked(true);
127 	pLayout->addWidget(m_pShowQueryesCheck, 1, 0, 1, 2);
128 
129 	m_pShowConsolesCheck = new QCheckBox(__tr2qs_ctx("Show console logs", "log"), m_pSearchTab);
130 	m_pShowConsolesCheck->setChecked(true);
131 	pLayout->addWidget(m_pShowConsolesCheck, 2, 0, 1, 2);
132 
133 	m_pShowDccChatCheck = new QCheckBox(__tr2qs_ctx("Show DCC chat logs", "log"), m_pSearchTab);
134 	m_pShowDccChatCheck->setChecked(true);
135 	pLayout->addWidget(m_pShowDccChatCheck, 3, 0, 1, 2);
136 
137 	m_pShowOtherCheck = new QCheckBox(__tr2qs_ctx("Show other logs", "log"), m_pSearchTab);
138 	m_pShowOtherCheck->setChecked(true);
139 	pLayout->addWidget(m_pShowOtherCheck, 4, 0, 1, 2);
140 
141 	QLabel * pLabel;
142 	pLabel = new QLabel(__tr2qs_ctx("Contents Filter", "log"), m_pSearchTab);
143 	pLayout->addWidget(pLabel, 5, 0, 1, 2);
144 
145 	pLabel = new QLabel(__tr2qs_ctx("Log name mask:", "log"), m_pSearchTab);
146 	m_pFileNameMask = new QLineEdit(m_pSearchTab);
147 	pLayout->addWidget(pLabel, 6, 0);
148 	pLayout->addWidget(m_pFileNameMask, 6, 1);
149 	connect(m_pFileNameMask, SIGNAL(returnPressed()), this, SLOT(applyFilter()));
150 
151 	pLabel = new QLabel(__tr2qs_ctx("Log contents mask:", "log"), m_pSearchTab);
152 	m_pContentsMask = new QLineEdit(m_pSearchTab);
153 	pLayout->addWidget(pLabel, 7, 0);
154 	pLayout->addWidget(m_pContentsMask, 7, 1);
155 	connect(m_pContentsMask, SIGNAL(returnPressed()), this, SLOT(applyFilter()));
156 
157 	m_pEnableFromFilter = new QCheckBox(__tr2qs_ctx("Only older than:", "log"), m_pSearchTab);
158 	m_pFromDateEdit = new QDateEdit(m_pSearchTab);
159 	m_pFromDateEdit->setDate(QDate::currentDate());
160 	m_pFromDateEdit->setEnabled(false);
161 	pLayout->addWidget(m_pEnableFromFilter, 8, 0);
162 	pLayout->addWidget(m_pFromDateEdit, 8, 1);
163 	connect(m_pEnableFromFilter, SIGNAL(toggled(bool)), m_pFromDateEdit, SLOT(setEnabled(bool)));
164 
165 	m_pEnableToFilter = new QCheckBox(__tr2qs_ctx("Only newer than:", "log"), m_pSearchTab);
166 	m_pToDateEdit = new QDateEdit(m_pSearchTab);
167 	m_pToDateEdit->setDate(QDate::currentDate());
168 	m_pToDateEdit->setEnabled(false);
169 	pLayout->addWidget(m_pEnableToFilter, 9, 0);
170 	pLayout->addWidget(m_pToDateEdit, 9, 1);
171 	connect(m_pEnableToFilter, SIGNAL(toggled(bool)), m_pToDateEdit, SLOT(setEnabled(bool)));
172 
173 	m_pFilterButton = new QPushButton(__tr2qs_ctx("Apply Filter", "log"), m_pSearchTab);
174 	pLayout->addWidget(m_pFilterButton, 10, 1);
175 	connect(m_pFilterButton, SIGNAL(clicked()), this, SLOT(applyFilter()));
176 
177 	QWidget * pWidget = new QWidget(m_pSearchTab);
178 	pWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
179 	pLayout->addWidget(pWidget, 11, 1);
180 
181 	m_pIrcView = new KviIrcView(m_pSplitter, this);
182 	m_pIrcView->setMaxBufferSize(INT_MAX);
183 	m_pIrcView->setFocusPolicy(Qt::ClickFocus);
184 
185 	QList<int> li;
186 	li.append(110);
187 	li.append(width() - 110);
188 	m_pSplitter->setSizes(li);
189 
190 	// Using setData to track the option ordinal that used to be passed as id
191 	m_pExportLogPopup = new QMenu("exportlog", this);
192 	QAction * pAction = m_pExportLogPopup->addAction(__tr2qs_ctx("Plain Text File", "log"));
193 	pAction->setData(LogFile::PlainText);
194 	pAction = m_pExportLogPopup->addAction(__tr2qs_ctx("HTML Archive", "log"));
195 	pAction->setData(LogFile::HTML);
196 	//m_pExportLogPopup->addAction(__tr2qs_ctx("XML file","log"));
197 	//m_pExportLogPopup->addAction(__tr2qs_ctx("database file","log"));
198 
199 	// 18.11.14: Originally this hooked activated, but Qt4 kept sending bullshit IDs here
200 	// https://github.com/kvirc/KVIrc/issues/934#issuecomment-124933890
201 	connect(m_pExportLogPopup, SIGNAL(triggered(QAction *)), this, SLOT(exportLog(QAction *)));
202 
203 	m_pTimer = new QTimer(this);
204 	m_pTimer->setSingleShot(true);
205 	m_pTimer->setInterval(0);
206 	connect(m_pTimer, SIGNAL(timeout()), this, SLOT(filterNext()));
207 	//avoid to execute the long time-consuming procedure of log indexing here:
208 	//we could still be inside the context of the "Browse log files" QAction
209 	QTimer::singleShot(0, this, SLOT(cacheFileList()));
210 }
211 
~LogViewWindow()212 LogViewWindow::~LogViewWindow()
213 {
214 	g_pLogViewWindow = nullptr;
215 }
216 
keyPressEvent(QKeyEvent * pEvent)217 void LogViewWindow::keyPressEvent(QKeyEvent * pEvent)
218 {
219 	if((pEvent->modifiers() & Qt::ControlModifier) || (pEvent->modifiers() & Qt::AltModifier) || (pEvent->modifiers() & Qt::MetaModifier))
220 	{
221 		if(pEvent->key() == Qt::Key_F)
222 		{
223 			m_pIrcView->toggleToolWidget();
224 			return;
225 		}
226 	}
227 	KviWindow::keyPressEvent(pEvent);
228 }
229 
myIconPtr()230 QPixmap * LogViewWindow::myIconPtr()
231 {
232 	return g_pIconManager->getSmallIcon(KviIconManager::Log);
233 }
234 
resizeEvent(QResizeEvent *)235 void LogViewWindow::resizeEvent(QResizeEvent *)
236 {
237 	m_pSplitter->setGeometry(0, 0, width(), height());
238 }
239 
fillCaptionBuffers()240 void LogViewWindow::fillCaptionBuffers()
241 {
242 	m_szPlainTextCaption = __tr2qs_ctx("Log Viewer", "log");
243 }
244 
die()245 void LogViewWindow::die()
246 {
247 	close();
248 }
249 
sizeHint() const250 QSize LogViewWindow::sizeHint() const
251 {
252 	QSize ret(m_pSplitter->sizeHint().width(), m_pIrcView->sizeHint().height());
253 	return ret;
254 }
255 
recurseDirectory(const QString & szDir)256 void LogViewWindow::recurseDirectory(const QString & szDir)
257 {
258 	QDir dir(szDir);
259 	QFileInfoList list = dir.entryInfoList();
260 	for(int i = 0; i < list.count(); i++)
261 	{
262 		QFileInfo info = list[i];
263 		if(info.isDir())
264 		{
265 			// recursive
266 			if((info.fileName() != "..") && (info.fileName() != "."))
267 				recurseDirectory(info.filePath());
268 		}
269 		else if((info.suffix() == "gz") || (info.suffix() == "log"))
270 		{
271 			m_logList.append(new LogFile(info.filePath()));
272 		}
273 	}
274 }
275 
setupItemList()276 void LogViewWindow::setupItemList()
277 {
278 	if(m_logList.isEmpty())
279 		return;
280 
281 	m_pFilterButton->setEnabled(false);
282 	m_pListView->clear();
283 
284 	m_bAborted = false;
285 	m_pBottomLayout->setVisible(true);
286 	m_pProgressBar->setRange(0, m_logList.count());
287 	m_pProgressBar->setValue(0);
288 
289 	m_pLastCategory = nullptr;
290 	m_pLastGroupItem = nullptr;
291 	m_logList.first();
292 	m_pTimer->start(); //singleshot
293 }
294 
applyFilter()295 void LogViewWindow::applyFilter()
296 {
297 	setupItemList();
298 }
299 
abortFilter()300 void LogViewWindow::abortFilter()
301 {
302 	m_bAborted = true;
303 }
304 
filterNext()305 void LogViewWindow::filterNext()
306 {
307 	QString szCurGroup;
308 	LogFile * pFile = m_logList.current();
309 	if(!pFile)
310 		goto filter_last;
311 
312 	if(pFile->type() == LogFile::Channel && !m_pShowChannelsCheck->isChecked())
313 		goto filter_next;
314 	if(pFile->type() == LogFile::Console && !m_pShowConsolesCheck->isChecked())
315 		goto filter_next;
316 	if(pFile->type() == LogFile::DccChat && !m_pShowDccChatCheck->isChecked())
317 		goto filter_next;
318 	if(pFile->type() == LogFile::Other && !m_pShowOtherCheck->isChecked())
319 		goto filter_next;
320 	if(pFile->type() == LogFile::Query && !m_pShowQueryesCheck->isChecked())
321 		goto filter_next;
322 
323 	if(m_pEnableFromFilter->isChecked())
324 		if(pFile->date() > m_pFromDateEdit->date())
325 			goto filter_next;
326 
327 	if(m_pEnableToFilter->isChecked())
328 		if(pFile->date() < m_pToDateEdit->date())
329 			goto filter_next;
330 
331 	if(!m_pFileNameMask->text().isEmpty())
332 		if(!KviQString::matchString(m_pFileNameMask->text(), pFile->name()))
333 			goto filter_next;
334 
335 	if(!m_pContentsMask->text().isEmpty())
336 	{
337 		QString szBuffer;
338 		pFile->getText(szBuffer);
339 		if(!KviQString::matchString(m_pContentsMask->text(), szBuffer))
340 			goto filter_next;
341 	}
342 
343 	if(m_pLastCategory)
344 	{
345 		if(m_pLastCategory->m_eType != pFile->type())
346 		{
347 			m_pLastCategory = nullptr;
348 			for(int i = 0; i < m_pListView->topLevelItemCount(); ++i)
349 			{
350 				LogListViewItemType * pTmp = (LogListViewItemType *)m_pListView->topLevelItem(i);
351 				if(pTmp->m_eType == pFile->type())
352 				{
353 					m_pLastCategory = pTmp;
354 					break;
355 				}
356 			}
357 			if(!m_pLastCategory)
358 				m_pLastCategory = new LogListViewItemType(m_pListView, pFile->type());
359 		}
360 	}
361 	else
362 	{
363 		m_pLastCategory = new LogListViewItemType(m_pListView, pFile->type());
364 	}
365 
366 	szCurGroup = __tr2qs_ctx("%1 on %2", "log").arg(pFile->name(), pFile->network());
367 
368 	if(m_szLastGroup != szCurGroup)
369 	{
370 		m_szLastGroup = szCurGroup;
371 		m_pLastGroupItem = nullptr;
372 		for(int i = 0; i < m_pLastCategory->childCount(); ++i)
373 		{
374 			LogListViewItemFolder * pTmp = (LogListViewItemFolder *)m_pLastCategory->child(i);
375 			if(pTmp->text(0) == m_szLastGroup)
376 			{
377 				m_pLastGroupItem = pTmp;
378 				break;
379 			}
380 		}
381 
382 		if(!m_pLastGroupItem)
383 			m_pLastGroupItem = new LogListViewItemFolder(m_pLastCategory, m_szLastGroup);
384 	}
385 
386 	new LogListViewLog(m_pLastGroupItem, pFile->type(), pFile);
387 
388 filter_next:
389 	pFile = m_logList.next();
390 
391 filter_last:
392 	if(pFile && !m_bAborted)
393 	{
394 		m_pProgressBar->setValue(m_pProgressBar->value() + 1);
395 		m_pTimer->start(); //singleshot
396 	}
397 	else
398 	{
399 		m_pBottomLayout->setVisible(false);
400 		m_pListView->sortItems(0, Qt::AscendingOrder);
401 		m_pProgressBar->setValue(0);
402 		m_pFilterButton->setEnabled(true);
403 
404 		// Reset m_szLastGroup for next search
405 		m_szLastGroup = "";
406 	}
407 }
408 
cacheFileList()409 void LogViewWindow::cacheFileList()
410 {
411 	QString szLogPath;
412 	g_pApp->getLocalKvircDirectory(szLogPath, KviApplication::Log);
413 	recurseDirectory(szLogPath);
414 
415 	setupItemList();
416 }
417 
itemSelected(QTreeWidgetItem * it,QTreeWidgetItem *)418 void LogViewWindow::itemSelected(QTreeWidgetItem * it, QTreeWidgetItem *)
419 {
420 	//A parent node
421 	m_pIrcView->clearBuffer();
422 	if(!it || !it->parent() || !(((LogListViewItem *)it)->m_pFileData))
423 		return;
424 
425 	QString szText;
426 	((LogListViewItem *)it)->m_pFileData->getText(szText);
427 
428 	QStringList lines = szText.split('\n');
429 	bool bOk;
430 	int iMsgType;
431 	for(auto & line : lines)
432 	{
433 		QString szNum = line.section(' ', 0, 0);
434 		iMsgType = szNum.toInt(&bOk);
435 		if(iMsgType < 0 || iMsgType > (KVI_NUM_MSGTYPE_OPTIONS - 1))
436 			iMsgType = 0;
437 		if(bOk)
438 			outputNoFmt(iMsgType, line.section(' ', 1), KviIrcView::NoRepaint | KviIrcView::NoTimestamp);
439 		else
440 			outputNoFmt(0, line, KviIrcView::NoRepaint | KviIrcView::NoTimestamp);
441 	}
442 	m_pIrcView->repaint();
443 }
444 
rightButtonClicked(QTreeWidgetItem * pItem,const QPoint &)445 void LogViewWindow::rightButtonClicked(QTreeWidgetItem * pItem, const QPoint &)
446 {
447 	if(!pItem)
448 		return;
449 	m_pListView->setCurrentItem(pItem);
450 
451 	QMenu * pPopup = new QMenu(this);
452 	if(((LogListViewItem *)pItem)->childCount())
453 	{
454 		// TODO: probably should allow to specify a directory instead of asking for file path on each log file
455 		pPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Save)), __tr2qs_ctx("Export All Log Files to", "log"))->setMenu(m_pExportLogPopup);
456 		pPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Discard)), __tr2qs_ctx("Remove All Log Files Within This Folder", "log"), this, SLOT(deleteCurrent()));
457 	}
458 	else
459 	{
460 		pPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Save)), __tr2qs_ctx("Export Log File to", "log"))->setMenu(m_pExportLogPopup);
461 		pPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Discard)), __tr2qs_ctx("Remove Log File", "log"), this, SLOT(deleteCurrent()));
462 	}
463 
464 	pPopup->exec(QCursor::pos());
465 }
466 
deleteCurrent()467 void LogViewWindow::deleteCurrent()
468 {
469 	LogListViewItem * pItem = dynamic_cast<LogListViewItem *>(m_pListView->currentItem());
470 	if(!pItem)
471 		return;
472 
473 	if(!pItem->childCount())
474 	{
475 		if(!pItem->fileName().isNull())
476 		{
477 			if(QMessageBox::question(
478 			       this,
479 			       __tr2qs_ctx("Confirm Current User Log Deletion", "log"),
480 			       __tr2qs_ctx("Do you really wish to delete this log?", "log"),
481 			       __tr2qs("Yes"), __tr2qs("No"), nullptr, 1)
482 			    != 0)
483 				return;
484 
485 			KviFileUtils::removeFile(pItem->fileName());
486 			if(!pItem->parent()->childCount())
487 				delete pItem->parent();
488 
489 			delete pItem;
490 			m_pIrcView->clearBuffer();
491 		}
492 		return;
493 	}
494 
495 	if(QMessageBox::question(
496 	       this,
497 	       __tr2qs_ctx("Confirm Current User Logs Deletion", "log"),
498 	       __tr2qs_ctx("Do you really wish to delete all these logs?", "log"),
499 	       __tr2qs("Yes"), __tr2qs("No"), nullptr, 1)
500 	    != 0)
501 		return;
502 	KviPointerList<LogListViewItem> itemsList;
503 	itemsList.setAutoDelete(false);
504 	for(int i = 0; i < pItem->childCount(); i++)
505 	{
506 		if(!pItem->child(i)->childCount())
507 		{
508 			itemsList.append((LogListViewItem *)pItem->child(i));
509 			continue;
510 		}
511 		LogListViewItem * pChild = (LogListViewItem *)pItem->child(i);
512 		for(int j = 0; j < pChild->childCount(); j++)
513 		{
514 			if(!(LogListViewItem *)pChild->child(j))
515 			{
516 				qDebug("Null pointer in logviewitem");
517 				continue;
518 			}
519 			itemsList.append((LogListViewItem *)pChild->child(j));
520 		}
521 	}
522 	for(unsigned int u = 0; u < itemsList.count(); u++)
523 	{
524 		LogListViewItem * pCurItem = itemsList.at(u);
525 		if(!pCurItem->fileName().isNull())
526 			KviFileUtils::removeFile(pCurItem->fileName());
527 	}
528 	delete pItem;
529 }
530 
exportLog(QAction * pAction)531 void LogViewWindow::exportLog(QAction * pAction)
532 {
533 	/* This slot is required as Qt4 has started to screw up the menu ordinal
534 	 * sent with the old activated signal - the ordinal is now stored as
535 	 * QAction user data */
536 	if(pAction)
537 		exportLog(pAction->data().toInt());
538 	else
539 		qDebug("LogViewWindow::exportLog called with invalid pAction");
540 }
541 
exportLog(int iId)542 void LogViewWindow::exportLog(int iId)
543 {
544 	LogListViewItem * pItem = (LogListViewItem *)(m_pListView->currentItem());
545 	if(!pItem)
546 		return;
547 
548 	if(!pItem->childCount())
549 	{
550 		// Export the log
551 		createLog(pItem->log(), iId);
552 		return;
553 	}
554 
555 	// We selected a node in the log list, scan the children
556 	KviPointerList<LogListViewItem> logList;
557 	logList.setAutoDelete(false);
558 	for(int i = 0; i < pItem->childCount(); i++)
559 	{
560 		if(!pItem->child(i)->childCount())
561 		{
562 			// The child is a log file, append it to the list
563 			logList.append((LogListViewItem *)pItem->child(i));
564 			continue;
565 		}
566 
567 		// The child is a node, scan it
568 		LogListViewItem * pChild = (LogListViewItem *)pItem->child(i);
569 		for(int j = 0; j < pChild->childCount(); j++)
570 		{
571 			if(!(LogListViewItem *)pChild->child(j))
572 			{
573 				qDebug("Null pointer in logviewitem");
574 				continue;
575 			}
576 
577 			// Add the child to the list
578 			logList.append((LogListViewItem *)pChild->child(j));
579 		}
580 	}
581 
582 	// Scan the list
583 	for(unsigned int u = 0; u < logList.count(); u++)
584 	{
585 		LogListViewItem * pCurItem = logList.at(u);
586 		createLog(pCurItem->log(), iId);
587 	}
588 }
589 
createLog(LogFile * pLog,int iId,QString * pszFile)590 void LogViewWindow::createLog(LogFile * pLog, int iId, QString * pszFile)
591 {
592 	if(!pLog)
593 		return;
594 
595 	QRegExp rx;
596 	QString szLog, szLogDir, szInputBuffer, szOutputBuffer, szLine, szTmp;
597 	QString szDate = pLog->date().toString("yyyy.MM.dd");
598 
599 	/* Fetching previous export path and concatenating with generated filename
600 	 * adjustFilePath is for file paths not directory paths */
601 	szLog = KVI_OPTION_STRING(KviOption_stringLogsExportPath).trimmed();
602 	if(!szLog.isEmpty())
603 		szLog += KVI_PATH_SEPARATOR_CHAR;
604 	szLog += QString("%1_%2.%3_%4").arg(pLog->typeString(), pLog->name(), pLog->network(), szDate);
605 	KviFileUtils::adjustFilePath(szLog);
606 
607 	// Getting output file path from the user, with overwrite confirmation
608 	if(!KviFileDialog::askForSaveFileName(
609 	       szLog,
610 	       __tr2qs_ctx("Export Log - KVIrc", "log"),
611 	       szLog,
612 	       QString(),
613 	       false,
614 	       true,
615 	       true,
616 	       this))
617 		return;
618 
619 	/* Save export directory - this directory path is also used in the HTML export
620 	 * and info is used when working with pszFile */
621 	QFileInfo info(szLog);
622 	szLogDir = info.absoluteDir().absolutePath();
623 	KVI_OPTION_STRING(KviOption_stringLogsExportPath) = szLogDir;
624 
625 	/* Reading in log file - LogFiles are read in as bytes, so '\r' isn't
626 	 * sanitised by default */
627 	pLog->getText(szInputBuffer);
628 	QStringList lines = szInputBuffer.replace('\r', "").split('\n');
629 
630 	switch(iId)
631 	{
632 		case LogFile::PlainText:
633 		{
634 			/* Only append extension if it isn't there already (e.g. a specific
635 			 * file is to be overwritten) */
636 			if(!szLog.endsWith(".txt"))
637 				szLog += ".txt";
638 
639 			// Scan the file
640 			for(auto & line : lines)
641 			{
642 				szTmp = line;
643 				szLine = KviControlCodes::stripControlBytes(szTmp);
644 
645 				// Remove icons' code
646 				rx.setPattern("^\\d{1,3}\\s");
647 				szLine.replace(rx, "");
648 
649 				// Remove link from a user speaking, deal with (and keep) various ranks
650 				// e.g.: <!ncHelLViS69>  -->  <HelLViS69>
651 				rx.setPattern("\\s<([+%@&~!]?)!nc");
652 				szLine.replace(rx, " <\\1");
653 
654 				// Remove link from a nick in a mask
655 				// e.g.: !nFoo [~bar@!hfoo.bar]  -->  Foo [~bar@!hfoo.bar]
656 				rx.setPattern("\\s!n");
657 				szLine.replace(rx, " ");
658 
659 				// Remove link from a host in a mask
660 				// e.g.: Foo [~bar@!hfoo.bar]  -->  Foo [~bar@foo.bar]
661 				rx.setPattern("@!h");
662 				szLine.replace(rx, "@");
663 
664 				// Remove link from a channel
665 				// e.g.: !c#KVIrc  -->  #KVIrc
666 				rx.setPattern("!c#");
667 				szLine.replace(rx, "#");
668 
669 				szOutputBuffer += szLine;
670 				szOutputBuffer += "\n";
671 			}
672 
673 			break;
674 		}
675 		case LogFile::HTML:
676 		{
677 			/* Only append extension if it isn't there already (e.g. a specific
678 			 * file is to be overwritten) */
679 			if(!szLog.endsWith(".html"))
680 				szLog += ".html";
681 
682 			szTmp = QString("KVIrc %1 %2").arg(KVI_VERSION).arg(KVI_RELEASE_NAME);
683 			QString szNick = "";
684 			bool bFirstLine = true;
685 
686 			QString szTitle;
687 			switch(pLog->type())
688 			{
689 				case LogFile::Channel:
690 					szTitle = __tr2qs_ctx("Channel %1 on %2", "log").arg(pLog->name(), pLog->network());
691 					break;
692 				case LogFile::Console:
693 					szTitle = __tr2qs_ctx("Console on %1", "log").arg(pLog->network());
694 					break;
695 				case LogFile::Query:
696 					szTitle = __tr2qs_ctx("Query with: %1 on %2", "log").arg(pLog->name(), pLog->network());
697 					break;
698 				case LogFile::DccChat:
699 					szTitle = __tr2qs_ctx("DCC Chat with: %1", "log").arg(pLog->name());
700 					break;
701 				case LogFile::Other:
702 					szTitle = __tr2qs_ctx("Something on: %1", "log").arg(pLog->network());
703 					break;
704 			}
705 
706 			// Prepare HTML document
707 			szOutputBuffer += "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n";
708 			szOutputBuffer += "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n";
709 			szOutputBuffer += "<head>\n";
710 			szOutputBuffer += "\t<meta http-equiv=\"content-type\" content=\"application/xhtml+xml; charset=utf-8\" />\n";
711 			szOutputBuffer += "\t<meta name=\"author\" content=\"" + szTmp + "\" />\n";
712 			szOutputBuffer += "\t<title>" + szTitle + "</title>\n";
713 			szOutputBuffer += "</head>\n<body>\n";
714 			szOutputBuffer += "<h2>" + szTitle + "</h2>\n<h3>Date: " + szDate + "</h3>\n";
715 
716 			// Scan the file
717 			for(auto & line : lines)
718 			{
719 				szTmp = line;
720 
721 				// Find who has talked
722 				QString szTmpNick = szTmp.section(" ", 2, 2);
723 				if((szTmpNick.left(1) != "<") && (szTmpNick.right(1) != ">"))
724 					szTmpNick = "";
725 
726 				// locate msgtype
727 				QString szNum = szTmp.section(' ', 0, 0);
728 				bool bOk;
729 				int iMsgType = szNum.toInt(&bOk);
730 
731 				// only human text for now...
732 				if(iMsgType != 24 && iMsgType != 25 && iMsgType != 26)
733 					continue;
734 
735 				// remove msgtype tag
736 				szTmp = szTmp.remove(0, szNum.length() + 1);
737 
738 				szTmp = KviHtmlGenerator::convertToHtml(szTmp, true);
739 
740 				// insert msgtype icon at start of the current text line
741 				KviMessageTypeSettings msg(KVI_OPTION_MSGTYPE(iMsgType));
742 				QString szIcon = g_pIconManager->getSmallIconResourceName((KviIconManager::SmallIcon)msg.pixId());
743 				szTmp.prepend("<img src=\"" + szIcon + R"(" alt="" /> )");
744 
745 				/*
746 				 * Check if the nick who has talked is the same of the above line.
747 				 * If so, we have to put the line as it is, otherwise we have to
748 				 * open a new paragraph
749 				 */
750 				if(szTmpNick != szNick)
751 				{
752 					/*
753 					 * People is not the same, close the paragraph opened
754 					 * before and open a new one
755 					 */
756 					if(!bFirstLine)
757 						szOutputBuffer += "</p>\n";
758 					szTmp.prepend("<p>");
759 
760 					szNick = szTmpNick;
761 				}
762 				else
763 				{
764 					// Break the line
765 					szTmp.prepend("<br />\n");
766 				}
767 
768 				szOutputBuffer += szTmp;
769 				bFirstLine = false;
770 			}
771 
772 			// Close the last paragraph
773 			szOutputBuffer += "</p>\n";
774 
775 			// regexp to search all embedded icons
776 			rx.setPattern("<img src=\"smallicons:([^\"]+)");
777 			int iIndex = szOutputBuffer.indexOf(rx);
778 			QStringList szImagesList;
779 
780 			// search for icons
781 			while(iIndex >= 0)
782 			{
783 				int iLength = rx.matchedLength();
784 				QString szCap = rx.cap(1);
785 
786 				// if the icon isn't in the images list then add
787 				if(szImagesList.indexOf(szCap) == -1)
788 					szImagesList.append(szCap);
789 				iIndex = szOutputBuffer.indexOf(rx, iIndex + iLength);
790 			}
791 
792 			// get current theme path
793 			QString szCurrentThemePath;
794 			g_pApp->getLocalKvircDirectory(szCurrentThemePath, KviApplication::Themes, KVI_OPTION_STRING(KviOption_stringIconThemeSubdir));
795 			szCurrentThemePath += KVI_PATH_SEPARATOR_CHAR;
796 
797 			// current coresmall path
798 			szCurrentThemePath += "coresmall";
799 			szCurrentThemePath += KVI_PATH_SEPARATOR_CHAR;
800 
801 			// check if coresmall exists in current theme
802 			if(!KviFileUtils::directoryExists(szCurrentThemePath))
803 			{
804 				// get global coresmall path
805 				g_pApp->getGlobalKvircDirectory(szCurrentThemePath, KviApplication::Pics, "coresmall");
806 				KviQString::ensureLastCharIs(szCurrentThemePath, QChar(KVI_PATH_SEPARATOR_CHAR));
807 			}
808 
809 			// copy all icons to the log destination folder
810 			for(int i = 0; i < szImagesList.count(); i++)
811 			{
812 				QString szSourceFile = szCurrentThemePath + szImagesList.at(i);
813 				QString szDestFile = szLogDir + szImagesList.at(i);
814 				KviFileUtils::copyFile(szSourceFile, szDestFile);
815 			}
816 
817 			// remove internal tags
818 			rx.setPattern("<qt>|</qt>|smallicons:");
819 			szOutputBuffer.replace(rx, "");
820 			szOutputBuffer.replace(">!nc", ">");
821 			szOutputBuffer.replace("@!nc", "@");
822 			szOutputBuffer.replace("%!nc", "%");
823 
824 			// Close the document
825 			szOutputBuffer += "</body>\n</html>\n";
826 
827 			break;
828 		}
829 	}
830 
831 	// File overwriting already dealt with when file path was obtained
832 	QFile log(szLog);
833 	if(!log.open(QIODevice::WriteOnly | QIODevice::Text))
834 		return;
835 
836 	if(pszFile)
837 	{
838 		*pszFile = "";
839 		*pszFile = info.filePath();
840 	}
841 
842 	// Ensure we're writing in UTF-8
843 	QTextStream output(&log);
844 	output.setCodec("UTF-8");
845 	output << szOutputBuffer;
846 
847 	// Close file descriptors
848 	log.close();
849 }
850