1 //=============================================================================
2 //
3 //   File : LinksWindow.cpp
4 //   Creation date : Thu Dec 21 2001 12:41:18 by Szymon Stefanek
5 //
6 //   This file is part of the KVIrc IRC client distribution
7 //   Copyright (C) 2001-2010 Szymon Stefanek (pragma at kvirc dot net)
8 //
9 //   This program is FREE software. You can redistribute it and/or
10 //   modify it under the terms of the GNU General Public License
11 //   as published by the Free Software Foundation; either version 2
12 //   of the License, or (at your option) any later version.
13 //
14 //   This program is distributed in the HOPE that it will be USEFUL,
15 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
16 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 //   See the GNU General Public License for more details.
18 //
19 //   You should have received a copy of the GNU General Public License
20 //   along with this program. If not, write to the Free Software Foundation,
21 //   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 //
23 //=============================================================================
24 
25 #include "LinksWindow.h"
26 
27 #include "kvi_debug.h"
28 #include "KviIconManager.h"
29 #include "KviIrcView.h"
30 #include "kvi_out.h"
31 #include "KviOptions.h"
32 #include "KviLocale.h"
33 #include "KviControlCodes.h"
34 #include "KviThemedLabel.h"
35 #include "KviIrcContext.h"
36 #include "KviIrcConnection.h"
37 #include "KviTalHBox.h"
38 #include "KviIrcMessage.h"
39 
40 #include <QToolTip>
41 #include <QLabel>
42 #include <QMouseEvent>
43 #include <QHeaderView>
44 
45 #include <unordered_set>
46 #include <utility>
47 
48 extern std::unordered_set<LinksWindow *> g_pLinksWindowList;
49 
LinksWindow(KviConsoleWindow * lpConsole)50 LinksWindow::LinksWindow(KviConsoleWindow * lpConsole)
51     : KviWindow(KviWindow::Links, "links", lpConsole), KviExternalServerDataParser()
52 {
53 	g_pLinksWindowList.insert(this);
54 
55 	m_pTopSplitter = new KviTalSplitter(Qt::Horizontal, this);
56 	m_pTopSplitter->setObjectName("top_splitter");
57 	m_pTopSplitter->setChildrenCollapsible(false);
58 
59 	// The button box on the left
60 	KviTalHBox * box = new KviTalHBox(m_pTopSplitter);
61 
62 	m_pRequestButton = new QToolButton(box);
63 	m_pRequestButton->setObjectName("request_button");
64 	m_pRequestButton->setIconSize(QSize(16, 16));
65 	m_pRequestButton->setIcon(*(g_pIconManager->getSmallIcon(KviIconManager::Update)));
66 	connect(m_pRequestButton, SIGNAL(clicked()), this, SLOT(requestLinks()));
67 	m_pRequestButton->setToolTip(__tr2qs("Request links"));
68 
69 	QLabel * l = new QLabel(box);
70 	box->setStretchFactor(l, 1);
71 	m_pInfoLabel = new KviThemedLabel(m_pTopSplitter, this, "info_label");
72 
73 	connect(lpConsole->context(), SIGNAL(stateChanged()),
74 	    this, SLOT(connectionStateChange()));
75 
76 	m_pSplitter = new KviTalSplitter(Qt::Horizontal, this);
77 	m_pSplitter->setObjectName("splitter");
78 	m_pSplitter->setChildrenCollapsible(false);
79 
80 	m_pVertSplitter = new KviTalSplitter(Qt::Vertical, m_pSplitter);
81 	m_pVertSplitter->setObjectName("vsplitter");
82 	m_pVertSplitter->setChildrenCollapsible(false);
83 
84 	m_pListView = new LinksListView(m_pVertSplitter, this, "links_treewidget");
85 
86 	connect(m_pListView, SIGNAL(rightButtonPressed(QTreeWidgetItem *, const QPoint &)),
87 	    this, SLOT(showHostPopup(QTreeWidgetItem *, const QPoint &)));
88 
89 	m_pIrcView = new KviIrcView(m_pVertSplitter, this);
90 
91 	m_pHostPopup = new QMenu();
92 	connect(m_pHostPopup, SIGNAL(triggered(QAction *)), this, SLOT(hostPopupClicked(QAction *)));
93 
94 	connectionStateChange();
95 
96 	m_pConsole->context()->setLinksWindowPointer(this);
97 
98 	m_szRootServer = __tr2qs("(None)");
99 }
100 
~LinksWindow()101 LinksWindow::~LinksWindow()
102 {
103 	g_pLinksWindowList.erase(this);
104 	m_pConsole->context()->setLinksWindowPointer(nullptr);
105 	delete m_pHostPopup;
106 }
107 
getBaseLogFileName(QString & buffer)108 void LinksWindow::getBaseLogFileName(QString & buffer)
109 {
110 	buffer.sprintf("LINKS_%d", context()->id());
111 }
112 
requestLinks()113 void LinksWindow::requestLinks()
114 {
115 	if(m_pConsole->isConnected())
116 	{
117 		m_pConsole->connection()->sendFmtData("links");
118 		outputNoFmt(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Sent links request, waiting for reply..."));
119 		m_pRequestButton->setEnabled(false);
120 	}
121 	else
122 	{
123 		outputNoFmt(KVI_OUT_SYSTEMERROR, __tr2qs("Can't request links: no active connection"));
124 	}
125 }
126 
connectionStateChange()127 void LinksWindow::connectionStateChange()
128 {
129 	KviIrcContext::State st = m_pConsole->context()->state();
130 	m_pRequestButton->setEnabled(st == KviIrcContext::Connected);
131 	if(st == KviIrcContext::Connected)
132 	{
133 		QString szTmp = QString(__tr2qs("Connected to %1 (%2)")).arg(m_pConsole->connection()->currentServerName(), m_pConsole->currentNetworkName());
134 		m_pInfoLabel->setText(szTmp);
135 	}
136 	else
137 	{
138 		m_pInfoLabel->setText(__tr2qs("Links can't be requested: not connected to a server"));
139 	}
140 }
141 
myIconPtr()142 QPixmap * LinksWindow::myIconPtr()
143 {
144 	return g_pIconManager->getSmallIcon(KviIconManager::Links);
145 }
146 
resizeEvent(QResizeEvent *)147 void LinksWindow::resizeEvent(QResizeEvent *)
148 {
149 	int hght2 = m_pTopSplitter->sizeHint().height();
150 	m_pTopSplitter->setGeometry(0, 0, width(), hght2);
151 	m_pSplitter->setGeometry(0, hght2, width(), height() - hght2);
152 }
153 
sizeHint() const154 QSize LinksWindow::sizeHint() const
155 {
156 	QSize ret(m_pSplitter->sizeHint().width(),
157 	    m_pSplitter->sizeHint().height() + m_pTopSplitter->sizeHint().height());
158 	return ret;
159 }
160 
fillCaptionBuffers()161 void LinksWindow::fillCaptionBuffers()
162 {
163 	m_szPlainTextCaption = QString(__tr2qs("Links for %1 [IRC Context %2]")).arg(m_szRootServer).arg(m_pConsole->context()->id());
164 }
165 
die()166 void LinksWindow::die()
167 {
168 	close();
169 }
170 
control(int message)171 void LinksWindow::control(int message)
172 {
173 	switch(message)
174 	{
175 		case EXTERNAL_SERVER_DATA_PARSER_CONTROL_RESET:
176 			reset();
177 			break;
178 		case EXTERNAL_SERVER_DATA_PARSER_CONTROL_ENDOFDATA:
179 			endOfLinks();
180 			break;
181 	}
182 }
183 
endOfLinks()184 void LinksWindow::endOfLinks()
185 {
186 	m_pRequestButton->setEnabled(true);
187 
188 	m_pListView->clear();
189 	//m_pListView->setSorting(-1);
190 
191 	outputNoFmt(KVI_OUT_SYSTEMMESSAGE, "======================");
192 	outputNoFmt(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Received end of links."));
193 	outputNoFmt(KVI_OUT_SYSTEMMESSAGE, "======================");
194 
195 	QTreeWidgetItem * it = nullptr;
196 	QTreeWidgetItem * root = nullptr;
197 
198 	int totalHosts = 0;
199 	int totalHops = 0;
200 	int maxHops = 0;
201 	int avgHops = 0;
202 	int directLinks = 0;
203 	int nearLinks = 0;
204 	int brokenLinks = 0;
205 	int maxLinks = 0;
206 	int farLinks = 0;
207 	int wildServers = 0;
208 
209 	KviCString szMaxHop, szMaxLinks;
210 
211 	m_pListView->setUpdatesEnabled(false);
212 	for(auto & l : m_pLinkList)
213 	{
214 		totalHosts++;
215 		if(l->hops == 0)
216 		{
217 			root = new QTreeWidgetItem(m_pListView);
218 			root->setText(0, QString(l->host.ptr()));
219 			root->setText(1, QString("0"));
220 			root->setText(2, QString(l->description.ptr()));
221 		}
222 		else
223 		{
224 			totalHops += l->hops;
225 			if(l->hops < 4)
226 			{
227 				nearLinks++;
228 				if(l->hops == 1)
229 					directLinks++;
230 			}
231 			else
232 			{
233 				if(l->hops >= 7)
234 					farLinks++;
235 			}
236 			if(l->hops == maxHops)
237 			{
238 				szMaxHop.append(',');
239 				szMaxHop.append(l->host);
240 			}
241 			else if(l->hops > maxHops)
242 			{
243 				maxHops = l->hops;
244 				szMaxHop = l->host;
245 			}
246 			if(l->host.contains('*'))
247 				wildServers++;
248 			it = insertLink(l.get());
249 			if(!it)
250 			{
251 				output(KVI_OUT_SYSTEMERROR, __tr2qs("Broken link: missing parent (%s) for %s (%d hops): %s (used /LINKS <mask> ?)"),
252 				    l->parent.ptr(), l->host.ptr(), l->hops, l->description.ptr());
253 				brokenLinks++;
254 				QString szTmp = QString(__tr2qs("%1: Parent link %2")).arg(l->description.ptr(), l->parent.ptr());
255 				KviCString tmp2(KviCString::Format, "%d", l->hops);
256 				if(root)
257 				{
258 					it = new QTreeWidgetItem(root);
259 					it->setText(0, QString(l->host.ptr()));
260 					it->setText(1, QString(tmp2.ptr()));
261 					it->setText(2, szTmp);
262 				}
263 				else
264 				{
265 					outputNoFmt(KVI_OUT_SYSTEMERROR, __tr2qs("Warning: no root link was sent by the server, the stats may be invalid."));
266 					it = new QTreeWidgetItem(m_pListView);
267 					it->setText(0, QString(l->host.ptr()));
268 					it->setText(1, QString(tmp2.ptr()));
269 					it->setText(2, szTmp);
270 				}
271 			}
272 			else
273 			{
274 				it = (QTreeWidgetItem *)it->parent();
275 				if(it)
276 				{
277 					int links = it->childCount() + 1;
278 					if(links == maxLinks)
279 					{
280 						szMaxLinks.append(',');
281 						szMaxLinks.append(it->text(0));
282 					}
283 					else if(links > maxLinks)
284 					{
285 						maxLinks = links;
286 						szMaxLinks = it->text(0);
287 					}
288 				}
289 			}
290 		}
291 	}
292 
293 	avgHops = ((totalHosts > 0) ? ((totalHops * 100) / totalHosts) : 0);
294 	int nearProcent = ((totalHosts > 0) ? ((nearLinks * 10000) / totalHosts) : 0);
295 	int directProcent = ((totalHosts > 0) ? ((directLinks * 10000) / totalHosts) : 0);
296 	int midLinks = totalHosts - (farLinks + nearLinks + 1 + brokenLinks);
297 	if(midLinks < 0)
298 		midLinks = 0;
299 	int midProcent = ((totalHosts > 0) ? ((midLinks * 10000) / totalHosts) : 0);
300 	int farProcent = ((totalHosts > 0) ? ((farLinks * 10000) / totalHosts) : 0);
301 
302 	outputNoFmt(KVI_OUT_LINKS, "======================");
303 
304 	//	if(!root)root = m_pListView->firstChild();
305 	if(root)
306 	{
307 		m_szRootServer = root->text(0);
308 		output(KVI_OUT_LINKS, __tr2qs("%c%cLinks for %Q"), KviControlCodes::Bold, KviControlCodes::Underline, &m_szRootServer);
309 		outputNoFmt(KVI_OUT_LINKS, "======================");
310 		QString tmpo = wildServers ? __tr2qs("Total hosts listed") : __tr2qs("Total hosts in the network");
311 		output(KVI_OUT_LINKS, "%Q: %d", &tmpo, totalHosts);
312 		if(wildServers)
313 			output(KVI_OUT_LINKS, __tr2qs("Wildcard servers (hubs?): %d"), wildServers);
314 		output(KVI_OUT_LINKS, __tr2qs("Direct links: %d (~%d.%d %)"), directLinks, directProcent / 100, directProcent % 100);
315 		output(KVI_OUT_LINKS, __tr2qs("Close links (1 <= hops <= 3): %d (~%d.%d %)"), nearLinks, nearProcent / 100, nearProcent % 100);
316 		output(KVI_OUT_LINKS, __tr2qs("Mid-range links (4 <= hops <= 6): %d (~%d.%d %)"), midLinks, midProcent / 100, midProcent % 100);
317 		output(KVI_OUT_LINKS, __tr2qs("Distant links (7 <= hops): %d (~%d.%d %)"), farLinks, farProcent / 100, farProcent % 100);
318 		output(KVI_OUT_LINKS, __tr2qs("Broken (unknown) links: %d"), brokenLinks);
319 		output(KVI_OUT_LINKS, __tr2qs("Maximum links per host: %d [%s]"), maxLinks, szMaxLinks.ptr());
320 		output(KVI_OUT_LINKS, __tr2qs("Total links: %d"), totalHops);
321 		output(KVI_OUT_LINKS, __tr2qs("Maximum hops: %d [%s]"), maxHops, szMaxHop.ptr());
322 		output(KVI_OUT_LINKS, __tr2qs("Average hops: ~%d.%d"), avgHops / 100, avgHops % 100);
323 	}
324 	else
325 	{
326 		m_szRootServer = __tr2qs("(Unknown)");
327 		outputNoFmt(KVI_OUT_LINKS, __tr2qs("Incomplete links result, no stats available"));
328 	}
329 	outputNoFmt(KVI_OUT_LINKS, "======================");
330 
331 	updateCaption();
332 
333 	m_pLinkList.clear();
334 
335 	m_pListView->resizeColumnToContents(0);
336 	m_pListView->setUpdatesEnabled(true);
337 	m_pListView->repaint();
338 }
339 
insertLink(KviLink * l)340 QTreeWidgetItem * LinksWindow::insertLink(KviLink * l)
341 {
342 	KVI_ASSERT(l->hops > 0);
343 	QTreeWidgetItem * i = getItemByHost(l->parent.ptr(), nullptr);
344 	QTreeWidgetItem * it = nullptr;
345 	if(!i)
346 	{
347 		return nullptr;
348 	}
349 	else
350 	{
351 		KviCString hops(KviCString::Format, "%d", l->hops);
352 		it = new QTreeWidgetItem(i);
353 		it->setText(0, QString(l->host.ptr()));
354 		it->setText(1, QString(hops.ptr()));
355 		it->setText(2, QString(l->description.ptr()));
356 		i->setExpanded(true);
357 	}
358 	return it;
359 }
360 
getItemByHost(const char * host,QTreeWidgetItem * par)361 QTreeWidgetItem * LinksWindow::getItemByHost(const char * host, QTreeWidgetItem * par)
362 {
363 	KviCString tmp;
364 	if(par)
365 	{
366 		for(int i = 0; i < par->childCount(); i++)
367 		{
368 			tmp = par->child(i)->text(0);
369 			if(kvi_strEqualCI(tmp.ptr(), host))
370 				return par->child(i);
371 			QTreeWidgetItem * ch = getItemByHost(host, par->child(i));
372 			if(ch)
373 				return ch;
374 		}
375 	}
376 	else
377 	{
378 		for(int i = 0; i < m_pListView->topLevelItemCount(); i++)
379 		{
380 			tmp = m_pListView->topLevelItem(i)->text(0);
381 			if(kvi_strEqualCI(tmp.ptr(), host))
382 				return m_pListView->topLevelItem(i);
383 			QTreeWidgetItem * ch = getItemByHost(host, m_pListView->topLevelItem(i));
384 			if(ch)
385 				return ch;
386 		}
387 	}
388 	return nullptr;
389 }
390 
showHostPopup(QTreeWidgetItem * i,const QPoint & p)391 void LinksWindow::showHostPopup(QTreeWidgetItem * i, const QPoint & p)
392 {
393 	if(!i)
394 		return;
395 	KviCString host = i->text(0);
396 	if(host.isEmpty())
397 		return;
398 	m_pHostPopup->clear();
399 	KviCString tmp(KviCString::Format, "LINKS %s *", host.ptr());
400 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Links)), tmp.ptr());
401 	m_pHostPopup->addSeparator();
402 	tmp.sprintf("TIME %s", host.ptr());
403 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Time)), tmp.ptr());
404 	tmp.sprintf("ADMIN %s", host.ptr());
405 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::ChanUnOwner)), tmp.ptr());
406 	tmp.sprintf("INFO %s", host.ptr());
407 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::ServerInfo)), tmp.ptr());
408 	tmp.sprintf("MOTD %s", host.ptr());
409 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Motd)), tmp.ptr());
410 	tmp.sprintf("VERSION %s", host.ptr());
411 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::KVIrc)), tmp.ptr());
412 	tmp.sprintf("TRACE %s", host.ptr());
413 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::ServerPing)), tmp.ptr());
414 	tmp.sprintf("USERS %s", host.ptr());
415 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::User)), tmp.ptr());
416 	m_pHostPopup->addSeparator();
417 	tmp.sprintf("STATS c %s", host.ptr());
418 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
419 	tmp.sprintf("STATS d %s", host.ptr());
420 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
421 	tmp.sprintf("STATS h %s", host.ptr());
422 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
423 	tmp.sprintf("STATS i %s", host.ptr());
424 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
425 	tmp.sprintf("STATS k %s", host.ptr());
426 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
427 	tmp.sprintf("STATS l %s", host.ptr());
428 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
429 	tmp.sprintf("STATS m %s", host.ptr());
430 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
431 	tmp.sprintf("STATS o %s", host.ptr());
432 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
433 	tmp.sprintf("STATS t %s", host.ptr());
434 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
435 	tmp.sprintf("STATS u %s", host.ptr());
436 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
437 	tmp.sprintf("STATS y %s", host.ptr());
438 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
439 	tmp.sprintf("STATS z %s", host.ptr());
440 	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
441 	m_pHostPopup->popup(p);
442 }
443 
hostPopupClicked(QAction * pAction)444 void LinksWindow::hostPopupClicked(QAction * pAction)
445 {
446 	KviCString tmp = pAction->text();
447 	if(tmp.hasData())
448 	{
449 		if(!connection())
450 			output(KVI_OUT_SYSTEMERROR, __tr2qs("You're not connected to a server"));
451 		m_pConsole->connection()->sendData(tmp.ptr());
452 	}
453 }
454 
reset()455 void LinksWindow::reset()
456 {
457 	outputNoFmt(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Reset"));
458 	m_pLinkList.clear();
459 }
460 
processData(KviIrcMessage * msg)461 void LinksWindow::processData(KviIrcMessage * msg)
462 {
463 	output(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Processing link: %s"), msg->allParams());
464 	std::unique_ptr<KviLink> l(new KviLink);
465 
466 	l->host = msg->safeParam(1);
467 	l->parent = msg->safeParam(2);
468 
469 	const char * tr = msg->safeTrailing();
470 
471 	if(isdigit(*tr))
472 	{
473 		const char * aux = tr;
474 		while(*tr && (isdigit(*tr)))
475 			tr++;
476 		KviCString tmp(aux, tr - aux);
477 		l->hops = tmp.toInt();
478 	}
479 	else
480 	{
481 		outputNoFmt(KVI_OUT_SYSTEMERROR, __tr2qs("Broken message syntax, can't extract hops number, assuming 0"));
482 		l->hops = 0;
483 	}
484 	while(*tr && (*tr == ' '))
485 		tr++;
486 	l->description = tr;
487 	std::size_t idx = 0;
488 	for(auto & m : m_pLinkList)
489 	{
490 		if(m->hops >= l->hops)
491 		{
492 			m_pLinkList.insert(m_pLinkList.begin() + idx, std::move(l));
493 			return;
494 		}
495 		idx++;
496 	}
497 	m_pLinkList.push_back(std::move(l));
498 }
499 
applyOptions()500 void LinksWindow::applyOptions()
501 {
502 	m_pListView->applyOptions();
503 	m_pInfoLabel->applyOptions();
504 	m_pIrcView->applyOptions();
505 	KviWindow::applyOptions();
506 }
507 
LinksListView(QWidget * par,KviWindow * wnd,const char * txt)508 LinksListView::LinksListView(QWidget * par, KviWindow * wnd, const char * txt)
509     : KviThemedTreeWidget(par, wnd, txt)
510 {
511 	header()->setSortIndicatorShown(true);
512 	setColumnCount(3);
513 	setHeaderLabels(QStringList() << __tr2qs("Link") << __tr2qs("Hops") << __tr2qs("Description"));
514 	setRootIsDecorated(true);
515 	setAnimated(true);
516 	setSortingEnabled(true);
517 	setAllColumnsShowFocus(true);
518 }
519 
mousePressEvent(QMouseEvent * e)520 void LinksListView::mousePressEvent(QMouseEvent * e)
521 {
522 	if(e->button() == Qt::RightButton)
523 	{
524 		QTreeWidgetItem * i = itemAt(e->pos());
525 		if(i)
526 			emit rightButtonPressed(i, QCursor::pos());
527 	}
528 	KviThemedTreeWidget::mousePressEvent(e);
529 }
530