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