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