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(" ");
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