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 &currentTextLE = 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"