Implement custom rate limits
[quassel.git] / src / qtui / settingspages / networkssettingspage.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2016 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 <QHeaderView>
22 #include <QIcon>
23 #include <QMessageBox>
24 #include <QTextCodec>
25
26 #include "networkssettingspage.h"
27
28 #include "client.h"
29 #include "identity.h"
30 #include "network.h"
31 #include "presetnetworks.h"
32 #include "settingspagedlg.h"
33 #include "util.h"
34
35 #include "settingspages/identitiessettingspage.h"
36
37 NetworksSettingsPage::NetworksSettingsPage(QWidget *parent)
38     : SettingsPage(tr("IRC"), tr("Networks"), parent)
39 #ifdef HAVE_SSL
40       , _cid(0)
41 #endif
42 {
43     ui.setupUi(this);
44
45     // hide SASL options for older cores
46     if (!(Client::coreFeatures() & Quassel::SaslAuthentication))
47         ui.sasl->hide();
48     if (!(Client::coreFeatures() & Quassel::SaslExternal))
49         ui.saslExtInfo->hide();
50 #ifndef HAVE_SSL
51     ui.saslExtInfo->hide();
52 #endif
53
54     // set up icons
55     ui.renameNetwork->setIcon(QIcon::fromTheme("edit-rename"));
56     ui.addNetwork->setIcon(QIcon::fromTheme("list-add"));
57     ui.deleteNetwork->setIcon(QIcon::fromTheme("edit-delete"));
58     ui.addServer->setIcon(QIcon::fromTheme("list-add"));
59     ui.deleteServer->setIcon(QIcon::fromTheme("edit-delete"));
60     ui.editServer->setIcon(QIcon::fromTheme("configure"));
61     ui.upServer->setIcon(QIcon::fromTheme("go-up"));
62     ui.downServer->setIcon(QIcon::fromTheme("go-down"));
63     ui.editIdentities->setIcon(QIcon::fromTheme("configure"));
64
65     _ignoreWidgetChanges = false;
66
67     connectedIcon = QIcon::fromTheme("network-connect");
68     connectingIcon = QIcon::fromTheme("network-wired"); // FIXME network-connecting
69     disconnectedIcon = QIcon::fromTheme("network-disconnect");
70
71     foreach(int mib, QTextCodec::availableMibs()) {
72         QByteArray codec = QTextCodec::codecForMib(mib)->name();
73         ui.sendEncoding->addItem(codec);
74         ui.recvEncoding->addItem(codec);
75         ui.serverEncoding->addItem(codec);
76     }
77     ui.sendEncoding->model()->sort(0);
78     ui.recvEncoding->model()->sort(0);
79     ui.serverEncoding->model()->sort(0);
80     currentId = 0;
81     setEnabled(Client::isConnected()); // need a core connection!
82     setWidgetStates();
83     connect(Client::instance(), SIGNAL(coreConnectionStateChanged(bool)), this, SLOT(coreConnectionStateChanged(bool)));
84     connect(Client::instance(), SIGNAL(networkCreated(NetworkId)), this, SLOT(clientNetworkAdded(NetworkId)));
85     connect(Client::instance(), SIGNAL(networkRemoved(NetworkId)), this, SLOT(clientNetworkRemoved(NetworkId)));
86     connect(Client::instance(), SIGNAL(identityCreated(IdentityId)), this, SLOT(clientIdentityAdded(IdentityId)));
87     connect(Client::instance(), SIGNAL(identityRemoved(IdentityId)), this, SLOT(clientIdentityRemoved(IdentityId)));
88
89     connect(ui.identityList, SIGNAL(currentIndexChanged(int)), this, SLOT(widgetHasChanged()));
90     //connect(ui.randomServer, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
91     connect(ui.performEdit, SIGNAL(textChanged()), this, SLOT(widgetHasChanged()));
92     connect(ui.autoIdentify, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
93     connect(ui.autoIdentifyService, SIGNAL(textEdited(const QString &)), this, SLOT(widgetHasChanged()));
94     connect(ui.autoIdentifyPassword, SIGNAL(textEdited(const QString &)), this, SLOT(widgetHasChanged()));
95     connect(ui.sasl, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
96     connect(ui.saslAccount, SIGNAL(textEdited(QString)), this, SLOT(widgetHasChanged()));
97     connect(ui.saslPassword, SIGNAL(textEdited(QString)), this, SLOT(widgetHasChanged()));
98     connect(ui.useCustomEncodings, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
99     connect(ui.sendEncoding, SIGNAL(currentIndexChanged(int)), this, SLOT(widgetHasChanged()));
100     connect(ui.recvEncoding, SIGNAL(currentIndexChanged(int)), this, SLOT(widgetHasChanged()));
101     connect(ui.serverEncoding, SIGNAL(currentIndexChanged(int)), this, SLOT(widgetHasChanged()));
102     connect(ui.autoReconnect, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
103     connect(ui.reconnectInterval, SIGNAL(valueChanged(int)), this, SLOT(widgetHasChanged()));
104     connect(ui.reconnectRetries, SIGNAL(valueChanged(int)), this, SLOT(widgetHasChanged()));
105     connect(ui.unlimitedRetries, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
106     connect(ui.rejoinOnReconnect, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
107
108     // Core features can change during a reconnect.  Always connect these here, delaying testing for
109     // the core feature flag in load().
110     connect(ui.useCustomMessageRate, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
111     connect(ui.messageRateBurstSize, SIGNAL(valueChanged(int)), this, SLOT(widgetHasChanged()));
112     connect(ui.messageRateDelay, SIGNAL(valueChanged(double)), this, SLOT(widgetHasChanged()));
113     connect(ui.unlimitedMessageRate, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
114
115     // Add additional widgets here
116     //connect(ui., SIGNAL(), this, SLOT(widgetHasChanged()));
117     //connect(ui., SIGNAL(), this, SLOT(widgetHasChanged()));
118
119     foreach(IdentityId id, Client::identityIds()) {
120         clientIdentityAdded(id);
121     }
122 }
123
124
125 void NetworksSettingsPage::save()
126 {
127     setEnabled(false);
128     if (currentId != 0) saveToNetworkInfo(networkInfos[currentId]);
129
130     QList<NetworkInfo> toCreate, toUpdate;
131     QList<NetworkId> toRemove;
132     QHash<NetworkId, NetworkInfo>::iterator i = networkInfos.begin();
133     while (i != networkInfos.end()) {
134         NetworkId id = (*i).networkId;
135         if (id < 0) {
136             toCreate.append(*i);
137             //if(id == currentId) currentId = 0;
138             //QList<QListWidgetItem *> items = ui.networkList->findItems((*i).networkName, Qt::MatchExactly);
139             //if(items.count()) {
140             //  Q_ASSERT(items[0]->data(Qt::UserRole).value<NetworkId>() == id);
141             //  delete items[0];
142             //}
143             //i = networkInfos.erase(i);
144             ++i;
145         }
146         else {
147             if ((*i) != Client::network((*i).networkId)->networkInfo()) {
148                 toUpdate.append(*i);
149             }
150             ++i;
151         }
152     }
153     foreach(NetworkId id, Client::networkIds()) {
154         if (!networkInfos.contains(id)) toRemove.append(id);
155     }
156     SaveNetworksDlg dlg(toCreate, toUpdate, toRemove, this);
157     int ret = dlg.exec();
158     if (ret == QDialog::Rejected) {
159         // canceled -> reload everything to be safe
160         load();
161     }
162     setChangedState(false);
163     setEnabled(true);
164 }
165
166
167 void NetworksSettingsPage::load()
168 {
169     reset();
170
171     // Handle UI dependent on core feature flags here
172     if (Client::coreFeatures() & Quassel::CustomRateLimits) {
173         // Custom rate limiting supported, allow toggling
174         ui.useCustomMessageRate->setEnabled(true);
175         // Reset tooltip to default.
176         ui.useCustomMessageRate->setToolTip(QString("%1").arg(
177                                           tr("<p>Override default message rate limiting.</p>"
178                                              "<p><b>Setting limits too low may get you disconnected"
179                                              " from the server!</b></p>")));
180         // If changed, update the message below!
181     } else {
182         // Custom rate limiting not supported, disallow toggling
183         ui.useCustomMessageRate->setEnabled(false);
184         // Split up the message to allow re-using translations:
185         // [Original tool-tip]
186         // [Bold 'does not support feature' message]
187         // [Specific version needed and feature details]
188         ui.useCustomMessageRate->setToolTip(QString("%1<br/><b>%2</b><br/>%3").arg(
189                                           tr("<p>Override default message rate limiting.</p>"
190                                              "<p><b>Setting limits too low may get you disconnected"
191                                              " from the server!</b></p>"),
192                                           tr("Your Quassel core does not support this feature"),
193                                           tr("You need a Quassel core v0.13.0 or newer in order to "
194                                           "modify message rate limits.")));
195     }
196
197     foreach(NetworkId netid, Client::networkIds()) {
198         clientNetworkAdded(netid);
199     }
200     ui.networkList->setCurrentRow(0);
201
202     setChangedState(false);
203 }
204
205
206 void NetworksSettingsPage::reset()
207 {
208     currentId = 0;
209     ui.networkList->clear();
210     networkInfos.clear();
211 }
212
213
214 bool NetworksSettingsPage::aboutToSave()
215 {
216     if (currentId != 0) saveToNetworkInfo(networkInfos[currentId]);
217     QList<int> errors;
218     foreach(NetworkInfo info, networkInfos.values()) {
219         if (!info.serverList.count()) errors.append(1);
220     }
221     if (!errors.count()) return true;
222     QString error(tr("<b>The following problems need to be corrected before your changes can be applied:</b><ul>"));
223     if (errors.contains(1)) error += tr("<li>All networks need at least one server defined</li>");
224     error += tr("</ul>");
225     QMessageBox::warning(this, tr("Invalid Network Settings"), error);
226     return false;
227 }
228
229
230 void NetworksSettingsPage::widgetHasChanged()
231 {
232     if (_ignoreWidgetChanges) return;
233     bool changed = testHasChanged();
234     if (changed != hasChanged()) setChangedState(changed);
235 }
236
237
238 bool NetworksSettingsPage::testHasChanged()
239 {
240     if (currentId != 0) {
241         saveToNetworkInfo(networkInfos[currentId]);
242     }
243     if (Client::networkIds().count() != networkInfos.count()) return true;
244     foreach(NetworkId id, networkInfos.keys()) {
245         if (id < 0) return true;
246         if (Client::network(id)->networkInfo() != networkInfos[id]) return true;
247     }
248     return false;
249 }
250
251
252 void NetworksSettingsPage::setWidgetStates()
253 {
254     // network list
255     if (ui.networkList->selectedItems().count()) {
256         ui.detailsBox->setEnabled(true);
257         ui.renameNetwork->setEnabled(true);
258         ui.deleteNetwork->setEnabled(true);
259
260         /* button disabled for now
261         NetworkId id = ui.networkList->selectedItems()[0]->data(Qt::UserRole).value<NetworkId>();
262         const Network *net = id > 0 ? Client::network(id) : 0;
263         ui.connectNow->setEnabled(net);
264         //    && (Client::network(id)->connectionState() == Network::Initialized
265         //    || Client::network(id)->connectionState() == Network::Disconnected));
266         if(net) {
267           if(net->connectionState() == Network::Disconnected) {
268             ui.connectNow->setIcon(connectedIcon);
269             ui.connectNow->setText(tr("Connect"));
270           } else {
271             ui.connectNow->setIcon(disconnectedIcon);
272             ui.connectNow->setText(tr("Disconnect"));
273           }
274         } else {
275           ui.connectNow->setIcon(QIcon());
276           ui.connectNow->setText(tr("Apply first!"));
277         } */
278     }
279     else {
280         ui.renameNetwork->setEnabled(false);
281         ui.deleteNetwork->setEnabled(false);
282         //ui.connectNow->setEnabled(false);
283         ui.detailsBox->setEnabled(false);
284     }
285     // network details
286     if (ui.serverList->selectedItems().count()) {
287         ui.editServer->setEnabled(true);
288         ui.deleteServer->setEnabled(true);
289         ui.upServer->setEnabled(ui.serverList->currentRow() > 0);
290         ui.downServer->setEnabled(ui.serverList->currentRow() < ui.serverList->count() - 1);
291     }
292     else {
293         ui.editServer->setEnabled(false);
294         ui.deleteServer->setEnabled(false);
295         ui.upServer->setEnabled(false);
296         ui.downServer->setEnabled(false);
297     }
298 }
299
300
301 void NetworksSettingsPage::setItemState(NetworkId id, QListWidgetItem *item)
302 {
303     if (!item && !(item = networkItem(id))) return;
304     const Network *net = Client::network(id);
305     if (!net || net->isInitialized()) item->setFlags(item->flags() | Qt::ItemIsEnabled);
306     else item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
307     if (net && net->connectionState() == Network::Initialized) {
308         item->setIcon(connectedIcon);
309     }
310     else if (net && net->connectionState() != Network::Disconnected) {
311         item->setIcon(connectingIcon);
312     }
313     else {
314         item->setIcon(disconnectedIcon);
315     }
316     if (net) {
317         bool select = false;
318         // check if we already have another net of this name in the list, and replace it
319         QList<QListWidgetItem *> items = ui.networkList->findItems(net->networkName(), Qt::MatchExactly);
320         if (items.count()) {
321             foreach(QListWidgetItem *i, items) {
322                 NetworkId oldid = i->data(Qt::UserRole).value<NetworkId>();
323                 if (oldid > 0) continue;  // only locally created nets should be replaced
324                 if (oldid == currentId) {
325                     select = true;
326                     currentId = 0;
327                     ui.networkList->clearSelection();
328                 }
329                 int row = ui.networkList->row(i);
330                 if (row >= 0) {
331                     QListWidgetItem *olditem = ui.networkList->takeItem(row);
332                     Q_ASSERT(olditem);
333                     delete olditem;
334                 }
335                 networkInfos.remove(oldid);
336                 break;
337             }
338         }
339         item->setText(net->networkName());
340         if (select) item->setSelected(true);
341     }
342 }
343
344
345 void NetworksSettingsPage::coreConnectionStateChanged(bool state)
346 {
347     this->setEnabled(state);
348     if (state) {
349         load();
350     }
351     else {
352         // reset
353         //currentId = 0;
354     }
355 }
356
357
358 void NetworksSettingsPage::clientIdentityAdded(IdentityId id)
359 {
360     const Identity *identity = Client::identity(id);
361     connect(identity, SIGNAL(updatedRemotely()), this, SLOT(clientIdentityUpdated()));
362
363     QString name = identity->identityName();
364     for (int j = 0; j < ui.identityList->count(); j++) {
365         if ((j > 0 || ui.identityList->itemData(0).toInt() != 1) && name.localeAwareCompare(ui.identityList->itemText(j)) < 0) {
366             ui.identityList->insertItem(j, name, id.toInt());
367             widgetHasChanged();
368             return;
369         }
370     }
371     // append
372     ui.identityList->insertItem(ui.identityList->count(), name, id.toInt());
373     widgetHasChanged();
374 }
375
376
377 void NetworksSettingsPage::clientIdentityUpdated()
378 {
379     const Identity *identity = qobject_cast<const Identity *>(sender());
380     if (!identity) {
381         qWarning() << "NetworksSettingsPage: Invalid identity to update!";
382         return;
383     }
384     int row = ui.identityList->findData(identity->id().toInt());
385     if (row < 0) {
386         qWarning() << "NetworksSettingsPage: Invalid identity to update!";
387         return;
388     }
389     if (ui.identityList->itemText(row) != identity->identityName()) {
390         ui.identityList->setItemText(row, identity->identityName());
391     }
392 }
393
394
395 void NetworksSettingsPage::clientIdentityRemoved(IdentityId id)
396 {
397     IdentityId defaultId = defaultIdentity();
398     if (currentId != 0) saveToNetworkInfo(networkInfos[currentId]);
399     foreach(NetworkInfo info, networkInfos.values()) {
400         if (info.identity == id) {
401             if (info.networkId == currentId)
402                 ui.identityList->setCurrentIndex(0);
403             info.identity = defaultId;
404             networkInfos[info.networkId] = info;
405             if (info.networkId > 0) Client::updateNetwork(info);
406         }
407     }
408     ui.identityList->removeItem(ui.identityList->findData(id.toInt()));
409     widgetHasChanged();
410 }
411
412
413 QListWidgetItem *NetworksSettingsPage::networkItem(NetworkId id) const
414 {
415     for (int i = 0; i < ui.networkList->count(); i++) {
416         QListWidgetItem *item = ui.networkList->item(i);
417         if (item->data(Qt::UserRole).value<NetworkId>() == id) return item;
418     }
419     return 0;
420 }
421
422
423 void NetworksSettingsPage::clientNetworkAdded(NetworkId id)
424 {
425     insertNetwork(id);
426     //connect(Client::network(id), SIGNAL(updatedRemotely()), this, SLOT(clientNetworkUpdated()));
427     connect(Client::network(id), SIGNAL(configChanged()), this, SLOT(clientNetworkUpdated()));
428
429     connect(Client::network(id), SIGNAL(connectionStateSet(Network::ConnectionState)), this, SLOT(networkConnectionStateChanged(Network::ConnectionState)));
430     connect(Client::network(id), SIGNAL(connectionError(const QString &)), this, SLOT(networkConnectionError(const QString &)));
431 }
432
433
434 void NetworksSettingsPage::clientNetworkUpdated()
435 {
436     const Network *net = qobject_cast<const Network *>(sender());
437     if (!net) {
438         qWarning() << "Update request for unknown network received!";
439         return;
440     }
441     networkInfos[net->networkId()] = net->networkInfo();
442     setItemState(net->networkId());
443     if (net->networkId() == currentId) displayNetwork(net->networkId());
444     setWidgetStates();
445     widgetHasChanged();
446 }
447
448
449 void NetworksSettingsPage::clientNetworkRemoved(NetworkId id)
450 {
451     if (!networkInfos.contains(id)) return;
452     if (id == currentId) displayNetwork(0);
453     NetworkInfo info = networkInfos.take(id);
454     QList<QListWidgetItem *> items = ui.networkList->findItems(info.networkName, Qt::MatchExactly);
455     foreach(QListWidgetItem *item, items) {
456         if (item->data(Qt::UserRole).value<NetworkId>() == id)
457             delete ui.networkList->takeItem(ui.networkList->row(item));
458     }
459     setWidgetStates();
460     widgetHasChanged();
461 }
462
463
464 void NetworksSettingsPage::networkConnectionStateChanged(Network::ConnectionState state)
465 {
466     Q_UNUSED(state);
467     const Network *net = qobject_cast<const Network *>(sender());
468     if (!net) return;
469     /*
470     if(net->networkId() == currentId) {
471       ui.connectNow->setEnabled(state == Network::Initialized || state == Network::Disconnected);
472     }
473     */
474     setItemState(net->networkId());
475     setWidgetStates();
476 }
477
478
479 void NetworksSettingsPage::networkConnectionError(const QString &)
480 {
481 }
482
483
484 QListWidgetItem *NetworksSettingsPage::insertNetwork(NetworkId id)
485 {
486     NetworkInfo info = Client::network(id)->networkInfo();
487     networkInfos[id] = info;
488     return insertNetwork(info);
489 }
490
491
492 QListWidgetItem *NetworksSettingsPage::insertNetwork(const NetworkInfo &info)
493 {
494     QListWidgetItem *item = 0;
495     QList<QListWidgetItem *> items = ui.networkList->findItems(info.networkName, Qt::MatchExactly);
496     if (!items.count()) item = new QListWidgetItem(disconnectedIcon, info.networkName, ui.networkList);
497     else {
498         // we overwrite an existing net if it a) has the same name and b) has a negative ID meaning we created it locally before
499         // -> then we can be sure that this is the core-side replacement for the net we created
500         foreach(QListWidgetItem *i, items) {
501             NetworkId id = i->data(Qt::UserRole).value<NetworkId>();
502             if (id < 0) { item = i; break; }
503         }
504         if (!item) item = new QListWidgetItem(disconnectedIcon, info.networkName, ui.networkList);
505     }
506     item->setData(Qt::UserRole, QVariant::fromValue<NetworkId>(info.networkId));
507     setItemState(info.networkId, item);
508     widgetHasChanged();
509     return item;
510 }
511
512
513 void NetworksSettingsPage::displayNetwork(NetworkId id)
514 {
515     _ignoreWidgetChanges = true;
516     if (id != 0) {
517         NetworkInfo info = networkInfos[id];
518
519 #ifdef HAVE_SSL
520         // this is only needed when the core supports SASL EXTERNAL
521         if (Client::coreFeatures() & Quassel::SaslExternal) {
522             if (_cid) {
523                 disconnect(_cid, SIGNAL(sslSettingsUpdated()), this, SLOT(sslUpdated()));
524                 delete _cid;
525             }
526             _cid = new CertIdentity(*Client::identity(info.identity), this);
527             _cid->enableEditSsl(true);
528             connect(_cid, SIGNAL(sslSettingsUpdated()), this, SLOT(sslUpdated()));
529         }
530 #endif
531
532         ui.identityList->setCurrentIndex(ui.identityList->findData(info.identity.toInt()));
533         ui.serverList->clear();
534         foreach(Network::Server server, info.serverList) {
535             QListWidgetItem *item = new QListWidgetItem(QString("%1:%2").arg(server.host).arg(server.port));
536             if (server.useSsl)
537                 item->setIcon(QIcon::fromTheme("document-encrypt"));
538             ui.serverList->addItem(item);
539         }
540         //setItemState(id);
541         //ui.randomServer->setChecked(info.useRandomServer);
542         ui.performEdit->setPlainText(info.perform.join("\n"));
543         ui.autoIdentify->setChecked(info.useAutoIdentify);
544         ui.autoIdentifyService->setText(info.autoIdentifyService);
545         ui.autoIdentifyPassword->setText(info.autoIdentifyPassword);
546         ui.sasl->setChecked(info.useSasl);
547         ui.saslAccount->setText(info.saslAccount);
548         ui.saslPassword->setText(info.saslPassword);
549         if (info.codecForEncoding.isEmpty()) {
550             ui.sendEncoding->setCurrentIndex(ui.sendEncoding->findText(Network::defaultCodecForEncoding()));
551             ui.recvEncoding->setCurrentIndex(ui.recvEncoding->findText(Network::defaultCodecForDecoding()));
552             ui.serverEncoding->setCurrentIndex(ui.serverEncoding->findText(Network::defaultCodecForServer()));
553             ui.useCustomEncodings->setChecked(false);
554         }
555         else {
556             ui.sendEncoding->setCurrentIndex(ui.sendEncoding->findText(info.codecForEncoding));
557             ui.recvEncoding->setCurrentIndex(ui.recvEncoding->findText(info.codecForDecoding));
558             ui.serverEncoding->setCurrentIndex(ui.serverEncoding->findText(info.codecForServer));
559             ui.useCustomEncodings->setChecked(true);
560         }
561         ui.autoReconnect->setChecked(info.useAutoReconnect);
562         ui.reconnectInterval->setValue(info.autoReconnectInterval);
563         ui.reconnectRetries->setValue(info.autoReconnectRetries);
564         ui.unlimitedRetries->setChecked(info.unlimitedReconnectRetries);
565         ui.rejoinOnReconnect->setChecked(info.rejoinChannels);
566         // Custom rate limiting
567         ui.unlimitedMessageRate->setChecked(info.unlimitedMessageRate);
568         // Set 'ui.useCustomMessageRate' after 'ui.unlimitedMessageRate' so if the latter is
569         // disabled, 'ui.messageRateDelayFrame' will remain disabled.
570         ui.useCustomMessageRate->setChecked(info.useCustomMessageRate);
571         ui.messageRateBurstSize->setValue(info.messageRateBurstSize);
572         // Convert milliseconds (integer) into seconds (double)
573         ui.messageRateDelay->setValue(info.messageRateDelay / 1000.0f);
574     }
575     else {
576         // just clear widgets
577 #ifdef HAVE_SSL
578         if (_cid) {
579             disconnect(_cid, SIGNAL(sslSettingsUpdated()), this, SLOT(sslUpdated()));
580             delete _cid;
581         }
582 #endif
583         ui.identityList->setCurrentIndex(-1);
584         ui.serverList->clear();
585         ui.performEdit->clear();
586         ui.autoIdentifyService->clear();
587         ui.autoIdentifyPassword->clear();
588         ui.saslAccount->clear();
589         ui.saslPassword->clear();
590         setWidgetStates();
591     }
592     _ignoreWidgetChanges = false;
593     currentId = id;
594 }
595
596
597 void NetworksSettingsPage::saveToNetworkInfo(NetworkInfo &info)
598 {
599     info.identity = ui.identityList->itemData(ui.identityList->currentIndex()).toInt();
600     //info.useRandomServer = ui.randomServer->isChecked();
601     info.perform = ui.performEdit->toPlainText().split("\n");
602     info.useAutoIdentify = ui.autoIdentify->isChecked();
603     info.autoIdentifyService = ui.autoIdentifyService->text();
604     info.autoIdentifyPassword = ui.autoIdentifyPassword->text();
605     info.useSasl = ui.sasl->isChecked();
606     info.saslAccount = ui.saslAccount->text();
607     info.saslPassword = ui.saslPassword->text();
608     if (!ui.useCustomEncodings->isChecked()) {
609         info.codecForEncoding.clear();
610         info.codecForDecoding.clear();
611         info.codecForServer.clear();
612     }
613     else {
614         info.codecForEncoding = ui.sendEncoding->currentText().toLatin1();
615         info.codecForDecoding = ui.recvEncoding->currentText().toLatin1();
616         info.codecForServer = ui.serverEncoding->currentText().toLatin1();
617     }
618     info.useAutoReconnect = ui.autoReconnect->isChecked();
619     info.autoReconnectInterval = ui.reconnectInterval->value();
620     info.autoReconnectRetries = ui.reconnectRetries->value();
621     info.unlimitedReconnectRetries = ui.unlimitedRetries->isChecked();
622     info.rejoinChannels = ui.rejoinOnReconnect->isChecked();
623     // Custom rate limiting
624     info.useCustomMessageRate = ui.useCustomMessageRate->isChecked();
625     info.messageRateBurstSize = ui.messageRateBurstSize->value();
626     // Convert seconds (double) into milliseconds (integer)
627     info.messageRateDelay = static_cast<quint32>((ui.messageRateDelay->value() * 1000));
628     info.unlimitedMessageRate = ui.unlimitedMessageRate->isChecked();
629 }
630
631
632 #ifdef HAVE_SSL
633 void NetworksSettingsPage::sslUpdated()
634 {
635     if (_cid && !_cid->sslKey().isNull()) {
636         ui.saslAccount->setDisabled(true);
637         ui.saslAccountLabel->setDisabled(true);
638         ui.saslPassword->setDisabled(true);
639         ui.saslPasswordLabel->setDisabled(true);
640         ui.saslExtInfo->setHidden(false);
641     } else {
642         ui.saslAccount->setDisabled(false);
643         ui.saslAccountLabel->setDisabled(false);
644         ui.saslPassword->setDisabled(false);
645         ui.saslPasswordLabel->setDisabled(false);
646         ui.saslExtInfo->setHidden(true);
647     }
648 }
649 #endif
650
651
652 /*** Network list ***/
653
654 void NetworksSettingsPage::on_networkList_itemSelectionChanged()
655 {
656     if (currentId != 0) {
657         saveToNetworkInfo(networkInfos[currentId]);
658     }
659     if (ui.networkList->selectedItems().count()) {
660         NetworkId id = ui.networkList->selectedItems()[0]->data(Qt::UserRole).value<NetworkId>();
661         currentId = id;
662         displayNetwork(id);
663         ui.serverList->setCurrentRow(0);
664     }
665     else {
666         currentId = 0;
667     }
668     setWidgetStates();
669 }
670
671
672 void NetworksSettingsPage::on_addNetwork_clicked()
673 {
674     QStringList existing;
675     for (int i = 0; i < ui.networkList->count(); i++) existing << ui.networkList->item(i)->text();
676     NetworkAddDlg dlg(existing, this);
677     if (dlg.exec() == QDialog::Accepted) {
678         NetworkInfo info = dlg.networkInfo();
679         if (info.networkName.isEmpty())
680             return;  // sanity check
681
682         NetworkId id;
683         for (id = 1; id <= networkInfos.count(); id++) {
684             widgetHasChanged();
685             if (!networkInfos.keys().contains(-id.toInt())) break;
686         }
687         id = -id.toInt();
688         info.networkId = id;
689         info.identity = defaultIdentity();
690         networkInfos[id] = info;
691         QListWidgetItem *item = insertNetwork(info);
692         ui.networkList->setCurrentItem(item);
693         setWidgetStates();
694     }
695 }
696
697
698 void NetworksSettingsPage::on_deleteNetwork_clicked()
699 {
700     if (ui.networkList->selectedItems().count()) {
701         NetworkId netid = ui.networkList->selectedItems()[0]->data(Qt::UserRole).value<NetworkId>();
702         int ret = QMessageBox::question(this, tr("Delete Network?"),
703             tr("Do you really want to delete the network \"%1\" and all related settings, including the backlog?").arg(networkInfos[netid].networkName),
704             QMessageBox::Yes|QMessageBox::No, QMessageBox::No);
705         if (ret == QMessageBox::Yes) {
706             currentId = 0;
707             networkInfos.remove(netid);
708             delete ui.networkList->takeItem(ui.networkList->row(ui.networkList->selectedItems()[0]));
709             ui.networkList->setCurrentRow(qMin(ui.networkList->currentRow()+1, ui.networkList->count()-1));
710             setWidgetStates();
711             widgetHasChanged();
712         }
713     }
714 }
715
716
717 void NetworksSettingsPage::on_renameNetwork_clicked()
718 {
719     if (!ui.networkList->selectedItems().count()) return;
720     QString old = ui.networkList->selectedItems()[0]->text();
721     QStringList existing;
722     for (int i = 0; i < ui.networkList->count(); i++) existing << ui.networkList->item(i)->text();
723     NetworkEditDlg dlg(old, existing, this);
724     if (dlg.exec() == QDialog::Accepted) {
725         ui.networkList->selectedItems()[0]->setText(dlg.networkName());
726         NetworkId netid = ui.networkList->selectedItems()[0]->data(Qt::UserRole).value<NetworkId>();
727         networkInfos[netid].networkName = dlg.networkName();
728         widgetHasChanged();
729     }
730 }
731
732
733 /*
734 void NetworksSettingsPage::on_connectNow_clicked() {
735   if(!ui.networkList->selectedItems().count()) return;
736   NetworkId id = ui.networkList->selectedItems()[0]->data(Qt::UserRole).value<NetworkId>();
737   const Network *net = Client::network(id);
738   if(!net) return;
739   if(net->connectionState() == Network::Disconnected) net->requestConnect();
740   else net->requestDisconnect();
741 }
742 */
743
744 /*** Server list ***/
745
746 void NetworksSettingsPage::on_serverList_itemSelectionChanged()
747 {
748     setWidgetStates();
749 }
750
751
752 void NetworksSettingsPage::on_addServer_clicked()
753 {
754     if (currentId == 0) return;
755     ServerEditDlg dlg(Network::Server(), this);
756     if (dlg.exec() == QDialog::Accepted) {
757         networkInfos[currentId].serverList.append(dlg.serverData());
758         displayNetwork(currentId);
759         ui.serverList->setCurrentRow(ui.serverList->count()-1);
760         widgetHasChanged();
761     }
762 }
763
764
765 void NetworksSettingsPage::on_editServer_clicked()
766 {
767     if (currentId == 0) return;
768     int cur = ui.serverList->currentRow();
769     ServerEditDlg dlg(networkInfos[currentId].serverList[cur], this);
770     if (dlg.exec() == QDialog::Accepted) {
771         networkInfos[currentId].serverList[cur] = dlg.serverData();
772         displayNetwork(currentId);
773         ui.serverList->setCurrentRow(cur);
774         widgetHasChanged();
775     }
776 }
777
778
779 void NetworksSettingsPage::on_deleteServer_clicked()
780 {
781     if (currentId == 0) return;
782     int cur = ui.serverList->currentRow();
783     networkInfos[currentId].serverList.removeAt(cur);
784     displayNetwork(currentId);
785     ui.serverList->setCurrentRow(qMin(cur, ui.serverList->count()-1));
786     widgetHasChanged();
787 }
788
789
790 void NetworksSettingsPage::on_upServer_clicked()
791 {
792     int cur = ui.serverList->currentRow();
793     Network::Server server = networkInfos[currentId].serverList.takeAt(cur);
794     networkInfos[currentId].serverList.insert(cur-1, server);
795     displayNetwork(currentId);
796     ui.serverList->setCurrentRow(cur-1);
797     widgetHasChanged();
798 }
799
800
801 void NetworksSettingsPage::on_downServer_clicked()
802 {
803     int cur = ui.serverList->currentRow();
804     Network::Server server = networkInfos[currentId].serverList.takeAt(cur);
805     networkInfos[currentId].serverList.insert(cur+1, server);
806     displayNetwork(currentId);
807     ui.serverList->setCurrentRow(cur+1);
808     widgetHasChanged();
809 }
810
811
812 void NetworksSettingsPage::on_editIdentities_clicked()
813 {
814     SettingsPageDlg dlg(new IdentitiesSettingsPage(this), this);
815     dlg.exec();
816 }
817
818
819 IdentityId NetworksSettingsPage::defaultIdentity() const
820 {
821     IdentityId defaultId = 0;
822     QList<IdentityId> ids = Client::identityIds();
823     foreach(IdentityId id, ids) {
824         if (defaultId == 0 || id < defaultId)
825             defaultId = id;
826     }
827     return defaultId;
828 }
829
830
831 /**************************************************************************
832 * NetworkAddDlg
833 *************************************************************************/
834
835 NetworkAddDlg::NetworkAddDlg(const QStringList &exist, QWidget *parent) : QDialog(parent), existing(exist)
836 {
837     ui.setupUi(this);
838     ui.useSSL->setIcon(QIcon::fromTheme("document-encrypt"));
839
840     // Whenever useSSL is toggled, update the port number if not changed from the default
841     connect(ui.useSSL, SIGNAL(toggled(bool)), SLOT(updateSslPort(bool)));
842     // Do NOT call updateSslPort when loading settings, otherwise port settings may be overriden.
843     // If useSSL is later changed to be checked by default, change port's default value, too.
844
845     if (Client::coreFeatures() & Quassel::VerifyServerSSL) {
846         // Synchronize requiring SSL with the use SSL checkbox
847         ui.sslVerify->setEnabled(ui.useSSL->isChecked());
848         connect(ui.useSSL, SIGNAL(toggled(bool)), ui.sslVerify, SLOT(setEnabled(bool)));
849     } else {
850         // Core isn't new enough to allow requiring SSL; disable checkbox and uncheck
851         ui.sslVerify->setEnabled(false);
852         ui.sslVerify->setChecked(false);
853         // Split up the message to allow re-using translations:
854         // [Original tool-tip]
855         // [Bold 'does not support feature' message]
856         // [Specific version needed and feature details]
857         ui.sslVerify->setToolTip(QString("%1<br/><b>%2</b><br/>%3").arg(
858                                        ui.sslVerify->toolTip(),
859                                        tr("Your Quassel core does not support this feature"),
860                                        tr("You need a Quassel core v0.13.0 or newer in order to "
861                                           "verify connection security.")));
862     }
863
864     // read preset networks
865     QStringList networks = PresetNetworks::names();
866     foreach(QString s, existing)
867     networks.removeAll(s);
868     if (networks.count())
869         ui.presetList->addItems(networks);
870     else {
871         ui.useManual->setChecked(true);
872         ui.usePreset->setEnabled(false);
873     }
874     connect(ui.networkName, SIGNAL(textChanged(const QString &)), SLOT(setButtonStates()));
875     connect(ui.serverAddress, SIGNAL(textChanged(const QString &)), SLOT(setButtonStates()));
876     setButtonStates();
877 }
878
879
880 NetworkInfo NetworkAddDlg::networkInfo() const
881 {
882     if (ui.useManual->isChecked()) {
883         NetworkInfo info;
884         info.networkName = ui.networkName->text().trimmed();
885         info.serverList << Network::Server(ui.serverAddress->text().trimmed(), ui.port->value(),
886                                            ui.serverPassword->text(), ui.useSSL->isChecked(),
887                                            ui.sslVerify->isChecked());
888         return info;
889     }
890     else
891         return PresetNetworks::networkInfo(ui.presetList->currentText());
892 }
893
894
895 void NetworkAddDlg::setButtonStates()
896 {
897     bool ok = false;
898     if (ui.usePreset->isChecked() && ui.presetList->count())
899         ok = true;
900     else if (ui.useManual->isChecked()) {
901         ok = !ui.networkName->text().trimmed().isEmpty() && !existing.contains(ui.networkName->text().trimmed())
902              && !ui.serverAddress->text().isEmpty();
903     }
904     ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(ok);
905 }
906
907
908 void NetworkAddDlg::updateSslPort(bool isChecked)
909 {
910     // "Use encrypted connection" was toggled, check the state...
911     if (isChecked && ui.port->value() == Network::PORT_PLAINTEXT) {
912         // Had been using the plain-text port, use the SSL default
913         ui.port->setValue(Network::PORT_SSL);
914     } else if (!isChecked && ui.port->value() == Network::PORT_SSL) {
915         // Had been using the SSL port, use the plain-text default
916         ui.port->setValue(Network::PORT_PLAINTEXT);
917     }
918 }
919
920
921 /**************************************************************************
922  * NetworkEditDlg
923  *************************************************************************/
924
925 NetworkEditDlg::NetworkEditDlg(const QString &old, const QStringList &exist, QWidget *parent) : QDialog(parent), existing(exist)
926 {
927     ui.setupUi(this);
928
929     if (old.isEmpty()) {
930         // new network
931         setWindowTitle(tr("Add Network"));
932         on_networkEdit_textChanged(""); // disable ok button
933     }
934     else ui.networkEdit->setText(old);
935 }
936
937
938 QString NetworkEditDlg::networkName() const
939 {
940     return ui.networkEdit->text().trimmed();
941 }
942
943
944 void NetworkEditDlg::on_networkEdit_textChanged(const QString &text)
945 {
946     ui.buttonBox->button(QDialogButtonBox::Ok)->setDisabled(text.isEmpty() || existing.contains(text.trimmed()));
947 }
948
949
950 /**************************************************************************
951  * ServerEditDlg
952  *************************************************************************/
953 ServerEditDlg::ServerEditDlg(const Network::Server &server, QWidget *parent) : QDialog(parent)
954 {
955     ui.setupUi(this);
956     ui.useSSL->setIcon(QIcon::fromTheme("document-encrypt"));
957     ui.host->setText(server.host);
958     ui.host->setFocus();
959     ui.port->setValue(server.port);
960     ui.password->setText(server.password);
961     ui.useSSL->setChecked(server.useSsl);
962     ui.sslVerify->setChecked(server.sslVerify);
963     ui.sslVersion->setCurrentIndex(server.sslVersion);
964     ui.useProxy->setChecked(server.useProxy);
965     ui.proxyType->setCurrentIndex(server.proxyType == QNetworkProxy::Socks5Proxy ? 0 : 1);
966     ui.proxyHost->setText(server.proxyHost);
967     ui.proxyPort->setValue(server.proxyPort);
968     ui.proxyUsername->setText(server.proxyUser);
969     ui.proxyPassword->setText(server.proxyPass);
970
971     // This is a dirty hack to display the core->IRC SSL protocol dropdown
972     // only if the core won't use autonegotiation to determine the best
973     // protocol.  When autonegotiation was introduced, it would have been
974     // a good idea to use the CoreFeatures enum to accomplish this.
975     // However, since multiple versions have been released since then, that
976     // is no longer possible.  Instead, we rely on the fact that the
977     // Datastream protocol was introduced in the same version (0.10) as SSL
978     // autonegotiation.  Because of that, we can display the dropdown only
979     // if the Legacy protocol is in use.  If any other RemotePeer protocol
980     // is in use, that means a newer protocol is in use and therefore the
981     // core will use autonegotiation.
982     if (Client::coreConnection()->peer()->protocol() != Protocol::LegacyProtocol) {
983         ui.label_3->hide();
984         ui.sslVersion->hide();
985     }
986
987     // Whenever useSSL is toggled, update the port number if not changed from the default
988     connect(ui.useSSL, SIGNAL(toggled(bool)), SLOT(updateSslPort(bool)));
989     // Do NOT call updateSslPort when loading settings, otherwise port settings may be overriden.
990     // If useSSL is later changed to be checked by default, change port's default value, too.
991
992     if (Client::coreFeatures() & Quassel::VerifyServerSSL) {
993         // Synchronize requiring SSL with the use SSL checkbox
994         ui.sslVerify->setEnabled(ui.useSSL->isChecked());
995         connect(ui.useSSL, SIGNAL(toggled(bool)), ui.sslVerify, SLOT(setEnabled(bool)));
996     } else {
997         // Core isn't new enough to allow requiring SSL; disable checkbox and uncheck
998         ui.sslVerify->setEnabled(false);
999         ui.sslVerify->setChecked(false);
1000         // Split up the message to allow re-using translations:
1001         // [Original tool-tip]
1002         // [Bold 'does not support feature' message]
1003         // [Specific version needed and feature details]
1004         ui.sslVerify->setToolTip(QString("%1<br/><b>%2</b><br/>%3").arg(
1005                                        ui.sslVerify->toolTip(),
1006                                        tr("Your Quassel core does not support this feature"),
1007                                        tr("You need a Quassel core v0.13.0 or newer in order to "
1008                                           "verify connection security.")));
1009     }
1010
1011     on_host_textChanged();
1012 }
1013
1014
1015 Network::Server ServerEditDlg::serverData() const
1016 {
1017     Network::Server server(ui.host->text().trimmed(), ui.port->value(), ui.password->text(),
1018                            ui.useSSL->isChecked(), ui.sslVerify->isChecked());
1019     server.sslVersion = ui.sslVersion->currentIndex();
1020     server.useProxy = ui.useProxy->isChecked();
1021     server.proxyType = ui.proxyType->currentIndex() == 0 ? QNetworkProxy::Socks5Proxy : QNetworkProxy::HttpProxy;
1022     server.proxyHost = ui.proxyHost->text();
1023     server.proxyPort = ui.proxyPort->value();
1024     server.proxyUser = ui.proxyUsername->text();
1025     server.proxyPass = ui.proxyPassword->text();
1026     return server;
1027 }
1028
1029
1030 void ServerEditDlg::on_host_textChanged()
1031 {
1032     ui.buttonBox->button(QDialogButtonBox::Ok)->setDisabled(ui.host->text().trimmed().isEmpty());
1033 }
1034
1035
1036 void ServerEditDlg::updateSslPort(bool isChecked)
1037 {
1038     // "Use encrypted connection" was toggled, check the state...
1039     if (isChecked && ui.port->value() == Network::PORT_PLAINTEXT) {
1040         // Had been using the plain-text port, use the SSL default
1041         ui.port->setValue(Network::PORT_SSL);
1042     } else if (!isChecked && ui.port->value() == Network::PORT_SSL) {
1043         // Had been using the SSL port, use the plain-text default
1044         ui.port->setValue(Network::PORT_PLAINTEXT);
1045     }
1046 }
1047
1048
1049 /**************************************************************************
1050  * SaveNetworksDlg
1051  *************************************************************************/
1052
1053 SaveNetworksDlg::SaveNetworksDlg(const QList<NetworkInfo> &toCreate, const QList<NetworkInfo> &toUpdate, const QList<NetworkId> &toRemove, QWidget *parent) : QDialog(parent)
1054 {
1055     ui.setupUi(this);
1056
1057     numevents = toCreate.count() + toUpdate.count() + toRemove.count();
1058     rcvevents = 0;
1059     if (numevents) {
1060         ui.progressBar->setMaximum(numevents);
1061         ui.progressBar->setValue(0);
1062
1063         connect(Client::instance(), SIGNAL(networkCreated(NetworkId)), this, SLOT(clientEvent()));
1064         connect(Client::instance(), SIGNAL(networkRemoved(NetworkId)), this, SLOT(clientEvent()));
1065
1066         foreach(NetworkId id, toRemove) {
1067             Client::removeNetwork(id);
1068         }
1069         foreach(NetworkInfo info, toCreate) {
1070             Client::createNetwork(info);
1071         }
1072         foreach(NetworkInfo info, toUpdate) {
1073             const Network *net = Client::network(info.networkId);
1074             if (!net) {
1075                 qWarning() << "Invalid client network!";
1076                 numevents--;
1077                 continue;
1078             }
1079             // FIXME this only checks for one changed item rather than all!
1080             connect(net, SIGNAL(updatedRemotely()), this, SLOT(clientEvent()));
1081             Client::updateNetwork(info);
1082         }
1083     }
1084     else {
1085         qWarning() << "Sync dialog called without stuff to change!";
1086         accept();
1087     }
1088 }
1089
1090
1091 void SaveNetworksDlg::clientEvent()
1092 {
1093     ui.progressBar->setValue(++rcvevents);
1094     if (rcvevents >= numevents) accept();
1095 }