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