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