aboutsummaryrefslogtreecommitdiffstats
//=============================================================================
//
//   File : LinksWindow.cpp
//   Creation date : Thu Dec 21 2001 12:41:18 by Szymon Stefanek
//
//   This file is part of the KVIrc IRC client distribution
//   Copyright (C) 2001-2010 Szymon Stefanek (pragma at kvirc dot net)
//
//   This program is FREE software. You can redistribute it and/or
//   modify it under the terms of the GNU General Public License
//   as published by the Free Software Foundation; either version 2
//   of the License, or (at your option) any later version.
//
//   This program is distributed in the HOPE that it will be USEFUL,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//   See the GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with this program. If not, write to the Free Software Foundation,
//   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
//=============================================================================

#include "LinksWindow.h"

#include "kvi_debug.h"
#include "KviIconManager.h"
#include "KviIrcView.h"
#include "kvi_out.h"
#include "KviOptions.h"
#include "KviLocale.h"
#include "KviControlCodes.h"
#include "KviThemedLabel.h"
#include "KviIrcContext.h"
#include "KviIrcConnection.h"
#include "KviTalHBox.h"
#include "KviIrcMessage.h"

#include <QToolTip>
#include <QLabel>
#include <QMouseEvent>
#include <QHeaderView>

#include <unordered_set>
#include <utility>

extern std::unordered_set<LinksWindow *> g_pLinksWindowList;

LinksWindow::LinksWindow(KviConsoleWindow * lpConsole)
    : KviWindow(KviWindow::Links, "links", lpConsole), KviExternalServerDataParser()
{
	g_pLinksWindowList.insert(this);

	m_pTopSplitter = new KviTalSplitter(Qt::Horizontal, this);
	m_pTopSplitter->setObjectName("top_splitter");
	m_pTopSplitter->setChildrenCollapsible(false);

	// The button box on the left
	KviTalHBox * box = new KviTalHBox(m_pTopSplitter);

	m_pRequestButton = new QToolButton(box);
	m_pRequestButton->setObjectName("request_button");
	m_pRequestButton->setIconSize(QSize(16, 16));
	m_pRequestButton->setIcon(*(g_pIconManager->getSmallIcon(KviIconManager::Update)));
	connect(m_pRequestButton, SIGNAL(clicked()), this, SLOT(requestLinks()));
	m_pRequestButton->setToolTip(__tr2qs("Request links"));

	QLabel * l = new QLabel(box);
	box->setStretchFactor(l, 1);
	m_pInfoLabel = new KviThemedLabel(m_pTopSplitter, this, "info_label");

	connect(lpConsole->context(), SIGNAL(stateChanged()),
	    this, SLOT(connectionStateChange()));

	m_pSplitter = new KviTalSplitter(Qt::Horizontal, this);
	m_pSplitter->setObjectName("splitter");
	m_pSplitter->setChildrenCollapsible(false);

	m_pVertSplitter = new KviTalSplitter(Qt::Vertical, m_pSplitter);
	m_pVertSplitter->setObjectName("vsplitter");
	m_pVertSplitter->setChildrenCollapsible(false);

	m_pListView = new LinksListView(m_pVertSplitter, this, "links_treewidget");

	connect(m_pListView, SIGNAL(rightButtonPressed(QTreeWidgetItem *, const QPoint &)),
	    this, SLOT(showHostPopup(QTreeWidgetItem *, const QPoint &)));

	m_pIrcView = new KviIrcView(m_pVertSplitter, this);

	m_pHostPopup = new QMenu();
	connect(m_pHostPopup, SIGNAL(triggered(QAction *)), this, SLOT(hostPopupClicked(QAction *)));

	connectionStateChange();

	m_pConsole->context()->setLinksWindowPointer(this);

	m_szRootServer = __tr2qs("(None)");
}

LinksWindow::~LinksWindow()
{
	g_pLinksWindowList.erase(this);
	m_pConsole->context()->setLinksWindowPointer(nullptr);
	delete m_pHostPopup;
}

void LinksWindow::getBaseLogFileName(QString & buffer)
{
	buffer = QString::asprintf("LINKS_%d", context()->id());
}

void LinksWindow::requestLinks()
{
	if(m_pConsole->isConnected())
	{
		m_pConsole->connection()->sendFmtData("links");
		outputNoFmt(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Sent links request, waiting for reply..."));
		m_pRequestButton->setEnabled(false);
	}
	else
	{
		outputNoFmt(KVI_OUT_SYSTEMERROR, __tr2qs("Can't request links: no active connection"));
	}
}

void LinksWindow::connectionStateChange()
{
	KviIrcContext::State st = m_pConsole->context()->state();
	m_pRequestButton->setEnabled(st == KviIrcContext::Connected);
	if(st == KviIrcContext::Connected)
	{
		QString szTmp = QString(__tr2qs("Connected to %1 (%2)")).arg(m_pConsole->connection()->currentServerName(), m_pConsole->currentNetworkName());
		m_pInfoLabel->setText(szTmp);
	}
	else
	{
		m_pInfoLabel->setText(__tr2qs("Links can't be requested: not connected to a server"));
	}
}

QPixmap * LinksWindow::myIconPtr()
{
	return g_pIconManager->getSmallIcon(KviIconManager::Links);
}

void LinksWindow::resizeEvent(QResizeEvent *)
{
	int hght2 = m_pTopSplitter->sizeHint().height();
	m_pTopSplitter->setGeometry(0, 0, width(), hght2);
	m_pSplitter->setGeometry(0, hght2, width(), height() - hght2);
}

QSize LinksWindow::sizeHint() const
{
	QSize ret(m_pSplitter->sizeHint().width(),
	    m_pSplitter->sizeHint().height() + m_pTopSplitter->sizeHint().height());
	return ret;
}

void LinksWindow::fillCaptionBuffers()
{
	m_szPlainTextCaption = QString(__tr2qs("Links for %1 [IRC Context %2]")).arg(m_szRootServer).arg(m_pConsole->context()->id());
}

void LinksWindow::die()
{
	close();
}

void LinksWindow::control(int message)
{
	switch(message)
	{
		case EXTERNAL_SERVER_DATA_PARSER_CONTROL_RESET:
			reset();
			break;
		case EXTERNAL_SERVER_DATA_PARSER_CONTROL_ENDOFDATA:
			endOfLinks();
			break;
	}
}

void LinksWindow::endOfLinks()
{
	m_pRequestButton->setEnabled(true);

	m_pListView->clear();
	//m_pListView->setSorting(-1);

	outputNoFmt(KVI_OUT_SYSTEMMESSAGE, "======================");
	outputNoFmt(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Received end of links."));
	outputNoFmt(KVI_OUT_SYSTEMMESSAGE, "======================");

	QTreeWidgetItem * it = nullptr;
	QTreeWidgetItem * root = nullptr;

	int totalHosts = 0;
	int totalHops = 0;
	int maxHops = 0;
	int avgHops = 0;
	int directLinks = 0;
	int nearLinks = 0;
	int brokenLinks = 0;
	int maxLinks = 0;
	int farLinks = 0;
	int wildServers = 0;

	KviCString szMaxHop, szMaxLinks;

	m_pListView->setUpdatesEnabled(false);
	for(auto & l : m_pLinkList)
	{
		totalHosts++;
		if(l->hops == 0)
		{
			root = new QTreeWidgetItem(m_pListView);
			root->setText(0, QString(l->host.ptr()));
			root->setText(1, QString("0"));
			root->setText(2, QString(l->description.ptr()));
		}
		else
		{
			totalHops += l->hops;
			if(l->hops < 4)
			{
				nearLinks++;
				if(l->hops == 1)
					directLinks++;
			}
			else
			{
				if(l->hops >= 7)
					farLinks++;
			}
			if(l->hops == maxHops)
			{
				szMaxHop.append(',');
				szMaxHop.append(l->host);
			}
			else if(l->hops > maxHops)
			{
				maxHops = l->hops;
				szMaxHop = l->host;
			}
			if(l->host.contains('*'))
				wildServers++;
			it = insertLink(l.get());
			if(!it)
			{
				output(KVI_OUT_SYSTEMERROR, __tr2qs("Broken link: missing parent (%s) for %s (%d hops): %s (used /LINKS <mask> ?)"),
				    l->parent.ptr(), l->host.ptr(), l->hops, l->description.ptr());
				brokenLinks++;
				QString szTmp = QString(__tr2qs("%1: Parent link %2")).arg(l->description.ptr(), l->parent.ptr());
				KviCString tmp2(KviCString::Format, "%d", l->hops);
				if(root)
				{
					it = new QTreeWidgetItem(root);
					it->setText(0, QString(l->host.ptr()));
					it->setText(1, QString(tmp2.ptr()));
					it->setText(2, szTmp);
				}
				else
				{
					outputNoFmt(KVI_OUT_SYSTEMERROR, __tr2qs("Warning: no root link was sent by the server, the stats may be invalid."));
					it = new QTreeWidgetItem(m_pListView);
					it->setText(0, QString(l->host.ptr()));
					it->setText(1, QString(tmp2.ptr()));
					it->setText(2, szTmp);
				}
			}
			else
			{
				it = (QTreeWidgetItem *)it->parent();
				if(it)
				{
					int links = it->childCount() + 1;
					if(links == maxLinks)
					{
						szMaxLinks.append(',');
						szMaxLinks.append(it->text(0));
					}
					else if(links > maxLinks)
					{
						maxLinks = links;
						szMaxLinks = it->text(0);
					}
				}
			}
		}
	}

	avgHops = ((totalHosts > 0) ? ((totalHops * 100) / totalHosts) : 0);
	int nearProcent = ((totalHosts > 0) ? ((nearLinks * 10000) / totalHosts) : 0);
	int directProcent = ((totalHosts > 0) ? ((directLinks * 10000) / totalHosts) : 0);
	int midLinks = totalHosts - (farLinks + nearLinks + 1 + brokenLinks);
	if(midLinks < 0)
		midLinks = 0;
	int midProcent = ((totalHosts > 0) ? ((midLinks * 10000) / totalHosts) : 0);
	int farProcent = ((totalHosts > 0) ? ((farLinks * 10000) / totalHosts) : 0);

	outputNoFmt(KVI_OUT_LINKS, "======================");

	//	if(!root)root = m_pListView->firstChild();
	if(root)
	{
		m_szRootServer = root->text(0);
		output(KVI_OUT_LINKS, __tr2qs("%c%cLinks for %Q"), KviControlCodes::Bold, KviControlCodes::Underline, &m_szRootServer);
		outputNoFmt(KVI_OUT_LINKS, "======================");
		QString tmpo = wildServers ? __tr2qs("Total hosts listed") : __tr2qs("Total hosts in the network");
		output(KVI_OUT_LINKS, "%Q: %d", &tmpo, totalHosts);
		if(wildServers)
			output(KVI_OUT_LINKS, __tr2qs("Wildcard servers (hubs?): %d"), wildServers);
		output(KVI_OUT_LINKS, __tr2qs("Direct links: %d (~%d.%d %)"), directLinks, directProcent / 100, directProcent % 100);
		output(KVI_OUT_LINKS, __tr2qs("Close links (1 <= hops <= 3): %d (~%d.%d %)"), nearLinks, nearProcent / 100, nearProcent % 100);
		output(KVI_OUT_LINKS, __tr2qs("Mid-range links (4 <= hops <= 6): %d (~%d.%d %)"), midLinks, midProcent / 100, midProcent % 100);
		output(KVI_OUT_LINKS, __tr2qs("Distant links (7 <= hops): %d (~%d.%d %)"), farLinks, farProcent / 100, farProcent % 100);
		output(KVI_OUT_LINKS, __tr2qs("Broken (unknown) links: %d"), brokenLinks);
		output(KVI_OUT_LINKS, __tr2qs("Maximum links per host: %d [%s]"), maxLinks, szMaxLinks.ptr());
		output(KVI_OUT_LINKS, __tr2qs("Total links: %d"), totalHops);
		output(KVI_OUT_LINKS, __tr2qs("Maximum hops: %d [%s]"), maxHops, szMaxHop.ptr());
		output(KVI_OUT_LINKS, __tr2qs("Average hops: ~%d.%d"), avgHops / 100, avgHops % 100);
	}
	else
	{
		m_szRootServer = __tr2qs("(Unknown)");
		outputNoFmt(KVI_OUT_LINKS, __tr2qs("Incomplete links result, no stats available"));
	}
	outputNoFmt(KVI_OUT_LINKS, "======================");

	updateCaption();

	m_pLinkList.clear();

	m_pListView->resizeColumnToContents(0);
	m_pListView->setUpdatesEnabled(true);
	m_pListView->repaint();
}

QTreeWidgetItem * LinksWindow::insertLink(KviLink * l)
{
	KVI_ASSERT(l->hops > 0);
	QTreeWidgetItem * i = getItemByHost(l->parent.ptr(), nullptr);
	QTreeWidgetItem * it = nullptr;
	if(!i)
	{
		return nullptr;
	}
	else
	{
		KviCString hops(KviCString::Format, "%d", l->hops);
		it = new QTreeWidgetItem(i);
		it->setText(0, QString(l->host.ptr()));
		it->setText(1, QString(hops.ptr()));
		it->setText(2, QString(l->description.ptr()));
		i->setExpanded(true);
	}
	return it;
}

QTreeWidgetItem * LinksWindow::getItemByHost(const char * host, QTreeWidgetItem * par)
{
	KviCString tmp;
	if(par)
	{
		for(int i = 0; i < par->childCount(); i++)
		{
			tmp = par->child(i)->text(0);
			if(kvi_strEqualCI(tmp.ptr(), host))
				return par->child(i);
			QTreeWidgetItem * ch = getItemByHost(host, par->child(i));
			if(ch)
				return ch;
		}
	}
	else
	{
		for(int i = 0; i < m_pListView->topLevelItemCount(); i++)
		{
			tmp = m_pListView->topLevelItem(i)->text(0);
			if(kvi_strEqualCI(tmp.ptr(), host))
				return m_pListView->topLevelItem(i);
			QTreeWidgetItem * ch = getItemByHost(host, m_pListView->topLevelItem(i));
			if(ch)
				return ch;
		}
	}
	return nullptr;
}

void LinksWindow::showHostPopup(QTreeWidgetItem * i, const QPoint & p)
{
	if(!i)
		return;
	KviCString host = i->text(0);
	if(host.isEmpty())
		return;
	m_pHostPopup->clear();
	KviCString tmp(KviCString::Format, "LINKS %s *", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Links)), tmp.ptr());
	m_pHostPopup->addSeparator();
	tmp.sprintf("TIME %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Time)), tmp.ptr());
	tmp.sprintf("ADMIN %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::ChanUnOwner)), tmp.ptr());
	tmp.sprintf("INFO %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::ServerInfo)), tmp.ptr());
	tmp.sprintf("MOTD %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Motd)), tmp.ptr());
	tmp.sprintf("VERSION %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::KVIrc)), tmp.ptr());
	tmp.sprintf("TRACE %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::ServerPing)), tmp.ptr());
	tmp.sprintf("USERS %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::User)), tmp.ptr());
	m_pHostPopup->addSeparator();
	tmp.sprintf("STATS c %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
	tmp.sprintf("STATS d %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
	tmp.sprintf("STATS h %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
	tmp.sprintf("STATS i %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
	tmp.sprintf("STATS k %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
	tmp.sprintf("STATS l %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
	tmp.sprintf("STATS m %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
	tmp.sprintf("STATS o %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
	tmp.sprintf("STATS t %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
	tmp.sprintf("STATS u %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
	tmp.sprintf("STATS y %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
	tmp.sprintf("STATS z %s", host.ptr());
	m_pHostPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Stats)), tmp.ptr());
	m_pHostPopup->popup(p);
}

void LinksWindow::hostPopupClicked(QAction * pAction)
{
	KviCString tmp = pAction->text();
	if(tmp.hasData())
	{
		if(!connection())
			output(KVI_OUT_SYSTEMERROR, __tr2qs("You're not connected to a server"));
		m_pConsole->connection()->sendData(tmp.ptr());
	}
}

void LinksWindow::reset()
{
	outputNoFmt(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Reset"));
	m_pLinkList.clear();
}

void LinksWindow::processData(KviIrcMessage * msg)
{
	output(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Processing link: %s"), msg->allParams());
	std::unique_ptr<KviLink> l(new KviLink);

	l->host = msg->safeParam(1);
	l->parent = msg->safeParam(2);

	const char * tr = msg->safeTrailing();

	if(isdigit(*tr))
	{
		const char * aux = tr;
		while(*tr && (isdigit(*tr)))
			tr++;
		KviCString tmp(aux, tr - aux);
		l->hops = tmp.toInt();
	}
	else
	{
		outputNoFmt(KVI_OUT_SYSTEMERROR, __tr2qs("Broken message syntax, can't extract hops number, assuming 0"));
		l->hops = 0;
	}
	while(*tr && (*tr == ' '))
		tr++;
	l->description = tr;
	std::size_t idx = 0;
	for(auto & m : m_pLinkList)
	{
		if(m->hops >= l->hops)
		{
			m_pLinkList.insert(m_pLinkList.begin() + idx, std::move(l));
			return;
		}
		idx++;
	}
	m_pLinkList.push_back(std::move(l));
}

void LinksWindow::applyOptions()
{
	m_pListView->applyOptions();
	m_pInfoLabel->applyOptions();
	m_pIrcView->applyOptions();
	KviWindow::applyOptions();
}

LinksListView::LinksListView(QWidget * par, KviWindow * wnd, const char * txt)
    : KviThemedTreeWidget(par, wnd, txt)
{
	header()->setSortIndicatorShown(true);
	setColumnCount(3);
	setHeaderLabels(QStringList() << __tr2qs("Link") << __tr2qs("Hops") << __tr2qs("Description"));
	setRootIsDecorated(true);
	setAnimated(true);
	setSortingEnabled(true);
	setAllColumnsShowFocus(true);
}

void LinksListView::mousePressEvent(QMouseEvent * e)
{
	if(e->button() == Qt::RightButton)
	{
		QTreeWidgetItem * i = itemAt(e->pos());
		if(i)
			emit rightButtonPressed(i, QCursor::pos());
	}
	KviThemedTreeWidget::mousePressEvent(e);
}