5aa1712e4e0cc1fc4a2d753928f711e9a6fcc225
[quassel.git] / src / qtui / settingspages / identitiessettingspage.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-08 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) any later version.                                   *
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  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20
21 #include <QInputDialog>
22 #include <QMessageBox>
23
24 #include "identitiessettingspage.h"
25
26 #include "client.h"
27
28 IdentitiesSettingsPage::IdentitiesSettingsPage(QWidget *parent)
29   : SettingsPage(tr("General"), tr("Identities"), parent) {
30
31   ui.setupUi(this);
32   setEnabled(false);  // need a core connection!
33   setWidgetStates();
34   connect(Client::instance(), SIGNAL(coreConnectionStateChanged(bool)), this, SLOT(coreConnectionStateChanged(bool)));
35   connect(Client::instance(), SIGNAL(identityCreated(IdentityId)), this, SLOT(clientIdentityCreated(IdentityId)));
36   connect(Client::instance(), SIGNAL(identityRemoved(IdentityId)), this, SLOT(clientIdentityRemoved(IdentityId)));
37
38   currentId = 0;
39
40   // We need to know whenever the state of input widgets changes...
41   //connect(ui.identityList, SIGNAL(editTextChanged(const QString &)), this, SLOT(widgetHasChanged()));
42   connect(ui.realName, SIGNAL(textEdited(const QString &)), this, SLOT(widgetHasChanged()));
43   connect(ui.nicknameList, SIGNAL(itemChanged(QListWidgetItem *)), this, SLOT(widgetHasChanged()));
44   connect(ui.awayNick, SIGNAL(textEdited(const QString &)), this, SLOT(widgetHasChanged()));
45   connect(ui.awayNickEnabled, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
46   connect(ui.awayReason, SIGNAL(textEdited(const QString &)), this, SLOT(widgetHasChanged()));
47   connect(ui.awayReasonEnabled, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
48   connect(ui.returnMessage, SIGNAL(textEdited(const QString &)), this, SLOT(widgetHasChanged()));
49   connect(ui.returnMessageEnabled, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
50   connect(ui.autoAwayEnabled, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
51   connect(ui.autoAwayTime, SIGNAL(valueChanged(int)), this, SLOT(widgetHasChanged()));
52   connect(ui.autoAwayReason, SIGNAL(textEdited(const QString &)), this, SLOT(widgetHasChanged()));
53   connect(ui.autoAwayReasonEnabled, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
54   connect(ui.autoReturnMessage, SIGNAL(textEdited(const QString &)), this, SLOT(widgetHasChanged()));
55   connect(ui.autoReturnMessageEnabled, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
56   connect(ui.ident, SIGNAL(textEdited(const QString &)), this, SLOT(widgetHasChanged()));
57   connect(ui.kickReason, SIGNAL(textEdited(const QString &)), this, SLOT(widgetHasChanged()));
58   connect(ui.partReason, SIGNAL(textEdited(const QString &)), this, SLOT(widgetHasChanged()));
59   connect(ui.quitReason, SIGNAL(textEdited(const QString &)), this, SLOT(widgetHasChanged()));
60
61   connect(ui.nicknameList, SIGNAL(itemSelectionChanged()), this, SLOT(setWidgetStates()));
62
63   // we would need this if we enabled drag and drop in the nicklist...
64   //connect(ui.nicknameList, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(setWidgetStates()));
65   //connect(ui.nicknameList->model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(nicklistHasChanged()));
66
67 }
68
69 void IdentitiesSettingsPage::setWidgetStates() {
70   if(ui.nicknameList->selectedItems().count()) {
71     ui.renameNick->setEnabled(true);
72     ui.nickUp->setEnabled(ui.nicknameList->row(ui.nicknameList->selectedItems()[0]) > 0);
73     ui.nickDown->setEnabled(ui.nicknameList->row(ui.nicknameList->selectedItems()[0]) < ui.nicknameList->count()-1);
74   } else {
75     ui.renameNick->setDisabled(true);
76     ui.nickUp->setDisabled(true);
77     ui.nickDown->setDisabled(true);
78   }
79   ui.deleteNick->setEnabled(ui.nicknameList->count() > 1);
80
81 }
82
83 void IdentitiesSettingsPage::coreConnectionStateChanged(bool state) {
84   this->setEnabled(state);
85   if(state) {
86     load();
87   } else {
88     // reset
89     currentId = 0;
90   }
91 }
92
93 void IdentitiesSettingsPage::save() {
94   setEnabled(false);
95   QList<Identity *> toCreate, toUpdate;
96   // we need to remove our temporarily created identities.
97   // these are going to be re-added after the core has propagated them back...
98   QHash<IdentityId, Identity *>::iterator i = identities.begin();
99   while(i != identities.end()) {
100     if((*i)->id() < 0) {
101       Identity *temp = *i;
102       i = identities.erase(i);
103       toCreate.append(temp);
104       ui.identityList->removeItem(ui.identityList->findData(temp->id().toInt()));
105     } else {
106       if(**i != *Client::identity((*i)->id())) {
107         toUpdate.append(*i);
108       }
109       ++i;
110     }
111   }
112   SaveIdentitiesDlg dlg(toCreate, toUpdate, deletedIdentities, this);
113   int ret = dlg.exec();
114   if(ret == QDialog::Rejected) {
115     // canceled -> reload everything to be safe
116     load();
117   }
118   foreach(Identity *id, toCreate) {
119     id->deleteLater();
120   }
121   changedIdentities.clear();
122   deletedIdentities.clear();
123   setChangedState(false);
124   setEnabled(true);
125 }
126
127 void IdentitiesSettingsPage::load() {
128   currentId = 0;
129   foreach(Identity *identity, identities.values()) {
130     identity->deleteLater();
131   }
132   identities.clear();
133   deletedIdentities.clear();
134   changedIdentities.clear();
135   ui.identityList->clear();
136   foreach(IdentityId id, Client::identityIds()) {
137     clientIdentityCreated(id);
138   }
139   setChangedState(false);
140 }
141
142 void IdentitiesSettingsPage::widgetHasChanged() {
143   bool changed = testHasChanged();
144   if(changed != hasChanged()) setChangedState(changed);
145 }
146
147 bool IdentitiesSettingsPage::testHasChanged() {
148   if(deletedIdentities.count()) return true;
149   if(currentId < 0) {
150     return true; // new identity
151   } else {
152     if(currentId != 0) {
153       changedIdentities.removeAll(currentId);
154       Identity temp(currentId, this);
155       saveToIdentity(&temp);
156       temp.setIdentityName(identities[currentId]->identityName());
157       if(temp != *Client::identity(currentId)) changedIdentities.append(currentId);
158     }
159     return changedIdentities.count();
160   }
161 }
162
163 bool IdentitiesSettingsPage::aboutToSave() {
164   saveToIdentity(identities[currentId]);
165   QList<int> errors;
166   foreach(Identity *id, identities.values()) {
167     if(id->identityName().isEmpty()) errors.append(1);
168     if(!id->nicks().count()) errors.append(2);
169     if(id->realName().isEmpty()) errors.append(3);
170     if(id->ident().isEmpty()) errors.append(4);
171   }
172   if(!errors.count()) return true;
173   QString error(tr("<b>The following problems need to be corrected before your changes can be applied:</b><ul>"));
174   if(errors.contains(1)) error += tr("<li>All identities need an identity name set</li>");
175   if(errors.contains(2)) error += tr("<li>Every identity needs at least one nickname defined</li>");
176   if(errors.contains(3)) error += tr("<li>You need to specify a real name for every identity</li>");
177   if(errors.contains(4)) error += tr("<li>You need to specify an ident for every identity</li>");
178   error += tr("</ul>");
179   QMessageBox::warning(this, tr("One or more identities are invalid"), error);
180   return false;
181 }
182
183 void IdentitiesSettingsPage::clientIdentityCreated(IdentityId id) {
184   insertIdentity(new Identity(*Client::identity(id), this));
185   connect(Client::identity(id), SIGNAL(updatedRemotely()), this, SLOT(clientIdentityUpdated()));
186 }
187
188 void IdentitiesSettingsPage::clientIdentityUpdated() {
189   const Identity *clientIdentity = qobject_cast<Identity *>(sender());
190   if(!clientIdentity) {
191     qWarning() << "Invalid identity to update!";
192     return;
193   }
194   if(!identities.contains(clientIdentity->id())) {
195     qWarning() << "Unknown identity to update:" << clientIdentity->identityName();
196     return;
197   }
198   Identity *identity = identities[clientIdentity->id()];
199   if(identity->identityName() != clientIdentity->identityName()) renameIdentity(identity->id(), clientIdentity->identityName());
200   identity->update(*clientIdentity);
201   if(identity->id() == currentId) displayIdentity(identity, true);
202 }
203
204 void IdentitiesSettingsPage::clientIdentityRemoved(IdentityId id) {
205   if(identities.contains(id)) {
206     removeIdentity(identities[id]);
207     changedIdentities.removeAll(id);
208     deletedIdentities.removeAll(id);
209   }
210 }
211
212 void IdentitiesSettingsPage::insertIdentity(Identity *identity) {
213   IdentityId id = identity->id();
214   identities[id] = identity;
215   if(id == 1) {
216     // default identity is always the first one!
217     ui.identityList->insertItem(0, identity->identityName(), id.toInt());
218   } else {
219     QString name = identity->identityName();
220     for(int j = 0; j < ui.identityList->count(); j++) {
221       if((j>0 || ui.identityList->itemData(0).toInt() != 1) && name.localeAwareCompare(ui.identityList->itemText(j)) < 0) {
222         ui.identityList->insertItem(j, name, id.toInt());
223         widgetHasChanged();
224         return;
225       }
226     }
227     // append
228     ui.identityList->insertItem(ui.identityList->count(), name, id.toInt());
229     widgetHasChanged();
230   }
231 }
232
233 void IdentitiesSettingsPage::renameIdentity(IdentityId id, const QString &newName) {
234   Identity *identity = identities[id];
235   ui.identityList->setItemText(ui.identityList->findData(identity->id().toInt()), newName);
236   identity->setIdentityName(newName);
237 }
238
239 void IdentitiesSettingsPage::removeIdentity(Identity *id) {
240   identities.remove(id->id());
241   ui.identityList->removeItem(ui.identityList->findData(id->id().toInt()));
242   changedIdentities.removeAll(id->id());
243   if(currentId == id->id()) currentId = 0;
244   id->deleteLater();
245   widgetHasChanged();
246 }
247
248 void IdentitiesSettingsPage::on_identityList_currentIndexChanged(int index) {
249   if(index < 0) {
250     //ui.identityList->setEditable(false);
251     displayIdentity(0);
252   } else {
253     IdentityId id = ui.identityList->itemData(index).toInt();
254     if(identities.contains(id)) displayIdentity(identities[id]);
255     ui.deleteIdentity->setEnabled(id != 1); // default identity cannot be deleted
256     ui.renameIdentity->setEnabled(id != 1); // ...or renamed
257   }
258 }
259
260 void IdentitiesSettingsPage::displayIdentity(Identity *id, bool dontsave) {
261   if(currentId != 0 && !dontsave && identities.contains(currentId)) {
262     saveToIdentity(identities[currentId]);
263   }
264   if(id) {
265     currentId = id->id();
266     ui.realName->setText(id->realName());
267     ui.nicknameList->clear();
268     ui.nicknameList->addItems(id->nicks());
269     //for(int i = 0; i < ui.nicknameList->count(); i++) {
270     //  ui.nicknameList->item(i)->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEditable|Qt::ItemIsEnabled);
271     //}
272     if(ui.nicknameList->count()) ui.nicknameList->setCurrentRow(0);
273     ui.awayNick->setText(id->awayNick());
274     ui.awayNickEnabled->setChecked(id->awayNickEnabled());
275     ui.awayReason->setText(id->awayReason());
276     ui.awayReasonEnabled->setChecked(id->awayReasonEnabled());
277     ui.returnMessage->setText(id->returnMessage());
278     ui.returnMessageEnabled->setChecked(id->returnMessageEnabled());
279     ui.autoAwayEnabled->setChecked(id->autoAwayEnabled());
280     ui.autoAwayTime->setValue(id->autoAwayTime());
281     ui.autoAwayReason->setText(id->autoAwayReason());
282     ui.autoAwayReasonEnabled->setChecked(id->autoAwayReasonEnabled());
283     ui.autoReturnMessage->setText(id->autoReturnMessage());
284     ui.autoReturnMessageEnabled->setChecked(id->autoReturnMessageEnabled());
285     ui.ident->setText(id->ident());
286     ui.kickReason->setText(id->kickReason());
287     ui.partReason->setText(id->partReason());
288     ui.quitReason->setText(id->quitReason());
289   }
290 }
291
292 void IdentitiesSettingsPage::saveToIdentity(Identity *id) {
293   id->setRealName(ui.realName->text());
294   QStringList nicks;
295   for(int i = 0; i < ui.nicknameList->count(); i++) {
296     nicks << ui.nicknameList->item(i)->text();
297   }
298   id->setNicks(nicks);
299   id->setAwayNick(ui.awayNick->text());
300   id->setAwayNickEnabled(ui.awayNickEnabled->isChecked());
301   id->setAwayReason(ui.awayReason->text());
302   id->setAwayReasonEnabled(ui.awayReasonEnabled->isChecked());
303   id->setReturnMessage(ui.returnMessage->text());
304   id->setReturnMessageEnabled(ui.returnMessageEnabled->isChecked());
305   id->setAutoAwayEnabled(ui.autoAwayEnabled->isChecked());
306   id->setAutoAwayTime(ui.autoAwayTime->value());
307   id->setAutoAwayReason(ui.autoAwayReason->text());
308   id->setAutoAwayReasonEnabled(ui.autoAwayReasonEnabled->isChecked());
309   id->setAutoReturnMessage(ui.autoReturnMessage->text());
310   id->setAutoReturnMessageEnabled(ui.autoReturnMessageEnabled->isChecked());
311   id->setIdent(ui.ident->text());
312   id->setKickReason(ui.kickReason->text());
313   id->setPartReason(ui.partReason->text());
314   id->setQuitReason(ui.quitReason->text());
315 }
316
317 void IdentitiesSettingsPage::on_addIdentity_clicked() {
318   CreateIdentityDlg dlg(ui.identityList->model(), this);
319   if(dlg.exec() == QDialog::Accepted) {
320     // find a free (negative) ID
321     IdentityId id;
322     for(id = 1; id <= identities.count(); id++) {
323       if(!identities.keys().contains(-id.toInt())) break;
324     }
325     id = -id.toInt();
326     Identity *newId = new Identity(id, this);
327     if(dlg.duplicateId() != 0) {
328       // duplicate
329       newId->update(*identities[dlg.duplicateId()]);
330       newId->setId(id);
331     }
332     newId->setIdentityName(dlg.identityName());
333     identities[id] = newId;
334     insertIdentity(newId);
335     ui.identityList->setCurrentIndex(ui.identityList->findData(id.toInt()));
336     widgetHasChanged();
337   }
338 }
339
340 void IdentitiesSettingsPage::on_deleteIdentity_clicked() {
341   Identity *id = identities[currentId];
342   int ret = QMessageBox::question(this, tr("Delete Identity?"),
343                                   tr("Do you really want to delete identity \"%1\"?").arg(id->identityName()),
344                                   QMessageBox::Yes|QMessageBox::No, QMessageBox::No);
345   if(ret != QMessageBox::Yes) return;
346   if(id->id() > 0) deletedIdentities.append(id->id());
347   currentId = 0;
348   removeIdentity(id);
349 }
350
351 void IdentitiesSettingsPage::on_renameIdentity_clicked() {
352   QString oldName = identities[currentId]->identityName();
353   bool ok = false;
354   QString name = QInputDialog::getText(this, tr("Rename Identity"),
355                                        tr("Please enter a new name for the identity \"%1\"!").arg(oldName),
356                                        QLineEdit::Normal, oldName, &ok);
357   if(ok && !name.isEmpty()) {
358     renameIdentity(currentId, name);
359     widgetHasChanged();
360   }
361 }
362
363 void IdentitiesSettingsPage::on_addNick_clicked() {
364   QStringList existing;
365   for(int i = 0; i < ui.nicknameList->count(); i++) existing << ui.nicknameList->item(i)->text();
366   NickEditDlgNew dlg(QString(), existing, this);
367   if(dlg.exec() == QDialog::Accepted) {
368     ui.nicknameList->addItem(dlg.nick());
369     ui.nicknameList->setCurrentRow(ui.nicknameList->count()-1);
370     setWidgetStates();
371     widgetHasChanged();
372   }
373 }
374
375 void IdentitiesSettingsPage::on_deleteNick_clicked() {
376   // no confirmation, since a nickname is really nothing hard to recreate
377   if(ui.nicknameList->selectedItems().count()) {
378     delete ui.nicknameList->selectedItems()[0];
379     ui.nicknameList->setCurrentRow(qMin(ui.nicknameList->currentRow()+1, ui.nicknameList->count()-1));
380     setWidgetStates();
381     widgetHasChanged();
382   }
383 }
384
385 void IdentitiesSettingsPage::on_renameNick_clicked() {
386   if(!ui.nicknameList->selectedItems().count()) return;
387   QString old = ui.nicknameList->selectedItems()[0]->text();
388   QStringList existing;
389   for(int i = 0; i < ui.nicknameList->count(); i++) existing << ui.nicknameList->item(i)->text();
390   NickEditDlgNew dlg(old, existing, this);
391   if(dlg.exec() == QDialog::Accepted) {
392     ui.nicknameList->selectedItems()[0]->setText(dlg.nick());
393   }
394
395 }
396
397 void IdentitiesSettingsPage::on_nickUp_clicked() {
398   if(!ui.nicknameList->selectedItems().count()) return;
399   int row = ui.nicknameList->row(ui.nicknameList->selectedItems()[0]);
400   if(row > 0) {
401     ui.nicknameList->insertItem(row-1, ui.nicknameList->takeItem(row));
402     ui.nicknameList->setCurrentRow(row-1);
403     setWidgetStates();
404     widgetHasChanged();
405   }
406 }
407
408 void IdentitiesSettingsPage::on_nickDown_clicked() {
409   if(!ui.nicknameList->selectedItems().count()) return;
410   int row = ui.nicknameList->row(ui.nicknameList->selectedItems()[0]);
411   if(row < ui.nicknameList->count()-1) {
412     ui.nicknameList->insertItem(row+1, ui.nicknameList->takeItem(row));
413     ui.nicknameList->setCurrentRow(row+1);
414     setWidgetStates();
415     widgetHasChanged();
416   }
417 }
418
419 /*****************************************************************************************/
420
421 CreateIdentityDlg::CreateIdentityDlg(QAbstractItemModel *model, QWidget *parent) : QDialog(parent) {
422   ui.setupUi(this);
423
424   ui.identityList->setModel(model);  // now we use the identity list of the main page... Trolltech <3
425   on_identityName_textChanged("");   // disable ok button :)
426 }
427
428 QString CreateIdentityDlg::identityName() const {
429   return ui.identityName->text();
430 }
431
432 IdentityId CreateIdentityDlg::duplicateId() const {
433   if(!ui.duplicateIdentity->isChecked()) return 0;
434   if(ui.identityList->currentIndex() >= 0) {
435     return ui.identityList->itemData(ui.identityList->currentIndex()).toInt();
436   }
437   return 0;
438 }
439
440 void CreateIdentityDlg::on_identityName_textChanged(const QString &text) {
441   ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(text.count());
442
443 }
444
445 /*********************************************************************************************/
446
447 SaveIdentitiesDlg::SaveIdentitiesDlg(QList<Identity *> tocreate, QList<Identity *> toupdate, QList<IdentityId> toremove, QWidget *parent)
448   : QDialog(parent), toCreate(tocreate), toUpdate(toupdate), toRemove(toremove) {
449   ui.setupUi(this);
450   numevents = toCreate.count() + toUpdate.count() + toRemove.count();
451   rcvevents = 0;
452   if(numevents) {
453     ui.progressBar->setMaximum(numevents);
454     ui.progressBar->setValue(0);
455
456     connect(Client::instance(), SIGNAL(identityCreated(IdentityId)), this, SLOT(clientEvent()));
457     connect(Client::instance(), SIGNAL(identityRemoved(IdentityId)), this, SLOT(clientEvent()));
458
459     foreach(Identity *id, toCreate) {
460       Client::createIdentity(*id);
461     }
462     foreach(Identity *id, toUpdate) {
463       const Identity *cid = Client::identity(id->id());
464       if(!cid) {
465         qWarning() << "Invalid client identity!";
466         numevents--;
467         continue;
468       }
469       connect(cid, SIGNAL(updatedRemotely()), this, SLOT(clientEvent()));
470       Client::updateIdentity(*id);
471     }
472     foreach(IdentityId id, toRemove) {
473       Client::removeIdentity(id);
474     }
475   } else {
476     qWarning() << "Sync dialog called without stuff to change!";
477     accept();
478   }
479 }
480
481 void SaveIdentitiesDlg::clientEvent() {
482   ui.progressBar->setValue(++rcvevents);
483   if(rcvevents >= numevents) accept();
484 }
485
486 /*************************************************************************************************/
487
488 NickEditDlgNew::NickEditDlgNew(const QString &old, const QStringList &exist, QWidget *parent)
489   : QDialog(parent), oldNick(old), existing(exist) {
490   ui.setupUi(this);
491
492   // define a regexp for valid nicknames
493   // TODO: add max nicklength according to ISUPPORT
494   QString letter = "A-Za-z";
495   QString special = "\x5b-\x60\x7b-\x7d";
496   QRegExp rx(QString("[%1%2][%1%2\\d-]*").arg(letter, special));
497   ui.nickEdit->setValidator(new QRegExpValidator(rx, ui.nickEdit));
498   if(old.isEmpty()) {
499     // new nick
500     setWindowTitle(tr("Add Nickname"));
501     on_nickEdit_textChanged(""); // disable ok button
502   } else ui.nickEdit->setText(old);
503 }
504
505 QString NickEditDlgNew::nick() const {
506   return ui.nickEdit->text();
507
508 }
509
510 void NickEditDlgNew::on_nickEdit_textChanged(const QString &text) {
511   ui.buttonBox->button(QDialogButtonBox::Ok)->setDisabled(text.isEmpty() || existing.contains(text));
512 }
513
514
515