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 &parameter, 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 &parameter)
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("&amp;")).
1953             replace(QLatin1Char('<'),QStringLiteral("&lt;")).
1954             replace(QLatin1Char('>'),QStringLiteral("&gt;"));
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