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