File indexing completed on 2024-04-14 14:20:19
0001 /* This file is part of the KDE libraries 0002 Copyright (C) 2000 David Faure <faure@kde.org>, Alexander Neundorf <neundorf@kde.org> 0003 2000, 2002 Carsten Pfeiffer <pfeiffer@kde.org> 0004 0005 This library is free software; you can redistribute it and/or 0006 modify it under the terms of the GNU Library General Public 0007 License as published by the Free Software Foundation; either 0008 version 2 of the License, or (at your option) any later version. 0009 0010 This library is distributed in the hope that it will be useful, 0011 but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 Library General Public License for more details. 0014 0015 You should have received a copy of the GNU Library General Public License 0016 along with this library; see the file COPYING.LIB. If not, write to 0017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0018 Boston, MA 02110-1301, USA. 0019 */ 0020 0021 #include "keditlistbox.h" 0022 0023 #include <QStringList> 0024 #include <QKeyEvent> 0025 #include <QLabel> 0026 #include <QLayout> 0027 #include <QListView> 0028 #include <QPushButton> 0029 0030 #include <kcombobox.h> 0031 #include <kdebug.h> 0032 #include <klineedit.h> 0033 #include <klocalizedstring.h> 0034 #include <knotification.h> 0035 0036 #include <assert.h> 0037 0038 class KEditListBoxPrivate 0039 { 0040 public: 0041 KEditListBoxPrivate(KEditListBox *parent) 0042 : lineEdit(nullptr), 0043 editingWidget(nullptr), 0044 q(parent) 0045 { 0046 } 0047 QListView *listView; 0048 QPushButton *servUpButton, *servDownButton; 0049 QPushButton *servNewButton, *servRemoveButton; 0050 KLineEdit *lineEdit; 0051 QWidget *editingWidget; 0052 QVBoxLayout *mainLayout; 0053 QVBoxLayout *btnsLayout; 0054 QStringListModel *model; 0055 0056 bool checkAtEntering; 0057 KEditListBox::Buttons buttons; 0058 0059 void init(bool check = false, KEditListBox::Buttons buttons = KEditListBox::All, 0060 QWidget *representationWidget = nullptr); 0061 void setEditor(KLineEdit *lineEdit, QWidget *representationWidget = nullptr); 0062 void updateButtonState(); 0063 QModelIndex selectedIndex(); 0064 0065 private: 0066 KEditListBox *q; 0067 }; 0068 0069 void KEditListBoxPrivate::init(bool check, KEditListBox::Buttons newButtons, 0070 QWidget *representationWidget) 0071 { 0072 checkAtEntering = check; 0073 0074 servNewButton = servRemoveButton = servUpButton = servDownButton = nullptr; 0075 q->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, 0076 QSizePolicy::Preferred)); 0077 0078 mainLayout = new QVBoxLayout(q); 0079 0080 QHBoxLayout *subLayout = new QHBoxLayout; 0081 btnsLayout = new QVBoxLayout; 0082 btnsLayout->addStretch(); 0083 0084 model = new QStringListModel(); 0085 listView = new QListView(q); 0086 listView->setModel(model); 0087 0088 subLayout->addWidget(listView); 0089 subLayout->addLayout(btnsLayout); 0090 0091 mainLayout->insertLayout(1, subLayout); 0092 0093 setEditor(lineEdit, representationWidget); 0094 0095 buttons = KEditListBox::Buttons(); 0096 q->setButtons(newButtons); 0097 0098 q->connect(listView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), 0099 SLOT(slotSelectionChanged(QItemSelection,QItemSelection))); 0100 } 0101 0102 void KEditListBoxPrivate::setEditor(KLineEdit *newLineEdit, QWidget *representationWidget) 0103 { 0104 if (editingWidget != lineEdit && 0105 editingWidget != representationWidget) { 0106 delete editingWidget; 0107 } 0108 if (lineEdit != newLineEdit) { 0109 delete lineEdit; 0110 } 0111 lineEdit = newLineEdit ? newLineEdit : new KLineEdit(q); 0112 editingWidget = representationWidget ? 0113 representationWidget : lineEdit; 0114 0115 if (representationWidget) { 0116 representationWidget->setParent(q); 0117 } 0118 0119 mainLayout->insertWidget(0, editingWidget); 0120 0121 lineEdit->setTrapReturnKey(true); 0122 lineEdit->installEventFilter(q); 0123 0124 q->connect(lineEdit, SIGNAL(textChanged(QString)), SLOT(typedSomething(QString))); 0125 q->connect(lineEdit, SIGNAL(returnPressed()), SLOT(addItem())); 0126 0127 // maybe supplied lineedit has some text already 0128 q->typedSomething(lineEdit->text()); 0129 0130 // fix tab ordering 0131 q->setTabOrder(editingWidget, listView); 0132 QWidget *w = listView; 0133 if (servNewButton) { 0134 q->setTabOrder(w, servNewButton); 0135 w = servNewButton; 0136 } 0137 if (servRemoveButton) { 0138 q->setTabOrder(w, servRemoveButton); 0139 w = servRemoveButton; 0140 } 0141 if (servUpButton) { 0142 q->setTabOrder(w, servUpButton); 0143 w = servUpButton; 0144 } 0145 if (servDownButton) { 0146 q->setTabOrder(w, servDownButton); 0147 w = servDownButton; 0148 } 0149 } 0150 0151 void KEditListBoxPrivate::updateButtonState() 0152 { 0153 QModelIndex index = selectedIndex(); 0154 if (servUpButton) { 0155 servUpButton->setEnabled(index.isValid()); 0156 } 0157 if (servDownButton) { 0158 servDownButton->setEnabled(index.isValid()); 0159 } 0160 if (servRemoveButton) { 0161 servRemoveButton->setEnabled(index.isValid()); 0162 } 0163 } 0164 0165 QModelIndex KEditListBoxPrivate::selectedIndex() 0166 { 0167 QItemSelectionModel *selection = listView->selectionModel(); 0168 const QModelIndexList selectedIndexes = selection->selectedIndexes(); 0169 if (!selectedIndexes.isEmpty() && selectedIndexes[0].isValid()) { 0170 return selectedIndexes[0]; 0171 } else { 0172 return QModelIndex(); 0173 } 0174 } 0175 0176 class Q_DECL_HIDDEN KEditListBox::CustomEditorPrivate 0177 { 0178 public: 0179 CustomEditorPrivate(KEditListBox::CustomEditor *q): 0180 q(q), 0181 representationWidget(nullptr), 0182 lineEdit(nullptr) {} 0183 0184 KEditListBox::CustomEditor *q; 0185 QWidget *representationWidget; 0186 KLineEdit *lineEdit; 0187 }; 0188 0189 KEditListBox::CustomEditor::CustomEditor() 0190 : d(new CustomEditorPrivate(this)) 0191 { 0192 } 0193 0194 KEditListBox::CustomEditor::CustomEditor(QWidget *repWidget, KLineEdit *edit) 0195 : d(new CustomEditorPrivate(this)) 0196 { 0197 d->representationWidget = repWidget; 0198 d->lineEdit = edit; 0199 } 0200 0201 KEditListBox::CustomEditor::CustomEditor(KComboBox *combo) 0202 : d(new CustomEditorPrivate(this)) 0203 { 0204 d->representationWidget = combo; 0205 d->lineEdit = qobject_cast<KLineEdit *>(combo->lineEdit()); 0206 Q_ASSERT(d->lineEdit); 0207 } 0208 0209 KEditListBox::CustomEditor::~CustomEditor() 0210 { 0211 delete d; 0212 } 0213 0214 void KEditListBox::CustomEditor::setRepresentationWidget(QWidget *repWidget) 0215 { 0216 d->representationWidget = repWidget; 0217 } 0218 0219 void KEditListBox::CustomEditor::setLineEdit(KLineEdit *edit) 0220 { 0221 d->lineEdit = edit; 0222 } 0223 0224 QWidget *KEditListBox::CustomEditor::representationWidget() const 0225 { 0226 return d->representationWidget; 0227 } 0228 0229 KLineEdit *KEditListBox::CustomEditor::lineEdit() const 0230 { 0231 return d->lineEdit; 0232 } 0233 0234 KEditListBox::KEditListBox(QWidget *parent) 0235 : QGroupBox(parent), d(new KEditListBoxPrivate(this)) 0236 { 0237 d->init(); 0238 } 0239 0240 KEditListBox::KEditListBox(const QString &title, QWidget *parent) 0241 : QGroupBox(title, parent), d(new KEditListBoxPrivate(this)) 0242 { 0243 d->init(); 0244 } 0245 0246 KEditListBox::KEditListBox(QWidget *parent, const char *name, 0247 bool checkAtEntering, Buttons buttons) 0248 : QGroupBox(parent), d(new KEditListBoxPrivate(this)) 0249 { 0250 setObjectName(name); 0251 d->init(checkAtEntering, buttons); 0252 } 0253 0254 KEditListBox::KEditListBox(const QString &title, QWidget *parent, 0255 const char *name, bool checkAtEntering, Buttons buttons) 0256 : QGroupBox(title, parent), d(new KEditListBoxPrivate(this)) 0257 { 0258 setObjectName(name); 0259 d->init(checkAtEntering, buttons); 0260 } 0261 0262 KEditListBox::KEditListBox(const QString &title, const CustomEditor &custom, 0263 QWidget *parent, const char *name, 0264 bool checkAtEntering, Buttons buttons) 0265 : QGroupBox(title, parent), d(new KEditListBoxPrivate(this)) 0266 { 0267 setObjectName(name); 0268 d->lineEdit = custom.lineEdit(); 0269 d->init(checkAtEntering, buttons, custom.representationWidget()); 0270 } 0271 0272 KEditListBox::~KEditListBox() 0273 { 0274 delete d; 0275 } 0276 0277 void KEditListBox::setCustomEditor(const CustomEditor &editor) 0278 { 0279 d->setEditor(editor.lineEdit(), editor.representationWidget()); 0280 } 0281 0282 QListView *KEditListBox::listView() const 0283 { 0284 return d->listView; 0285 } 0286 0287 KLineEdit *KEditListBox::lineEdit() const 0288 { 0289 return d->lineEdit; 0290 } 0291 0292 QPushButton *KEditListBox::addButton() const 0293 { 0294 return d->servNewButton; 0295 } 0296 0297 QPushButton *KEditListBox::removeButton() const 0298 { 0299 return d->servRemoveButton; 0300 } 0301 0302 QPushButton *KEditListBox::upButton() const 0303 { 0304 return d->servUpButton; 0305 } 0306 0307 QPushButton *KEditListBox::downButton() const 0308 { 0309 return d->servDownButton; 0310 } 0311 0312 int KEditListBox::count() const 0313 { 0314 return int(d->model->rowCount()); 0315 } 0316 0317 void KEditListBox::setButtons(Buttons buttons) 0318 { 0319 if (d->buttons == buttons) { 0320 return; 0321 } 0322 0323 if ((buttons & Add) && !d->servNewButton) { 0324 d->servNewButton = new QPushButton(QIcon::fromTheme("list-add"), i18n("&Add"), this); 0325 d->servNewButton->setEnabled(false); 0326 d->servNewButton->show(); 0327 connect(d->servNewButton, SIGNAL(clicked()), SLOT(addItem())); 0328 0329 d->btnsLayout->insertWidget(0, d->servNewButton); 0330 } else if ((buttons & Add) == 0 && d->servNewButton) { 0331 delete d->servNewButton; 0332 d->servNewButton = nullptr; 0333 } 0334 0335 if ((buttons & Remove) && !d->servRemoveButton) { 0336 d->servRemoveButton = new QPushButton(QIcon::fromTheme("list-remove"), i18n("&Remove"), this); 0337 d->servRemoveButton->setEnabled(false); 0338 d->servRemoveButton->show(); 0339 connect(d->servRemoveButton, SIGNAL(clicked()), SLOT(removeItem())); 0340 0341 d->btnsLayout->insertWidget(1, d->servRemoveButton); 0342 } else if ((buttons & Remove) == 0 && d->servRemoveButton) { 0343 delete d->servRemoveButton; 0344 d->servRemoveButton = nullptr; 0345 } 0346 0347 if ((buttons & UpDown) && !d->servUpButton) { 0348 d->servUpButton = new QPushButton(QIcon::fromTheme("arrow-up"), i18n("Move &Up"), this); 0349 d->servUpButton->setEnabled(false); 0350 d->servUpButton->show(); 0351 connect(d->servUpButton, SIGNAL(clicked()), SLOT(moveItemUp())); 0352 0353 d->servDownButton = new QPushButton(QIcon::fromTheme("arrow-down"), i18n("Move &Down"), this); 0354 d->servDownButton->setEnabled(false); 0355 d->servDownButton->show(); 0356 connect(d->servDownButton, SIGNAL(clicked()), SLOT(moveItemDown())); 0357 0358 d->btnsLayout->insertWidget(2, d->servUpButton); 0359 d->btnsLayout->insertWidget(3, d->servDownButton); 0360 } else if ((buttons & UpDown) == 0 && d->servUpButton) { 0361 delete d->servUpButton; d->servUpButton = nullptr; 0362 delete d->servDownButton; d->servDownButton = nullptr; 0363 } 0364 0365 d->buttons = buttons; 0366 } 0367 0368 void KEditListBox::setCheckAtEntering(bool check) 0369 { 0370 d->checkAtEntering = check; 0371 } 0372 0373 bool KEditListBox::checkAtEntering() 0374 { 0375 return d->checkAtEntering; 0376 } 0377 0378 void KEditListBox::typedSomething(const QString &text) 0379 { 0380 if (currentItem() >= 0) { 0381 if (currentText() != d->lineEdit->text()) { 0382 // IMHO changeItem() shouldn't do anything with the value 0383 // of currentItem() ... like changing it or emitting signals ... 0384 // but TT disagree with me on this one (it's been that way since ages ... grrr) 0385 bool block = d->listView->signalsBlocked(); 0386 d->listView->blockSignals(true); 0387 QModelIndex currentIndex = d->selectedIndex(); 0388 if (currentIndex.isValid()) { 0389 d->model->setData(currentIndex, text); 0390 } 0391 d->listView->blockSignals(block); 0392 emit changed(); 0393 } 0394 } 0395 0396 if (!d->servNewButton) { 0397 return; 0398 } 0399 0400 if (!d->lineEdit->hasAcceptableInput()) { 0401 d->servNewButton->setEnabled(false); 0402 return; 0403 } 0404 0405 if (!d->checkAtEntering) { 0406 d->servNewButton->setEnabled(!text.isEmpty()); 0407 } else { 0408 if (text.isEmpty()) { 0409 d->servNewButton->setEnabled(false); 0410 } else { 0411 QStringList list = d->model->stringList(); 0412 bool enable = !list.contains(text, Qt::CaseSensitive); 0413 d->servNewButton->setEnabled(enable); 0414 } 0415 } 0416 } 0417 0418 void KEditListBox::moveItemUp() 0419 { 0420 if (!d->listView->isEnabled()) { 0421 KNotification::beep(); 0422 return; 0423 } 0424 0425 QModelIndex index = d->selectedIndex(); 0426 if (index.isValid()) { 0427 if (index.row() == 0) { 0428 KNotification::beep(); 0429 return; 0430 } 0431 0432 QModelIndex aboveIndex = d->model->index(index.row() - 1, index.column()); 0433 0434 QString tmp = d->model->data(aboveIndex, Qt::DisplayRole).toString(); 0435 d->model->setData(aboveIndex, d->model->data(index, Qt::DisplayRole)); 0436 d->model->setData(index, tmp); 0437 0438 d->listView->selectionModel()->select(index, QItemSelectionModel::Deselect); 0439 d->listView->selectionModel()->select(aboveIndex, QItemSelectionModel::Select); 0440 } 0441 0442 emit changed(); 0443 } 0444 0445 void KEditListBox::moveItemDown() 0446 { 0447 if (!d->listView->isEnabled()) { 0448 KNotification::beep(); 0449 return; 0450 } 0451 0452 QModelIndex index = d->selectedIndex(); 0453 if (index.isValid()) { 0454 if (index.row() == d->model->rowCount() - 1) { 0455 KNotification::beep(); 0456 return; 0457 } 0458 0459 QModelIndex belowIndex = d->model->index(index.row() + 1, index.column()); 0460 0461 QString tmp = d->model->data(belowIndex, Qt::DisplayRole).toString(); 0462 d->model->setData(belowIndex, d->model->data(index, Qt::DisplayRole)); 0463 d->model->setData(index, tmp); 0464 0465 d->listView->selectionModel()->select(index, QItemSelectionModel::Deselect); 0466 d->listView->selectionModel()->select(belowIndex, QItemSelectionModel::Select); 0467 } 0468 0469 emit changed(); 0470 } 0471 0472 void KEditListBox::addItem() 0473 { 0474 // when checkAtEntering is true, the add-button is disabled, but this 0475 // slot can still be called through Key_Return/Key_Enter. So we guard 0476 // against this. 0477 if (!d->servNewButton || !d->servNewButton->isEnabled()) { 0478 return; 0479 } 0480 0481 QModelIndex currentIndex = d->selectedIndex(); 0482 0483 const QString ¤tTextLE = d->lineEdit->text(); 0484 bool alreadyInList(false); 0485 //if we didn't check for dupes at the inserting we have to do it now 0486 if (!d->checkAtEntering) { 0487 // first check current item instead of dumb iterating the entire list 0488 if (currentIndex.isValid()) { 0489 if (d->model->data(currentIndex, Qt::DisplayRole).toString() == currentTextLE) { 0490 alreadyInList = true; 0491 } 0492 } else { 0493 alreadyInList = d->model->stringList().contains(currentTextLE, Qt::CaseSensitive); 0494 } 0495 } 0496 if (d->servNewButton) { 0497 d->servNewButton->setEnabled(false); 0498 } 0499 0500 bool block = d->lineEdit->signalsBlocked(); 0501 d->lineEdit->blockSignals(true); 0502 d->lineEdit->clear(); 0503 d->lineEdit->blockSignals(block); 0504 0505 d->listView->selectionModel()->setCurrentIndex(currentIndex, QItemSelectionModel::Deselect); 0506 0507 if (!alreadyInList) { 0508 block = d->listView->signalsBlocked(); 0509 0510 if (currentIndex.isValid()) { 0511 d->model->setData(currentIndex, currentTextLE); 0512 } else { 0513 QStringList lst; 0514 lst << currentTextLE; 0515 lst << d->model->stringList(); 0516 d->model->setStringList(lst); 0517 } 0518 emit changed(); 0519 emit added(currentTextLE); // TODO: pass the index too 0520 } 0521 0522 d->updateButtonState(); 0523 } 0524 0525 int KEditListBox::currentItem() const 0526 { 0527 QModelIndex selectedIndex = d->selectedIndex(); 0528 if (selectedIndex.isValid()) { 0529 return selectedIndex.row(); 0530 } else { 0531 return -1; 0532 } 0533 } 0534 0535 void KEditListBox::removeItem() 0536 { 0537 QModelIndex currentIndex = d->selectedIndex(); 0538 if (!currentIndex.isValid()) { 0539 return; 0540 } 0541 0542 if (currentIndex.row() >= 0) { 0543 QString removedText = d->model->data(currentIndex, Qt::DisplayRole).toString(); 0544 0545 d->model->removeRows(currentIndex.row(), 1); 0546 0547 d->listView->selectionModel()->clear(); 0548 0549 emit changed(); 0550 0551 emit removed(removedText); 0552 } 0553 0554 d->updateButtonState(); 0555 } 0556 0557 void KEditListBox::enableMoveButtons(const QModelIndex &newIndex, const QModelIndex &) 0558 { 0559 int index = newIndex.row(); 0560 0561 // Update the lineEdit when we select a different line. 0562 if (currentText() != d->lineEdit->text()) { 0563 d->lineEdit->setText(currentText()); 0564 } 0565 0566 bool moveEnabled = d->servUpButton && d->servDownButton; 0567 0568 if (moveEnabled) { 0569 if (d->model->rowCount() <= 1) { 0570 d->servUpButton->setEnabled(false); 0571 d->servDownButton->setEnabled(false); 0572 } else if (index == (d->model->rowCount() - 1)) { 0573 d->servUpButton->setEnabled(true); 0574 d->servDownButton->setEnabled(false); 0575 } else if (index == 0) { 0576 d->servUpButton->setEnabled(false); 0577 d->servDownButton->setEnabled(true); 0578 } else { 0579 d->servUpButton->setEnabled(true); 0580 d->servDownButton->setEnabled(true); 0581 } 0582 } 0583 0584 if (d->servRemoveButton) { 0585 d->servRemoveButton->setEnabled(true); 0586 } 0587 } 0588 0589 void KEditListBox::clear() 0590 { 0591 d->lineEdit->clear(); 0592 d->model->setStringList(QStringList()); 0593 emit changed(); 0594 } 0595 0596 void KEditListBox::insertStringList(const QStringList &list, int index) 0597 { 0598 QStringList content = d->model->stringList(); 0599 if (index < 0) { 0600 content += list; 0601 } else 0602 for (int i = 0, j = index; i < list.count(); ++i, ++j) { 0603 content.insert(j, list[ i ]); 0604 } 0605 0606 d->model->setStringList(content); 0607 } 0608 0609 void KEditListBox::insertItem(const QString &text, int index) 0610 { 0611 QStringList list = d->model->stringList(); 0612 0613 if (index < 0) { 0614 list.append(text); 0615 } else { 0616 list.insert(index, text); 0617 } 0618 0619 d->model->setStringList(list); 0620 } 0621 0622 QString KEditListBox::text(int index) const 0623 { 0624 const QStringList list = d->model->stringList(); 0625 0626 return list[ index ]; 0627 } 0628 0629 QString KEditListBox::currentText() const 0630 { 0631 QModelIndex index = d->selectedIndex(); 0632 if (!index.isValid()) { 0633 return QString(); 0634 } else { 0635 return text(index.row()); 0636 } 0637 } 0638 0639 QStringList KEditListBox::items() const 0640 { 0641 return d->model->stringList(); 0642 } 0643 0644 void KEditListBox::setItems(const QStringList &items) 0645 { 0646 d->model->setStringList(items); 0647 } 0648 0649 KEditListBox::Buttons KEditListBox::buttons() const 0650 { 0651 return d->buttons; 0652 } 0653 0654 void KEditListBox::slotSelectionChanged(const QItemSelection &, const QItemSelection &) 0655 { 0656 d->updateButtonState(); 0657 QModelIndex index = d->selectedIndex(); 0658 enableMoveButtons(index, QModelIndex()); 0659 if (index.isValid()) { 0660 d->lineEdit->setFocus(Qt::OtherFocusReason); 0661 } 0662 } 0663 0664 bool KEditListBox::eventFilter(QObject *o, QEvent *e) 0665 { 0666 if (o == d->lineEdit && e->type() == QEvent::KeyPress) { 0667 QKeyEvent *keyEvent = (QKeyEvent *)e; 0668 if (keyEvent->key() == Qt::Key_Down || 0669 keyEvent->key() == Qt::Key_Up) { 0670 return ((QObject *)d->listView)->event(e); 0671 } 0672 } 0673 0674 return false; 0675 } 0676 0677 #include "moc_keditlistbox.cpp"