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