modernize: Replace most remaining old-style connects by PMF ones
[quassel.git] / src / uisupport / networkmodelcontroller.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include "networkmodelcontroller.h"
22
23 #include <QComboBox>
24 #include <QDialogButtonBox>
25 #include <QGridLayout>
26 #include <QIcon>
27 #include <QLabel>
28 #include <QLineEdit>
29 #include <QInputDialog>
30 #include <QMessageBox>
31 #include <QPushButton>
32
33 #include "buffermodel.h"
34 #include "buffersettings.h"
35 #include "client.h"
36 #include "clientidentity.h"
37 #include "clientignorelistmanager.h"
38 #include "icon.h"
39 #include "network.h"
40 #include "util.h"
41
42 NetworkModelController::NetworkModelController(QObject *parent)
43     : QObject(parent),
44     _actionCollection(new ActionCollection(this))
45 {
46     connect(_actionCollection, &ActionCollection::actionTriggered, this, &NetworkModelController::actionTriggered);
47 }
48
49
50 Action *NetworkModelController::registerAction(ActionType type, const QString &text, bool checkable)
51 {
52     return registerAction(type, QPixmap(), text, checkable);
53 }
54
55
56 Action *NetworkModelController::registerAction(ActionType type, const QIcon &icon, const QString &text, bool checkable)
57 {
58     Action *act;
59     if (icon.isNull())
60         act = new Action(text, this);
61     else
62         act = new Action(icon, text, this);
63
64     act->setCheckable(checkable);
65     act->setData(type);
66
67     _actionCollection->addAction(QString::number(type, 16), act);
68     _actionByType[type] = act;
69     return act;
70 }
71
72
73 /******** Helper Functions ***********************************************************************/
74
75 void NetworkModelController::setIndexList(const QModelIndex &index)
76 {
77     _indexList = QList<QModelIndex>() << index;
78 }
79
80
81 void NetworkModelController::setIndexList(const QList<QModelIndex> &list)
82 {
83     _indexList = list;
84 }
85
86
87 void NetworkModelController::setMessageFilter(MessageFilter *filter)
88 {
89     _messageFilter = filter;
90 }
91
92
93 void NetworkModelController::setContextItem(const QString &contextItem)
94 {
95     _contextItem = contextItem;
96 }
97
98
99 void NetworkModelController::setSlot(QObject *receiver, const char *method)
100 {
101     _receiver = receiver;
102     _method = method;
103 }
104
105
106 bool NetworkModelController::checkRequirements(const QModelIndex &index, ItemActiveStates requiredActiveState)
107 {
108     if (!index.isValid())
109         return false;
110
111     ItemActiveStates isActive = index.data(NetworkModel::ItemActiveRole).toBool()
112                                 ? ActiveState
113                                 : InactiveState;
114
115     if (!(isActive & requiredActiveState))
116         return false;
117
118     return true;
119 }
120
121
122 QString NetworkModelController::nickName(const QModelIndex &index) const
123 {
124     auto *ircUser = qobject_cast<IrcUser *>(index.data(NetworkModel::IrcUserRole).value<QObject *>());
125     if (ircUser)
126         return ircUser->nick();
127
128     BufferInfo bufferInfo = index.data(NetworkModel::BufferInfoRole).value<BufferInfo>();
129     if (!bufferInfo.isValid())
130         return QString();
131     if (bufferInfo.type() != BufferInfo::QueryBuffer)
132         return QString();
133
134     return bufferInfo.bufferName(); // FIXME this might break with merged queries maybe
135 }
136
137
138 BufferId NetworkModelController::findQueryBuffer(const QModelIndex &index, const QString &predefinedNick) const
139 {
140     NetworkId networkId = index.data(NetworkModel::NetworkIdRole).value<NetworkId>();
141     if (!networkId.isValid())
142         return {};
143
144     QString nick = predefinedNick.isEmpty() ? nickName(index) : predefinedNick;
145     if (nick.isEmpty())
146         return {};
147
148     return findQueryBuffer(networkId, nick);
149 }
150
151
152 BufferId NetworkModelController::findQueryBuffer(NetworkId networkId, const QString &nick) const
153 {
154     return Client::networkModel()->bufferId(networkId, nick);
155 }
156
157
158 void NetworkModelController::removeBuffers(const QModelIndexList &indexList)
159 {
160     QList<BufferInfo> inactive;
161     foreach(QModelIndex index, indexList) {
162         BufferInfo info = index.data(NetworkModel::BufferInfoRole).value<BufferInfo>();
163         if (info.isValid()) {
164             if (info.type() == BufferInfo::QueryBuffer
165                 || (info.type() == BufferInfo::ChannelBuffer && !index.data(NetworkModel::ItemActiveRole).toBool()))
166                 inactive << info;
167         }
168     }
169     QString msg;
170     if (inactive.count()) {
171         msg = tr("Do you want to delete the following buffer(s) permanently?", "", inactive.count());
172         msg += "<ul>";
173         int count = 0;
174         foreach(BufferInfo info, inactive) {
175             if (count < 10) {
176                 msg += QString("<li>%1</li>").arg(info.bufferName());
177                 count++;
178             }
179             else
180                 break;
181         }
182         msg += "</ul>";
183         if (count > 9 && inactive.size() - count != 0)
184             msg += tr("...and <b>%1</b> more<br><br>").arg(inactive.size() - count);
185         msg += tr("<b>Note:</b> This will delete all related data, including all backlog data, from the core's database and cannot be undone.");
186         if (inactive.count() != indexList.count())
187             msg += tr("<br>Active channel buffers cannot be deleted, please part the channel first.");
188
189         if (QMessageBox::question(nullptr, tr("Remove buffers permanently?"), msg, QMessageBox::Yes|QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) {
190             foreach(BufferInfo info, inactive)
191             Client::removeBuffer(info.bufferId());
192         }
193     }
194 }
195
196
197 void NetworkModelController::handleExternalAction(ActionType type, QAction *action)
198 {
199     Q_UNUSED(type);
200     if (receiver() && method()) {
201         if (!QMetaObject::invokeMethod(receiver(), method(), Q_ARG(QAction *, action)))
202             qWarning() << "NetworkModelActionController::handleExternalAction(): Could not invoke slot" << receiver() << method();
203     }
204 }
205
206
207 /******** Handle Actions *************************************************************************/
208
209 void NetworkModelController::actionTriggered(QAction *action)
210 {
211     ActionType type = (ActionType)action->data().toInt();
212     if (type > 0) {
213         if (type & NetworkMask)
214             handleNetworkAction(type, action);
215         else if (type & BufferMask)
216             handleBufferAction(type, action);
217         else if (type & HideMask)
218             handleHideAction(type, action);
219         else if (type & GeneralMask)
220             handleGeneralAction(type, action);
221         else if (type & NickMask)
222             handleNickAction(type, action);
223         else if (type & ExternalMask)
224             handleExternalAction(type, action);
225         else
226             qWarning() << "NetworkModelController::actionTriggered(): Unhandled action!";
227     }
228 }
229
230
231 void NetworkModelController::handleNetworkAction(ActionType type, QAction *)
232 {
233     if (type == NetworkConnectAllWithDropdown || type == NetworkDisconnectAllWithDropdown || type == NetworkConnectAll || type == NetworkDisconnectAll) {
234         if (type == NetworkConnectAllWithDropdown && QMessageBox::question(nullptr, tr("Question"), tr("Really Connect to all IRC Networks?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::No)
235             return;
236         if (type == NetworkDisconnectAllWithDropdown && QMessageBox::question(nullptr, tr("Question"), tr("Really disconnect from all IRC Networks?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No)
237             return;
238         foreach(NetworkId id, Client::networkIds()) {
239             const Network *net = Client::network(id);
240             if ((type == NetworkConnectAllWithDropdown || type == NetworkConnectAll) && net->connectionState() == Network::Disconnected)
241                 net->requestConnect();
242             if ((type == NetworkDisconnectAllWithDropdown || type == NetworkDisconnectAll) && net->connectionState() != Network::Disconnected)
243                 net->requestDisconnect();
244         }
245         return;
246     }
247
248     if (!indexList().count())
249         return;
250
251     const Network *network = Client::network(indexList().at(0).data(NetworkModel::NetworkIdRole).value<NetworkId>());
252     Q_CHECK_PTR(network);
253     if (!network)
254         return;
255
256     switch (type) {
257     case NetworkConnect:
258         network->requestConnect();
259         break;
260     case NetworkDisconnect:
261         network->requestDisconnect();
262         break;
263     default:
264         break;
265     }
266 }
267
268
269 void NetworkModelController::handleBufferAction(ActionType type, QAction *)
270 {
271     if (type == BufferRemove) {
272         removeBuffers(indexList());
273     }
274     else {
275         QList<BufferInfo> bufferList; // create temp list because model indexes might change
276         foreach(QModelIndex index, indexList()) {
277             BufferInfo bufferInfo = index.data(NetworkModel::BufferInfoRole).value<BufferInfo>();
278             if (bufferInfo.isValid())
279                 bufferList << bufferInfo;
280         }
281
282         foreach(BufferInfo bufferInfo, bufferList) {
283             switch (type) {
284             case BufferJoin:
285                 Client::userInput(bufferInfo, QString("/JOIN %1").arg(bufferInfo.bufferName()));
286                 break;
287             case BufferPart:
288             {
289                 QString reason = Client::identity(Client::network(bufferInfo.networkId())->identity())->partReason();
290                 Client::userInput(bufferInfo, QString("/PART %1").arg(reason));
291                 break;
292             }
293             case BufferSwitchTo:
294                 Client::bufferModel()->switchToBuffer(bufferInfo.bufferId());
295                 break;
296             default:
297                 break;
298             }
299         }
300     }
301 }
302
303
304 void NetworkModelController::handleHideAction(ActionType type, QAction *action)
305 {
306     Q_UNUSED(action)
307
308     if (type == HideJoinPartQuit) {
309         bool anyChecked = NetworkModelController::action(HideJoin)->isChecked();
310         anyChecked |= NetworkModelController::action(HidePart)->isChecked();
311         anyChecked |= NetworkModelController::action(HideQuit)->isChecked();
312
313         // If any are checked, uncheck them all.
314         // If none are checked, check them all.
315         bool newCheckedState = !anyChecked;
316         NetworkModelController::action(HideJoin)->setChecked(newCheckedState);
317         NetworkModelController::action(HidePart)->setChecked(newCheckedState);
318         NetworkModelController::action(HideQuit)->setChecked(newCheckedState);
319     }
320
321     int filter = 0;
322     if (NetworkModelController::action(HideJoin)->isChecked())
323         filter |= Message::Join | Message::NetsplitJoin;
324     if (NetworkModelController::action(HidePart)->isChecked())
325         filter |= Message::Part;
326     if (NetworkModelController::action(HideQuit)->isChecked())
327         filter |= Message::Quit | Message::NetsplitQuit;
328     if (NetworkModelController::action(HideNick)->isChecked())
329         filter |= Message::Nick;
330     if (NetworkModelController::action(HideMode)->isChecked())
331         filter |= Message::Mode;
332     if (NetworkModelController::action(HideDayChange)->isChecked())
333         filter |= Message::DayChange;
334     if (NetworkModelController::action(HideTopic)->isChecked())
335         filter |= Message::Topic;
336
337     switch (type) {
338     case HideJoinPartQuit:
339     case HideJoin:
340     case HidePart:
341     case HideQuit:
342     case HideNick:
343     case HideMode:
344     case HideDayChange:
345     case HideTopic:
346         if (_messageFilter)
347             BufferSettings(_messageFilter->idString()).setMessageFilter(filter);
348         else {
349             foreach(QModelIndex index, _indexList) {
350                 BufferId bufferId = index.data(NetworkModel::BufferIdRole).value<BufferId>();
351                 if (!bufferId.isValid())
352                     continue;
353                 BufferSettings(bufferId).setMessageFilter(filter);
354             }
355         }
356         return;
357     case HideApplyToAll:
358         BufferSettings().setMessageFilter(filter);
359         // fallthrough
360     case HideUseDefaults:
361         if (_messageFilter)
362             BufferSettings(_messageFilter->idString()).removeFilter();
363         else {
364             foreach(QModelIndex index, _indexList) {
365                 BufferId bufferId = index.data(NetworkModel::BufferIdRole).value<BufferId>();
366                 if (!bufferId.isValid())
367                     continue;
368                 BufferSettings(bufferId).removeFilter();
369             }
370         }
371         return;
372     default:
373         return;
374     };
375 }
376
377
378 void NetworkModelController::handleGeneralAction(ActionType type, QAction *action)
379 {
380     Q_UNUSED(action)
381
382     if (!indexList().count())
383         return;
384     NetworkId networkId = indexList().at(0).data(NetworkModel::NetworkIdRole).value<NetworkId>();
385
386     switch (type) {
387     case JoinChannel:
388     {
389         QString channelName = contextItem();
390         QString channelPassword;
391         if (channelName.isEmpty()) {
392             JoinDlg dlg(indexList().first());
393             if (dlg.exec() == QDialog::Accepted) {
394                 channelName = dlg.channelName();
395                 networkId = dlg.networkId();
396                 channelPassword = dlg.channelPassword();
397             }
398         }
399         if (!channelName.isEmpty()) {
400             if (!channelPassword.isEmpty())
401                 Client::instance()->userInput(BufferInfo::fakeStatusBuffer(networkId), QString("/JOIN %1 %2").arg(channelName).arg(channelPassword));
402             else
403                 Client::instance()->userInput(BufferInfo::fakeStatusBuffer(networkId), QString("/JOIN %1").arg(channelName));
404         }
405         break;
406     }
407     case ShowChannelList:
408         if (networkId.isValid()) {
409             // Don't immediately list channels, allowing customization of filter first
410             emit showChannelList(networkId, {}, false);
411         }
412         break;
413     case ShowNetworkConfig:
414         if (networkId.isValid())
415             emit showNetworkConfig(networkId);
416         break;
417     case ShowIgnoreList:
418         if (networkId.isValid())
419             emit showIgnoreList(QString());
420         break;
421     default:
422         break;
423     }
424 }
425
426
427 void NetworkModelController::handleNickAction(ActionType type, QAction *action)
428 {
429     foreach(QModelIndex index, indexList()) {
430         NetworkId networkId = index.data(NetworkModel::NetworkIdRole).value<NetworkId>();
431         if (!networkId.isValid())
432             continue;
433         QString nick = nickName(index);
434         if (nick.isEmpty())
435             continue;
436         BufferInfo bufferInfo = index.data(NetworkModel::BufferInfoRole).value<BufferInfo>();
437         if (!bufferInfo.isValid())
438             continue;
439
440         switch (type) {
441         case NickWhois:
442             Client::userInput(bufferInfo, QString("/WHOIS %1 %1").arg(nick));
443             break;
444         case NickCtcpVersion:
445             Client::userInput(bufferInfo, QString("/CTCP %1 VERSION").arg(nick));
446             break;
447         case NickCtcpPing:
448             Client::userInput(bufferInfo, QString("/CTCP %1 PING").arg(nick));
449             break;
450         case NickCtcpTime:
451             Client::userInput(bufferInfo, QString("/CTCP %1 TIME").arg(nick));
452             break;
453         case NickCtcpClientinfo:
454             Client::userInput(bufferInfo, QString("/CTCP %1 CLIENTINFO").arg(nick));
455             break;
456         case NickOp:
457             Client::userInput(bufferInfo, QString("/OP %1").arg(nick));
458             break;
459         case NickDeop:
460             Client::userInput(bufferInfo, QString("/DEOP %1").arg(nick));
461             break;
462         case NickHalfop:
463             Client::userInput(bufferInfo, QString("/HALFOP %1").arg(nick));
464             break;
465         case NickDehalfop:
466             Client::userInput(bufferInfo, QString("/DEHALFOP %1").arg(nick));
467             break;
468         case NickVoice:
469             Client::userInput(bufferInfo, QString("/VOICE %1").arg(nick));
470             break;
471         case NickDevoice:
472             Client::userInput(bufferInfo, QString("/DEVOICE %1").arg(nick));
473             break;
474         case NickKick:
475             Client::userInput(bufferInfo, QString("/KICK %1").arg(nick));
476             break;
477         case NickBan:
478             Client::userInput(bufferInfo, QString("/BAN %1").arg(nick));
479             break;
480         case NickKickBan:
481             Client::userInput(bufferInfo, QString("/BAN %1").arg(nick));
482             Client::userInput(bufferInfo, QString("/KICK %1").arg(nick));
483             break;
484         case NickSwitchTo:
485         case NickQuery:
486             Client::bufferModel()->switchToOrStartQuery(networkId, nick);
487             break;
488         case NickIgnoreUser:
489         {
490             auto *ircUser = qobject_cast<IrcUser *>(index.data(NetworkModel::IrcUserRole).value<QObject *>());
491             if (!ircUser)
492                 break;
493             Client::ignoreListManager()->requestAddIgnoreListItem(IgnoreListManager::SenderIgnore,
494                 action->property("ignoreRule").toString(),
495                 false, IgnoreListManager::SoftStrictness,
496                 IgnoreListManager::NetworkScope,
497                 ircUser->network()->networkName(), true);
498             break;
499         }
500         case NickIgnoreHost:
501         {
502             auto *ircUser = qobject_cast<IrcUser *>(index.data(NetworkModel::IrcUserRole).value<QObject *>());
503             if (!ircUser)
504                 break;
505             Client::ignoreListManager()->requestAddIgnoreListItem(IgnoreListManager::SenderIgnore,
506                 action->property("ignoreRule").toString(),
507                 false, IgnoreListManager::SoftStrictness,
508                 IgnoreListManager::NetworkScope,
509                 ircUser->network()->networkName(), true);
510             break;
511         }
512         case NickIgnoreDomain:
513         {
514             auto *ircUser = qobject_cast<IrcUser *>(index.data(NetworkModel::IrcUserRole).value<QObject *>());
515             if (!ircUser)
516                 break;
517             Client::ignoreListManager()->requestAddIgnoreListItem(IgnoreListManager::SenderIgnore,
518                 action->property("ignoreRule").toString(),
519                 false, IgnoreListManager::SoftStrictness,
520                 IgnoreListManager::NetworkScope,
521                 ircUser->network()->networkName(), true);
522             break;
523         }
524         case NickIgnoreCustom:
525             // forward that to mainwin since we can access the settingspage only from there
526             emit showIgnoreList(action->property("ignoreRule").toString());
527             break;
528         case NickIgnoreToggleEnabled0:
529         case NickIgnoreToggleEnabled1:
530         case NickIgnoreToggleEnabled2:
531         case NickIgnoreToggleEnabled3:
532         case NickIgnoreToggleEnabled4:
533             Client::ignoreListManager()->requestToggleIgnoreRule(action->property("ignoreRule").toString());
534             break;
535         default:
536             qWarning() << "Unhandled nick action";
537         }
538     }
539 }
540
541
542 /***************************************************************************************************************
543  * JoinDlg
544  ***************************************************************************************************************/
545
546 NetworkModelController::JoinDlg::JoinDlg(const QModelIndex &index, QWidget *parent) : QDialog(parent)
547 {
548     setWindowIcon(icon::get("irc-join-channel"));
549     setWindowTitle(tr("Join Channel"));
550
551     auto *layout = new QGridLayout(this);
552     layout->addWidget(new QLabel(tr("Network:")), 0, 0);
553     layout->addWidget(networks = new QComboBox, 0, 1);
554     layout->addWidget(new QLabel(tr("Channel:")), 1, 0);
555     layout->addWidget(channel = new QLineEdit, 1, 1);
556     layout->addWidget(new QLabel(tr("Password:")), 2, 0);
557     layout->addWidget(password = new QLineEdit, 2, 1);
558     layout->addWidget(buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel), 3, 0, 1, 2);
559     setLayout(layout);
560
561     channel->setFocus();
562     buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
563     networks->setInsertPolicy(QComboBox::InsertAlphabetically);
564     password->setEchoMode(QLineEdit::Password);
565
566     connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
567     connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
568     connect(channel, &QLineEdit::textChanged, this, &JoinDlg::on_channel_textChanged);
569
570     foreach(NetworkId id, Client::networkIds()) {
571         const Network *net = Client::network(id);
572         if (net->isConnected()) {
573             networks->addItem(net->networkName(), QVariant::fromValue<NetworkId>(id));
574         }
575     }
576
577     if (index.isValid()) {
578         NetworkId networkId = index.data(NetworkModel::NetworkIdRole).value<NetworkId>();
579         if (networkId.isValid()) {
580             networks->setCurrentIndex(networks->findText(Client::network(networkId)->networkName()));
581             if (index.data(NetworkModel::BufferTypeRole) == BufferInfo::ChannelBuffer
582                 && !index.data(NetworkModel::ItemActiveRole).toBool())
583                 channel->setText(index.data(Qt::DisplayRole).toString());
584         }
585     }
586 }
587
588
589 NetworkId NetworkModelController::JoinDlg::networkId() const
590 {
591     return networks->itemData(networks->currentIndex()).value<NetworkId>();
592 }
593
594
595 QString NetworkModelController::JoinDlg::channelName() const
596 {
597     return channel->text();
598 }
599
600
601 QString NetworkModelController::JoinDlg::channelPassword() const
602 {
603     return password->text();
604 }
605
606
607 void NetworkModelController::JoinDlg::on_channel_textChanged(const QString &text)
608 {
609     buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty());
610 }