1 /*
2 SPDX-License-Identifier: GPL-2.0-or-later
3
4 SPDX-FileCopyrightText: 2002 Dario Abatianni <eisfuchs@tigress.com>
5 SPDX-FileCopyrightText: 2004-2016 Peter Simonsson <peter.simonsson@gmail.com>
6 SPDX-FileCopyrightText: 2006-2008 Eike Hein <hein@kde.org>
7 */
8
9 #include "channel.h"
10
11 #include "channeloptionsdialog.h"
12 #include "application.h"
13 #include "nick.h"
14 #include "nicklistview.h"
15 #include "quickbutton.h"
16 #include "modebutton.h"
17 #include "ircinput.h"
18 #include "ircviewbox.h"
19 #include "ircview.h"
20 #include "awaylabel.h"
21 #include "topiclabel.h"
22 #include "topichistorymodel.h"
23 #include "notificationhandler.h"
24 #include "viewcontainer.h"
25 #include "konversation_log.h"
26
27 #include <kwidgetsaddons_version.h>
28 #include <KAuthorized>
29 #include <KPasswordDialog>
30 #include <KMessageBox>
31 #include <KComboBox>
32
33 #include <QLineEdit>
34 #include <QRegularExpression>
35 #include <QSplitter>
36 #include <QToolButton>
37 #include <QHeaderView>
38 #include <QInputDialog>
39 #include <QRandomGenerator>
40
41 constexpr int DELAYED_SORT_TRIGGER = 10;
42
43 using namespace Konversation;
44
nickTimestampLessThan(const Nick * nick1,const Nick * nick2)45 bool nickTimestampLessThan(const Nick* nick1, const Nick* nick2)
46 {
47 if(nick2->getChannelNick()->timeStamp() == nick1->getChannelNick()->timeStamp()) {
48 return QString::compare(nick1->getChannelNick()->loweredNickname(),
49 nick2->getChannelNick()->loweredNickname()) < 0;
50 }
51
52 return nick1->getChannelNick()->timeStamp() < nick2->getChannelNick()->timeStamp();
53 }
54
nickLessThan(const Nick * nick1,const Nick * nick2)55 bool nickLessThan(const Nick* nick1, const Nick* nick2)
56 {
57 return nick1->getChannelNick()->loweredNickname() < nick2->getChannelNick()->loweredNickname();
58 }
59
60
61 using Konversation::ChannelOptionsDialog;
62
Channel(QWidget * parent,const QString & _name)63 Channel::Channel(QWidget* parent, const QString& _name) : ChatWindow(parent)
64 {
65 // init variables
66
67 //HACK I needed the channel name at time of setServer, but setName needs m_server..
68 // This effectively assigns the name twice, but none of the other logic has been moved or updated.
69 name=_name;
70 m_ownChannelNick = nullptr;
71 m_optionsDialog = nullptr;
72 m_delayedSortTimer = nullptr;
73 m_delayedSortTrigger = 0;
74 m_processedNicksCount = 0;
75 m_processedOpsCount = 0;
76 m_initialNamesReceived = false;
77 nicks = 0;
78 ops = 0;
79 completionPosition = 0;
80 m_nicknameListViewTextChanged = 0;
81
82 m_joined = false;
83
84 quickButtonsChanged = false;
85 quickButtonsState = false;
86
87 modeButtonsChanged = false;
88 modeButtonsState = false;
89
90 awayChanged = false;
91 awayState = false;
92
93 splittersInitialized = false;
94 topicSplitterHidden = false;
95 channelSplitterHidden = false;
96
97 setType(ChatWindow::Channel);
98 m_isTopLevelView = false;
99
100 setChannelEncodingSupported(true);
101
102 // Build some size policies for the widgets
103 QSizePolicy hfixed = QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
104 QSizePolicy hmodest = QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
105 QSizePolicy vfixed = QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
106 QSizePolicy greedy = QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
107
108 m_vertSplitter = new QSplitter(Qt::Vertical, this);
109
110
111 auto* topicWidget = new QWidget(m_vertSplitter);
112 m_vertSplitter->setStretchFactor(m_vertSplitter->indexOf(topicWidget), 0);
113
114 auto* topicLayout = new QGridLayout(topicWidget);
115 topicLayout->setContentsMargins(0, 0, 0, 0);
116 topicLayout->setSpacing(0);
117
118 m_topicButton = new QToolButton(topicWidget);
119 m_topicButton->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
120 m_topicButton->setToolTip(i18n("Edit Channel Settings"));
121 m_topicButton->setAutoRaise(true);
122 connect(m_topicButton, &QAbstractButton::clicked, this, &Channel::showOptionsDialog);
123
124 topicLine = new Konversation::TopicLabel(topicWidget);
125 topicLine->setContextMenuOptions(IrcContextMenus::ShowChannelActions | IrcContextMenus::ShowLogAction, true);
126 topicLine->setChannelName(getName());
127 topicLine->setWordWrap(true);
128 topicLine->setWhatsThis(i18n("<qt><p>Every channel on IRC has a topic associated with it. This is simply a message that everybody can see.</p><p>If you are an operator, or the channel mode <em>'T'</em> has not been set, then you can change the topic by clicking the Edit Channel Properties button to the left of the topic. You can also view the history of topics there.</p></qt>"));
129 connect(topicLine, &TopicLabel::setStatusBarTempText, this, &ChatWindow::setStatusBarTempText);
130 connect(topicLine, &TopicLabel::clearStatusBarTempText, this, &ChatWindow::clearStatusBarTempText);
131
132 m_topicHistory = new TopicHistoryModel(this);
133 connect(m_topicHistory, &TopicHistoryModel::currentTopicChanged, topicLine, &TopicLabel::setText);
134
135 topicLayout->addWidget(m_topicButton, 0, 0);
136 topicLayout->addWidget(topicLine, 0, 1, -1, 1);
137
138 // The box holding the channel modes
139 modeBox = new QFrame(topicWidget);
140 auto* modeBoxLayout = new QHBoxLayout(modeBox);
141 modeBoxLayout->setContentsMargins(0, 0, 0, 0);
142 modeBox->hide();
143 modeBox->setSizePolicy(hfixed);
144 modeT = new ModeButton(QStringLiteral("T"),modeBox,0);
145 modeBoxLayout->addWidget(modeT);
146 modeN = new ModeButton(QStringLiteral("N"),modeBox,1);
147 modeBoxLayout->addWidget(modeN);
148 modeS = new ModeButton(QStringLiteral("S"),modeBox,2);
149 modeBoxLayout->addWidget(modeS);
150 modeI = new ModeButton(QStringLiteral("I"),modeBox,3);
151 modeBoxLayout->addWidget(modeI);
152 modeP = new ModeButton(QStringLiteral("P"),modeBox,4);
153 modeBoxLayout->addWidget(modeP);
154 modeM = new ModeButton(QStringLiteral("M"),modeBox,5);
155 modeBoxLayout->addWidget(modeM);
156 modeK = new ModeButton(QStringLiteral("K"),modeBox,6);
157 modeBoxLayout->addWidget(modeK);
158 modeL = new ModeButton(QStringLiteral("L"),modeBox,7);
159 modeBoxLayout->addWidget(modeL);
160
161 modeT->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('T'));
162 modeN->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('N'));
163 modeS->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('S'));
164 modeI->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('I'));
165 modeP->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('P'));
166 modeM->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('M'));
167 modeK->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('K'));
168 modeL->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('L'));
169
170 connect(modeT, &ModeButton::modeClicked, this, &Channel::modeButtonClicked);
171 connect(modeN, &ModeButton::modeClicked, this, &Channel::modeButtonClicked);
172 connect(modeS, &ModeButton::modeClicked, this, &Channel::modeButtonClicked);
173 connect(modeI, &ModeButton::modeClicked, this, &Channel::modeButtonClicked);
174 connect(modeP, &ModeButton::modeClicked, this, &Channel::modeButtonClicked);
175 connect(modeM, &ModeButton::modeClicked, this, &Channel::modeButtonClicked);
176 connect(modeK, &ModeButton::modeClicked, this, &Channel::modeButtonClicked);
177 connect(modeL, &ModeButton::modeClicked, this, &Channel::modeButtonClicked);
178 connect(modeT, &ModeButton::modeClicked, this, &Channel::modeButtonClicked);
179
180 limit=new KLineEdit(modeBox);
181 modeBoxLayout->addWidget(limit);
182 limit->setToolTip(i18n("Maximum users allowed in channel"));
183 limit->setWhatsThis(i18n("<qt><p>This is the channel user limit - the maximum number of users that can be in the channel at a time. If you are an operator, you can set this. The channel mode <b>T</b>opic (button to left) will automatically be set if set this.</p></qt>"));
184 connect(limit, QOverload<>::of(&QLineEdit::returnPressed), this, &Channel::channelLimitChanged);
185 connect(limit,&QLineEdit::editingFinished, this, &Channel::channelLimitChanged );
186
187 topicLayout->addWidget(modeBox, 0, 2);
188 topicLayout->setRowStretch(1, 10);
189 topicLayout->setColumnStretch(1, 10);
190
191 showTopic(Preferences::self()->showTopic());
192 showModeButtons(Preferences::self()->showModeButtons());
193
194 // (this) The main Box, holding the channel view/topic and the input line
195 m_horizSplitter = new QSplitter(m_vertSplitter);
196 m_vertSplitter->setStretchFactor(m_vertSplitter->indexOf(m_horizSplitter), 1);
197
198 // Server will be set later in setServer()
199 auto* ircViewBox = new IRCViewBox(m_horizSplitter);
200 m_horizSplitter->setStretchFactor(m_horizSplitter->indexOf(ircViewBox), 1);
201 setTextView(ircViewBox->ircView());
202 ircViewBox->ircView()->setContextMenuOptions(IrcContextMenus::ShowChannelActions, true);
203
204 // The box that holds the Nick List and the quick action buttons
205 nickListButtons = new QFrame(m_horizSplitter);
206 m_horizSplitter->setStretchFactor(m_horizSplitter->indexOf(nickListButtons), 0);
207 auto* nickListButtonsLayout = new QVBoxLayout(nickListButtons);
208 nickListButtonsLayout->setSpacing(0);
209 nickListButtonsLayout->setContentsMargins(0, 0, 0, 0);
210
211 nicknameListView=new NickListView(nickListButtons, this);
212 nickListButtons->layout()->addWidget(nicknameListView);
213 nicknameListView->installEventFilter(this);
214
215 // initialize buttons grid, will be set up in updateQuickButtons
216 m_buttonsGrid = nullptr;
217
218 // The box holding the Nickname button and Channel input
219 commandLineBox = new QFrame(this);
220 auto* commandLineLayout = new QHBoxLayout(commandLineBox);
221 commandLineBox->setLayout(commandLineLayout);
222 commandLineLayout->setContentsMargins(0, 0, 0, 0);
223 commandLineLayout->setSpacing(spacing());
224
225 nicknameCombobox = new KComboBox(commandLineBox);
226 nicknameCombobox->setEditable(true);
227 nicknameCombobox->setSizeAdjustPolicy(KComboBox::AdjustToContents);
228 QLineEdit* nicknameComboboxLineEdit = nicknameCombobox->lineEdit();
229 nicknameComboboxLineEdit->setClearButtonEnabled(false);
230 nicknameCombobox->setWhatsThis(i18n("<qt><p>This shows your current nick, and any alternatives you have set up. If you select or type in a different nickname, then a request will be sent to the IRC server to change your nick. If the server allows it, the new nickname will be selected. If you type in a new nickname, you need to press 'Enter' at the end.</p><p>You can edit the alternative nicknames from the <em>Identities</em> option in the <em>Settings</em> menu.</p></qt>"));
231
232 awayLabel = new AwayLabel(commandLineBox);
233 awayLabel->hide();
234 cipherLabel = new QLabel(commandLineBox);
235 cipherLabel->hide();
236 const int toolBarIconSize = cipherLabel->style()->pixelMetric(QStyle::PixelMetric::PM_ToolBarIconSize);
237 cipherLabel->setPixmap(QIcon::fromTheme(QStringLiteral("document-encrypt")).pixmap(toolBarIconSize));
238 m_inputBar = new IRCInput(commandLineBox);
239
240 commandLineLayout->addWidget(nicknameCombobox);
241 commandLineLayout->addWidget(awayLabel);
242 commandLineLayout->addWidget(cipherLabel);
243 commandLineLayout->addWidget(m_inputBar);
244
245 getTextView()->installEventFilter(m_inputBar);
246 topicLine->installEventFilter(m_inputBar);
247 m_inputBar->installEventFilter(this);
248
249 // Set the widgets size policies
250 m_topicButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
251 topicLine->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum));
252
253 commandLineBox->setSizePolicy(vfixed);
254
255 limit->setMaximumSize(40,100);
256 limit->setSizePolicy(hfixed);
257
258 modeT->setMaximumSize(20,100);
259 modeT->setSizePolicy(hfixed);
260 modeN->setMaximumSize(20,100);
261 modeN->setSizePolicy(hfixed);
262 modeS->setMaximumSize(20,100);
263 modeS->setSizePolicy(hfixed);
264 modeI->setMaximumSize(20,100);
265 modeI->setSizePolicy(hfixed);
266 modeP->setMaximumSize(20,100);
267 modeP->setSizePolicy(hfixed);
268 modeM->setMaximumSize(20,100);
269 modeM->setSizePolicy(hfixed);
270 modeK->setMaximumSize(20,100);
271 modeK->setSizePolicy(hfixed);
272 modeL->setMaximumSize(20,100);
273 modeL->setSizePolicy(hfixed);
274
275 getTextView()->setSizePolicy(greedy);
276 nicknameListView->setSizePolicy(hmodest);
277
278 connect(m_inputBar,&IRCInput::submit,this,&Channel::channelTextEntered );
279 connect(m_inputBar,&IRCInput::envelopeCommand,this,&Channel::channelPassthroughCommand );
280 connect(m_inputBar,&IRCInput::nickCompletion,this,&Channel::completeNick );
281 connect(m_inputBar,&IRCInput::endCompletion,this,&Channel::endCompleteNick );
282 connect(m_inputBar,&IRCInput::textPasted,this,&Channel::textPasted );
283
284 connect(getTextView(), &IRCView::textPasted, m_inputBar, &IRCInput::paste);
285 connect(getTextView(), &IRCView::gotFocus, m_inputBar, QOverload<>::of(&IRCInput::setFocus));
286 connect(getTextView(), &IRCView::sendFile, this, &Channel::sendFileMenu );
287 connect(getTextView(), &IRCView::autoText, this, &Channel::sendText);
288
289 connect(nicknameListView,&QTreeWidget::itemDoubleClicked,this,&Channel::doubleClickCommand );
290 connect(nicknameCombobox, QOverload<int>::of(&KComboBox::activated), this, &Channel::nicknameComboboxChanged);
291
292 if(nicknameCombobox->lineEdit())
293 connect(nicknameCombobox->lineEdit(), &QLineEdit::returnPressed,this,&Channel::nicknameComboboxChanged);
294
295
296 connect(&userhostTimer,&QTimer::timeout,this,&Channel::autoUserhost);
297
298 m_whoTimer.setSingleShot(true);
299 connect(&m_whoTimer, &QTimer::timeout, this, &Channel::autoWho);
300 connect(Application::instance(), &Application::appearanceChanged, this, &Channel::updateAutoWho);
301
302 // every 5 minutes decrease everyone's activity by 1 unit
303 m_fadeActivityTimer.start(5*60*1000);
304
305 connect(&m_fadeActivityTimer, &QTimer::timeout, this, &Channel::fadeActivity);
306
307 updateAppearance();
308
309 #ifdef HAVE_QCA2
310 m_cipher = nullptr;
311 #endif
312
313 // Setup delayed sort timer
314 m_delayedSortTimer = new QTimer(this);
315 m_delayedSortTimer->setSingleShot(true);
316 connect(m_delayedSortTimer, &QTimer::timeout, this, &Channel::delayedSortNickList);
317 }
318
319 //FIXME there is some logic in setLogfileName that needs to be split out and called here if the server display name gets changed
setServer(Server * server)320 void Channel::setServer(Server* server)
321 {
322 if (m_server != server)
323 {
324 connect(server, &Server::connectionStateChanged,
325 this, &Channel::connectionStateChanged);
326 connect(server, QOverload<>::of(&Server::nickInfoChanged),
327 this, &Channel::updateNickInfos);
328 connect(server, &Server::channelNickChanged,
329 this, &Channel::updateChannelNicks);
330 }
331
332 ChatWindow::setServer(server);
333 if (!server->getKeyForRecipient(getName()).isEmpty())
334 cipherLabel->show();
335 topicLine->setServer(server);
336 refreshModeButtons();
337 nicknameCombobox->setModel(m_server->nickListModel());
338
339 connect(awayLabel, &AwayLabel::unaway, m_server, &Server::requestUnaway);
340 connect(awayLabel, &AwayLabel::awayMessageChanged, m_server, &Server::requestAway);
341 }
342
connectionStateChanged(Server * server,Konversation::ConnectionState state)343 void Channel::connectionStateChanged(Server* server, Konversation::ConnectionState state)
344 {
345 if (server == m_server)
346 {
347 if (state != Konversation::SSConnected)
348 {
349 m_joined = false;
350
351 ViewContainer* viewContainer = Application::instance()->getMainWindow()->getViewContainer();
352
353 //HACK the way the notification priorities work sucks, this forces the tab text color to gray right now.
354 if (viewContainer->getFrontView() == this
355 || m_currentTabNotify == Konversation::tnfNone
356 || (!Preferences::self()->tabNotificationsEvents() && m_currentTabNotify == Konversation::tnfControl))
357 {
358 viewContainer->unsetViewNotification(this);
359 }
360 }
361 }
362 }
363
setEncryptedOutput(bool e)364 void Channel::setEncryptedOutput(bool e)
365 {
366 #ifdef HAVE_QCA2
367 if (e)
368 {
369 cipherLabel->show();
370
371 if (!getCipher()->setKey(m_server->getKeyForRecipient(getName())))
372 return;
373
374 m_topicHistory->setCipher(getCipher());
375
376 topicLine->setText(m_topicHistory->currentTopic());
377 }
378 else
379 {
380 cipherLabel->hide();
381 m_topicHistory->clearCipher();
382 topicLine->setText(m_topicHistory->currentTopic());
383
384 }
385 #else
386 Q_UNUSED(e)
387 #endif
388 }
389
~Channel()390 Channel::~Channel()
391 {
392 qCDebug(KONVERSATION_LOG) << "(" << getName() << ")";
393
394 // Purge nickname list
395 purgeNicks();
396 qCDebug(KONVERSATION_LOG) << "Nicks purged.";
397
398 // Unlink this channel from channel list
399 m_server->removeChannel(this);
400 qCDebug(KONVERSATION_LOG) << "Channel removed.";
401
402 if (m_recreationScheduled)
403 {
404 QMetaObject::invokeMethod(m_server, "sendJoinCommand", Qt::QueuedConnection,
405 Q_ARG(QString, getName()), Q_ARG(QString, getPassword()));
406 }
407 }
408
rejoinable() const409 bool Channel::rejoinable() const
410 {
411 if (getServer() && getServer()->isConnected())
412 return !m_joined;
413
414 return false;
415 }
416
rejoin()417 void Channel::rejoin()
418 {
419 if (rejoinable())
420 m_server->sendJoinCommand(getName(), getPassword());
421 }
422
log() const423 bool Channel::log() const
424 {
425 return ChatWindow::log() && !Preferences::self()->privateOnly();
426 }
427
getOwnChannelNick() const428 ChannelNickPtr Channel::getOwnChannelNick() const
429 {
430 return m_ownChannelNick;
431 }
432
getChannelNick(const QString & ircnick) const433 ChannelNickPtr Channel::getChannelNick(const QString &ircnick) const
434 {
435 return m_server->getChannelNick(getName(), ircnick);
436 }
437
purgeNicks()438 void Channel::purgeNicks()
439 {
440 m_ownChannelNick = nullptr;
441
442 // Purge nickname list
443 qDeleteAll(nicknameList);
444 nicknameList.clear();
445 m_nicknameNickHash.clear();
446
447 // Execute this otherwise it may crash trying to access
448 // deleted nicks
449 nicknameListView->executeDelayedItemsLayout();
450
451 // clear stats counter
452 nicks=0;
453 ops=0;
454 }
455
showOptionsDialog()456 void Channel::showOptionsDialog()
457 {
458 if (!m_optionsDialog)
459 m_optionsDialog = new Konversation::ChannelOptionsDialog(this);
460
461 m_optionsDialog->show();
462 }
463
textPasted(const QString & text)464 void Channel::textPasted(const QString& text)
465 {
466 if(m_server)
467 {
468 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
469 const QStringList multiline = text.split(QLatin1Char('\n'), QString::SkipEmptyParts);
470 #else
471 const QStringList multiline = text.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
472 #endif
473 for (QString line : multiline) {
474 QString cChar(Preferences::self()->commandChar());
475 // make sure that lines starting with command char get escaped
476 if(line.startsWith(cChar)) line=cChar+line;
477 sendText(line);
478 }
479 }
480 }
481
482 // Will be connected to NickListView::doubleClicked()
doubleClickCommand(QTreeWidgetItem * item,int column)483 void Channel::doubleClickCommand(QTreeWidgetItem *item, int column)
484 {
485 Q_UNUSED(column)
486 if(item)
487 {
488 nicknameListView->clearSelection();
489 item->setSelected(true);
490 // TODO: put the quick button code in another function to make reusal more legitimate
491 quickButtonClicked(Preferences::self()->channelDoubleClickAction());
492 }
493 }
494
completeNick()495 void Channel::completeNick()
496 {
497 int pos, oldPos;
498 QTextCursor cursor = m_inputBar->textCursor();
499
500 pos = cursor.position();
501 oldPos = m_inputBar->getOldCursorPosition();
502
503 QString line=m_inputBar->toPlainText();
504 QString newLine;
505 // Check if completion position is out of range
506 if(completionPosition >= nicknameList.count()) completionPosition = 0;
507
508 // Check, which completion mode is active
509 char mode = m_inputBar->getCompletionMode();
510
511 if(mode == 'c')
512 {
513 line.remove(oldPos, pos - oldPos);
514 pos = oldPos;
515 }
516
517 // If the cursor is at beginning of line, insert last completion if the nick is still around
518 if(pos == 0 && !m_inputBar->lastCompletion().isEmpty() && nicknameList.containsNick(m_inputBar->lastCompletion()))
519 {
520 QString addStart(Preferences::self()->nickCompleteSuffixStart());
521 newLine = m_inputBar->lastCompletion() + addStart;
522 // New cursor position is behind nickname
523 pos = newLine.length();
524 // Add rest of the line
525 newLine += line;
526 }
527 else
528 {
529 // remember old cursor position in input field
530 m_inputBar->setOldCursorPosition(pos);
531 // remember old cursor position locally
532 oldPos = pos;
533 // step back to []{}-_^`\| or start of line
534 const QString prefixChar = !Preferences::self()->prefixCharacter().isEmpty() ?
535 QLatin1Char('\\') + Preferences::self()->prefixCharacter() : QString{};
536
537 const QString regexpStr(QStringLiteral("[^A-Z0-9a-z\\_\\[\\]\\{\\}\\-\\^\\`\\\\\\|%1]").arg(prefixChar));
538
539 const QRegularExpression re(regexpStr);
540 pos = line.lastIndexOf(re, pos - 1);
541 if (pos < 0)
542 pos = 0;
543 else
544 pos++;
545 // copy search pattern (lowercase)
546 QString pattern = line.mid(pos, oldPos - pos);
547 // copy line to newLine-buffer
548 newLine = line;
549
550 // did we find any pattern?
551 if(!pattern.isEmpty())
552 {
553 bool complete = false;
554 QString foundNick;
555
556 // try to find matching nickname in list of names
557 if(Preferences::self()->nickCompletionMode() == 1 ||
558 Preferences::self()->nickCompletionMode() == 2)
559 { // Shell like completion
560 QStringList found;
561 foundNick = nicknameList.completeNick(pattern, complete, found,
562 (Preferences::self()->nickCompletionMode() == 2),
563 Preferences::self()->nickCompletionCaseSensitive());
564
565 if(!complete && !found.isEmpty())
566 {
567 if(Preferences::self()->nickCompletionMode() == 1)
568 {
569 QString nicksFound = found.join(QLatin1Char(' '));
570 appendServerMessage(i18n("Completion"), i18n("Possible completions: %1.", nicksFound));
571 }
572 else
573 {
574 m_inputBar->showCompletionList(found);
575 }
576 }
577 } // Cycle completion
578 else if(Preferences::self()->nickCompletionMode() == 0 && !nicknameList.isEmpty())
579 {
580 if(mode == '\0') {
581 uint timeStamp = 0;
582 int listPosition = 0;
583
584 for (Nick* nick : qAsConst(nicknameList)) {
585 if(nick->getChannelNick()->getNickname().startsWith(pattern, Preferences::self()->nickCompletionCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive) &&
586 (nick->getChannelNick()->timeStamp() > timeStamp))
587 {
588 timeStamp = nick->getChannelNick()->timeStamp();
589 completionPosition = listPosition;
590 }
591 ++listPosition;
592 }
593 }
594
595 // remember old nick completion position
596 int oldCompletionPosition = completionPosition;
597 complete = true;
598 QString prefixCharacter = Preferences::self()->prefixCharacter();
599
600 do
601 {
602 QString lookNick = nicknameList.at(completionPosition)->getChannelNick()->getNickname();
603
604 if(!prefixCharacter.isEmpty() && lookNick.contains(prefixCharacter))
605 {
606 lookNick = lookNick.section( prefixCharacter,1 );
607 }
608
609 if(lookNick.startsWith(pattern, Preferences::self()->nickCompletionCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive))
610 {
611 foundNick = lookNick;
612 }
613
614 // increment search position
615 completionPosition++;
616
617 // wrap around
618 if(completionPosition == nicknameList.count())
619 {
620 completionPosition = 0;
621 }
622
623 // the search ends when we either find a suitable nick or we end up at the
624 // first search position
625 } while((completionPosition != oldCompletionPosition) && foundNick.isEmpty());
626 }
627
628 // did we find a suitable nick?
629 if(!foundNick.isEmpty())
630 {
631 // set channel nicks completion mode
632 m_inputBar->setCompletionMode('c');
633
634 // remove pattern from line
635 newLine.remove(pos, pattern.length());
636
637 // did we find the nick in the middle of the line?
638 if(pos && complete)
639 {
640 m_inputBar->setLastCompletion(foundNick);
641 QString addMiddle = Preferences::self()->nickCompleteSuffixMiddle();
642 newLine.insert(pos, foundNick + addMiddle);
643 pos = pos + foundNick.length() + addMiddle.length();
644 }
645 // no, it was at the beginning
646 else if(complete)
647 {
648 m_inputBar->setLastCompletion(foundNick);
649 QString addStart = Preferences::self()->nickCompleteSuffixStart();
650 newLine.insert(pos, foundNick + addStart);
651 pos = pos + foundNick.length() + addStart.length();
652 }
653 // the nick wasn't complete
654 else
655 {
656 newLine.insert(pos, foundNick);
657 pos = pos + foundNick.length();
658 }
659 }
660 // no pattern found, so restore old cursor position
661 else pos = oldPos;
662 }
663 }
664
665 // Set new text and cursor position
666 m_inputBar->setText(newLine);
667 cursor.setPosition(pos);
668 m_inputBar->setTextCursor(cursor);
669 }
670
671 // make sure to step back one position when completion ends so the user starts
672 // with the last complete they made
endCompleteNick()673 void Channel::endCompleteNick()
674 {
675 if(completionPosition) completionPosition--;
676 else completionPosition=nicknameList.count()-1;
677 }
678
setName(const QString & newName)679 void Channel::setName(const QString& newName)
680 {
681 ChatWindow::setName(newName);
682 setLogfileName(newName.toLower());
683 }
684
autoJoin()685 bool Channel::autoJoin()
686 {
687 if (!m_server->getServerGroup()) return false;
688
689 Konversation::ChannelList channelList = m_server->getServerGroup()->channelList();
690
691 return channelList.contains(channelSettings());
692 }
693
setAutoJoin(bool autojoin)694 void Channel::setAutoJoin(bool autojoin)
695 {
696 if (autojoin && !(autoJoin()))
697 {
698 Konversation::ChannelSettings before;
699
700 const QList<Channel *> channelList = m_server->getChannelList();
701
702 if (channelList.count() > 1)
703 {
704 QMap<int, Channel*> channelMap;
705
706 int index = -1;
707 int ownIndex = m_server->getViewContainer()->getViewIndex(this);
708
709 for (Channel* channel : channelList) {
710 index = m_server->getViewContainer()->getViewIndex(channel);
711
712 if (index && index > ownIndex) channelMap.insert(index, channel);
713 }
714
715 if (!channelMap.isEmpty()) {
716 for (Channel* channel : qAsConst(channelMap)) {
717 if (channel->autoJoin())
718 {
719 before = channel->channelSettings();
720
721 break;
722 }
723 }
724 }
725 }
726
727 if (m_server->getServerGroup())
728 m_server->getServerGroup()->addChannel(channelSettings(), before);
729 }
730 else
731 {
732 if (m_server->getServerGroup())
733 m_server->getServerGroup()->removeChannel(channelSettings());
734 }
735 }
736
737
738
getPassword() const739 QString Channel::getPassword() const
740 {
741 QString password;
742
743 for (const QString& mode : m_modeList) {
744 if (mode[0] == QLatin1Char('k')) password = mode.mid(1);
745 }
746
747 if (password.isEmpty() && m_server->getServerGroup())
748 {
749 Konversation::ChannelList channelSettingsList = m_server->getServerGroup()->channelList();
750 Konversation::ChannelSettings channelSettings(getName());
751 int index = channelSettingsList.indexOf(channelSettings);
752 if(index >= 0)
753 password = channelSettingsList.at(index).password();
754 }
755
756 return password;
757 }
758
channelSettings() const759 Konversation::ChannelSettings Channel::channelSettings() const
760 {
761 Konversation::ChannelSettings channel;
762
763 channel.setName(getName());
764 channel.setPassword(getPassword());
765 channel.setNotificationsEnabled(notificationsEnabled());
766
767 return channel;
768 }
769
sendFileMenu()770 void Channel::sendFileMenu()
771 {
772 Q_EMIT sendFile();
773 }
774
channelTextEntered()775 void Channel::channelTextEntered()
776 {
777 QString line = m_inputBar->toPlainText();
778
779 m_inputBar->clear();
780
781 if (!line.isEmpty()) sendText(sterilizeUnicode(line));
782 }
783
channelPassthroughCommand()784 void Channel::channelPassthroughCommand()
785 {
786 QString commandChar = Preferences::self()->commandChar();
787 QString line = m_inputBar->toPlainText();
788
789 m_inputBar->clear();
790
791 if(!line.isEmpty())
792 {
793 // Prepend commandChar on Ctrl+Enter to bypass outputfilter command recognition
794 if (line.startsWith(commandChar))
795 {
796 line = commandChar + line;
797 }
798 sendText(sterilizeUnicode(line));
799 }
800 }
801
sendText(const QString & sendLine)802 void Channel::sendText(const QString& sendLine)
803 {
804 // create a work copy
805 QString outputAll(sendLine);
806
807 // replace aliases and wildcards
808 OutputFilter::replaceAliases(outputAll, this);
809
810 // Send all strings, one after another
811 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
812 const QStringList outList = outputAll.split(QRegularExpression(QStringLiteral("[\r\n]+")), QString::SkipEmptyParts);
813 #else
814 const QStringList outList = outputAll.split(QRegularExpression(QStringLiteral("[\r\n]+")), Qt::SkipEmptyParts);
815 #endif
816 for (const QString& output : outList){
817 // encoding stuff is done in Server()
818 Konversation::OutputFilterResult result = m_server->getOutputFilter()->parse(m_server->getNickname(), output, getName(), this);
819
820 // Is there something we need to display for ourselves?
821 if(!result.output.isEmpty())
822 {
823 if(result.type == Konversation::Action) appendAction(m_server->getNickname(), result.output);
824 else if(result.type == Konversation::Command) appendCommandMessage(result.typeString, result.output);
825 else if(result.type == Konversation::Program) appendServerMessage(result.typeString, result.output);
826 else if(result.type == Konversation::PrivateMessage) msgHelper(result.typeString, result.output);
827 else append(m_server->getNickname(), result.output);
828 }
829 else if (!result.outputList.isEmpty()) {
830 if (result.type == Konversation::Message)
831 {
832 for (const QString& out : qAsConst(result.outputList)) {
833 append(m_server->getNickname(), out);
834 }
835 }
836 else if (result.type == Konversation::Action)
837 {
838 for (int i = 0; i < result.outputList.count(); ++i)
839 {
840 if (i == 0)
841 appendAction(m_server->getNickname(), result.outputList.at(i));
842 else
843 append(m_server->getNickname(), result.outputList.at(i));
844 }
845 }
846 }
847
848 // Send anything else to the server
849 if (!result.toServerList.empty())
850 m_server->queueList(result.toServerList);
851 else
852 m_server->queue(result.toServer);
853 }
854 }
855
setNickname(const QString & newNickname)856 void Channel::setNickname(const QString& newNickname)
857 {
858 nicknameCombobox->setCurrentIndex(nicknameCombobox->findText(newNickname));
859 }
860
getSelectedNickList() const861 QStringList Channel::getSelectedNickList() const
862 {
863 QStringList selectedNicks;
864
865 for (Nick* nick : qAsConst(nicknameList)) {
866 if (nick->isSelected())
867 selectedNicks << nick->getChannelNick()->getNickname();
868 }
869
870 return selectedNicks;
871 }
872
channelLimitChanged()873 void Channel::channelLimitChanged()
874 {
875 unsigned int lim=limit->text().toUInt();
876
877 modeButtonClicked(7,lim>0);
878 }
879
modeButtonClicked(int id,bool on)880 void Channel::modeButtonClicked(int id, bool on)
881 {
882 const char mode[] = {'t','n','s','i','p','m','k','l'};
883 QString command(QStringLiteral("MODE %1 %2%3 %4"));
884 QString args = getPassword();
885
886 if (mode[id] == 'k')
887 {
888 if (args.isEmpty())
889 {
890 QPointer<KPasswordDialog> dlg = new KPasswordDialog(this);
891 dlg->setPrompt(i18n("Channel Password"));
892 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 84, 0)
893 dlg->setRevealPasswordAvailable(KAuthorized::authorize(QStringLiteral("lineedit_reveal_password")));
894 #endif
895 if (dlg->exec() && !dlg->password().isEmpty())
896 {
897 args = dlg->password();
898 }
899 delete dlg;
900 }
901
902 }
903 else if(mode[id]=='l')
904 {
905 if(limit->text().isEmpty() && on)
906 {
907 bool ok=false;
908 // ask user how many nicks should be the limit
909 args=QInputDialog::getText(this, i18n("Channel User Limit"),
910 i18n("Enter the new user limit for the channel:"),
911 QLineEdit::Normal,
912 limit->text(), // will be always "" but what the hell ;)
913 &ok);
914 // leave this function if user cancels
915 if(!ok) return;
916 }
917 else if(on)
918 args=limit->text();
919 }
920 // put together the mode command and send it to the server queue
921 m_server->queue(command.arg(getName(), (on) ? QStringLiteral("+") : QStringLiteral("-")).arg(mode[id]).arg(args));
922 }
923
quickButtonClicked(const QString & buttonText)924 void Channel::quickButtonClicked(const QString &buttonText)
925 {
926 // parse wildcards (toParse,nickname,channelName,nickList,queryName,parameter)
927 QString out=m_server->parseWildcards(buttonText,m_server->getNickname(),getName(),getPassword(),getSelectedNickList(), m_inputBar->toPlainText());
928
929 // are there any newlines in the definition?
930 if (out.contains(QLatin1Char('\n')))
931 sendText(out);
932 // single line without newline needs to be copied into input line
933 else
934 m_inputBar->setText(out, true);
935 }
936
addNickname(ChannelNickPtr channelnick)937 void Channel::addNickname(ChannelNickPtr channelnick)
938 {
939 QString nickname = channelnick->loweredNickname();
940
941 Nick* nick=nullptr;
942
943 for (Nick* lookNick : qAsConst(nicknameList)) {
944 if(lookNick->getChannelNick()->loweredNickname() == nickname)
945 {
946 nick = lookNick;
947 break;
948 }
949 }
950
951 if (nick == nullptr)
952 {
953 fastAddNickname(channelnick);
954
955 if(channelnick->isAnyTypeOfOp())
956 {
957 adjustOps(1);
958 }
959
960 adjustNicks(1);
961 requestNickListSort();
962 }
963 // TODO (re)-investigate why it was thought unusual to add an already added nick
964 // -- see bug 333969
965 }
966
967 // Use with caution! Does not check for duplicates or may not
968 // sort if delayed sorting is in effect.
fastAddNickname(ChannelNickPtr channelnick,Nick * nick)969 void Channel::fastAddNickname(ChannelNickPtr channelnick, Nick *nick)
970 {
971 Q_ASSERT(channelnick);
972 if(!channelnick) return;
973
974 if (!nick || !nick->treeWidget())
975 {
976 // Deal with nicknameListView now (creating nick if necessary)
977 NickListView::NoSorting noSorting(nicknameListView);
978 int index = nicknameListView->topLevelItemCount();
979
980 // Append nick to the lists
981 if (nick)
982 {
983 nicknameListView->addTopLevelItem(nick);
984 }
985 else
986 {
987 nick = new Nick(nicknameListView, this, channelnick);
988 m_nicknameListViewTextChanged |= 0xFF; // new nick, text changed.
989 }
990
991 if (!m_delayedSortTimer->isActive()) {
992 // Find its right place and insert where it belongs
993 int newindex = nicknameListView->findLowerBound(*nick);
994 if (newindex != index) {
995 if (newindex >= index)
996 newindex--;
997 nicknameListView->takeTopLevelItem(index);
998 nicknameListView->insertTopLevelItem(newindex, nick);
999 }
1000 }
1001 // Otherwise it will be sorted by delayed sort.
1002 }
1003
1004 // Now deal with nicknameList
1005 if (m_delayedSortTimer->isActive())
1006 {
1007 // nicks get sorted later
1008 nicknameList.append(nick);
1009 } else {
1010 NickList::iterator it = std::lower_bound(nicknameList.begin(), nicknameList.end(), nick, nickLessThan);
1011 nicknameList.insert(it, nick);
1012 }
1013
1014 m_nicknameNickHash.insert (channelnick->loweredNickname(), nick);
1015 }
1016
1017 /* Determines whether Nick/Part/Join event should be shown or skipped based on user settings. */
shouldShowEvent(ChannelNickPtr channelNick) const1018 bool Channel::shouldShowEvent(ChannelNickPtr channelNick) const
1019 {
1020 if (Preferences::self()->hideUnimportantEvents())
1021 {
1022 if (channelNick && Preferences::self()->hideUnimportantEventsExcludeActive())
1023 {
1024 uint activityThreshold = 3600;
1025
1026 if (Preferences::self()->hideUnimportantEventsExcludeActiveThreshold() == 0) // last 10 minutes
1027 activityThreshold = 600;
1028 else if (Preferences::self()->hideUnimportantEventsExcludeActiveThreshold() == 1) // last hour
1029 activityThreshold = 3600;
1030 else if (Preferences::self()->hideUnimportantEventsExcludeActiveThreshold() == 2) // last day
1031 activityThreshold = 86400;
1032 else if (Preferences::self()->hideUnimportantEventsExcludeActiveThreshold() == 3) // last week
1033 activityThreshold = 604800;
1034
1035 if (m_server->isWatchedNick(channelNick->getNickname()))
1036 return true; // nick is on our watched list, so we probably want to see the event
1037 else if (channelNick->timeStamp()+activityThreshold > QDateTime::currentDateTime().toSecsSinceEpoch())
1038 return true; // the nick has spoken within activity threshold
1039 else
1040 return false;
1041 }
1042 else
1043 return false; // if hideUnimportantEventsExcludeActive is off, we hide all events
1044 }
1045 else
1046 return true; // if hideUnimportantEvents is off we don't care and just show the event
1047 }
1048
nickRenamed(const QString & oldNick,const NickInfo & nickInfo,const QHash<QString,QString> & messageTags)1049 void Channel::nickRenamed(const QString &oldNick, const NickInfo& nickInfo, const QHash<QString, QString> &messageTags)
1050 {
1051 QString newNick = nickInfo.getNickname();
1052 Nick *nick = getNickByName(oldNick);
1053 bool displayCommandMessage;
1054
1055 if (Preferences::self()->hideUnimportantEventsExcludeActive() && m_server->isWatchedNick(oldNick))
1056 displayCommandMessage = true; // this is for displaying watched people NICK events both ways (watched->unwatched and unwatched->watched)
1057 else if (nick)
1058 displayCommandMessage = shouldShowEvent(nick->getChannelNick());
1059 else
1060 displayCommandMessage = shouldShowEvent(ChannelNickPtr()); // passing null pointer
1061
1062 /* Did we change our nick name? */
1063 if(newNick == m_server->getNickname()) /* Check newNick because m_server->getNickname() is already updated to new nick */
1064 {
1065 setNickname(newNick);
1066 if (displayCommandMessage)
1067 appendCommandMessage(i18n("Nick"),i18n("You are now known as %1.", newNick), messageTags, true, true);
1068 }
1069 else if (displayCommandMessage)
1070 {
1071 /* No, must've been someone else */
1072 appendCommandMessage(i18n("Nick"),i18n("%1 is now known as %2.", oldNick, newNick), messageTags);
1073 }
1074
1075 if (nick)
1076 {
1077 m_nicknameNickHash.remove(oldNick.toLower());
1078 m_nicknameNickHash.insert(newNick.toLower(), nick);
1079
1080 repositionNick(nick);
1081 }
1082 }
1083
joinNickname(ChannelNickPtr channelNick,const QHash<QString,QString> & messageTags)1084 void Channel::joinNickname(ChannelNickPtr channelNick, const QHash<QString, QString> &messageTags)
1085 {
1086 bool displayCommandMessage = shouldShowEvent(channelNick);
1087
1088 if(channelNick->getNickname() == m_server->getNickname())
1089 {
1090 m_joined = true;
1091 Q_EMIT joined(this);
1092 if (displayCommandMessage)
1093 appendCommandMessage(i18nc("Message type", "Join"), i18nc("%1 = our hostmask, %2 = channel",
1094 "You (%1) have joined the channel %2.", channelNick->getHostmask(), getName()), messageTags, false, true);
1095
1096 // Prepare for impending NAMES.
1097 purgeNicks();
1098 nicknameListView->setUpdatesEnabled(false);
1099
1100 m_ownChannelNick = channelNick;
1101 refreshModeButtons();
1102 setActive(true);
1103
1104 ViewContainer* viewContainer = Application::instance()->getMainWindow()->getViewContainer();
1105
1106 //HACK the way the notification priorities work sucks, this forces the tab text color to ungray right now.
1107 if (viewContainer->getFrontView() == this
1108 || m_currentTabNotify == Konversation::tnfNone
1109 || (!Preferences::self()->tabNotificationsEvents() && m_currentTabNotify == Konversation::tnfControl))
1110 {
1111 Application::instance()->getMainWindow()->getViewContainer()->unsetViewNotification(this);
1112 }
1113
1114 Application::instance()->notificationHandler()->channelJoin(this,getName());
1115 }
1116 else
1117 {
1118 QString nick = channelNick->getNickname();
1119 QString hostname = channelNick->getHostmask();
1120 if (displayCommandMessage)
1121 appendCommandMessage(i18nc("Message type", "Join"), i18nc("%1 is the nick joining and %2 the hostmask of that nick",
1122 "%1 (%2) has joined this channel.", nick, hostname), messageTags, false);
1123 addNickname(channelNick);
1124 }
1125 }
1126
removeNick(ChannelNickPtr channelNick,const QString & reason,bool quit,const QHash<QString,QString> & messageTags)1127 void Channel::removeNick(ChannelNickPtr channelNick, const QString &reason, bool quit, const QHash<QString, QString> &messageTags)
1128 {
1129 bool displayCommandMessage = shouldShowEvent(channelNick);
1130
1131 QString displayReason = reason;
1132
1133 if(!displayReason.isEmpty())
1134 {
1135 // if the reason contains text markup characters, play it safe and reset all
1136 if (hasIRCMarkups(displayReason))
1137 displayReason += QStringLiteral("\017");
1138 }
1139
1140 if(channelNick->getNickname() == m_server->getNickname())
1141 {
1142 if (displayCommandMessage)
1143 {
1144 //If in the future we can leave a channel, but not close the window, refreshModeButtons() has to be called.
1145 if (quit)
1146 {
1147 if (displayReason.isEmpty())
1148 appendCommandMessage(i18nc("Message type", "Quit"), i18n("You (%1) have left this server.", channelNick->getHostmask()), messageTags);
1149 else
1150 appendCommandMessage(i18nc("Message type", "Quit"), i18nc("%1 = our hostmask, %2 = reason", "You (%1) have left this server (%2).",
1151 channelNick->getHostmask(), displayReason), messageTags, false);
1152 }
1153 else
1154 {
1155 if (displayReason.isEmpty())
1156 appendCommandMessage(i18nc("Message type", "Part"), i18n("You have left channel %1.", getName()), messageTags);
1157 else
1158 appendCommandMessage(i18nc("Message type", "Part"), i18nc("%1 = our hostmask, %2 = channel, %3 = reason",
1159 "You (%1) have left channel %2 (%3).", channelNick->getHostmask(), getName(), displayReason), messageTags, false);
1160 }
1161 }
1162
1163 delete this;
1164 }
1165 else
1166 {
1167 if (displayCommandMessage)
1168 {
1169 if (quit)
1170 {
1171 if (displayReason.isEmpty())
1172 appendCommandMessage(i18nc("Message type", "Quit"), i18n("%1 (%2) has left this server.", channelNick->getNickname(),
1173 channelNick->getHostmask()), messageTags, false);
1174 else
1175 appendCommandMessage(i18nc("Message type", "Quit"), i18nc("%1 = nick, %2 = hostname, %3 = reason",
1176 "%1 (%2) has left this server (%3).", channelNick->getNickname(), channelNick->getHostmask(), displayReason), messageTags, false);
1177 }
1178 else
1179 {
1180 if (displayReason.isEmpty())
1181 appendCommandMessage(i18nc("Message type", "Part"), i18n("%1 (%2) has left this channel.", channelNick->getNickname(),
1182 channelNick->getHostmask()), messageTags, false);
1183 else
1184 appendCommandMessage(i18nc("Message type", "Part"), i18nc("%1 = nick, %2 = hostmask, %3 = reason",
1185 "%1 (%2) has left this channel (%3).", channelNick->getNickname(), channelNick->getHostmask(), displayReason), messageTags, false);
1186 }
1187 }
1188
1189 if(channelNick->isAnyTypeOfOp())
1190 {
1191 adjustOps(-1);
1192 }
1193
1194 adjustNicks(-1);
1195 Nick* nick = getNickByName(channelNick->loweredNickname());
1196
1197 if(nick)
1198 {
1199 nicknameList.removeOne(nick);
1200 m_nicknameNickHash.remove(channelNick->loweredNickname());
1201 delete nick;
1202 // Execute this otherwise it may crash trying to access deleted nick
1203 nicknameListView->executeDelayedItemsLayout();
1204 }
1205 else
1206 {
1207 qCWarning(KONVERSATION_LOG) << "Nickname " << channelNick->getNickname() << " not found!";
1208 }
1209 }
1210 }
1211
flushNickQueue()1212 void Channel::flushNickQueue()
1213 {
1214 processQueuedNicks(true);
1215 }
1216
kickNick(ChannelNickPtr channelNick,const QString & kicker,const QString & reason,const QHash<QString,QString> & messageTags)1217 void Channel::kickNick(ChannelNickPtr channelNick, const QString &kicker, const QString &reason, const QHash<QString, QString> &messageTags)
1218 {
1219 QString displayReason = reason;
1220
1221 if(!displayReason.isEmpty())
1222 {
1223 // if the reason contains text markup characters, play it safe and reset all
1224 if (hasIRCMarkups(displayReason))
1225 displayReason += QStringLiteral("\017");
1226 }
1227
1228 if(channelNick->getNickname() == m_server->getNickname())
1229 {
1230 if(kicker == m_server->getNickname())
1231 {
1232 if (displayReason.isEmpty())
1233 appendCommandMessage(i18n("Kick"), i18n("You have kicked yourself from channel %1.", getName()), messageTags);
1234 else
1235 appendCommandMessage(i18n("Kick"), i18nc("%1 adds the channel and %2 the reason",
1236 "You have kicked yourself from channel %1 (%2).", getName(), displayReason), messageTags);
1237 }
1238 else
1239 {
1240 if (displayReason.isEmpty())
1241 {
1242 appendCommandMessage(i18n("Kick"), i18nc("%1 adds the channel, %2 adds the kicker",
1243 "You have been kicked from channel %1 by %2.", getName(), kicker), messageTags);
1244 }
1245 else
1246 {
1247 appendCommandMessage(i18n("Kick"), i18nc("%1 adds the channel, %2 the kicker and %3 the reason",
1248 "You have been kicked from channel %1 by %2 (%3).", getName(), kicker, displayReason), messageTags);
1249 }
1250
1251 Application::instance()->notificationHandler()->kick(this,getName(), kicker);
1252 }
1253
1254 m_joined=false;
1255 setActive(false);
1256
1257 //HACK the way the notification priorities work sucks, this forces the tab text color to gray right now.
1258 if (m_currentTabNotify == Konversation::tnfNone || (!Preferences::self()->tabNotificationsEvents() && m_currentTabNotify == Konversation::tnfControl))
1259 Application::instance()->getMainWindow()->getViewContainer()->unsetViewNotification(this);
1260
1261 return;
1262 }
1263 else
1264 {
1265 if(kicker == m_server->getNickname())
1266 {
1267 if (displayReason.isEmpty())
1268 appendCommandMessage(i18n("Kick"), i18n("You have kicked %1 from the channel.", channelNick->getNickname()), messageTags);
1269 else
1270 appendCommandMessage(i18n("Kick"), i18nc("%1 adds the kicked nick and %2 the reason",
1271 "You have kicked %1 from the channel (%2).", channelNick->getNickname(), displayReason), messageTags);
1272 }
1273 else
1274 {
1275 if (displayReason.isEmpty())
1276 {
1277 appendCommandMessage(i18n("Kick"), i18nc("%1 adds the kicked nick, %2 adds the kicker",
1278 "%1 has been kicked from the channel by %2.", channelNick->getNickname(), kicker), messageTags);
1279 }
1280 else
1281 {
1282 appendCommandMessage(i18n("Kick"), i18nc("%1 adds the kicked nick, %2 the kicker and %3 the reason",
1283 "%1 has been kicked from the channel by %2 (%3).", channelNick->getNickname(), kicker, displayReason), messageTags);
1284 }
1285 }
1286
1287 if(channelNick->isAnyTypeOfOp())
1288 adjustOps(-1);
1289
1290 adjustNicks(-1);
1291 Nick* nick = getNickByName(channelNick->loweredNickname());
1292
1293 if(nick == nullptr)
1294 {
1295 qCWarning(KONVERSATION_LOG) << "Nickname " << channelNick->getNickname() << " not found!";
1296 }
1297 else
1298 {
1299 nicknameList.removeOne(nick);
1300 m_nicknameNickHash.remove(channelNick->loweredNickname());
1301 delete nick;
1302 }
1303 }
1304 }
1305
getNickByName(const QString & lookname) const1306 Nick* Channel::getNickByName(const QString &lookname) const
1307 {
1308 QString lcLookname(lookname.toLower());
1309
1310 return m_nicknameNickHash.value(lcLookname);
1311 }
1312
adjustNicks(int value)1313 void Channel::adjustNicks(int value)
1314 {
1315 if((nicks == 0) && (value <= 0))
1316 {
1317 return;
1318 }
1319
1320 nicks += value;
1321
1322 if(nicks < 0)
1323 {
1324 nicks = 0;
1325 }
1326
1327 emitUpdateInfo();
1328 }
1329
adjustOps(int value)1330 void Channel::adjustOps(int value)
1331 {
1332 if((ops == 0) && (value <= 0))
1333 {
1334 return;
1335 }
1336
1337 ops += value;
1338
1339 if(ops < 0)
1340 {
1341 ops = 0;
1342 }
1343
1344 emitUpdateInfo();
1345 }
1346
emitUpdateInfo()1347 void Channel::emitUpdateInfo()
1348 {
1349 QString info = getName() + QStringLiteral(" - ");
1350 info += i18np("%1 nick", "%1 nicks", numberOfNicks());
1351 info += i18np(" (%1 op)", " (%1 ops)", numberOfOps());
1352
1353 Q_EMIT updateInfo(info);
1354 }
1355
getTopic() const1356 QString Channel::getTopic() const
1357 {
1358 return m_topicHistory->currentTopic();
1359 }
1360
setTopic(const QString & text,const QHash<QString,QString> & messageTags)1361 void Channel::setTopic(const QString& text, const QHash<QString, QString> &messageTags)
1362 {
1363 QString cleanTopic = text;
1364
1365 // If the reason contains text markup characters, play it safe and reset all.
1366 if (!cleanTopic.isEmpty() && hasIRCMarkups(cleanTopic))
1367 cleanTopic += QStringLiteral("\017");
1368
1369 appendCommandMessage(i18n("Topic"), i18n("The channel topic is \"%1\".", cleanTopic), messageTags);
1370
1371 m_topicHistory->appendTopic(replaceIRCMarkups(Konversation::removeIrcMarkup(text)));
1372 }
1373
setTopic(const QString & nickname,const QString & text,const QHash<QString,QString> & messageTags)1374 void Channel::setTopic(const QString& nickname, const QString& text, const QHash<QString, QString> &messageTags)
1375 {
1376 QString cleanTopic = text;
1377
1378 // If the reason contains text markup characters, play it safe and reset all.
1379 if (!cleanTopic.isEmpty() && hasIRCMarkups(cleanTopic))
1380 cleanTopic += QStringLiteral("\017");
1381
1382 if (nickname == m_server->getNickname())
1383 appendCommandMessage(i18n("Topic"), i18n("You set the channel topic to \"%1\".", cleanTopic), messageTags);
1384 else
1385 appendCommandMessage(i18n("Topic"), i18n("%1 sets the channel topic to \"%2\".", nickname, cleanTopic), messageTags);
1386
1387 m_topicHistory->appendTopic(replaceIRCMarkups(Konversation::removeIrcMarkup(text)), nickname);
1388 }
1389
setTopicAuthor(const QString & author,QDateTime time)1390 void Channel::setTopicAuthor(const QString& author, QDateTime time)
1391 {
1392 if (time.isNull() || !time.isValid())
1393 time = QDateTime::currentDateTime();
1394
1395 m_topicHistory->setCurrentTopicMetadata(author, time);
1396 }
1397
updateMode(const QString & sourceNick,char mode,bool plus,const QString & parameter,const QHash<QString,QString> & messageTags)1398 void Channel::updateMode(const QString& sourceNick, char mode, bool plus, const QString ¶meter, const QHash<QString, QString> &messageTags)
1399 {
1400 // Note for future expansion:
1401 // m_server->getChannelNick(getName(), sourceNick);
1402 // may not return a valid channelNickPtr if the mode is updated by
1403 // the server.
1404 // --johnflux, 9 September 2004
1405
1406 // Note: nick repositioning in the nicknameListView should be
1407 // triggered by nickinfo / channelnick signals
1408
1409 QString message;
1410 ChannelNickPtr parameterChannelNick = m_server->getChannelNick(getName(), parameter);
1411
1412 bool fromMe = false;
1413 bool toMe = false;
1414
1415 // HACK right now Server only keeps type A modes
1416 bool banTypeThang = m_server->banAddressListModes().contains(QLatin1Char(mode));
1417
1418 // remember if this nick had any type of op.
1419 bool wasAnyOp = false;
1420 if (parameterChannelNick)
1421 {
1422 addNickname(parameterChannelNick);
1423
1424 wasAnyOp = parameterChannelNick->isAnyTypeOfOp();
1425 }
1426
1427 if (sourceNick.toLower() == m_server->loweredNickname())
1428 fromMe = true;
1429 if (parameter.toLower() == m_server->loweredNickname())
1430 toMe = true;
1431
1432 switch (mode)
1433 {
1434 case 'q':
1435 if (banTypeThang)
1436 {
1437 if (plus)
1438 {
1439 if (fromMe) message = i18n("You set a quiet on %1.", parameter);
1440 else message = i18n("%1 sets a quiet on %2.", sourceNick, parameter);
1441 }
1442 else
1443 {
1444 if (fromMe) message = i18n("You remove the quiet on %1.", parameter);
1445 else message = i18n("%1 removes the quiet on %2.", sourceNick, parameter);
1446 }
1447 }
1448 else
1449 {
1450 if (plus)
1451 {
1452 if (fromMe)
1453 {
1454 if (toMe) message = i18n("You give channel owner privileges to yourself.");
1455 else message = i18n("You give channel owner privileges to %1.", parameter);
1456 }
1457 else
1458 {
1459 if (toMe) message = i18n("%1 gives channel owner privileges to you.", sourceNick);
1460 else message = i18n("%1 gives channel owner privileges to %2.", sourceNick, parameter);
1461 }
1462 }
1463 else
1464 {
1465 if (fromMe)
1466 {
1467 if (toMe) message = i18n("You take channel owner privileges from yourself.");
1468 else message = i18n("You take channel owner privileges from %1.", parameter);
1469 }
1470 else
1471 {
1472 if (toMe) message = i18n("%1 takes channel owner privileges from you.", sourceNick);
1473 else message = i18n("%1 takes channel owner privileges from %2.", sourceNick, parameter);
1474 }
1475 }
1476 if (parameterChannelNick)
1477 {
1478 parameterChannelNick->setOwner(plus);
1479 emitUpdateInfo();
1480 }
1481 }
1482 break;
1483
1484 case 'a':
1485 if (plus)
1486 {
1487 if (fromMe)
1488 {
1489 if (toMe) message = i18n("You give channel admin privileges to yourself.");
1490 else message = i18n("You give channel admin privileges to %1.", parameter);
1491 }
1492 else
1493 {
1494 if (toMe) message = i18n("%1 gives channel admin privileges to you.", sourceNick);
1495 else message = i18n("%1 gives channel admin privileges to %2.", sourceNick, parameter);
1496 }
1497 }
1498 else
1499 {
1500 if (fromMe)
1501 {
1502 if (toMe) message = i18n("You take channel admin privileges from yourself.");
1503 else message = i18n("You take channel admin privileges from %1.", parameter);
1504 }
1505 else
1506 {
1507 if (toMe) message = i18n("%1 takes channel admin privileges from you.", sourceNick);
1508 else message = i18n("%1 takes channel admin privileges from %2.", sourceNick, parameter);
1509 }
1510 }
1511 if (parameterChannelNick)
1512 {
1513 parameterChannelNick->setAdmin(plus);
1514 emitUpdateInfo();
1515 }
1516 break;
1517
1518 case 'o':
1519 if (plus)
1520 {
1521 if (fromMe)
1522 {
1523 if (toMe) message = i18n("You give channel operator privileges to yourself.");
1524 else message = i18n("You give channel operator privileges to %1.", parameter);
1525 }
1526 else
1527 {
1528 if (toMe) message = i18n("%1 gives channel operator privileges to you.", sourceNick);
1529 else message = i18n("%1 gives channel operator privileges to %2.", sourceNick, parameter);
1530 }
1531 }
1532 else
1533 {
1534 if (fromMe)
1535 {
1536 if (toMe) message = i18n("You take channel operator privileges from yourself.");
1537 else message = i18n("You take channel operator privileges from %1.", parameter);
1538 }
1539 else
1540 {
1541 if (toMe) message = i18n("%1 takes channel operator privileges from you.", sourceNick);
1542 else message = i18n("%1 takes channel operator privileges from %2.", sourceNick, parameter);
1543 }
1544 }
1545 if (parameterChannelNick)
1546 {
1547 parameterChannelNick->setOp(plus);
1548 emitUpdateInfo();
1549 }
1550 break;
1551
1552 case 'h':
1553 if (plus)
1554 {
1555 if (fromMe)
1556 {
1557 if (toMe) message = i18n("You give channel halfop privileges to yourself.");
1558 else message = i18n("You give channel halfop privileges to %1.", parameter);
1559 }
1560 else
1561 {
1562 if (toMe) message = i18n("%1 gives channel halfop privileges to you.", sourceNick);
1563 else message = i18n("%1 gives channel halfop privileges to %2.", sourceNick, parameter);
1564 }
1565 }
1566 else
1567 {
1568 if (fromMe)
1569 {
1570 if (toMe) message = i18n("You take channel halfop privileges from yourself.");
1571 else message = i18n("You take channel halfop privileges from %1.", parameter);
1572 }
1573 else
1574 {
1575 if (toMe) message = i18n("%1 takes channel halfop privileges from you.", sourceNick);
1576 else message = i18n("%1 takes channel halfop privileges from %2.", sourceNick, parameter);
1577 }
1578 }
1579 if (parameterChannelNick)
1580 {
1581 parameterChannelNick->setHalfOp(plus);
1582 emitUpdateInfo();
1583 }
1584 break;
1585
1586 //case 'O': break;
1587
1588 case 'v':
1589 if (plus)
1590 {
1591 if (fromMe)
1592 {
1593 if (toMe) message = i18n("You give yourself permission to talk.");
1594 else message = i18n("You give %1 permission to talk.", parameter);
1595 }
1596 else
1597 {
1598 if (toMe) message = i18n("%1 gives you permission to talk.", sourceNick);
1599 else message = i18n("%1 gives %2 permission to talk.", sourceNick, parameter);
1600 }
1601 }
1602 else
1603 {
1604 if (fromMe)
1605 {
1606 if (toMe) message = i18n("You take the permission to talk from yourself.");
1607 else message = i18n("You take the permission to talk from %1.", parameter);
1608 }
1609 else
1610 {
1611 if (toMe) message = i18n("%1 takes the permission to talk from you.", sourceNick);
1612 else message = i18n("%1 takes the permission to talk from %2.", sourceNick, parameter);
1613 }
1614 }
1615 if (parameterChannelNick)
1616 {
1617 parameterChannelNick->setVoice(plus);
1618 }
1619 break;
1620
1621 case 'c':
1622 if (plus)
1623 {
1624 if (fromMe) message = i18n("You set the channel mode to 'no colors allowed'.");
1625 else message = i18n("%1 sets the channel mode to 'no colors allowed'.", sourceNick);
1626 }
1627 else
1628 {
1629 if (fromMe) message = i18n("You set the channel mode to 'allow color codes'.");
1630 else message = i18n("%1 sets the channel mode to 'allow color codes'.", sourceNick);
1631 }
1632 break;
1633
1634 case 'i':
1635 if (plus)
1636 {
1637 if (fromMe) message = i18n("You set the channel mode to 'invite only'.");
1638 else message = i18n("%1 sets the channel mode to 'invite only'.", sourceNick);
1639 }
1640 else
1641 {
1642 if (fromMe) message = i18n("You remove the 'invite only' mode from the channel.");
1643 else message = i18n("%1 removes the 'invite only' mode from the channel.", sourceNick);
1644 }
1645 modeI->setDown(plus);
1646 break;
1647
1648 case 'm':
1649 if (plus)
1650 {
1651 if (fromMe) message = i18n("You set the channel mode to 'moderated'.");
1652 else message = i18n("%1 sets the channel mode to 'moderated'.", sourceNick);
1653 }
1654 else
1655 {
1656 if (fromMe) message = i18n("You set the channel mode to 'unmoderated'.");
1657 else message = i18n("%1 sets the channel mode to 'unmoderated'.", sourceNick);
1658 }
1659 modeM->setDown(plus);
1660 break;
1661
1662 case 'n':
1663 if (plus)
1664 {
1665 if (fromMe) message = i18n("You set the channel mode to 'no messages from outside'.");
1666 else message = i18n("%1 sets the channel mode to 'no messages from outside'.", sourceNick);
1667 }
1668 else
1669 {
1670 if (fromMe) message = i18n("You set the channel mode to 'allow messages from outside'.");
1671 else message = i18n("%1 sets the channel mode to 'allow messages from outside'.", sourceNick);
1672 }
1673 modeN->setDown(plus);
1674 break;
1675
1676 case 'p':
1677 if (plus)
1678 {
1679 if (fromMe) message = i18n("You set the channel mode to 'private'.");
1680 else message = i18n("%1 sets the channel mode to 'private'.", sourceNick);
1681 }
1682 else
1683 {
1684 if (fromMe) message = i18n("You set the channel mode to 'public'.");
1685 else message = i18n("%1 sets the channel mode to 'public'.", sourceNick);
1686 }
1687 modeP->setDown(plus);
1688 if (plus) modeS->setDown(false);
1689 break;
1690
1691 case 's':
1692 if (plus)
1693 {
1694 if (fromMe) message = i18n("You set the channel mode to 'secret'.");
1695 else message = i18n("%1 sets the channel mode to 'secret'.", sourceNick);
1696 }
1697 else
1698 {
1699 if (fromMe) message = i18n("You set the channel mode to 'visible'.");
1700 else message = i18n("%1 sets the channel mode to 'visible'.", sourceNick);
1701 }
1702 modeS->setDown(plus);
1703 if (plus) modeP->setDown(false);
1704 break;
1705
1706 //case 'r': break;
1707
1708 case 't':
1709 if (plus)
1710 {
1711 if (fromMe) message = i18n("You switch on 'topic protection'.");
1712 else message = i18n("%1 switches on 'topic protection'.", sourceNick);
1713 }
1714 else
1715 {
1716 if (fromMe) message = i18n("You switch off 'topic protection'.");
1717 else message = i18n("%1 switches off 'topic protection'.", sourceNick);
1718 }
1719 modeT->setDown(plus);
1720 break;
1721
1722 case 'k':
1723 if (plus)
1724 {
1725 if (fromMe) message = i18n("You set the channel key to '%1'.", parameter);
1726 else message = i18n("%1 sets the channel key to '%2'.", sourceNick, parameter);
1727 }
1728 else
1729 {
1730 if (fromMe) message = i18n("You remove the channel key.");
1731 else message = i18n("%1 removes the channel key.", sourceNick);
1732 }
1733 modeK->setDown(plus);
1734 break;
1735
1736 case 'l':
1737 if (plus)
1738 {
1739 if (fromMe) message = i18np("You set the channel limit to 1 nick.", "You set the channel limit to %1 nicks.", parameter.toInt());
1740 else message = i18np("%2 sets the channel limit to 1 nick.", "%2 sets the channel limit to %1 nicks.", parameter.toInt(), sourceNick);
1741 }
1742 else
1743 {
1744 if (fromMe) message = i18n("You remove the channel limit.");
1745 else message = i18n("%1 removes the channel limit.", sourceNick);
1746 }
1747 modeL->setDown(plus);
1748 if (plus) limit->setText(parameter);
1749 else limit->clear();
1750 break;
1751
1752 case 'b':
1753 if (plus)
1754 {
1755 if (fromMe) message = i18n("You set a ban on %1.", parameter);
1756 else message = i18n("%1 sets a ban on %2.", sourceNick, parameter);
1757 }
1758 else
1759 {
1760 if (fromMe) message = i18n("You remove the ban on %1.", parameter);
1761 else message = i18n("%1 removes the ban on %2.", sourceNick, parameter);
1762 }
1763 break;
1764
1765 case 'e':
1766 if (plus)
1767 {
1768 if (fromMe) message = i18n("You set a ban exception on %1.", parameter);
1769 else message = i18n("%1 sets a ban exception on %2.", sourceNick, parameter);
1770 }
1771 else
1772 {
1773 if (fromMe) message = i18n("You remove the ban exception on %1.", parameter);
1774 else message = i18n("%1 removes the ban exception on %2.", sourceNick, parameter);
1775 }
1776 break;
1777
1778 case 'I':
1779 if (plus)
1780 {
1781 if (fromMe) message = i18n("You set invitation mask %1.", parameter);
1782 else message = i18n("%1 sets invitation mask %2.", sourceNick, parameter);
1783 }
1784 else
1785 {
1786 if (fromMe) message = i18n("You remove the invitation mask %1.", parameter);
1787 else message = i18n("%1 removes the invitation mask %2.", sourceNick, parameter);
1788 }
1789 break;
1790 default:
1791 if (plus)
1792 {
1793 if (Konversation::getChannelModesHash().contains(QLatin1Char(mode)))
1794 {
1795 if (fromMe) message = i18n("You set the channel mode '%1'.", Konversation::getChannelModesHash().value(QLatin1Char(mode)));
1796 else message= i18n("%1 sets the channel mode '%2'.", sourceNick, Konversation::getChannelModesHash().value(QLatin1Char(mode)));
1797 }
1798 else
1799 {
1800 if (fromMe) message = i18n("You set channel mode +%1", QLatin1Char(mode));
1801 else message = i18n("%1 sets channel mode +%2", sourceNick, QLatin1Char(mode));
1802 }
1803 }
1804 else
1805 {
1806 if (Konversation::getChannelModesHash().contains(QLatin1Char(mode)))
1807 {
1808 if (fromMe) message = i18n("You remove the channel mode '%1'.", Konversation::getChannelModesHash().value(QLatin1Char(mode)));
1809 else message= i18n("%1 removes the channel mode '%2'.", sourceNick, Konversation::getChannelModesHash().value(QLatin1Char(mode)));
1810 }
1811 else
1812 {
1813 if (fromMe) message = i18n("You set channel mode -%1", QLatin1Char(mode));
1814 else message = i18n("%1 sets channel mode -%2", sourceNick, QLatin1Char(mode));
1815 }
1816 }
1817 }
1818
1819 // check if this nick's anyOp-status has changed and adjust ops accordingly
1820 if (parameterChannelNick)
1821 {
1822 if (wasAnyOp && (!parameterChannelNick->isAnyTypeOfOp()))
1823 adjustOps(-1);
1824 else if (!wasAnyOp && parameterChannelNick->isAnyTypeOfOp())
1825 adjustOps(1);
1826 }
1827
1828 if (!message.isEmpty() && !Preferences::self()->useLiteralModes())
1829 {
1830 appendCommandMessage(i18n("Mode"), message, messageTags);
1831 }
1832
1833 updateModeWidgets(mode, plus, parameter);
1834 }
1835
clearModeList()1836 void Channel::clearModeList()
1837 {
1838 QString k;
1839
1840 // Keep channel password in the backing store, for rejoins.
1841 for (const QString& mode : qAsConst(m_modeList)) {
1842 if (mode[0] == QLatin1Char('k'))
1843 k = mode;
1844 }
1845
1846 m_modeList.clear();
1847
1848 if (!k.isEmpty()) m_modeList << k;
1849
1850 modeT->setOn(false);
1851 modeT->setDown(false);
1852
1853 modeN->setOn(false);
1854 modeN->setDown(false);
1855
1856 modeS->setOn(false);
1857 modeS->setDown(false);
1858
1859 modeI->setOn(false);
1860 modeI->setDown(false);
1861
1862 modeP->setOn(false);
1863 modeP->setDown(false);
1864
1865 modeM->setOn(false);
1866 modeM->setDown(false);
1867
1868 modeK->setOn(false);
1869 modeK->setDown(false);
1870
1871 modeL->setOn(false);
1872 modeL->setDown(false);
1873
1874 limit->clear();
1875
1876 Q_EMIT modesChanged();
1877 }
1878
updateModeWidgets(char mode,bool plus,const QString & parameter)1879 void Channel::updateModeWidgets(char mode, bool plus, const QString ¶meter)
1880 {
1881 ModeButton* widget=nullptr;
1882
1883 if(mode=='t') widget=modeT;
1884 else if(mode=='n') widget=modeN;
1885 else if(mode=='s') widget=modeS;
1886 else if(mode=='i') widget=modeI;
1887 else if(mode=='p') widget=modeP;
1888 else if(mode=='m') widget=modeM;
1889 else if(mode=='k') widget=modeK;
1890 else if(mode=='l')
1891 {
1892 widget=modeL;
1893 if(plus) limit->setText(parameter);
1894 else limit->clear();
1895 }
1896
1897 if(widget) widget->setOn(plus);
1898
1899 if(plus)
1900 {
1901 m_modeList.append(QString(QLatin1Char(mode) + parameter));
1902 }
1903 else
1904 {
1905 const QStringList removable = m_modeList.filter(QRegularExpression(QStringLiteral("^%1.*").arg(mode)));
1906 for (const QString &mode : removable) {
1907 m_modeList.removeOne(mode);
1908 }
1909 }
1910 Q_EMIT modesChanged();
1911 }
1912
updateQuickButtons()1913 void Channel::updateQuickButtons()
1914 {
1915 delete m_buttonsGrid;
1916 m_buttonsGrid = nullptr;
1917
1918 // the grid that holds the quick action buttons
1919 m_buttonsGrid = new QWidget (nickListButtons); //Q3Grid(2, nickListButtons);
1920 nickListButtons->layout()->addWidget(m_buttonsGrid);
1921 m_buttonsGrid->hide();
1922 auto* layout = new QGridLayout (m_buttonsGrid);
1923 layout->setContentsMargins(0, 0, 0, 0);
1924
1925 int col = 0;
1926 int row = 0;
1927
1928 const QStringList &newButtonList = Preferences::quickButtonList();
1929
1930 // add new quick buttons
1931 for(int index=0;index<newButtonList.count();index++)
1932 {
1933 // generate empty buttons first, text will be added later
1934 auto* quickButton = new QuickButton(QString(), QString(), m_buttonsGrid);
1935 col = index % 2;
1936 layout->addWidget (quickButton, row, col);
1937 row += col;
1938
1939 connect(quickButton, QOverload<const QString&>::of(&QuickButton::clicked), this, &Channel::quickButtonClicked);
1940
1941 // Get the button definition
1942 QString buttonText=newButtonList[index];
1943 // Extract button label
1944 QString buttonLabel=buttonText.section(QLatin1Char(','),0,0);
1945 // Extract button definition
1946 buttonText=buttonText.section(QLatin1Char(','),1);
1947
1948 quickButton->setText(buttonLabel);
1949 quickButton->setDefinition(buttonText);
1950
1951 // Add tool tips
1952 QString toolTip=buttonText.replace(QLatin1Char('&'),QStringLiteral("&")).
1953 replace(QLatin1Char('<'),QStringLiteral("<")).
1954 replace(QLatin1Char('>'),QStringLiteral(">"));
1955
1956 quickButton->setToolTip(toolTip);
1957
1958 quickButton->show();
1959 } // for
1960
1961 // set hide() or show() on grid
1962 showQuickButtons(Preferences::self()->showQuickButtons());
1963 }
1964
showQuickButtons(bool show)1965 void Channel::showQuickButtons(bool show)
1966 {
1967 // Qt does not redraw the buttons properly when they are not on screen
1968 // while getting hidden, so we remember the "soon to be" state here.
1969 if(isHidden() || !m_buttonsGrid)
1970 {
1971 quickButtonsChanged=true;
1972 quickButtonsState=show;
1973 }
1974 else
1975 {
1976 if(show)
1977 m_buttonsGrid->show();
1978 else
1979 m_buttonsGrid->hide();
1980 }
1981 }
1982
showModeButtons(bool show)1983 void Channel::showModeButtons(bool show)
1984 {
1985 // Qt does not redraw the buttons properly when they are not on screen
1986 // while getting hidden, so we remember the "soon to be" state here.
1987 if(isHidden())
1988 {
1989 modeButtonsChanged=true;
1990 modeButtonsState=show;
1991 }
1992 else
1993 {
1994 if(show)
1995 {
1996 topicSplitterHidden = false;
1997 modeBox->show();
1998 modeBox->parentWidget()->show();
1999 }
2000 else
2001 {
2002 modeBox->hide();
2003
2004 if(topicLine->isHidden())
2005 {
2006 topicSplitterHidden = true;
2007 modeBox->parentWidget()->hide();
2008 }
2009 }
2010 }
2011 }
2012
indicateAway(bool show)2013 void Channel::indicateAway(bool show)
2014 {
2015 // Qt does not redraw the label properly when they are not on screen
2016 // while getting hidden, so we remember the "soon to be" state here.
2017 if(isHidden())
2018 {
2019 awayChanged=true;
2020 awayState=show;
2021 }
2022 else
2023 {
2024 if(show)
2025 awayLabel->show();
2026 else
2027 awayLabel->hide();
2028 }
2029 }
2030
showEvent(QShowEvent *)2031 void Channel::showEvent(QShowEvent*)
2032 {
2033 // If the show quick/mode button settings have changed, apply the changes now
2034 if(quickButtonsChanged)
2035 {
2036 quickButtonsChanged=false;
2037 showQuickButtons(quickButtonsState);
2038 }
2039
2040 if(modeButtonsChanged)
2041 {
2042 modeButtonsChanged=false;
2043 showModeButtons(modeButtonsState);
2044 }
2045
2046 if(awayChanged)
2047 {
2048 awayChanged=false;
2049 indicateAway(awayState);
2050 }
2051
2052 syncSplitters();
2053 }
2054
syncSplitters()2055 void Channel::syncSplitters()
2056 {
2057 QList<int> vertSizes = Preferences::self()->topicSplitterSizes();
2058 QList<int> horizSizes = Preferences::self()->channelSplitterSizes();
2059
2060 if (vertSizes.isEmpty())
2061 {
2062 vertSizes = { m_topicButton->height(), (height() - m_topicButton->height()) };
2063 Preferences::self()->setTopicSplitterSizes(vertSizes);
2064 }
2065
2066 if (horizSizes.isEmpty())
2067 {
2068 // An approximation of a common NICKLEN plus the width of the icon,
2069 // tested with 8pt and 10pt DejaVu Sans and Droid Sans.
2070 int listWidth = fontMetrics().averageCharWidth() * 17 + 20;
2071 horizSizes = { (width() - listWidth), listWidth };
2072 Preferences::self()->setChannelSplitterSizes(horizSizes);
2073 }
2074
2075 m_vertSplitter->setSizes(vertSizes);
2076 m_horizSplitter->setSizes(horizSizes);
2077
2078 splittersInitialized = true;
2079 }
2080
updateAppearance()2081 void Channel::updateAppearance()
2082 {
2083 QPalette palette;
2084
2085 if (Preferences::self()->inputFieldsBackgroundColor())
2086 {
2087 palette.setColor(QPalette::Text, Preferences::self()->color(Preferences::ChannelMessage));
2088 palette.setColor(QPalette::Base, Preferences::self()->color(Preferences::TextViewBackground));
2089 palette.setColor(QPalette::AlternateBase, Preferences::self()->color(Preferences::AlternateBackground));
2090 }
2091
2092 limit->setPalette(palette);
2093 topicLine->setPalette(QPalette());
2094
2095 if (Preferences::self()->customTextFont())
2096 {
2097 topicLine->setFont(Preferences::self()->textFont());
2098 m_inputBar->setFont(Preferences::self()->textFont());
2099 nicknameCombobox->setFont(Preferences::self()->textFont());
2100 limit->setFont(Preferences::self()->textFont());
2101 }
2102 else
2103 {
2104 topicLine->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
2105 m_inputBar->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
2106 nicknameCombobox->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
2107 limit->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
2108 }
2109
2110 nicknameListView->resort();
2111 nicknameListView->setPalette(palette);
2112 nicknameListView->setAlternatingRowColors(Preferences::self()->inputFieldsBackgroundColor());
2113
2114 if (Preferences::self()->customListFont())
2115 nicknameListView->setFont(Preferences::self()->listFont());
2116 else
2117 nicknameListView->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
2118
2119 nicknameListView->refresh();
2120
2121 showModeButtons(Preferences::self()->showModeButtons());
2122 showNicknameList(Preferences::self()->showNickList());
2123 showNicknameBox(Preferences::self()->showNicknameBox());
2124 showTopic(Preferences::self()->showTopic());
2125 setAutoUserhost(Preferences::self()->autoUserhost());
2126
2127 QMetaObject::invokeMethod(this, "updateQuickButtons", Qt::QueuedConnection);
2128
2129 // Nick sorting settings might have changed. Trigger timer
2130 if (m_delayedSortTimer)
2131 {
2132 m_delayedSortTrigger = DELAYED_SORT_TRIGGER + 1;
2133 m_delayedSortTimer->start(500 + QRandomGenerator::global()->bounded(2000));
2134 }
2135
2136 ChatWindow::updateAppearance();
2137 }
2138
nicknameComboboxChanged()2139 void Channel::nicknameComboboxChanged()
2140 {
2141 QString newNick=nicknameCombobox->currentText();
2142 oldNick=m_server->getNickname();
2143 if (oldNick != newNick)
2144 {
2145 nicknameCombobox->setCurrentIndex(nicknameCombobox->findText(oldNick));
2146 changeNickname(newNick);
2147 // return focus to input line
2148 m_inputBar->setFocus();
2149 }
2150 }
2151
changeNickname(const QString & newNickname)2152 void Channel::changeNickname(const QString& newNickname)
2153 {
2154 if (!newNickname.isEmpty())
2155 m_server->queue(QStringLiteral("NICK ")+newNickname);
2156 }
2157
queueNicks(const QStringList & nicknameList)2158 void Channel::queueNicks(const QStringList& nicknameList)
2159 {
2160 if (nicknameList.isEmpty())
2161 return;
2162
2163 m_nickQueue.append(nicknameList);
2164
2165 processQueuedNicks();
2166 }
2167
endOfNames()2168 void Channel::endOfNames()
2169 {
2170 if (!m_initialNamesReceived)
2171 {
2172 m_initialNamesReceived = true;
2173
2174 if (m_server->capabilities() & Server::AwayNotify && !Preferences::self()->autoWhoContinuousEnabled())
2175 {
2176 // Do one who request to get the initial away state for the channel
2177 QMetaObject::invokeMethod(m_server, "requestWho", Qt::QueuedConnection, Q_ARG(QString, getName()));
2178 }
2179
2180 scheduleAutoWho();
2181 }
2182 }
2183
childAdjustFocus()2184 void Channel::childAdjustFocus()
2185 {
2186 m_inputBar->setFocus();
2187 refreshModeButtons();
2188 }
2189
refreshModeButtons()2190 void Channel::refreshModeButtons()
2191 {
2192 bool enable = true;
2193 if(getOwnChannelNick())
2194 {
2195 enable=getOwnChannelNick()->isAnyTypeOfOp();
2196 } // if not channel nick, then enable is true - fall back to assuming they are op
2197
2198 //don't disable the mode buttons since you can't then tell if they are enabled or not.
2199 //needs to be fixed somehow
2200
2201 /* modeT->setEnabled(enable);
2202 modeN->setEnabled(enable);
2203 modeS->setEnabled(enable);
2204 modeI->setEnabled(enable);
2205 modeP->setEnabled(enable);
2206 modeM->setEnabled(enable);
2207 modeK->setEnabled(enable);
2208 modeL->setEnabled(enable);*/
2209 limit->setEnabled(enable);
2210
2211 // Tooltips for the ModeButtons
2212 QString opOnly;
2213 if(!enable) opOnly = i18n("You have to be an operator to change this.");
2214
2215 modeT->setToolTip(i18n("Topic can be changed by channel operator only. %1", opOnly));
2216 modeN->setToolTip(i18n("No messages to channel from clients on the outside. %1", opOnly));
2217 modeS->setToolTip(i18n("Secret channel. %1", opOnly));
2218 modeI->setToolTip(i18n("Invite only channel. %1", opOnly));
2219 modeP->setToolTip(i18n("Private channel. %1", opOnly));
2220 modeM->setToolTip(i18n("Moderated channel. %1", opOnly));
2221 modeK->setToolTip(i18n("Protect channel with a password."));
2222 modeL->setToolTip(i18n("Set user limit to channel."));
2223
2224 }
2225
nicknameListViewTextChanged(int textChangedFlags)2226 void Channel::nicknameListViewTextChanged(int textChangedFlags)
2227 {
2228 m_nicknameListViewTextChanged |= textChangedFlags;
2229 }
2230
autoUserhost()2231 void Channel::autoUserhost()
2232 {
2233 if(Preferences::self()->autoUserhost() && !Preferences::self()->autoWhoContinuousEnabled())
2234 {
2235 int limit = 5;
2236
2237 QString nickString;
2238
2239 for (Nick* nick : qAsConst(nicknameList)) {
2240 if(nick->getChannelNick()->getHostmask().isEmpty())
2241 {
2242 if(limit--) nickString = nickString + nick->getChannelNick()->getNickname() + QLatin1Char(' ');
2243 else break;
2244 }
2245 }
2246
2247 if(!nickString.isEmpty()) m_server->requestUserhost(nickString);
2248 }
2249
2250 if(!nicknameList.isEmpty())
2251 {
2252 resizeNicknameListViewColumns();
2253 }
2254 }
2255
setAutoUserhost(bool state)2256 void Channel::setAutoUserhost(bool state)
2257 {
2258 nicknameListView->setColumnHidden(Nick::HostmaskColumn, !state);
2259 if (state)
2260 {
2261 nicknameListView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
2262 // Cannot use QHeaderView::ResizeToContents here because it is slow
2263 // and it gets triggered by setSortingEnabled(). Using timed resize
2264 // instead, see Channel::autoUserhost() above.
2265 nicknameListView->header()->setSectionResizeMode(Nick::NicknameColumn, QHeaderView::Fixed);
2266 nicknameListView->header()->setSectionResizeMode(Nick::HostmaskColumn, QHeaderView::Fixed);
2267 userhostTimer.start(10000);
2268 m_nicknameListViewTextChanged |= 0xFF; // ResizeColumnsToContents
2269 QTimer::singleShot(0, this, &Channel::autoUserhost); // resize columns ASAP
2270 }
2271 else
2272 {
2273 nicknameListView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2274 nicknameListView->header()->setSectionResizeMode(Nick::NicknameColumn, QHeaderView::Stretch);
2275 userhostTimer.stop();
2276 }
2277 }
2278
scheduleAutoWho(int msec)2279 void Channel::scheduleAutoWho(int msec)
2280 {
2281 // The first auto-who is scheduled by ENDOFNAMES in InputFilter, which means
2282 // the first auto-who occurs one interval after it. This has two desirable
2283 // consequences specifically related to the startup phase: auto-who dispatch
2284 // doesn't occur at the same time for all channels that are auto-joined, and
2285 // it gives some breathing room to process the NAMES replies for all channels
2286 // first before getting started on WHO.
2287 // Subsequent auto-whos are scheduled by ENDOFWHO in InputFilter. However,
2288 // autoWho() might refuse to actually do the request if the number of nicks
2289 // in the channel exceeds the threshold, and will instead schedule another
2290 // attempt later. Thus scheduling an auto-who does not guarantee it will be
2291 // performed.
2292 // If this is called mid-interval (e.g. due to the ENDOFWHO from a manual WHO)
2293 // it will reset the interval to avoid cutting it short.
2294
2295 if (m_server->whoRequestsDisabled())
2296 return;
2297
2298 if (m_whoTimer.isActive())
2299 m_whoTimer.stop();
2300
2301 if (Preferences::self()->autoWhoContinuousEnabled())
2302 {
2303 if (msec > 0)
2304 m_whoTimer.start(msec);
2305 else
2306 m_whoTimer.start(Preferences::self()->autoWhoContinuousInterval() * 1000);
2307 }
2308 }
2309
autoWho()2310 void Channel::autoWho()
2311 {
2312 // Try again later if there are too many nicks or we're already processing a WHO request.
2313 if ((nicks > Preferences::self()->autoWhoNicksLimit()) ||
2314 m_server->getInputFilter()->isWhoRequestUnderProcess(getName()))
2315 {
2316 scheduleAutoWho();
2317
2318 return;
2319 }
2320
2321 m_server->requestWho(getName());
2322 m_whoTimerStarted.start();
2323 }
2324
updateAutoWho()2325 void Channel::updateAutoWho()
2326 {
2327 if (!Preferences::self()->autoWhoContinuousEnabled())
2328 m_whoTimer.stop();
2329 else if (Preferences::self()->autoWhoContinuousEnabled() && !m_whoTimer.isActive())
2330 autoWho();
2331 else if (m_whoTimer.isActive())
2332 {
2333 // The below tries to meet user expectations on an interval settings change,
2334 // making two assumptions:
2335 // - If the new interval is lower than the old one, the user may be impatient
2336 // and desires an information update.
2337 // - If the new interval is longer than the old one, the user may be trying to
2338 // avoid Konversation producing too much traffic in a given timeframe, and
2339 // wants it to stop doing so sooner rather than later.
2340 // Both require rescheduling the next auto-who request.
2341
2342 int interval = Preferences::self()->autoWhoContinuousInterval() * 1000;
2343
2344 if (interval != m_whoTimer.interval())
2345 {
2346 if (m_whoTimerStarted.elapsed() >= interval)
2347 {
2348 // If the time since the last auto-who request is longer than (or
2349 // equal to) the new interval setting, it follows that the new
2350 // setting is lower than the old setting. In this case issue a new
2351 // request immediately, which is the closest we can come to acting
2352 // as if the new setting had been active all along, short of tra-
2353 // velling back in time to change history. This handles the impa-
2354 // tient user.
2355 // FIXME: Adjust algorithm when time machine becomes available.
2356
2357 m_whoTimer.stop();
2358 autoWho();
2359 }
2360 else
2361 {
2362 // If on the other hand the elapsed time is shorter than the new
2363 // interval setting, the new setting could be either shorter or
2364 // _longer_ than the old setting. Happily, this time we can actually
2365 // behave as if the new setting had been active all along, by sched-
2366 // uling the next request to happen in the new interval time minus
2367 // the already elapsed time, meeting user expecations for both cases
2368 // originally laid out.
2369
2370 scheduleAutoWho(interval - m_whoTimerStarted.elapsed());
2371 }
2372 }
2373 }
2374 }
2375
fadeActivity()2376 void Channel::fadeActivity()
2377 {
2378 for (Nick *nick : qAsConst(nicknameList)) {
2379 nick->getChannelNick()->lessActive();
2380 }
2381 }
2382
canBeFrontView() const2383 bool Channel::canBeFrontView() const
2384 {
2385 return true;
2386 }
2387
searchView() const2388 bool Channel::searchView() const
2389 {
2390 return true;
2391 }
2392
closeYourself(bool confirm)2393 bool Channel::closeYourself(bool confirm)
2394 {
2395 int result=KMessageBox::Continue;
2396
2397 if (confirm)
2398 result = KMessageBox::warningContinueCancel(this,
2399 i18n("Do you want to leave %1?", getName()),
2400 i18n("Leave Channel"),
2401 KGuiItem(i18n("Leave")),
2402 KStandardGuiItem::cancel(),
2403 QStringLiteral("QuitChannelTab"));
2404
2405 if (result==KMessageBox::Continue)
2406 {
2407 m_server->closeChannel(getName());
2408 m_server->removeChannel(this);
2409
2410 deleteLater();
2411
2412 return true;
2413 }
2414 else
2415 m_recreationScheduled = false;
2416
2417 return false;
2418 }
2419
serverOnline(bool online)2420 void Channel::serverOnline(bool online)
2421 {
2422 setActive(online);
2423 }
2424
2425 //Used to disable functions when not connected, does not necessarily mean the server is offline
setActive(bool active)2426 void Channel::setActive(bool active)
2427 {
2428 if (active)
2429 nicknameCombobox->setEnabled(true);
2430 else
2431 {
2432 m_initialNamesReceived = false;
2433 purgeNicks();
2434 nicknameCombobox->setEnabled(false);
2435 topicLine->clear();
2436 clearModeList();
2437 clearBanList();
2438 m_whoTimer.stop();
2439 }
2440 }
2441
showTopic(bool show)2442 void Channel::showTopic(bool show)
2443 {
2444 if(show)
2445 {
2446 topicSplitterHidden = false;
2447 topicLine->show();
2448 m_topicButton->show();
2449 topicLine->parentWidget()->show();
2450 }
2451 else
2452 {
2453 topicLine->hide();
2454 m_topicButton->hide();
2455
2456 if(modeBox->isHidden())
2457 {
2458 topicSplitterHidden = true;
2459 topicLine->parentWidget()->hide();
2460 }
2461 }
2462 }
2463
processQueuedNicks(bool flush)2464 void Channel::processQueuedNicks(bool flush)
2465 {
2466 // This pops nicks from the front of a queue added to by incoming NAMES
2467 // messages and adds them to the channel nicklist, calling itself via
2468 // the event loop until the last invocation finds the queue empty and
2469 // adjusts the nicks/ops counters and requests a nicklist sort, but only
2470 // if previous invocations actually processed any nicks. The latter is
2471 // an optimization for the common case of processing being kicked off by
2472 // flushNickQueue(), which is done e.g. before a nick rename or part to
2473 // make sure the channel is up to date and will usually find an empty
2474 // queue. This is also the use case for the 'flush' parameter, which if
2475 // true causes the recursion to block in a tight loop instead of queueing
2476 // via the event loop.
2477
2478 if (m_nickQueue.isEmpty())
2479 {
2480 if (m_processedNicksCount)
2481 {
2482 adjustNicks(m_processedNicksCount);
2483 adjustOps(m_processedOpsCount);
2484 m_processedNicksCount = 0;
2485 m_processedOpsCount = 0;
2486
2487 sortNickList();
2488 nicknameListView->setUpdatesEnabled(true);
2489
2490 if (Preferences::self()->autoUserhost())
2491 resizeNicknameListViewColumns();
2492 }
2493 }
2494 else
2495 {
2496 QString nickname;
2497
2498 while (nickname.isEmpty() && !m_nickQueue.isEmpty())
2499 nickname = m_nickQueue.takeFirst();
2500
2501 QString userHost;
2502
2503 if(m_server->capabilities() & Server::UserHostInNames)
2504 {
2505 int index = nickname.indexOf(QLatin1Char('!'));
2506
2507 if(index >= 0)
2508 {
2509 userHost = nickname.mid(index + 1);
2510 nickname.truncate(index);
2511 }
2512 }
2513
2514 bool admin = false;
2515 bool owner = false;
2516 bool op = false;
2517 bool halfop = false;
2518 bool voice = false;
2519
2520 // Remove possible mode characters from nickname and store the resulting mode.
2521 m_server->mangleNicknameWithModes(nickname, admin, owner, op, halfop, voice);
2522
2523 // TODO: Make these an enumeration in KApplication or somewhere, we can use them as well.
2524 unsigned int mode = (admin ? 16 : 0) +
2525 (owner ? 8 : 0) +
2526 (op ? 4 : 0) +
2527 (halfop ? 2 : 0) +
2528 (voice ? 1 : 0);
2529
2530 // Check if nick is already in the nicklist.
2531 if (!nickname.isEmpty() && !getNickByName(nickname))
2532 {
2533 ChannelNickPtr nick = m_server->addNickToJoinedChannelsList(getName(), nickname);
2534 Q_ASSERT(nick);
2535 nick->setMode(mode);
2536
2537 if(!userHost.isEmpty())
2538 {
2539 nick->getNickInfo()->setHostmask(userHost);
2540 }
2541
2542 fastAddNickname(nick);
2543
2544 ++m_processedNicksCount;
2545
2546 if (nick->isAdmin() || nick->isOwner() || nick->isOp() || nick->isHalfOp())
2547 ++m_processedOpsCount;
2548 }
2549
2550 QMetaObject::invokeMethod(this, "processQueuedNicks",
2551 flush ? Qt::DirectConnection : Qt::QueuedConnection, Q_ARG(bool, flush));
2552 }
2553 }
2554
setChannelEncoding(const QString & encoding)2555 void Channel::setChannelEncoding(const QString& encoding) // virtual
2556 {
2557 if(m_server->getServerGroup())
2558 Preferences::setChannelEncoding(m_server->getServerGroup()->id(), getName(), encoding);
2559 else
2560 Preferences::setChannelEncoding(m_server->getDisplayName(), getName(), encoding);
2561 }
2562
getChannelEncoding() const2563 QString Channel::getChannelEncoding() const // virtual
2564 {
2565 if(m_server->getServerGroup())
2566 return Preferences::channelEncoding(m_server->getServerGroup()->id(), getName());
2567 return Preferences::channelEncoding(m_server->getDisplayName(), getName());
2568 }
2569
getChannelEncodingDefaultDesc() const2570 QString Channel::getChannelEncodingDefaultDesc() const // virtual
2571 {
2572 return i18n("Identity Default ( %1 )", getServer()->getIdentity()->getCodecName());
2573 }
2574
showNicknameBox(bool show)2575 void Channel::showNicknameBox(bool show)
2576 {
2577 if(show)
2578 {
2579 nicknameCombobox->show();
2580 }
2581 else
2582 {
2583 nicknameCombobox->hide();
2584 }
2585 }
2586
showNicknameList(bool show)2587 void Channel::showNicknameList(bool show)
2588 {
2589 if (show)
2590 {
2591 channelSplitterHidden = false;
2592 nickListButtons->show();
2593 }
2594 else
2595 {
2596 channelSplitterHidden = true;
2597 nickListButtons->hide();
2598 }
2599 }
2600
requestNickListSort()2601 void Channel::requestNickListSort()
2602 {
2603 m_delayedSortTrigger++;
2604 if (m_delayedSortTrigger == DELAYED_SORT_TRIGGER &&
2605 !m_delayedSortTimer->isActive())
2606 {
2607 nicknameListView->fastSetSortingEnabled(false);
2608 m_delayedSortTimer->start(1000);
2609 }
2610 }
2611
delayedSortNickList()2612 void Channel::delayedSortNickList()
2613 {
2614 sortNickList(true);
2615 }
2616
sortNickList(bool delayed)2617 void Channel::sortNickList(bool delayed)
2618 {
2619 if (!delayed || m_delayedSortTrigger > DELAYED_SORT_TRIGGER) {
2620 std::sort(nicknameList.begin(), nicknameList.end(), nickLessThan);
2621 nicknameListView->resort();
2622 }
2623 if (!nicknameListView->isSortingEnabled())
2624 nicknameListView->fastSetSortingEnabled(true);
2625 m_delayedSortTrigger = 0;
2626 m_delayedSortTimer->stop();
2627 }
2628
repositionNick(Nick * nick)2629 void Channel::repositionNick(Nick *nick)
2630 {
2631 int index = nicknameList.indexOf(nick);
2632
2633 if (index > -1) {
2634 // Trigger nick reposition in the nicklist including
2635 // field updates
2636 nick->refresh();
2637 // Readd nick to the nicknameList
2638 nicknameList.removeAt(index);
2639 fastAddNickname(nick->getChannelNick(), nick);
2640 } else {
2641 qCWarning(KONVERSATION_LOG) << "Nickname " << nick->getChannelNick()->getNickname() << " not found!";
2642 }
2643 }
2644
eventFilter(QObject * watched,QEvent * e)2645 bool Channel::eventFilter(QObject* watched, QEvent* e)
2646 {
2647 if((watched == nicknameListView) && (e->type() == QEvent::Resize) && splittersInitialized && isVisible())
2648 {
2649 if (!topicSplitterHidden && !channelSplitterHidden)
2650 {
2651 Preferences::self()->setChannelSplitterSizes(m_horizSplitter->sizes());
2652 Preferences::self()->setTopicSplitterSizes(m_vertSplitter->sizes());
2653 }
2654 if (!topicSplitterHidden && channelSplitterHidden)
2655 {
2656 Preferences::self()->setTopicSplitterSizes(m_vertSplitter->sizes());
2657 }
2658 if (!channelSplitterHidden && topicSplitterHidden)
2659 {
2660 Preferences::self()->setChannelSplitterSizes(m_horizSplitter->sizes());
2661 }
2662 }
2663
2664 return ChatWindow::eventFilter(watched, e);
2665 }
2666
addBan(const QString & ban)2667 void Channel::addBan(const QString& ban)
2668 {
2669 QStringList::iterator it = m_BanList.begin();
2670 while (it != m_BanList.end()) {
2671 if ((*it).section(QLatin1Char(' '), 0, 0) == ban.section(QLatin1Char(' '), 0, 0))
2672 {
2673 // Ban is already in list.
2674 it = m_BanList.erase(it);
2675
2676 Q_EMIT banRemoved(ban.section(QLatin1Char(' '), 0, 0));
2677 } else {
2678 ++it;
2679 }
2680 }
2681
2682 m_BanList.prepend(ban);
2683
2684 Q_EMIT banAdded(ban);
2685 }
2686
removeBan(const QString & ban)2687 void Channel::removeBan(const QString& ban)
2688 {
2689 const QStringList currentBanList = m_BanList;
2690 for (const QString &string : currentBanList) {
2691 if (string.section(QLatin1Char(' '), 0, 0) == ban)
2692 {
2693 m_BanList.removeOne(string);
2694
2695 Q_EMIT banRemoved(ban);
2696 }
2697 }
2698 }
2699
clearBanList()2700 void Channel::clearBanList()
2701 {
2702 m_BanList.clear();
2703
2704 Q_EMIT banListCleared();
2705 }
2706
append(const QString & nickname,const QString & message,const QHash<QString,QString> & messageTags,const QString & label)2707 void Channel::append(const QString& nickname, const QString& message, const QHash<QString, QString> &messageTags, const QString& label)
2708 {
2709 if(nickname != getServer()->getNickname()) {
2710 Nick* nick = getNickByName(nickname);
2711
2712 if(nick) {
2713 nick->getChannelNick()->setTimeStamp(QDateTime::currentDateTime().toSecsSinceEpoch());
2714 }
2715 }
2716
2717 ChatWindow::append(nickname, message, messageTags, label);
2718 nickActive(nickname);
2719 }
2720
appendAction(const QString & nickname,const QString & message,const QHash<QString,QString> & messageTags)2721 void Channel::appendAction(const QString& nickname, const QString& message, const QHash<QString, QString> &messageTags)
2722 {
2723 if(nickname != getServer()->getNickname()) {
2724 Nick* nick = getNickByName(nickname);
2725
2726 if(nick) {
2727 nick->getChannelNick()->setTimeStamp(QDateTime::currentDateTime().toSecsSinceEpoch());
2728 }
2729 }
2730
2731 ChatWindow::appendAction(nickname, message, messageTags);
2732 nickActive(nickname);
2733 }
2734
nickActive(const QString & nickname)2735 void Channel::nickActive(const QString& nickname) //FIXME reported to crash, can't reproduce
2736 {
2737 ChannelNickPtr channelnick=getChannelNick(nickname);
2738 //XXX Would be nice to know why it can be null here...
2739 if (channelnick)
2740 {
2741 channelnick->moreActive();
2742 if (Preferences::self()->sortByActivity())
2743 {
2744 Nick* nick = getNickByName(nickname);
2745 if (nick)
2746 {
2747 nick->repositionMe();
2748 }
2749 }
2750 }
2751 }
2752
2753 #ifdef HAVE_QCA2
getCipher() const2754 Konversation::Cipher* Channel::getCipher() const
2755 {
2756 if(!m_cipher)
2757 m_cipher = new Konversation::Cipher();
2758 return m_cipher;
2759 }
2760 #endif
2761
updateNickInfos()2762 void Channel::updateNickInfos()
2763 {
2764 for (Nick* nick : qAsConst(nicknameList)) {
2765 if(nick->getChannelNick()->getNickInfo()->isChanged())
2766 {
2767 nick->refresh();
2768 }
2769 }
2770 }
2771
updateChannelNicks(const QString & channel)2772 void Channel::updateChannelNicks(const QString& channel)
2773 {
2774 if(channel != name.toLower())
2775 return;
2776
2777 for (Nick* nick : qAsConst(nicknameList)) {
2778 if(nick->getChannelNick()->isChanged())
2779 {
2780 nick->refresh();
2781
2782 if(nick->getChannelNick() == m_ownChannelNick)
2783 {
2784 refreshModeButtons();
2785 }
2786 }
2787 }
2788 }
2789
resizeNicknameListViewColumns()2790 void Channel::resizeNicknameListViewColumns()
2791 {
2792 // Resize columns if needed (on regular basis)
2793 if (m_nicknameListViewTextChanged & (1 << Nick::NicknameColumn))
2794 nicknameListView->resizeColumnToContents(Nick::NicknameColumn);
2795 if (m_nicknameListViewTextChanged & (1 << Nick::HostmaskColumn))
2796 nicknameListView->resizeColumnToContents(Nick::HostmaskColumn);
2797 m_nicknameListViewTextChanged = 0;
2798 }
2799
2800
2801 //
2802 // NickList
2803 //
2804
NickList()2805 NickList::NickList() : QList<Nick*>()
2806 {
2807 }
2808
completeNick(const QString & pattern,bool & complete,QStringList & found,bool skipNonAlfaNum,bool caseSensitive) const2809 QString NickList::completeNick(const QString& pattern, bool& complete, QStringList& found,
2810 bool skipNonAlfaNum, bool caseSensitive) const
2811 {
2812 found.clear();
2813 QString prefix(QLatin1Char('^'));
2814 QString newNick;
2815 QString prefixCharacter = Preferences::self()->prefixCharacter();
2816 NickList foundNicks;
2817
2818 if(pattern.contains(QRegularExpression(QStringLiteral("^(\\d|\\w)"))) && skipNonAlfaNum)
2819 {
2820 prefix = QStringLiteral("^([^\\d\\w]|[\\_]){0,}");
2821 }
2822
2823 const QRegularExpression regexp(
2824 prefix + QRegularExpression::escape(pattern),
2825 caseSensitive ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption);
2826
2827 for (Nick* nick : *this) {
2828 newNick = nick->getChannelNick()->getNickname();
2829
2830 if(!prefix.isEmpty() && newNick.contains(prefixCharacter))
2831 {
2832 newNick = newNick.section( prefixCharacter,1 );
2833 }
2834
2835 if(newNick.contains(regexp))
2836 {
2837 foundNicks.append(nick);
2838 }
2839 }
2840
2841 std::sort(foundNicks.begin(), foundNicks.end(), nickTimestampLessThan);
2842
2843 found.reserve(found.size() + foundNicks.size());
2844 for (Nick *nick : qAsConst(foundNicks)) {
2845 found.append(nick->getChannelNick()->getNickname());
2846 }
2847
2848 if(found.count() > 1)
2849 {
2850 bool ok = true;
2851 int patternLength = pattern.length();
2852 QString firstNick = found[0];
2853 int firstNickLength = firstNick.length();
2854 int foundCount = found.count();
2855
2856 while(ok && ((patternLength) < firstNickLength))
2857 {
2858 ++patternLength;
2859 QStringList tmp = found.filter(firstNick.left(patternLength), caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
2860
2861 if(tmp.count() != foundCount)
2862 {
2863 ok = false;
2864 --patternLength;
2865 }
2866 }
2867
2868 complete = false;
2869 return firstNick.left(patternLength);
2870 }
2871 else if(found.count() == 1)
2872 {
2873 complete = true;
2874 return found[0];
2875 }
2876
2877 return QString();
2878 }
2879
containsNick(const QString & nickname) const2880 bool NickList::containsNick(const QString& nickname) const
2881 {
2882 for (Nick* nick : *this) {
2883 if (nick->getChannelNick()->getNickname()==nickname)
2884 return true;
2885 }
2886
2887 return false;
2888 }
2889
2890
2891
2892 // kate: space-indent on; tab-width 4; indent-width 4; mixed-indent off; replace-tabs on;
2893 // vim: set et sw=4 ts=4 cino=l1,cs,U1:
2894