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