1 //=============================================================================
2 //
3 //   File : KviChannelWindow.cpp
4 //   Creation date : Tue Aug  1 2000 02:20:22 by Szymon Stefanek
5 //
6 //   This file is part of the KVIrc IRC client distribution
7 //   Copyright (C) 2000-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 "kvi_defaults.h"
26 #include "kvi_out.h"
27 #include "KviChannelWindow.h"
28 #include "KviConsoleWindow.h"
29 #include "KviIrcNetwork.h"
30 #include "KviIconManager.h"
31 #include "KviIrcView.h"
32 #include "KviInput.h"
33 #include "KviOptions.h"
34 #include "KviLocale.h"
35 #include "KviTopicWidget.h"
36 #include "KviIrcSocket.h"
37 #include "KviMemory.h"
38 #include "KviWindowListBase.h"
39 #include "KviMainWindow.h"
40 #include "KviConfigurationFile.h"
41 #include "KviMaskEditor.h"
42 #include "KviControlCodes.h"
43 #include "KviModeEditor.h"
44 #include "KviApplication.h"
45 #include "KviUserAction.h"
46 #include "KviWindowToolWidget.h"
47 #include "KviIrcConnection.h"
48 #include "KviIrcConnectionUserInfo.h"
49 #include "KviIrcConnectionServerInfo.h"
50 #include "KviIrcConnectionRequestQueue.h"
51 #include "KviIrcServerParser.h"
52 #include "KviModeWidget.h"
53 #include "KviKvsScript.h"
54 #include "KviKvsEventTriggers.h"
55 
56 #ifdef COMPILE_CRYPT_SUPPORT
57 #include "KviCryptEngine.h"
58 #include "KviCryptController.h"
59 #endif //COMPILE_CRYPT_SUPPORT
60 
61 #include <set>
62 #include <ctime>
63 
64 #include <QDate>
65 #include <QByteArray>
66 #include <QLabel>
67 #include <QEvent>
68 #include <QPalette>
69 #include <QMessageBox>
70 #include <QCloseEvent>
71 #include <QMenu>
72 
73 // FIXME: #warning "+a Anonymous channel mode!"
74 // FIXME: #warning "OnChannelFlood event...."
75 
KviChannelWindow(KviConsoleWindow * lpConsole,const QString & szName)76 KviChannelWindow::KviChannelWindow(KviConsoleWindow * lpConsole, const QString & szName)
77     : KviWindow(KviWindow::Channel, szName, lpConsole)
78 {
79 	// Init some member variables
80 	m_pInput = nullptr;
81 	m_iStateFlags = 0;
82 	m_uActionHistoryHotActionCount = 0;
83 
84 	m_pTmpHighLighted = new QStringList();
85 
86 	// Register ourselves
87 	connection()->registerChannel(this);
88 	// And create the widgets layout
89 	// Button box
90 	m_pButtonBox = new KviTalHBox(this);
91 	m_pButtonBox->setSpacing(0);
92 	m_pButtonBox->setMargin(0);
93 
94 	m_pTopSplitter = new KviTalSplitter(Qt::Horizontal, m_pButtonBox);
95 	m_pTopSplitter->setChildrenCollapsible(false);
96 
97 	m_pButtonContainer = new KviTalHBox(m_pButtonBox);
98 	m_pButtonContainer->setSpacing(0);
99 	m_pButtonContainer->setMargin(0);
100 	// Topic widget on the left
101 	m_pTopicWidget = new KviTopicWidget(m_pTopSplitter, this, "topic_widget");
102 
103 	connect(m_pTopicWidget, SIGNAL(topicSelected(const QString &)),
104 	    this, SLOT(topicSelected(const QString &)));
105 	// mode label follows the topic widget
106 	m_pModeWidget = new KviModeWidget(m_pTopSplitter, *this, "mode_");
107 	KviTalToolTip::add(m_pModeWidget, __tr2qs("Channel modes"));
108 	connect(m_pModeWidget, SIGNAL(setMode(const QString &)), this, SLOT(setMode(const QString &)));
109 
110 	createTextEncodingButton(m_pButtonContainer);
111 
112 	// Central splitter
113 	m_pSplitter = new KviTalSplitter(Qt::Horizontal, this);
114 	m_pSplitter->setObjectName(szName);
115 	m_pSplitter->setChildrenCollapsible(false);
116 
117 	// Spitted vertially on the left
118 	m_pVertSplitter = new KviTalSplitter(Qt::Vertical, m_pSplitter);
119 	m_pVertSplitter->setChildrenCollapsible(false);
120 
121 	QSizePolicy oPolicy = m_pVertSplitter->sizePolicy();
122 	oPolicy.setHorizontalPolicy(QSizePolicy::Expanding);
123 	m_pVertSplitter->setSizePolicy(oPolicy);
124 
125 	// With the IRC view over
126 	m_pIrcView = new KviIrcView(m_pVertSplitter, this);
127 	m_pIrcView->setObjectName(szName);
128 	connect(m_pIrcView, SIGNAL(rightClicked()), this, SLOT(textViewRightClicked()));
129 	// And the double view (that may be unused)
130 	m_pMessageView = nullptr;
131 	// The userlist on the right
132 	//m_pEditorsContainer= new KviToolWindowsContainer(m_pSplitter);
133 
134 	// and the related buttons
135 	m_pDoubleViewButton = new KviWindowToolPageButton(KviIconManager::HideDoubleView, KviIconManager::ShowDoubleView, __tr2qs("Split view"), buttonContainer(), false);
136 	m_pDoubleViewButton->setObjectName("double_view_button");
137 	connect(m_pDoubleViewButton, SIGNAL(clicked()), this, SLOT(toggleDoubleView()));
138 
139 	m_pListViewButton = new KviWindowToolPageButton(KviIconManager::HideListView, KviIconManager::ShowListView, __tr2qs("User list"), buttonContainer(), true);
140 	m_pListViewButton->setObjectName("list_view_button");
141 	connect(m_pListViewButton, SIGNAL(clicked()), this, SLOT(toggleListView()));
142 
143 	//list modes (bans, bans exceptions, etc)
144 	KviWindowToolPageButton * pButton = nullptr;
145 	char cMode = 0;
146 	QString szDescription = "";
147 	KviIrcConnectionServerInfo * pServerInfo = serverInfo();
148 	// bans are hardcoded
149 	cMode = 'b';
150 
151 	if(pServerInfo)
152 		szDescription = pServerInfo->getChannelModeDescription(cMode);
153 	if(szDescription.isEmpty())
154 		szDescription = __tr2qs("Mode \"%1\" masks").arg(cMode);
155 
156 	pButton = new KviWindowToolPageButton(KviIconManager::UnBan, KviIconManager::Ban, szDescription, buttonContainer(), false);
157 	pButton->setObjectName("ban_editor_button");
158 	connect(pButton, SIGNAL(clicked()), this, SLOT(toggleListModeEditor()));
159 	m_ListEditorButtons.emplace(cMode, pButton);
160 
161 	//other list modes (dynamic)
162 	QString szListModes = "";
163 	if(pServerInfo)
164 	{
165 		szListModes = pServerInfo->supportedListModes();
166 		szListModes.remove('b');
167 
168 		for(auto szListMode : szListModes)
169 		{
170 			char cMode = szListMode.unicode();
171 			QString szDescription = pServerInfo->getChannelModeDescription(cMode);
172 			if(szDescription.isEmpty())
173 				szDescription = __tr2qs("Mode \"%1\" masks").arg(cMode);
174 			KviIconManager::SmallIcon eIconOn, eIconOff;
175 			switch(cMode)
176 			{
177 				// must fix on/off icons here eventually once Ive patience to figure it out.
178 				// to Un<somethings> is to undo/turn off :p blimey O'reilly.
179 				case 'e':
180 					eIconOn = KviIconManager::BanUnExcept;
181 					eIconOff = KviIconManager::BanExcept;
182 					break;
183 				case 'I':
184 					eIconOn = KviIconManager::InviteUnExcept;
185 					eIconOff = KviIconManager::InviteExcept;
186 					break;
187 				case 'a':
188 					eIconOn = KviIconManager::ChanUnAdmin;
189 					eIconOff = KviIconManager::ChanAdmin;
190 					break;
191 				case 'q':
192 					// this could also be quiet bans..
193 					// on/off are inverted here on purpose because its broken somewhere, must fix eventually.
194 					eIconOn = KviIconManager::KickOff;
195 					eIconOff = KviIconManager::Kick;
196 					break;
197 				default:
198 					eIconOn = KviIconManager::UnBan;
199 					eIconOff = KviIconManager::Ban;
200 					break;
201 			}
202 
203 			pButton = new KviWindowToolPageButton(eIconOn, eIconOff, szDescription, buttonContainer(), false);
204 			pButton->setObjectName("list_mode_editor_button");
205 			connect(pButton, SIGNAL(clicked()), this, SLOT(toggleListModeEditor()));
206 			m_ListEditorButtons.emplace(cMode, pButton);
207 		}
208 	}
209 
210 	m_pModeEditorButton = new KviWindowToolPageButton(KviIconManager::ChanModeHide, KviIconManager::ChanMode, __tr2qs("Mode editor"), buttonContainer(), false);
211 	m_pModeEditorButton->setObjectName("mode_editor_button");
212 	connect(m_pModeEditorButton, SIGNAL(clicked()), this, SLOT(toggleModeEditor()));
213 	m_pModeEditor = nullptr;
214 
215 #ifdef COMPILE_CRYPT_SUPPORT
216 	createCryptControllerButton(m_pButtonContainer);
217 #endif
218 
219 	m_pHideToolsButton = new QToolButton(m_pButtonBox);
220 	m_pHideToolsButton->setObjectName("hide_container_button");
221 	m_pHideToolsButton->setAutoRaise(true);
222 	m_pHideToolsButton->setIconSize(QSize(22, 22));
223 	m_pHideToolsButton->setFixedWidth(16);
224 
225 	if(g_pIconManager->getBigIcon("kvi_horizontal_left.png"))
226 		m_pHideToolsButton->setIcon(QIcon(*(g_pIconManager->getBigIcon("kvi_horizontal_left.png"))));
227 
228 	connect(m_pHideToolsButton, SIGNAL(clicked()), this, SLOT(toggleToolButtons()));
229 
230 	m_pUserListView = new KviUserListView(m_pSplitter, m_pListViewButton, connection()->userDataBase(), this, KVI_CHANNEL_AVERAGE_USERS, __tr2qs("User list"), "user_list_view");
231 	//m_pEditorsContainer->addWidget(m_pUserListView);
232 	//m_pEditorsContainer->raiseWidget(m_pUserListView);
233 	// And finally the input line on the bottom
234 	m_pInput = new KviInput(this, m_pUserListView);
235 
236 	// Ensure proper focusing
237 	//setFocusHandler(m_pInput,this);
238 	// And turn on the secondary IRC view if needed
239 
240 	if(KVI_OPTION_BOOL(KviOption_boolPasteLastLogOnChannelJoin))
241 		pasteLastLog();
242 
243 	if(KVI_OPTION_BOOL(KviOption_boolAutoLogChannels))
244 		m_pIrcView->startLogging();
245 
246 	applyOptions();
247 	m_joinTime = QDateTime::currentDateTime();
248 	m_tLastReceivedWhoReply = (kvi_time_t)m_joinTime.toTime_t();
249 }
250 
~KviChannelWindow()251 KviChannelWindow::~KviChannelWindow()
252 {
253 	// Unregister ourself
254 	if(type() == KviWindow::DeadChannel && context())
255 		context()->unregisterDeadChannel(this);
256 	else if(connection())
257 		connection()->unregisterChannel(this);
258 
259 	// Then remove all the users and free mem
260 	m_pUserListView->enableUpdates(false);
261 	m_pUserListView->partAll();
262 
263 	delete m_pTmpHighLighted;
264 
265 	for(auto i : m_ListEditors)
266 		delete i.second;
267 
268 	for(auto i : m_ListEditorButtons)
269 		delete i.second;
270 
271 	for(auto i : m_ModeLists)
272 		for(auto ii : i.second)
273 			delete ii;
274 
275 	qDeleteAll(m_lActionHistory);
276 }
277 
toggleToolButtons()278 void KviChannelWindow::toggleToolButtons()
279 {
280 	if(!buttonContainer())
281 		return;
282 
283 	toggleButtonContainer();
284 	QPixmap * pPix = buttonContainer()->isVisible() ? g_pIconManager->getBigIcon("kvi_horizontal_left.png") : g_pIconManager->getBigIcon("kvi_horizontal_right.png");
285 	if(pPix)
286 		m_pHideToolsButton->setIcon(QIcon(*pPix));
287 }
288 
triggerCreationEvents()289 void KviChannelWindow::triggerCreationEvents()
290 {
291 	KVS_TRIGGER_EVENT_0(KviEvent_OnChannelWindowCreated, this);
292 }
293 
textViewRightClicked()294 void KviChannelWindow::textViewRightClicked()
295 {
296 	KVS_TRIGGER_EVENT_0(KviEvent_OnChannelPopupRequest, this);
297 }
298 
getBaseLogFileName(QString & szBuffer)299 void KviChannelWindow::getBaseLogFileName(QString & szBuffer)
300 {
301 	QString szChan(windowName());
302 	szChan.replace(".", "%2e");
303 	if(connection())
304 	{
305 		szBuffer = szChan;
306 		szBuffer.append(".");
307 		szBuffer.append(connection()->currentNetworkName());
308 	}
309 	else
310 	{
311 		szBuffer = szChan;
312 		szBuffer.append(".");
313 		if(context())
314 			szBuffer.append(context()->id());
315 		else
316 			szBuffer.append("0");
317 	}
318 }
319 
applyOptions()320 void KviChannelWindow::applyOptions()
321 {
322 	m_pUserListView->applyOptions();
323 	m_pTopicWidget->applyOptions();
324 
325 	if(m_pMessageView)
326 		m_pMessageView->applyOptions();
327 
328 	m_pModeWidget->applyOptions();
329 
330 	// this applies options for IrcView and Input and forces the window to relayout
331 	KviWindow::applyOptions();
332 }
333 
getConfigGroupName(QString & szBuffer)334 void KviChannelWindow::getConfigGroupName(QString & szBuffer)
335 {
336 	szBuffer = windowName();
337 
338 	// save per-network channel settings, so that the settings of two channels
339 	// with the same name but of different networks gets different config entries.
340 	if(connection() && connection()->target() && connection()->target() && connection()->target()->network())
341 	{
342 		szBuffer.append("@");
343 		// don't use the actual network name, because it could change during the connection
344 		szBuffer.append(connection()->target()->network()->name());
345 	}
346 }
347 
saveProperties(KviConfigurationFile * pCfg)348 void KviChannelWindow::saveProperties(KviConfigurationFile * pCfg)
349 {
350 	KviWindow::saveProperties(pCfg);
351 	pCfg->writeEntry("TopSplitter", m_pTopSplitter->sizes());
352 	pCfg->writeEntry("Splitter", m_pUserListView->isHidden() ? m_SplitterSizesList : m_pSplitter->sizes());
353 	//	int iTimeStamp= pCfg->readIntEntry("EntryTimestamp", 0);
354 	// 	qDebug("window %s, group %s, view %d==%d, ulist %d==%d timestamp %d",
355 	// 		m_szName.toUtf8().data(), pCfg->group().toUtf8().data(),
356 	// 		m_pIrcView->width(), sizes.at(0), m_pUserListView->width(), sizes.at(1), iTimeStamp);
357 	pCfg->writeEntry("VertSplitter", m_pMessageView ? m_pVertSplitter->sizes() : m_VertSplitterSizesList);
358 	pCfg->writeEntry("PrivateBackground", m_privateBackground);
359 	pCfg->writeEntry("DoubleView", m_pMessageView ? true : false);
360 
361 	pCfg->writeEntry("UserListHidden", m_pUserListView->isHidden());
362 	pCfg->writeEntry("ToolButtonsHidden", buttonContainer()->isHidden());
363 }
364 
loadProperties(KviConfigurationFile * pCfg)365 void KviChannelWindow::loadProperties(KviConfigurationFile * pCfg)
366 {
367 	int iWidth = width();
368 	QList<int> def;
369 	def.append((iWidth * 82) / 100);
370 	def.append((iWidth * 18) / 100);
371 	m_pTopSplitter->setSizes(pCfg->readIntListEntry("TopSplitter", def));
372 
373 	//this is an hack to simulate qt3's ResizeMode = Stretch
374 	for(int iWidget = 0; iWidget < m_pTopSplitter->count(); iWidget++)
375 		m_pTopSplitter->setStretchFactor(iWidget, 1);
376 
377 	def.clear();
378 	def.append((iWidth * 75) / 100);
379 	def.append((iWidth * 25) / 100);
380 	m_SplitterSizesList = pCfg->readIntListEntry("Splitter", def);
381 	m_pSplitter->setStretchFactor(0, 1);
382 
383 	def.clear();
384 
385 	def.append((iWidth * 60) / 100);
386 	def.append((iWidth * 40) / 100);
387 	m_VertSplitterSizesList = pCfg->readIntListEntry("VertSplitter", def);
388 	showDoubleView(pCfg->readBoolEntry("DoubleView", false));
389 
390 	m_privateBackground = pCfg->readPixmapEntry("PrivateBackground", KviPixmap());
391 	if(m_privateBackground.pixmap())
392 	{
393 		m_pIrcView->setPrivateBackgroundPixmap(*(m_privateBackground.pixmap()));
394 		if(m_pMessageView)
395 			m_pMessageView->setPrivateBackgroundPixmap(*(m_privateBackground.pixmap()));
396 	}
397 
398 	KviWindow::loadProperties(pCfg);
399 	if(m_pUserListView)
400 	{
401 		bool bHidden = pCfg->readBoolEntry("UserListHidden", false);
402 		m_pUserListView->setHidden(bHidden);
403 		m_pListViewButton->setChecked(!bHidden);
404 		if(!bHidden)
405 			m_pSplitter->setSizes(m_SplitterSizesList);
406 		resizeEvent(nullptr);
407 	}
408 
409 	if(pCfg->readBoolEntry("ToolButtonsHidden", KVI_OPTION_BOOL(KviOption_boolHideWindowToolButtons)) != buttonContainer()->isHidden())
410 		toggleToolButtons();
411 }
412 
showDoubleView(bool bShow)413 void KviChannelWindow::showDoubleView(bool bShow)
414 {
415 	if(m_pMessageView)
416 	{
417 		if(bShow)
418 			return;
419 
420 		m_pIrcView->joinMessagesFrom(m_pMessageView);
421 		m_VertSplitterSizesList = m_pVertSplitter->sizes();
422 
423 		delete m_pMessageView;
424 		m_pMessageView = nullptr;
425 
426 		if(m_pDoubleViewButton->isChecked())
427 			m_pDoubleViewButton->setChecked(false);
428 	}
429 	else
430 	{
431 		if(!bShow)
432 			return;
433 
434 		m_pMessageView = new KviIrcView(m_pVertSplitter, this);
435 		m_pVertSplitter->setSizes(m_VertSplitterSizesList);
436 
437 		//this is an hack to simulate qt3's ResizeMode = Stretch
438 		for(int iWidget = 0; iWidget < m_pVertSplitter->count(); iWidget++)
439 			m_pVertSplitter->setStretchFactor(iWidget, 1);
440 
441 		//setFocusHandler(m_pInput,m_pMessageView); //socket it!
442 		if(!(m_pDoubleViewButton->isChecked()))
443 			m_pDoubleViewButton->setChecked(true);
444 
445 		if(m_pIrcView->hasPrivateBackgroundPixmap())
446 			m_pMessageView->setPrivateBackgroundPixmap(*(m_pIrcView->getPrivateBackgroundPixmap()));
447 
448 		connect(m_pMessageView, SIGNAL(rightClicked()), this, SLOT(textViewRightClicked()));
449 		m_pMessageView->setMasterView(m_pIrcView);
450 		m_pIrcView->splitMessagesTo(m_pMessageView);
451 		m_pMessageView->show();
452 	}
453 }
454 
toggleDoubleView()455 void KviChannelWindow::toggleDoubleView()
456 {
457 	if(m_pMessageView)
458 	{
459 		showDoubleView(false);
460 		if(m_pDoubleViewButton->isChecked())
461 			m_pDoubleViewButton->setChecked(false);
462 	}
463 	else
464 	{
465 		showDoubleView(true);
466 		if(!(m_pDoubleViewButton->isChecked()))
467 			m_pDoubleViewButton->setChecked(true);
468 	}
469 }
470 
toggleListView()471 void KviChannelWindow::toggleListView()
472 {
473 	if(m_pUserListView->isVisible())
474 	{
475 		m_SplitterSizesList = m_pSplitter->sizes();
476 
477 		m_pUserListView->hide();
478 		if(m_pListViewButton->isChecked())
479 			m_pListViewButton->setChecked(false);
480 	}
481 	else
482 	{
483 		m_pUserListView->show();
484 		if(!(m_pListViewButton->isChecked()))
485 			m_pListViewButton->setChecked(true);
486 
487 		m_pSplitter->setSizes(m_SplitterSizesList);
488 	}
489 }
490 
toggleModeEditor()491 void KviChannelWindow::toggleModeEditor()
492 {
493 	if(m_pModeEditor)
494 	{
495 		delete m_pModeEditor;
496 		m_pModeEditor = nullptr;
497 
498 		m_pSplitter->setMinimumHeight(20);
499 		if(m_pModeEditorButton->isChecked())
500 			m_pModeEditorButton->setChecked(false);
501 		resizeEvent(nullptr);
502 	}
503 	else
504 	{
505 		m_pModeEditor = new KviModeEditor(m_pSplitter, m_pModeEditorButton, "mode_editor", this);
506 		connect(m_pModeEditor, SIGNAL(setMode(const QString &)), this, SLOT(setMode(const QString &)));
507 		connect(m_pModeEditor, SIGNAL(done()), this, SLOT(modeSelectorDone()));
508 		m_pModeEditor->show();
509 		//setFocusHandlerNoClass(m_pInput,m_pModeEditor,"QLineEdit");
510 		if(!m_pModeEditorButton->isChecked())
511 			m_pModeEditorButton->setChecked(true);
512 	}
513 }
514 
modeSelectorDone()515 void KviChannelWindow::modeSelectorDone()
516 {
517 	if(m_pModeEditor)
518 		toggleModeEditor();
519 }
520 
setMode(const QString & szMode)521 void KviChannelWindow::setMode(const QString & szMode)
522 {
523 	if(!connection())
524 		return;
525 
526 	QByteArray channelName = connection()->encodeText(m_szName);
527 	QByteArray modes = connection()->encodeText(szMode);
528 
529 	connection()->sendFmtData("MODE %s %s", channelName.data(), modes.data());
530 }
531 
toggleListModeEditor()532 void KviChannelWindow::toggleListModeEditor()
533 {
534 	KviWindowToolPageButton * pButton = (KviWindowToolPageButton *)sender();
535 	if(!pButton)
536 		return; //wtf?
537 
538 	char cMode = 0;
539 
540 	for(auto iter : m_ListEditorButtons)
541 	{
542 		if(iter.second == pButton)
543 		{
544 			cMode = iter.first;
545 			break;
546 		}
547 	}
548 
549 	if(!cMode)
550 		return; // wtf?
551 
552 	const auto iEditor = m_ListEditors.find(cMode);
553 	if(iEditor != m_ListEditors.end())
554 	{
555 		KviMaskEditor * pEditor = iEditor->second;
556 		m_ListEditors.erase(iEditor);
557 		pEditor->deleteLater();
558 
559 		pButton->setChecked(false);
560 	}
561 	else
562 	{
563 		if(!m_ModeLists.count(cMode))
564 		{
565 			m_ModeLists[cMode];
566 
567 			m_szSentModeRequests.append(cMode);
568 
569 			if(connection())
570 			{
571 				QByteArray szName = connection()->encodeText(m_szName);
572 				connection()->sendFmtData("MODE %s %c", szName.data(), cMode);
573 			}
574 		}
575 
576 		std::vector<KviMaskEntry *> pMaskList = m_ModeLists[cMode];
577 		KviMaskEditor * pEditor = new KviMaskEditor(m_pSplitter, this, pButton, pMaskList, cMode, "list_mode_editor");
578 		connect(pEditor, SIGNAL(removeMasks(KviMaskEditor *, std::vector<KviMaskEntry *>)), this, SLOT(removeMasks(KviMaskEditor *, std::vector<KviMaskEntry *>)));
579 		m_ListEditors.emplace(cMode, pEditor);
580 		pEditor->show();
581 		pButton->setChecked(true);
582 	}
583 }
584 
removeMasks(KviMaskEditor * pEditor,const std::vector<KviMaskEntry * > & pList)585 void KviChannelWindow::removeMasks(KviMaskEditor * pEditor, const std::vector<KviMaskEntry *> & pList)
586 {
587 	if(!connection())
588 		return;
589 
590 	QString szMasks;
591 	QByteArray szFlags;
592 	QByteArray szTarget = connection()->encodeText(m_szName);
593 
594 	int iMaxModeChangesPerLine = 3; // a good default
595 	KviIrcConnectionServerInfo * pServerInfo = serverInfo();
596 	if(pServerInfo)
597 	{
598 		iMaxModeChangesPerLine = pServerInfo->maxModeChanges();
599 		if(iMaxModeChangesPerLine < 1)
600 			iMaxModeChangesPerLine = 1;
601 	}
602 
603 	int iCurModeChangesPerLine = iMaxModeChangesPerLine;
604 
605 	// Calculate the max number of available characters in a MODE command
606 	// 512 (max) - 2 (cr, lf) - 4 (MODE) - 3 (spaces) - 1 (plus/minus)
607 	// since we already know the channel name, take it in account, too
608 
609 	const int iMaxCharsPerLine = 502 - szTarget.size();
610 	int iCurCharsPerLine = iMaxCharsPerLine;
611 
612 	for(auto i = pList.begin(); i != pList.end();)
613 	{
614 		KviMaskEntry * pEntry = *i;
615 		szFlags += pEditor->flag();
616 		iCurCharsPerLine--;
617 
618 		if(!szMasks.isEmpty())
619 		{
620 			szMasks += " ";
621 			iCurCharsPerLine--;
622 		}
623 
624 		szMasks += pEntry->szMask;
625 		iCurCharsPerLine -= pEntry->szMask.size();
626 
627 		iCurModeChangesPerLine--;
628 		if(iCurModeChangesPerLine < 0 || iCurCharsPerLine < 0)
629 		{
630 			connection()->sendFmtData("MODE %s -%s %s", szTarget.data(), szFlags.data(), connection()->encodeText(szMasks).data());
631 
632 			// move back the iterator by one position
633 			// WARNING: this could lead to an infinite loop if a single specified mode
634 			// change is longer than iMaxCharsPerLine
635 			pEntry = *--i;
636 
637 			// reset cycle variables
638 			iCurCharsPerLine = iMaxCharsPerLine;
639 			iCurModeChangesPerLine = iMaxModeChangesPerLine;
640 			szFlags = "";
641 			szMasks = "";
642 		}
643 		else
644 		{
645 			++i;
646 		}
647 	}
648 
649 	if(iCurModeChangesPerLine != iMaxModeChangesPerLine)
650 		connection()->sendFmtData("MODE %s -%s %s", szTarget.data(), szFlags.data(), connection()->encodeText(szMasks).data());
651 }
652 
myIconPtr()653 QPixmap * KviChannelWindow::myIconPtr()
654 {
655 	return g_pIconManager->getSmallIcon((m_iStateFlags & DeadChan) ? KviIconManager::DeadChannel : KviIconManager::Channel);
656 }
657 
resizeEvent(QResizeEvent *)658 void KviChannelWindow::resizeEvent(QResizeEvent *)
659 {
660 	int iHeight = m_pInput->heightHint();
661 	int iHeight2 = m_pTopicWidget->sizeHint().height();
662 	m_pButtonBox->setGeometry(0, 0, width(), iHeight2);
663 	m_pSplitter->setGeometry(0, iHeight2, width(), height() - (iHeight + iHeight2));
664 	m_pInput->setGeometry(0, height() - iHeight, width(), iHeight);
665 }
666 
sizeHint() const667 QSize KviChannelWindow::sizeHint() const
668 {
669 	QSize ret(m_pSplitter->sizeHint().width(),
670 	    m_pIrcView->sizeHint().height() + m_pInput->heightHint() + m_pButtonBox->sizeHint().height());
671 	return ret;
672 }
673 
setChannelMode(char cMode,bool bAdd)674 void KviChannelWindow::setChannelMode(char cMode, bool bAdd)
675 {
676 	if(bAdd)
677 	{
678 		if(!(m_szChannelMode.contains(cMode)))
679 			m_szChannelMode.append(cMode);
680 	}
681 	else
682 	{
683 		if(m_szChannelMode.contains(cMode))
684 			m_szChannelMode.replace(cMode, "");
685 	}
686 	updateModeLabel();
687 	updateCaption();
688 }
689 
setChannelModeWithParam(char cMode,QString & szParam)690 void KviChannelWindow::setChannelModeWithParam(char cMode, QString & szParam)
691 {
692 	if(szParam.isEmpty())
693 		m_szChannelParameterModes.erase(cMode);
694 	else
695 		m_szChannelParameterModes[cMode] = szParam;
696 	updateModeLabel();
697 	updateCaption();
698 }
699 
addHighlightedUser(const QString & szNick)700 void KviChannelWindow::addHighlightedUser(const QString & szNick)
701 {
702 	if(!m_pUserListView->findEntry(szNick) || m_pTmpHighLighted->contains(szNick, Qt::CaseInsensitive))
703 		return;
704 
705 	m_pTmpHighLighted->append(szNick);
706 }
707 
removeHighlightedUser(const QString & szNick)708 void KviChannelWindow::removeHighlightedUser(const QString & szNick)
709 {
710 	m_pTmpHighLighted->removeOne(szNick);
711 }
712 
getChannelModeString(QString & szBuffer)713 void KviChannelWindow::getChannelModeString(QString & szBuffer)
714 {
715 	szBuffer = m_szChannelMode;
716 
717 	//add modes that use a parameter
718 	for(auto iter : m_szChannelParameterModes)
719 		szBuffer.append(QChar(iter.first));
720 }
721 
getChannelModeStringWithEmbeddedParams()722 QString KviChannelWindow::getChannelModeStringWithEmbeddedParams()
723 {
724 	QString szBuffer = m_szChannelMode;
725 
726 	//add modes that use a parameter
727 	for(auto iter : m_szChannelParameterModes)
728 		szBuffer.append(QString(" %1:%2").arg(QChar(iter.first)).arg(iter.second));
729 
730 	return szBuffer;
731 }
732 
setOp(const QString & szNick,bool bOp,bool bIsMe)733 bool KviChannelWindow::setOp(const QString & szNick, bool bOp, bool bIsMe)
734 {
735 	bool bRet = m_pUserListView->setOp(szNick, bOp);
736 	if(bIsMe)
737 		emit opStatusChanged();
738 	return bRet;
739 }
740 
setDeadChan()741 void KviChannelWindow::setDeadChan()
742 {
743 	m_iStateFlags |= DeadChan;
744 	m_iStateFlags &= ~(NoCloseOnPart | SentSyncWhoRequest);
745 
746 	m_pUserListView->enableUpdates(false);
747 	m_pUserListView->partAll();
748 	m_pUserListView->enableUpdates(true);
749 	m_pUserListView->setUserDataBase(nullptr);
750 
751 	//clear all mask editors
752 	for(auto & iter : m_ListEditors)
753 		iter.second->clear();
754 
755 	//clear all mask lists (eg bans)
756 	for(auto & iter : m_ModeLists)
757 		iter.second.clear();
758 
759 	m_ModeLists.clear();
760 	m_szSentModeRequests.clear();
761 
762 	m_pTopicWidget->reset();
763 
764 	qDeleteAll(m_lActionHistory);
765 	m_lActionHistory.clear();
766 	m_uActionHistoryHotActionCount = 0;
767 
768 	m_szChannelMode = "";
769 	m_szChannelParameterModes.clear();
770 
771 	// this should be moved to irc context!
772 	if(connection())
773 		connection()->unregisterChannel(this);
774 	if(context())
775 		context()->registerDeadChannel(this);
776 
777 	setType(KviWindow::DeadChannel);
778 
779 	updateIcon();
780 	updateModeLabel();
781 	updateCaption();
782 }
783 
setAliveChan()784 void KviChannelWindow::setAliveChan()
785 {
786 	// Rise like phoenix!
787 	m_iStateFlags = 0;
788 	setType(KviWindow::Channel);
789 	m_pUserListView->setUserDataBase(connection()->userDataBase());
790 	m_joinTime = QDateTime::currentDateTime();
791 	if(context())
792 		context()->unregisterDeadChannel(this);
793 	if(connection())
794 		connection()->registerChannel(this);
795 	// Update log file name
796 	if(m_pIrcView->isLogging())
797 		m_pIrcView->startLogging();
798 
799 	updateIcon();
800 	updateCaption();
801 	m_pTopicWidget->reset(); // reset the topic (fixes bug #20 signaled by Klaus Weidenbach)
802 
803 	// refresh all open mask editors
804 	for(auto & iter : m_ListEditors)
805 	{
806 		char cMode = iter.second->flag();
807 		m_szSentModeRequests.append(cMode);
808 
809 		if(connection())
810 		{
811 			QByteArray szName = connection()->encodeText(m_szName);
812 			connection()->sendFmtData("MODE %s %c", szName.data(), cMode);
813 		}
814 	}
815 }
816 
getTalkingUsersStats(QString & szBuffer,QStringList & list,bool bPast)817 void KviChannelWindow::getTalkingUsersStats(QString & szBuffer, QStringList & list, bool bPast)
818 {
819 	if(list.count() < 1)
820 		return;
821 
822 	if(list.count() == 1)
823 	{
824 		szBuffer += "<b>";
825 		szBuffer += KviQString::toHtmlEscaped(list.first());
826 		szBuffer += "</b>";
827 		szBuffer += " ";
828 		szBuffer += bPast ? __tr2qs("said something recently") : __tr2qs("is talking");
829 	}
830 	else if(list.count() == 2)
831 	{
832 		szBuffer += "<b>";
833 		szBuffer += KviQString::toHtmlEscaped(list.first());
834 		szBuffer += "</b> ";
835 		szBuffer += __tr2qs("and");
836 		szBuffer += " <b>";
837 		list.erase(list.begin());
838 		szBuffer += KviQString::toHtmlEscaped(list.first());
839 		szBuffer += "</b> ";
840 		szBuffer += bPast ? __tr2qs("were talking recently") : __tr2qs("are talking");
841 	}
842 	else
843 	{
844 		szBuffer += "<b>";
845 		szBuffer += KviQString::toHtmlEscaped(list.first());
846 		szBuffer += "</b>, <b>";
847 		list.erase(list.begin());
848 		szBuffer += KviQString::toHtmlEscaped(list.first());
849 		if(list.count() == 2)
850 		{
851 			szBuffer += "</b> ";
852 			szBuffer += __tr2qs("and");
853 			szBuffer += " <b>";
854 			list.erase(list.begin());
855 			szBuffer += KviQString::toHtmlEscaped(list.first());
856 			szBuffer += "</b>";
857 		}
858 		else
859 		{
860 			// (list.count() - 1) is > 1
861 			szBuffer += "</b> ";
862 			szBuffer += __tr2qs("and another %1 users").arg(list.count() - 1);
863 		}
864 		szBuffer += " ";
865 		szBuffer += bPast ? __tr2qs("were talking recently") : __tr2qs("are talking");
866 	}
867 }
868 
getWindowListTipText(QString & szBuffer)869 void KviChannelWindow::getWindowListTipText(QString & szBuffer)
870 {
871 	static QString szHtmlBold("<b>");
872 	static QString szHtmlTab("&nbsp;");
873 	static QString szHtmlBoldEnd("</b> ");
874 	static QString p5(" (");
875 	// p6 == p4
876 	static QString p7(" (");
877 	static QString p8(": ");
878 	static QString p9(")");
879 	static QString p10("<br>");
880 
881 	static QString szEndOfDoc = "</table></body></html>";
882 	static QString szEndOfFontBoldRow = END_TABLE_BOLD_ROW;
883 	static QString szRowStart = "<tr><td>";
884 	static QString szRowEnd = "</td></tr>";
885 
886 	szBuffer = "<html><body><table style=\"white-space: pre\">" START_TABLE_BOLD_ROW;
887 
888 	if(m_iStateFlags & DeadChan)
889 	{
890 		szBuffer += __tr2qs("Dead channel");
891 		szBuffer += szEndOfFontBoldRow;
892 		szBuffer += szEndOfDoc;
893 		return;
894 	}
895 
896 	KviUserListViewUserStats s;
897 	m_pUserListView->userStats(&s);
898 
899 	szBuffer += KviQString::toHtmlEscaped(m_szPlainTextCaption);
900 	szBuffer += szEndOfFontBoldRow;
901 
902 	szBuffer += szRowStart;
903 
904 	QString szOp = __tr2qs("operator");
905 	QString szOps = __tr2qs("operators");
906 
907 
908 	szBuffer += szHtmlTab;
909 	szBuffer += szHtmlBold;
910 
911 	QString szNum;
912 
913 	szNum.setNum(s.uActive);
914 	szBuffer += szNum;
915 
916 	szBuffer += szHtmlBoldEnd;
917 	szBuffer += (s.uActive == 1 ? __tr2qs("active user") : __tr2qs("active users"));
918 
919 	szBuffer += p5;
920 	szBuffer += szHtmlBold;
921 
922 	szNum.setNum(s.uActiveOp);
923 
924 	szBuffer += szNum;
925 	szBuffer += szHtmlBoldEnd;
926 	szBuffer += (s.uActiveOp == 1 ? szOp : szOps);
927 
928 	szBuffer += p9;
929 
930 	szBuffer += p10;
931 
932 	szBuffer += szHtmlTab;
933 	szBuffer += szHtmlBold;
934 
935 	szNum.setNum(s.uHot);
936 	szBuffer += szNum;
937 
938 	szBuffer += szHtmlBoldEnd;
939 	szBuffer += (s.uHot == 1 ? __tr2qs("hot user") : __tr2qs("hot users"));
940 
941 	szBuffer += p5;
942 	szBuffer += szHtmlBold;
943 
944 	szNum.setNum(s.uHotOp);
945 
946 	szBuffer += szNum;
947 	szBuffer += szHtmlBoldEnd;
948 	szBuffer += (s.uHotOp == 1 ? szOp : szOps);
949 
950 	szBuffer += p9;
951 
952 
953 	szBuffer += szRowEnd;
954 	szBuffer += szRowStart;
955 
956 
957 	if(s.uIrcOp > 0)
958 	{
959 		szBuffer += szHtmlTab;
960 		szBuffer += szHtmlBold;
961 		szNum.setNum(s.uIrcOp);
962 		szBuffer += szNum;
963 		szBuffer += szHtmlBoldEnd;
964 		szBuffer += (s.uIrcOp == 1 ? __tr2qs("IRC operator") : __tr2qs("IRC operators"));
965 		szBuffer += p10;
966 	}
967 
968 	if(s.uChanOwner > 0)
969 	{
970 		szBuffer += szHtmlTab;
971 		szBuffer += szHtmlBold;
972 		szNum.setNum(s.uChanOwner);
973 		szBuffer += szNum;
974 		szBuffer += szHtmlBoldEnd;
975 		szBuffer += (s.uChanOwner == 1 ? __tr2qs("channel owner") : __tr2qs("channel owners"));
976 		szBuffer += p10;
977 	}
978 
979 	if(s.uChanAdmin > 0)
980 	{
981 		szBuffer += szHtmlTab;
982 		szBuffer += szHtmlBold;
983 		szNum.setNum(s.uChanAdmin);
984 		szBuffer += szNum;
985 		szBuffer += szHtmlBoldEnd;
986 		szBuffer += (s.uChanAdmin == 1 ? __tr2qs("channel administrator") : __tr2qs("channel administrators"));
987 		szBuffer += p10;
988 	}
989 
990 	if(s.uOp > 0)
991 	{
992 		szBuffer += szHtmlTab;
993 		szBuffer += szHtmlBold;
994 		szNum.setNum(s.uOp);
995 		szBuffer += szNum;
996 		szBuffer += szHtmlBoldEnd;
997 		szBuffer += (s.uOp == 1 ? szOp : szOps);
998 		szBuffer += p10;
999 	}
1000 
1001 	if(s.uHalfOp > 0)
1002 	{
1003 		szBuffer += szHtmlTab;
1004 		szBuffer += szHtmlBold;
1005 		szNum.setNum(s.uHalfOp);
1006 		szBuffer += szNum;
1007 		szBuffer += szHtmlBoldEnd;
1008 		szBuffer += (s.uHalfOp == 1 ? __tr2qs("half-operator") : __tr2qs("half-operators"));
1009 		szBuffer += p10;
1010 	}
1011 
1012 	if(s.uVoiced > 0)
1013 	{
1014 		szBuffer += szHtmlTab;
1015 		szBuffer += szHtmlBold;
1016 		szNum.setNum(s.uVoiced);
1017 		szBuffer += szNum;
1018 		szBuffer += szHtmlBoldEnd;
1019 		szBuffer += (s.uVoiced == 1 ? __tr2qs("voiced user") : __tr2qs("voiced users"));
1020 		szBuffer += p10;
1021 	}
1022 
1023 	if(s.uUserOp > 0)
1024 	{
1025 		szBuffer += szHtmlTab;
1026 		szBuffer += szHtmlBold;
1027 		szNum.setNum(s.uUserOp);
1028 		szBuffer += szNum;
1029 		szBuffer += szHtmlBoldEnd;
1030 		szBuffer += (s.uUserOp == 1 ? __tr2qs("user-operator") : __tr2qs("user-operators"));
1031 		szBuffer += p10;
1032 	}
1033 
1034 	szBuffer += szHtmlTab;
1035 	szBuffer += szHtmlBold;
1036 	szNum.setNum(s.uTotal);
1037 	szBuffer += szNum;
1038 	szBuffer += szHtmlBoldEnd;
1039 	szBuffer += (s.uTotal == 1 ? __tr2qs("user total") : __tr2qs("users total"));
1040 
1041 	szBuffer += szRowEnd;
1042 
1043 	KviChannelActivityStats cas;
1044 	getChannelActivityStats(&cas);
1045 
1046 	//FIXME hardcoding styles sucks
1047 	if(cas.lTalkingUsers.count() > 0)
1048 	{
1049 		if((cas.lTalkingUsers.count() < 3) && (cas.lWereTalkingUsers.count() > 0))
1050 		{
1051 			szBuffer += R"(<tr><td bgcolor="#E0E0E0"><font color="#000000">)";
1052 			getTalkingUsersStats(szBuffer, cas.lWereTalkingUsers, true);
1053 			szBuffer += "</font>";
1054 			szBuffer += szRowEnd;
1055 		}
1056 		szBuffer += R"(<tr><td bgcolor="#E0E0E0"><font color="#000000">)";
1057 		getTalkingUsersStats(szBuffer, cas.lTalkingUsers, false);
1058 		szBuffer += "</font>";
1059 		szBuffer += szRowEnd;
1060 	}
1061 	else
1062 	{
1063 		if(cas.lWereTalkingUsers.count() > 0)
1064 		{
1065 			szBuffer += R"(<tr><td bgcolor="#E0E0E0"><font color="#000000">)";
1066 			getTalkingUsersStats(szBuffer, cas.lWereTalkingUsers, true);
1067 			szBuffer += "</font>";
1068 			szBuffer += szRowEnd;
1069 		}
1070 	}
1071 
1072 	szBuffer += R"(<tr><td bgcolor="#E0E0E0"><b><font color="#000000">)";
1073 
1074 	if(cas.dActionsPerMinute < 0.1)
1075 		szBuffer += __tr2qs("No activity");
1076 	else if(cas.dActionsPerMinute < 0.3)
1077 		szBuffer += __tr2qs("Minimal activity");
1078 	else if(cas.dActionsPerMinute < 1.0)
1079 		szBuffer += __tr2qs("Very low activity");
1080 	else if(cas.dActionsPerMinute < 3.0)
1081 		szBuffer += cas.bStatsInaccurate ? __tr2qs("Might be low activity") : __tr2qs("Low activity");
1082 	else if(cas.dActionsPerMinute < 10.0)
1083 		szBuffer += cas.bStatsInaccurate ? __tr2qs("Might be medium activity") : __tr2qs("Medium activity");
1084 	else if(cas.dActionsPerMinute < 30.0)
1085 		szBuffer += cas.bStatsInaccurate ? __tr2qs("Might be high activity") : __tr2qs("High activity");
1086 	else if(cas.dActionsPerMinute < 60.0)
1087 		szBuffer += cas.bStatsInaccurate ? __tr2qs("Might be very high activity") : __tr2qs("Very high activity");
1088 	else
1089 		szBuffer += cas.bStatsInaccurate ? __tr2qs("Might be flooded with messages") : __tr2qs("Flooded with messages");
1090 
1091 	if(cas.dActionsPerMinute >= 0.1)
1092 	{
1093 		QString szNum;
1094 		szNum.sprintf(" [%u%% ", cas.uHotActionPercent);
1095 		szBuffer += szNum;
1096 		szBuffer += __tr2qs("human");
1097 		szBuffer += "]";
1098 	}
1099 
1100 	szBuffer += "</font></b></td></tr>";
1101 
1102 	szBuffer += szEndOfDoc;
1103 }
1104 
fillCaptionBuffers()1105 void KviChannelWindow::fillCaptionBuffers()
1106 {
1107 	if(!connection())
1108 	{
1109 		QString szDead = __tr2qs("[Dead channel]");
1110 
1111 		m_szNameWithUserFlag = m_szName;
1112 
1113 		m_szPlainTextCaption = m_szName;
1114 		m_szPlainTextCaption += " : ";
1115 		m_szPlainTextCaption += szDead;
1116 
1117 		return;
1118 	}
1119 
1120 	char uFlag = getUserFlag(connection()->currentNickName());
1121 
1122 	if(uFlag)
1123 	{
1124 		m_szNameWithUserFlag = QChar(uFlag);
1125 		m_szNameWithUserFlag += m_szName;
1126 	}
1127 	else
1128 	{
1129 		m_szNameWithUserFlag = m_szName;
1130 	}
1131 
1132 	QString szChanMode;
1133 	getChannelModeString(szChanMode);
1134 
1135 	m_szPlainTextCaption = m_szNameWithUserFlag;
1136 	if(!szChanMode.isEmpty())
1137 	{
1138 		m_szPlainTextCaption += " (+";
1139 		m_szPlainTextCaption += szChanMode;
1140 		m_szPlainTextCaption += QChar(')');
1141 	}
1142 
1143 	QString szNickOnServer = QChar('[');
1144 	szNickOnServer += connection()->currentNickName();
1145 	szNickOnServer += __tr2qs(" on ");
1146 	szNickOnServer += connection()->currentServerName();
1147 	szNickOnServer += QChar(']');
1148 
1149 	m_szPlainTextCaption += QChar(' ');
1150 	m_szPlainTextCaption += szNickOnServer;
1151 }
1152 
ownMessage(const QString & szBuffer,bool bUserFeedback)1153 void KviChannelWindow::ownMessage(const QString & szBuffer, bool bUserFeedback)
1154 {
1155 	if(!connection())
1156 		return;
1157 
1158 	//my full mask as seen by other users
1159 	QString szMyFullMask = connection()->userInfo()->nickName() + "!" + connection()->userInfo()->userName() + "@" + connection()->userInfo()->hostName();
1160 	QByteArray myFullMask = connection()->encodeText(szMyFullMask); //target name
1161 	QByteArray name = connection()->encodeText(windowName());       //message
1162 	QByteArray data = encodeText(szBuffer);
1163 	const char * pData = data.data();
1164 	/* max length of a PRIVMSG text. Max buffer length for our send is 512 byte, but we have to
1165 	* remember that the server will prepend to the message our full mask and truncate the resulting
1166 	* at 512 bytes again..
1167 	* So we have:
1168 	* :NickName!~ident@hostname.tld PRIVMSG #channel :text of message(CrLf)
1169 	* NickName!~ident@hostname.tld#channeltext of message
1170 	* 512(rfc) -2(CrLf) -2(:) -3(spaces) -7(PRIVMSG) = 498
1171 	* usable bytes, excluding our full mask and the target name.
1172 	*/
1173 	int iMaxMsgLen = 498 - name.length() - myFullMask.length();
1174 
1175 	// our copy of the message
1176 	QString szTmpBuffer(szBuffer);
1177 
1178 	if(!pData)
1179 		return;
1180 
1181 #ifdef COMPILE_CRYPT_SUPPORT
1182 	if(cryptSessionInfo())
1183 	{
1184 		if(cryptSessionInfo()->m_bDoEncrypt)
1185 		{
1186 			if(*pData != KviControlCodes::CryptEscape)
1187 			{
1188 				KviCString szEncrypted;
1189 				cryptSessionInfo()->m_pEngine->setMaxEncryptLen(iMaxMsgLen);
1190 				switch(cryptSessionInfo()->m_pEngine->encrypt(pData, szEncrypted))
1191 				{
1192 					case KviCryptEngine::Encrypted:
1193 						if(!connection()->sendFmtData("PRIVMSG %s :%s", name.data(), szEncrypted.ptr()))
1194 							return;
1195 						if(bUserFeedback)
1196 							m_pConsole->outputPrivmsg(this, KVI_OUT_OWNPRIVMSGCRYPTED,
1197 							    QString(), QString(), QString(), szBuffer, KviConsoleWindow::NoNotifications);
1198 						break;
1199 					case KviCryptEngine::Encoded:
1200 					{
1201 						if(!connection()->sendFmtData("PRIVMSG %s :%s", name.data(), szEncrypted.ptr()))
1202 							return;
1203 						if(bUserFeedback)
1204 						{
1205 							// ugly, but we must redecode here
1206 							QString szRedecoded = decodeText(szEncrypted.ptr());
1207 							m_pConsole->outputPrivmsg(this, KVI_OUT_OWNPRIVMSG,
1208 							    QString(), QString(), QString(), szRedecoded, KviConsoleWindow::NoNotifications);
1209 						}
1210 					}
1211 					break;
1212 					default: // also case KviCryptEngine::EncryptError
1213 					{
1214 						QString szEngineError = cryptSessionInfo()->m_pEngine->lastError();
1215 						output(KVI_OUT_SYSTEMERROR,
1216 						    __tr2qs("The encryption engine was unable to encrypt the current message (%Q): %Q, no data sent to the server"),
1217 						    &szBuffer, &szEngineError);
1218 					}
1219 					break;
1220 				}
1221 				userAction(connection()->currentNickName(), KVI_USERACTION_PRIVMSG);
1222 				return;
1223 			}
1224 			else
1225 			{
1226 				//eat the escape code
1227 				pData++;
1228 				szTmpBuffer.remove(0, 1);
1229 				//let the normal function do it
1230 			}
1231 		}
1232 	}
1233 #endif
1234 
1235 	if(data.length() > iMaxMsgLen)
1236 	{
1237 		/* Multi message; we want to split the message, preferably on a word boundary,
1238 		 * and send each message part as a different PRIVMSG
1239 		 * Due to encoding stuff, this is frikin'time eater
1240 		 */
1241 		QTextEncoder * pEncoder = makeEncoder(); // our temp encoder
1242 		QByteArray szTmp;                        // used to calculate the length of an encoded message
1243 		int iPos;                                // contains the index where to truncate szTmpBuffer
1244 		int iC;                                  // cycles counter (debugging/profiling purpose)
1245 		float fPosDiff;                          // optimization internal; aggressivity factor
1246 		QString szCurSubString;                  // truncated parts as reported to the user
1247 
1248 		// run until we've something remaining in the message
1249 		while(szTmpBuffer.length())
1250 		{
1251 			// init counters
1252 			iPos = szTmpBuffer.length();
1253 			iC = 0;
1254 
1255 			// first part (optimization): quickly find an high index that is _surely_lesser_
1256 			// than the correct one
1257 			while(true)
1258 			{
1259 				iC++;
1260 				szTmp = pEncoder->fromUnicode(szTmpBuffer.left(iPos));
1261 				if(szTmp.length() <= iMaxMsgLen)
1262 					break;
1263 				//if szTmp.length() == 0 we already have break'ed out from here,
1264 				// so we can safely use it as a divisor
1265 				fPosDiff = (float)iMaxMsgLen / szTmp.length();
1266 				iPos = (int)(iPos * fPosDiff); // cut the string at each cycle
1267 				                               //printf("OPTIMIZATION: fPosDiff %f, iPos %d\n", fPosDiff, iPos);
1268 			}
1269 			//printf("Multi message: %d optimization cyles", iC);
1270 
1271 			// now, do it the simple way: increment our index until we perfectly fit into the
1272 			// available space
1273 			while(true)
1274 			{
1275 				iC++;
1276 
1277 				szTmp = pEncoder->fromUnicode(szTmpBuffer.left(iPos));
1278 
1279 				// perfect match
1280 				if(iPos == szTmpBuffer.length())
1281 					break;
1282 
1283 				if(szTmp.length() > iMaxMsgLen)
1284 				{
1285 					// overflowed.. last one was the good one
1286 					iPos--;
1287 					szTmp = pEncoder->fromUnicode(szTmpBuffer.left(iPos));
1288 					break;
1289 				}
1290 				else
1291 				{
1292 					//there's still free space.. add another char
1293 					iPos++;
1294 				}
1295 			}
1296 			//printf(", finished at %d cycles, truncated at pos %d\n", iC, iPos);
1297 			//prepare the feedback string for the user
1298 			szCurSubString = szTmpBuffer.left(iPos);
1299 
1300 			//send the string to the server
1301 			if(connection()->sendFmtData("PRIVMSG %s :%s", name.data(), szTmp.data()))
1302 			{
1303 				//feeedback the user
1304 				if(bUserFeedback)
1305 					m_pConsole->outputPrivmsg(this, KVI_OUT_OWNPRIVMSG, QString(), QString(), QString(), szCurSubString, KviConsoleWindow::NoNotifications);
1306 				userAction(connection()->currentNickName(), KVI_USERACTION_PRIVMSG);
1307 			}
1308 			else
1309 			{
1310 				// skipped a part in this multi message.. we don't want to continue
1311 				return;
1312 			}
1313 
1314 			// remove the sent part of the string
1315 			szTmpBuffer.remove(0, iPos);
1316 			//printf("Sent %d chars, %d remaining in the Qstring\n",iPos, szTmpBuffer.length());
1317 		}
1318 	}
1319 	else
1320 	{
1321 		if(connection()->sendFmtData("PRIVMSG %s :%s", name.data(), pData))
1322 		{
1323 			if(bUserFeedback)
1324 				m_pConsole->outputPrivmsg(this, KVI_OUT_OWNPRIVMSG, QString(), QString(), QString(), szTmpBuffer, KviConsoleWindow::NoNotifications);
1325 			userAction(connection()->currentNickName(), KVI_USERACTION_PRIVMSG);
1326 		}
1327 	}
1328 }
1329 
ownAction(const QString & szBuffer)1330 void KviChannelWindow::ownAction(const QString & szBuffer)
1331 {
1332 	if(!connection())
1333 		return;
1334 
1335 	QString szTmpBuffer;
1336 
1337 	//see bug ticket #220
1338 	if(KVI_OPTION_BOOL(KviOption_boolStripMircColorsInUserMessages))
1339 		szTmpBuffer = KviControlCodes::stripControlBytes(szBuffer);
1340 	else
1341 		szTmpBuffer = szBuffer;
1342 
1343 	//my full mask as seen by other users
1344 	QString szMyName = connection()->userInfo()->nickName();
1345 	QString szMyFullMask = szMyName + "!" + connection()->userInfo()->userName() + "@" + connection()->userInfo()->hostName();
1346 	QByteArray myFullMask = connection()->encodeText(szMyFullMask); //target name
1347 	QByteArray name = connection()->encodeText(windowName());       //message
1348 	QByteArray data = encodeText(szBuffer);
1349 	/* max length of a PRIVMSG text. Max buffer length for our send is 512 byte, but we have to
1350 	* remember that the server will prepend to the message our full mask and truncate the resulting
1351 	* at 512 bytes again..
1352 	* So we have:
1353 	* :NickName!~ident@hostname.tld PRIVMSG #channel :text of message(CrLf)
1354 	* NickName!~ident@hostname.tld#channeltext of message
1355 	* 512(rfc) -2(CrLf) -2(:) -3(spaces) -7(PRIVMSG) -8(\x01ACTION\x01) = 490
1356 	* usable bytes, excluding our full mask and the target name.
1357 	*/
1358 	int iMaxMsgLen = 490 - name.length() - myFullMask.length();
1359 
1360 	if(KVS_TRIGGER_EVENT_2_HALTED(KviEvent_OnMeAction, this, szTmpBuffer, windowName()))
1361 		return;
1362 
1363 #ifdef COMPILE_CRYPT_SUPPORT
1364 	if(cryptSessionInfo() && cryptSessionInfo()->m_bDoEncrypt)
1365 	{
1366 		if(szTmpBuffer[0] != KviControlCodes::CryptEscape)
1367 		{
1368 			KviCString szEncrypted;
1369 			cryptSessionInfo()->m_pEngine->setMaxEncryptLen(iMaxMsgLen);
1370 			switch(cryptSessionInfo()->m_pEngine->encrypt(data.data(), szEncrypted))
1371 			{
1372 				case KviCryptEngine::Encrypted:
1373 				{
1374 					if(!connection()->sendFmtData("PRIVMSG %s :%cACTION %s%c", name.data(), 0x01, szEncrypted.ptr(), 0x01))
1375 						return;
1376 
1377 					QString szBuf = "\r!nc\r";
1378 					szBuf += szMyName;
1379 					szBuf += "\r ";
1380 					szBuf += szTmpBuffer;
1381 					outputMessage(KVI_OUT_OWNACTIONCRYPTED, szBuf);
1382 				}
1383 				break;
1384 				case KviCryptEngine::Encoded:
1385 				{
1386 					if(!connection()->sendFmtData("PRIVMSG %s :%cACTION %s%c", name.data(), 0x01, szEncrypted.ptr(), 0x01))
1387 						return;
1388 
1389 					// ugly, but we must redecode here
1390 					QString szRedecoded = decodeText(szEncrypted.ptr());
1391 
1392 					QString szBuf = "\r!nc\r";
1393 					szBuf += szMyName;
1394 					szBuf += "\r ";
1395 					szBuf += szRedecoded;
1396 					outputMessage(KVI_OUT_OWNACTIONCRYPTED, szBuf);
1397 				}
1398 				break;
1399 				default:
1400 				{
1401 					QString szEngineError = cryptSessionInfo()->m_pEngine->lastError();
1402 					output(KVI_OUT_SYSTEMERROR,
1403 					    __tr2qs("The encryption engine was unable to encrypt the current message (%Q): %Q, no data sent to the server"),
1404 					    &szBuffer, &szEngineError);
1405 				}
1406 			}
1407 			userAction(szMyName, KVI_USERACTION_ACTION);
1408 			return;
1409 		}
1410 		else
1411 		{
1412 			//eat the escape code
1413 			szTmpBuffer.remove(0, 1);
1414 			data = encodeText(szTmpBuffer);
1415 		}
1416 	}
1417 #endif
1418 
1419 	if(data.length() > iMaxMsgLen)
1420 	{
1421 		/* Multi message; we want to split the message, preferably on a word boundary,
1422 		 * and send each message part as a different PRIVMSG
1423 		 * Due to encoding stuff, this is frikin'time eater
1424 		 */
1425 		QTextEncoder * pEncoder = makeEncoder(); // our temp encoder
1426 		QByteArray szTmp;                        // used to calculate the length of an encoded message
1427 		int iPos;                                // contains the index where to truncate szTmpBuffer
1428 		int iC;                                  // cycles counter (debugging/profiling purpose)
1429 		float fPosDiff;                          // optimization internal; aggressivity factor
1430 		QString szCurSubString;                  // truncated parts as reported to the user
1431 
1432 		// run until we've something remaining in the message
1433 		while(szTmpBuffer.length())
1434 		{
1435 			// init counters
1436 			iPos = szTmpBuffer.length();
1437 			iC = 0;
1438 
1439 			// first part (optimization): quickly find an high index that is _surely_lesser_
1440 			// than the correct one
1441 			while(true)
1442 			{
1443 				iC++;
1444 				szTmp = pEncoder->fromUnicode(szTmpBuffer.left(iPos));
1445 				if(szTmp.length() <= iMaxMsgLen)
1446 					break;
1447 				//if szTmp.length() == 0 we already have break'ed out from here,
1448 				// so we can safely use it as a divisor
1449 				fPosDiff = (float)iMaxMsgLen / szTmp.length();
1450 				iPos = (int)(iPos * fPosDiff); // cut the string at each cycle
1451 				                               //printf("OPTIMIZATION: fPosDiff %f, iPos %d\n", fPosDiff, iPos);
1452 			}
1453 			//printf("Multi message: %d optimization cyles", iC);
1454 			// now, do it the simple way: increment our index until we perfectly fit into the
1455 			// available space
1456 			while(true)
1457 			{
1458 				iC++;
1459 
1460 				szTmp = pEncoder->fromUnicode(szTmpBuffer.left(iPos));
1461 
1462 				// perfect match
1463 				if(iPos == szTmpBuffer.length())
1464 					break;
1465 
1466 				if(szTmp.length() > iMaxMsgLen)
1467 				{
1468 					// overflowed.. last one was the good one
1469 					iPos--;
1470 					szTmp = pEncoder->fromUnicode(szTmpBuffer.left(iPos));
1471 					break;
1472 				}
1473 				else
1474 				{
1475 					//there's still free space.. add another char
1476 					iPos++;
1477 				}
1478 			}
1479 			//printf(", finished at %d cycles, truncated at pos %d\n", iC, iPos);
1480 			//prepare the feedback string for the user
1481 			szCurSubString = szTmpBuffer.left(iPos);
1482 			//send the string to the server
1483 			if(connection()->sendFmtData("PRIVMSG %s :%cACTION %s%c", name.data(), 0x01, szTmp.data(), 0x01))
1484 			{
1485 				//feeedback the user
1486 				QString szBuf = "\r!nc\r";
1487 				szBuf += connection()->currentNickName();
1488 				szBuf += "\r ";
1489 				szBuf += szTmp.data();
1490 				outputMessage(KVI_OUT_OWNACTION, szBuf);
1491 				userAction(connection()->currentNickName(), KVI_USERACTION_ACTION);
1492 			}
1493 			else
1494 			{
1495 				// skipped a part in this multi message.. we don't want to continue
1496 				return;
1497 			}
1498 
1499 			// remove the sent part of the string
1500 			szTmpBuffer.remove(0, iPos);
1501 			//printf("Sent %d chars, %d remaining in the Qstring\n",iPos, szTmpBuffer.length());
1502 		}
1503 	}
1504 	else
1505 	{
1506 		if(!connection()->sendFmtData("PRIVMSG %s :%cACTION %s%c", name.data(), 0x01, data.data(), 0x01))
1507 			return;
1508 
1509 		QString szBuf = "\r!nc\r";
1510 		szBuf += connection()->currentNickName();
1511 		szBuf += "\r ";
1512 		szBuf += szTmpBuffer;
1513 		outputMessage(KVI_OUT_OWNACTION, szBuf);
1514 		userAction(connection()->currentNickName(), KVI_USERACTION_ACTION);
1515 	}
1516 }
1517 
nickChange(const QString & szOldNick,const QString & szNewNick)1518 bool KviChannelWindow::nickChange(const QString & szOldNick, const QString & szNewNick)
1519 {
1520 	bool bWasHere = m_pUserListView->nickChange(szOldNick, szNewNick);
1521 	if(bWasHere)
1522 	{
1523 		channelAction(szNewNick, KVI_USERACTION_NICK, kvi_getUserActionTemperature(KVI_USERACTION_NICK));
1524 		// update any nick/mask editor; by now we limit to the q and a mode
1525 		if(m_ModeLists.count('q'))
1526 			setModeInList('q', szOldNick, false, QString(), 0, szNewNick);
1527 		if(m_ModeLists.count('a'))
1528 			setModeInList('a', szOldNick, false, QString(), 0, szNewNick);
1529 	}
1530 	return bWasHere;
1531 }
1532 
part(const QString & szNick)1533 bool KviChannelWindow::part(const QString & szNick)
1534 {
1535 	bool bWasHere = m_pUserListView->part(szNick);
1536 	if(bWasHere)
1537 	{
1538 		channelAction(szNick, KVI_USERACTION_PART, kvi_getUserActionTemperature(KVI_USERACTION_PART));
1539 		// update any nick/mask editor; by now we limit to the q and a mode
1540 		if(m_ModeLists.count('q'))
1541 			setModeInList('q', szNick, false, QString(), 0);
1542 		if(m_ModeLists.count('a'))
1543 			setModeInList('a', szNick, false, QString(), 0);
1544 	}
1545 	return bWasHere;
1546 }
1547 
activityMeter(unsigned int * puActivityValue,unsigned int * puActivityTemperature)1548 bool KviChannelWindow::activityMeter(unsigned int * puActivityValue, unsigned int * puActivityTemperature)
1549 {
1550 	fixActionHistory();
1551 
1552 	unsigned int uHotActionPercent;
1553 	double dActionsPerMinute;
1554 
1555 	if(m_lActionHistory.count() < 1)
1556 	{
1557 		// nothing is happening
1558 		uHotActionPercent = 0;
1559 		dActionsPerMinute = 0;
1560 	}
1561 	else
1562 	{
1563 		kvi_time_t tNow = kvi_unixTime();
1564 
1565 		KviChannelAction * pAction = m_lActionHistory.last();
1566 
1567 		double dSpan = (double)(tNow - pAction->tTime);
1568 
1569 		if(m_lActionHistory.count() < KVI_CHANNEL_ACTION_HISTORY_MAX_COUNT)
1570 		{
1571 			if(m_joinTime.secsTo(QDateTime::currentDateTime()) < KVI_CHANNEL_ACTION_HISTORY_MAX_TIMESPAN)
1572 			{
1573 				// we can't exactly estimate
1574 				if(dSpan < 60.0)
1575 					dSpan = 60.0;
1576 			}
1577 			else
1578 			{
1579 				// there are less actions at all or they have been pushed out because of the timespan
1580 				dSpan = KVI_CHANNEL_ACTION_HISTORY_MAX_TIMESPAN;
1581 			}
1582 		} // else the actions have been pushed out of the history because they were too much
1583 
1584 		if(dSpan > 0.0)
1585 			dActionsPerMinute = (((double)(m_lActionHistory.count())) / (dSpan)) * 60.0;
1586 		else
1587 			dActionsPerMinute = (double)(m_lActionHistory.count()); // ???
1588 
1589 		uHotActionPercent = (m_uActionHistoryHotActionCount * 100) / (m_lActionHistory.count());
1590 	}
1591 
1592 	if(dActionsPerMinute < 0.3)
1593 		*puActivityValue = KviWindow::None;
1594 	else if(dActionsPerMinute < 1.0)
1595 		*puActivityValue = KviWindow::VeryLow;
1596 	else if(dActionsPerMinute < 4.0)
1597 		*puActivityValue = KviWindow::Low;
1598 	else if(dActionsPerMinute < 10.0)
1599 		*puActivityValue = KviWindow::Medium;
1600 	else if(dActionsPerMinute < 30.0)
1601 		*puActivityValue = KviWindow::High;
1602 	else
1603 		*puActivityValue = KviWindow::VeryHigh;
1604 
1605 	if(uHotActionPercent < Ice)
1606 		*puActivityTemperature = KviWindow::Ice;
1607 	else if(uHotActionPercent < VeryCold)
1608 		*puActivityTemperature = KviWindow::VeryCold;
1609 	else if(uHotActionPercent < Cold)
1610 		*puActivityTemperature = KviWindow::Cold;
1611 	else if(uHotActionPercent < Undefined)
1612 		*puActivityTemperature = KviWindow::Undefined;
1613 	else if(uHotActionPercent < Hot)
1614 		*puActivityTemperature = KviWindow::Hot;
1615 	else if(uHotActionPercent < VeryHot)
1616 		*puActivityTemperature = KviWindow::VeryHot;
1617 	else
1618 		*puActivityTemperature = KviWindow::Fire;
1619 
1620 	return true;
1621 }
1622 
channelAction(const QString & szNick,unsigned int uActionType,int iTemperature)1623 void KviChannelWindow::channelAction(const QString & szNick, unsigned int uActionType, int iTemperature)
1624 {
1625 	KviChannelAction * pAction = new KviChannelAction;
1626 	pAction->tTime = kvi_unixTime();
1627 	pAction->uActionType = uActionType;
1628 	pAction->iTemperature = iTemperature;
1629 	pAction->szNick = szNick;
1630 
1631 	if(iTemperature > 0)
1632 		m_uActionHistoryHotActionCount++;
1633 
1634 	m_lActionHistory.append(pAction);
1635 	fixActionHistory();
1636 }
1637 
fixActionHistory()1638 void KviChannelWindow::fixActionHistory()
1639 {
1640 	while(m_lActionHistory.count() > KVI_CHANNEL_ACTION_HISTORY_MAX_COUNT)
1641 		delete m_lActionHistory.takeFirst();
1642 
1643 	if(m_lActionHistory.isEmpty())
1644 		return;
1645 
1646 	KviChannelAction * pAction = m_lActionHistory.last();
1647 	kvi_time_t tMinimum = pAction->tTime - KVI_CHANNEL_ACTION_HISTORY_MAX_TIMESPAN;
1648 
1649 	KviChannelAction * pAct = m_lActionHistory.first();
1650 	while(pAct && (pAct->tTime < tMinimum))
1651 	{
1652 		if(pAct->iTemperature > 0)
1653 			m_uActionHistoryHotActionCount--;
1654 		delete m_lActionHistory.takeFirst();
1655 		pAct = m_lActionHistory.first();
1656 	}
1657 }
1658 
lostUserFocus()1659 void KviChannelWindow::lostUserFocus()
1660 {
1661 	KviWindow::lostUserFocus();
1662 	if(!m_pMessageView)
1663 		return;
1664 	if(m_pMessageView->hasLineMark())
1665 		m_pMessageView->clearLineMark(true);
1666 }
1667 
getChannelActivityStats(KviChannelActivityStats * pStats)1668 void KviChannelWindow::getChannelActivityStats(KviChannelActivityStats * pStats)
1669 {
1670 	fixActionHistory();
1671 
1672 	pStats->uActionCount = m_lActionHistory.count();
1673 	pStats->iAverageActionTemperature = 0;
1674 	pStats->uActionsInTheLastMinute = 0;
1675 	pStats->uHotActionCount = 0;
1676 	pStats->uHotActionPercent = 0;
1677 	pStats->bStatsInaccurate = false;
1678 
1679 	if(pStats->uActionCount < 1)
1680 	{
1681 		// nothing is happening
1682 		pStats->uLastActionTimeSpan = 0;
1683 		pStats->uFirstActionTimeSpan = 0;
1684 		pStats->dActionsPerMinute = 0;
1685 
1686 		return;
1687 	}
1688 
1689 	kvi_time_t tNow = kvi_unixTime();
1690 
1691 	KviChannelAction * pAction = m_lActionHistory.last();
1692 	pStats->uLastActionTimeSpan = tNow - pAction->tTime;
1693 
1694 	pAction = m_lActionHistory.first();
1695 	pStats->uFirstActionTimeSpan = tNow - pAction->tTime;
1696 
1697 	double dSpan = (double)pStats->uFirstActionTimeSpan;
1698 
1699 	if(pStats->uActionCount < KVI_CHANNEL_ACTION_HISTORY_MAX_COUNT)
1700 	{
1701 		if(m_joinTime.secsTo(QDateTime::currentDateTime()) < KVI_CHANNEL_ACTION_HISTORY_MAX_TIMESPAN)
1702 		{
1703 			// we can't exactly estimate
1704 			pStats->bStatsInaccurate = true;
1705 			if(dSpan < 60.0)
1706 				dSpan = 60.0;
1707 		}
1708 		else
1709 		{
1710 			// there are less actions at all or they have been pushed out because of the timespan
1711 			dSpan = KVI_CHANNEL_ACTION_HISTORY_MAX_TIMESPAN;
1712 		}
1713 	} // else the actions have been pushed out of the history because they were too much
1714 
1715 	if(dSpan > 0.0)
1716 		pStats->dActionsPerMinute = (((double)pStats->uActionCount) / (dSpan)) * 60.0;
1717 	else
1718 		pStats->dActionsPerMinute = (double)pStats->uActionCount; // ???
1719 
1720 	kvi_time_t tTwoMinsAgo = tNow;
1721 	tTwoMinsAgo -= 120;
1722 	tNow -= 60;
1723 
1724 	std::set<QString> userDict;
1725 
1726 	pStats->lTalkingUsers.clear();
1727 	pStats->lWereTalkingUsers.clear();
1728 
1729 	for(unsigned i = m_lActionHistory.count(); i-- > 0; )
1730 	{
1731 		pAction = m_lActionHistory[i];
1732 
1733 		if(pAction->tTime >= tNow)
1734 			pStats->uActionsInTheLastMinute++;
1735 
1736 		if(pAction->iTemperature > 0)
1737 			pStats->uHotActionCount++;
1738 
1739 		pStats->iAverageActionTemperature += pAction->iTemperature;
1740 
1741 		if((pAction->uActionType == KVI_USERACTION_PRIVMSG) || (pAction->uActionType == KVI_USERACTION_NOTICE) || (pAction->uActionType == KVI_USERACTION_ACTION))
1742 		{
1743 			if(userDict.count(pAction->szNick) == 0)
1744 			{
1745 				if(isOn(pAction->szNick.toLatin1()))
1746 				{
1747 					if(pAction->tTime >= tTwoMinsAgo)
1748 						pStats->lTalkingUsers.append(pAction->szNick);
1749 					else
1750 						pStats->lWereTalkingUsers.append(pAction->szNick);
1751 
1752 					userDict.insert(pAction->szNick);
1753 				}
1754 			}
1755 		}
1756 	}
1757 
1758 	pStats->iAverageActionTemperature = pStats->iAverageActionTemperature / (int)pStats->uActionCount;
1759 
1760 	pStats->uHotActionPercent = (pStats->uHotActionCount * 100) / pStats->uActionCount;
1761 }
1762 
userAction(const QString & szNick,const QString & szUser,const QString & szHost,unsigned int uActionType)1763 void KviChannelWindow::userAction(const QString & szNick, const QString & szUser, const QString & szHost, unsigned int uActionType)
1764 {
1765 	int iTemperature = kvi_getUserActionTemperature(uActionType);
1766 	channelAction(szNick, uActionType, iTemperature);
1767 	m_pUserListView->userAction(szNick, szUser, szHost, iTemperature);
1768 }
1769 
userAction(const QString & szNick,unsigned int uActionType)1770 void KviChannelWindow::userAction(const QString & szNick, unsigned int uActionType)
1771 {
1772 	int iTemperature = kvi_getUserActionTemperature(uActionType);
1773 	channelAction(szNick, uActionType, iTemperature);
1774 	m_pUserListView->userAction(szNick, iTemperature);
1775 }
1776 
userAction(KviIrcMask * user,unsigned int uActionType)1777 void KviChannelWindow::userAction(KviIrcMask * user, unsigned int uActionType)
1778 {
1779 	int iTemperature = kvi_getUserActionTemperature(uActionType);
1780 	channelAction(user->nick(), uActionType, iTemperature);
1781 	m_pUserListView->userAction(user, iTemperature);
1782 }
1783 
topicSelected(const QString & szTopic)1784 void KviChannelWindow::topicSelected(const QString & szTopic)
1785 {
1786 	if(!connection())
1787 		return;
1788 
1789 	QByteArray encoded = encodeText(szTopic);
1790 	QByteArray name = connection()->encodeText(m_szName);
1791 
1792 #ifdef COMPILE_CRYPT_SUPPORT
1793 	if(encoded.length() <= 0)
1794 	{
1795 		connection()->sendFmtData("TOPIC %s :", name.data());
1796 		return;
1797 	}
1798 
1799 	const char * pcData = encoded.data();
1800 
1801 	QString szTmpTopic(szTopic);
1802 
1803 	if(!pcData)
1804 		return;
1805 
1806 	if(cryptSessionInfo())
1807 	{
1808 		if(cryptSessionInfo()->m_bDoEncrypt)
1809 		{
1810 			if(*pcData != KviControlCodes::CryptEscape)
1811 			{
1812 				KviCString szEncrypted;
1813 				switch(cryptSessionInfo()->m_pEngine->encrypt(pcData, szEncrypted))
1814 				{
1815 					case KviCryptEngine::Encrypted:
1816 					case KviCryptEngine::Encoded:
1817 						connection()->sendFmtData("TOPIC %s :%s", name.data(), szEncrypted.ptr());
1818 						return;
1819 						break;
1820 					default:
1821 						QString szEngineError = cryptSessionInfo()->m_pEngine->lastError();
1822 						output(KVI_OUT_SYSTEMERROR,
1823 						    __tr2qs("The encryption engine was unable to encrypt the current message (%Q): %s, no data sent to the server"),
1824 						    &szTopic, &szEngineError);
1825 						break;
1826 				}
1827 			}
1828 			else
1829 			{
1830 				szTmpTopic.remove(0, 1);
1831 				// re-encode for removed CryptEscape
1832 				encoded = encodeText(szTmpTopic);
1833 			}
1834 		}
1835 	}
1836 #endif
1837 
1838 	connection()->sendFmtData("TOPIC %s :%s", name.data(), encoded.length() ? encoded.data() : "");
1839 }
1840 
closeEvent(QCloseEvent * pEvent)1841 void KviChannelWindow::closeEvent(QCloseEvent * pEvent)
1842 {
1843 	if((m_iStateFlags & SentPart) || (m_iStateFlags & DeadChan) || !(m_pConsole->isConnected()))
1844 	{
1845 		if(context())
1846 			context()->unregisterDeadChannel(this);
1847 		KviWindow::closeEvent(pEvent);
1848 	}
1849 	else
1850 	{
1851 		pEvent->ignore();
1852 		// FIXME: #warning "THIS PART SHOULD BECOME A COMMAND /PART $option()..so the identifiers are parsed"
1853 		if(connection())
1854 		{
1855 			QString szTmp = KVI_OPTION_STRING(KviOption_stringPartMessage);
1856 			KviQString::escapeKvs(&szTmp, KviQString::PermitVariables | KviQString::PermitFunctions);
1857 			KviKvsVariant vRet;
1858 
1859 			if(KviKvsScript::evaluate(szTmp, this, nullptr, &vRet))
1860 				vRet.asString(szTmp);
1861 
1862 			QByteArray dat = encodeText(szTmp);
1863 			partMessageSent();
1864 			QByteArray name = connection()->encodeText(m_szName);
1865 			connection()->sendFmtData("PART %s :%s", name.data(), dat.data() ? dat.data() : "");
1866 			// be sure to not reference ourselves here.. we could be disconnected!
1867 		}
1868 		else
1869 		{
1870 			partMessageSent(); // huh ?
1871 		}
1872 	}
1873 }
1874 
partMessageSent(bool bCloseOnPart,bool bShowMessage)1875 void KviChannelWindow::partMessageSent(bool bCloseOnPart, bool bShowMessage)
1876 {
1877 	m_iStateFlags |= SentPart;
1878 	if(!bCloseOnPart)
1879 		m_iStateFlags |= NoCloseOnPart;
1880 	if(bShowMessage)
1881 		outputNoFmt(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Sent part request, waiting for reply..."));
1882 }
1883 
1884 #define IS_FNC(__name, __ulvname)                                                     \
1885 	bool KviChannelWindow::__name(bool bAtLeast)                                      \
1886 	{                                                                                 \
1887 		if(!connection())                                                             \
1888 			return false;                                                             \
1889 		return m_pUserListView->__ulvname(connection()->currentNickName(), bAtLeast); \
1890 	}
1891 
IS_FNC(isMeChanOwner,isChanOwner)1892 IS_FNC(isMeChanOwner, isChanOwner)
1893 IS_FNC(isMeChanAdmin, isChanAdmin)
1894 IS_FNC(isMeOp, isOp)
1895 IS_FNC(isMeVoice, isVoice)
1896 IS_FNC(isMeHalfOp, isHalfOp)
1897 IS_FNC(isMeUserOp, isUserOp)
1898 
1899 int KviChannelWindow::myFlags()
1900 {
1901 	if(!connection())
1902 		return 0;
1903 
1904 	return m_pUserListView->flags(connection()->currentNickName());
1905 }
1906 
setModeInList(char cMode,const QString & szMask,bool bAdd,const QString & szSetBy,unsigned int uSetAt,QString szChangeMask)1907 void KviChannelWindow::setModeInList(char cMode, const QString & szMask, bool bAdd, const QString & szSetBy, unsigned int uSetAt, QString szChangeMask)
1908 {
1909 	if(!connection())
1910 		return;
1911 
1912 	if(!m_ModeLists.count(cMode))
1913 	{
1914 		// we want to remove an item but we don't have any list?
1915 		if(!bAdd)
1916 			return;
1917 		// lazily insert it
1918 		m_ModeLists[cMode];
1919 	}
1920 
1921 	std::vector<KviMaskEntry *> & pList = m_ModeLists[cMode];
1922 	KviMaskEditor * pEditor = nullptr;
1923 	if(m_ListEditors.count(cMode))
1924 		pEditor = m_ListEditors[cMode];
1925 
1926 	internalMask(szMask, bAdd, szSetBy, uSetAt, pList, &pEditor, szChangeMask);
1927 	m_pUserListView->setMaskEntries(cMode, pList.size());
1928 }
1929 
internalMask(const QString & szMask,bool bAdd,const QString & szSetBy,unsigned int uSetAt,std::vector<KviMaskEntry * > & pList,KviMaskEditor ** ppEd,QString & szChangeMask)1930 void KviChannelWindow::internalMask(const QString & szMask, bool bAdd, const QString & szSetBy, unsigned int uSetAt, std::vector<KviMaskEntry *> & pList, KviMaskEditor ** ppEd, QString & szChangeMask)
1931 {
1932 	KviMaskEntry * pEntry = nullptr;
1933 	if(bAdd)
1934 	{
1935 		for(auto & e : pList)
1936 		{
1937 			if(KviQString::equalCI(e->szMask, szMask))
1938 				return; //already there
1939 		}
1940 		pEntry = new KviMaskEntry;
1941 		pEntry->szMask = szMask;
1942 		pEntry->szSetBy = (!szSetBy.isEmpty()) ? szSetBy : __tr2qs("(Unknown)");
1943 		pEntry->uSetAt = uSetAt;
1944 		pList.push_back(pEntry);
1945 		if(*ppEd)
1946 			(*ppEd)->addMask(pEntry);
1947 	}
1948 	else
1949 	{
1950 		auto iter = pList.begin();
1951 		for(; iter != pList.end() && !KviQString::equalCI((*iter)->szMask, szMask); ++iter);
1952 
1953 		if(iter != pList.end())
1954 		{
1955 			//delete mask from the editor
1956 			if(*ppEd)
1957 				(*ppEd)->removeMask(*iter);
1958 
1959 			if(szChangeMask.isNull())
1960 			{
1961 				//delete mask
1962 				pList.erase(iter);
1963 			}
1964 			else
1965 			{
1966 				//update mask
1967 				(*iter)->szMask = szChangeMask;
1968 				if(*ppEd)
1969 					(*ppEd)->addMask(*iter);
1970 			}
1971 		}
1972 	}
1973 }
1974 
updateModeLabel()1975 void KviChannelWindow::updateModeLabel()
1976 {
1977 	static QString br("<br>");
1978 	static QString tds = "<tr><td style=\"background-color: rgb(48,48,48); white-space: nowrap; font-weight: bold; color: rgb(255,255,255); text-align:center; padding-left: 5px; padding-right: 5px;\">";
1979 	static QString snr = "<tr><td style=\"white-space: pre; padding-left: 5px; padding-right: 5px;\">";
1980 	static QString enr = "</td></tr>";
1981 
1982 	QString szTip = "<html><body><table>";
1983 	szTip += tds;
1984 	szTip += __tr2qs("Channel Modes");
1985 	szTip += enr;
1986 
1987 	KviCString szMod = m_szChannelMode;
1988 	const char * pcAux = szMod.ptr();
1989 	KviIrcConnectionServerInfo * pServerInfo = serverInfo();
1990 
1991 	szTip += snr;
1992 	while(*pcAux)
1993 	{
1994 		if(pServerInfo)
1995 			KviQString::appendFormatted(szTip, "<b>%c</b>: %Q" + br, *pcAux, &(m_pConsole->connection()->serverInfo()->getChannelModeDescription(*pcAux)));
1996 		++pcAux;
1997 	}
1998 
1999 	for(auto & iter : m_szChannelParameterModes)
2000 	{
2001 		QString szDescription = KviQString::toHtmlEscaped(m_pConsole->connection()->serverInfo()->getChannelModeDescription(iter.first));
2002 		QString szValue = KviQString::toHtmlEscaped(iter.second);
2003 		KviQString::appendFormatted(szTip, br + "<b>%c</b>: %Q: <b>%Q</b>", iter.first, &szDescription, &szValue);
2004 	}
2005 
2006 	szTip += enr + snr + "<hr>";
2007 	szTip +=  __tr2qs("Double-click to edit...");
2008 	szTip += enr;
2009 	szTip += "</table></body></html>";
2010 
2011 	m_pModeWidget->refreshModes();
2012 	KviTalToolTip::remove(m_pModeWidget);
2013 	KviTalToolTip::add(m_pModeWidget, szTip);
2014 }
2015 
outputMessage(int iMsgType,const QString & szMsg,const QDateTime & datetime)2016 void KviChannelWindow::outputMessage(int iMsgType, const QString & szMsg, const QDateTime & datetime)
2017 {
2018 	QString szBuf(szMsg);
2019 	preprocessMessage(szBuf);
2020 
2021 	const QChar * pC = szBuf.constData();
2022 	if(!pC)
2023 		return;
2024 
2025 	if(m_pMessageView && m_pIrcView->messageShouldGoToMessageView(iMsgType))
2026 		internalOutput(m_pMessageView, iMsgType, (const kvi_wchar_t *)pC, 0, datetime);
2027 	else
2028 		internalOutput(m_pIrcView, iMsgType, (const kvi_wchar_t *)pC, 0, datetime);
2029 }
2030 
checkChannelSync()2031 void KviChannelWindow::checkChannelSync()
2032 {
2033 	if(m_iStateFlags & Synchronized)
2034 		return;
2035 
2036 	if(m_iStateFlags & SentWhoRequest)
2037 	{
2038 		if(!(m_iStateFlags & HaveWhoList))
2039 			return;
2040 	}
2041 
2042 	// check if we're in the on-join request queue list
2043 	if(connection()->requestQueue()->isQueued(this))
2044 		return;
2045 
2046 	// check if there's any request still runinng
2047 	if(m_szSentModeRequests.size() != 0)
2048 		return;
2049 
2050 	m_iStateFlags |= Synchronized;
2051 	// we already have all the spontaneous server replies
2052 	// (so probably mode, topic (or no topic is set),names)
2053 	// we have already received the I and e lists (if requested)
2054 	kvs_int_t iSyncTime = m_joinTime.time().msecsTo(QTime::currentTime());
2055 	if(iSyncTime < 0)
2056 		iSyncTime += 86400000;
2057 
2058 	bool bStop = KVS_TRIGGER_EVENT_1_HALTED(KviEvent_OnChannelSync, this, iSyncTime);
2059 
2060 	if(!bStop && KVI_OPTION_BOOL(KviOption_boolShowChannelSyncTime))
2061 	{
2062 		output(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Channel synchronized in %d.%d seconds"), iSyncTime / 1000, iSyncTime % 1000);
2063 	}
2064 }
2065 
lastClickedView() const2066 KviIrcView * KviChannelWindow::lastClickedView() const
2067 {
2068 	if(m_pMessageView && m_pIrcView && m_pMessageView->lastMouseClickTime() >= m_pIrcView->lastMouseClickTime())
2069 			return m_pMessageView;
2070 
2071 	return m_pIrcView;
2072 }
2073 
eventFilter(QObject * pObject,QEvent * pEvent)2074 bool KviChannelWindow::eventFilter(QObject * pObject, QEvent * pEvent)
2075 {
2076 	if(pEvent->type() == QEvent::FocusOut && pObject == m_pTopicWidget && m_pTopicWidget->isVisible())
2077 		m_pTopicWidget->deactivate();
2078 
2079 	return KviWindow::eventFilter(pObject, pEvent);
2080 }
2081 
preprocessMessage(QString & szMessage)2082 void KviChannelWindow::preprocessMessage(QString & szMessage)
2083 {
2084 	// FIXME: slow
2085 
2086 	KviIrcConnectionServerInfo * pServerInfo = serverInfo();
2087 	if(!pServerInfo)
2088 		return;
2089 
2090 	static QString szNonStandardLinkPrefix = QString::fromLatin1("\r![");
2091 
2092 	if(szMessage.contains(szNonStandardLinkPrefix))
2093 		return; // contains a non standard link that may contain spaces, do not break it.
2094 
2095 	QStringList strings = szMessage.split(" ", QString::KeepEmptyParts);
2096 	for(auto & it : strings)
2097 	{
2098 		if(it.contains('\r'))
2099 			continue;
2100 
2101 		QString szTmp = KviControlCodes::stripControlBytes(it).trimmed();
2102 		if(szTmp.length() < 1)
2103 			continue;
2104 
2105 		// FIXME: Do we REALLY need this ?
2106 		if(findEntry(it))
2107 		{
2108 			it = QString("\r!n\r%1\r").arg(it);
2109 			continue;
2110 		}
2111 
2112 		if(pServerInfo->supportedChannelTypes().contains(szTmp[0]))
2113 		{
2114 			if(it == szTmp)
2115 				it = QString("\r!c\r%1\r").arg(it);
2116 			else
2117 				it = QString("\r!c%1\r%2\r").arg(szTmp, it);
2118 		}
2119 	}
2120 	szMessage = strings.join(" ");
2121 }
2122 
unhighlight()2123 void KviChannelWindow::unhighlight()
2124 {
2125 	if(!m_pWindowListItem)
2126 		return;
2127 	m_pWindowListItem->unhighlight();
2128 }
2129 
serverInfo()2130 KviIrcConnectionServerInfo * KviChannelWindow::serverInfo()
2131 {
2132 	return connection() ? connection()->serverInfo() : nullptr;
2133 }
2134