KWidgetsAddons

keditlistwidget.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
4 SPDX-FileCopyrightText: 2000 Alexander Neundorf <neundorf@kde.org>
5 SPDX-FileCopyrightText: 2000, 2002 Carsten Pfeiffer <pfeiffer@kde.org>
6 SPDX-FileCopyrightText: 2010 Sebastian Trueg <trueg@kde.org>
7
8 SPDX-License-Identifier: LGPL-2.0-or-later
9*/
10
11#include "keditlistwidget.h"
12
13#include <QApplication>
14#include <QComboBox>
15#include <QHBoxLayout>
16#include <QKeyEvent>
17#include <QLineEdit>
18#include <QListView>
19#include <QPushButton>
20#include <QStringList>
21#include <QStringListModel>
22#include <QVBoxLayout>
23
24class KEditListWidgetPrivate
25{
26public:
27 KEditListWidgetPrivate(KEditListWidget *parent)
28 : q(parent)
29 {
30 }
31 QListView *listView = nullptr;
32 QPushButton *servUpButton = nullptr;
33 QPushButton *servDownButton = nullptr;
34 QPushButton *servNewButton = nullptr;
35 QPushButton *servRemoveButton = nullptr;
36 QLineEdit *lineEdit = nullptr;
37 QWidget *editingWidget = nullptr;
38 QVBoxLayout *mainLayout = nullptr;
39 QVBoxLayout *btnsLayout = nullptr;
40 QStringListModel *model = nullptr;
41
42 bool checkAtEntering;
44
45 void init(bool check = false, KEditListWidget::Buttons buttons = KEditListWidget::All, QWidget *representationWidget = nullptr);
46 void setEditor(QLineEdit *lineEdit, QWidget *representationWidget = nullptr);
47 void updateButtonState();
48 QModelIndex selectedIndex();
49
50private:
51 KEditListWidget *const q;
52};
53
54void KEditListWidgetPrivate::init(bool check, KEditListWidget::Buttons newButtons, QWidget *representationWidget)
55{
56 checkAtEntering = check;
57
58 servNewButton = servRemoveButton = servUpButton = servDownButton = nullptr;
60
61 mainLayout = new QVBoxLayout(q);
62 mainLayout->setContentsMargins(0, 0, 0, 0);
63
64 QHBoxLayout *subLayout = new QHBoxLayout;
65 btnsLayout = new QVBoxLayout;
66 btnsLayout->addStretch();
67
68 model = new QStringListModel(q);
69 listView = new QListView(q);
70 listView->setModel(model);
71
72 subLayout->addWidget(listView);
73 subLayout->addLayout(btnsLayout);
74
75 mainLayout->addLayout(subLayout);
76
77 setEditor(lineEdit, representationWidget);
78
79 buttons = KEditListWidget::Buttons();
80 q->setButtons(newButtons);
81
82 q->connect(listView->selectionModel(), &QItemSelectionModel::selectionChanged, q, &KEditListWidget::slotSelectionChanged);
83}
84
85void KEditListWidgetPrivate::setEditor(QLineEdit *newLineEdit, QWidget *representationWidget)
86{
87 if (editingWidget != lineEdit && editingWidget != representationWidget) {
88 delete editingWidget;
89 }
90 if (lineEdit != newLineEdit) {
91 delete lineEdit;
92 }
93 lineEdit = newLineEdit ? newLineEdit : new QLineEdit(q);
94 editingWidget = representationWidget ? representationWidget : lineEdit;
95
96 if (representationWidget) {
97 representationWidget->setParent(q);
98 }
99
100 mainLayout->insertWidget(0, editingWidget); // before subLayout
101
102 lineEdit->installEventFilter(q);
103
104 q->connect(lineEdit, &QLineEdit::textChanged, q, &KEditListWidget::typedSomething);
105 q->connect(lineEdit, &QLineEdit::returnPressed, q, &KEditListWidget::addItem);
106
107 // maybe supplied lineedit has some text already
108 q->typedSomething(lineEdit->text());
109
110 // fix tab ordering
111 q->setTabOrder(editingWidget, listView);
112 QWidget *w = listView;
113 if (servNewButton) {
114 q->setTabOrder(w, servNewButton);
115 w = servNewButton;
116 }
117 if (servRemoveButton) {
118 q->setTabOrder(w, servRemoveButton);
119 w = servRemoveButton;
120 }
121 if (servUpButton) {
122 q->setTabOrder(w, servUpButton);
123 w = servUpButton;
124 }
125 if (servDownButton) {
126 q->setTabOrder(w, servDownButton);
127 w = servDownButton;
128 }
129}
130
131void KEditListWidgetPrivate::updateButtonState()
132{
133 const bool hasSelectedItem = selectedIndex().isValid();
134
135 // TODO: merge with enableMoveButtons()
136 QPushButton *const buttons[3] = {servUpButton, servDownButton, servRemoveButton};
137
138 for (QPushButton *button : buttons) {
139 if (button) {
140 // keep focus in widget
141 if (!hasSelectedItem && button->hasFocus()) {
143 }
144 button->setEnabled(hasSelectedItem);
145 }
146 }
147}
148
149QModelIndex KEditListWidgetPrivate::selectedIndex()
150{
151 QItemSelectionModel *selection = listView->selectionModel();
152 const QModelIndexList selectedIndexes = selection->selectedIndexes();
153 if (!selectedIndexes.isEmpty() && selectedIndexes[0].isValid()) {
154 return selectedIndexes[0];
155 } else {
156 return QModelIndex();
157 }
158}
159
160class KEditListWidgetCustomEditorPrivate
161{
162public:
163 KEditListWidgetCustomEditorPrivate(KEditListWidget::CustomEditor *qq)
164 : q(qq)
165 , representationWidget(nullptr)
166 , lineEdit(nullptr)
167 {
168 }
169
171 QWidget *representationWidget;
172 QLineEdit *lineEdit;
173};
174
175KEditListWidget::CustomEditor::CustomEditor()
176 : d(new KEditListWidgetCustomEditorPrivate(this))
177{
178}
179
180KEditListWidget::CustomEditor::CustomEditor(QWidget *repWidget, QLineEdit *edit)
181 : d(new KEditListWidgetCustomEditorPrivate(this))
182{
183 d->representationWidget = repWidget;
184 d->lineEdit = edit;
185}
186
187KEditListWidget::CustomEditor::CustomEditor(QComboBox *combo)
188 : d(new KEditListWidgetCustomEditorPrivate(this))
189{
190 d->representationWidget = combo;
191 d->lineEdit = qobject_cast<QLineEdit *>(combo->lineEdit());
192 Q_ASSERT(d->lineEdit);
193}
194
195KEditListWidget::CustomEditor::~CustomEditor() = default;
196
197void KEditListWidget::CustomEditor::setRepresentationWidget(QWidget *repWidget)
198{
199 d->representationWidget = repWidget;
200}
201
202void KEditListWidget::CustomEditor::setLineEdit(QLineEdit *edit)
203{
204 d->lineEdit = edit;
205}
206
207QWidget *KEditListWidget::CustomEditor::representationWidget() const
208{
209 return d->representationWidget;
210}
211
212QLineEdit *KEditListWidget::CustomEditor::lineEdit() const
213{
214 return d->lineEdit;
215}
216
218 : QWidget(parent)
219 , d(new KEditListWidgetPrivate(this))
220{
221 d->init();
222}
223
224KEditListWidget::KEditListWidget(const CustomEditor &custom, QWidget *parent, bool checkAtEntering, Buttons buttons)
225 : QWidget(parent)
226 , d(new KEditListWidgetPrivate(this))
227{
228 d->lineEdit = custom.lineEdit();
229 d->init(checkAtEntering, buttons, custom.representationWidget());
230}
231
232KEditListWidget::~KEditListWidget() = default;
233
235{
236 d->setEditor(editor.lineEdit(), editor.representationWidget());
237}
238
240{
241 return d->listView;
242}
243
245{
246 return d->lineEdit;
247}
248
250{
251 return d->servNewButton;
252}
253
255{
256 return d->servRemoveButton;
257}
258
260{
261 return d->servUpButton;
262}
263
265{
266 return d->servDownButton;
267}
268
270{
271 return int(d->model->rowCount());
272}
273
275{
276 if (d->buttons == buttons) {
277 return;
278 }
279
280 if ((buttons & Add) && !d->servNewButton) {
281 d->servNewButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add")), tr("&Add", "@action:button"), this);
282 d->servNewButton->setEnabled(false);
283 d->servNewButton->show();
284 connect(d->servNewButton, &QAbstractButton::clicked, this, &KEditListWidget::addItem);
285
286 d->btnsLayout->insertWidget(0, d->servNewButton);
287 } else if ((buttons & Add) == 0 && d->servNewButton) {
288 delete d->servNewButton;
289 d->servNewButton = nullptr;
290 }
291
292 if ((buttons & Remove) && !d->servRemoveButton) {
293 d->servRemoveButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-remove")), tr("&Remove", "@action:button"), this);
294 d->servRemoveButton->setEnabled(false);
295 d->servRemoveButton->show();
296 connect(d->servRemoveButton, &QAbstractButton::clicked, this, &KEditListWidget::removeItem);
297
298 d->btnsLayout->insertWidget(1, d->servRemoveButton);
299 } else if ((buttons & Remove) == 0 && d->servRemoveButton) {
300 delete d->servRemoveButton;
301 d->servRemoveButton = nullptr;
302 }
303
304 if ((buttons & UpDown) && !d->servUpButton) {
305 d->servUpButton = new QPushButton(QIcon::fromTheme(QStringLiteral("arrow-up")), tr("Move &Up", "@action:button"), this);
306 d->servUpButton->setEnabled(false);
307 d->servUpButton->show();
308 connect(d->servUpButton, &QAbstractButton::clicked, this, &KEditListWidget::moveItemUp);
309
310 d->servDownButton = new QPushButton(QIcon::fromTheme(QStringLiteral("arrow-down")), tr("Move &Down", "@action:button"), this);
311 d->servDownButton->setEnabled(false);
312 d->servDownButton->show();
313 connect(d->servDownButton, &QAbstractButton::clicked, this, &KEditListWidget::moveItemDown);
314
315 d->btnsLayout->insertWidget(2, d->servUpButton);
316 d->btnsLayout->insertWidget(3, d->servDownButton);
317 } else if ((buttons & UpDown) == 0 && d->servUpButton) {
318 delete d->servUpButton;
319 d->servUpButton = nullptr;
320 delete d->servDownButton;
321 d->servDownButton = nullptr;
322 }
323
324 d->buttons = buttons;
325}
326
328{
329 d->checkAtEntering = check;
330}
331
332bool KEditListWidget::checkAtEntering()
333{
334 return d->checkAtEntering;
335}
336
337void KEditListWidget::typedSomething(const QString &text)
338{
339 if (currentItem() >= 0) {
340 if (currentText() != d->lineEdit->text()) {
341 // IMHO changeItem() shouldn't do anything with the value
342 // of currentItem() ... like changing it or emitting signals ...
343 // but TT disagree with me on this one (it's been that way since ages ... grrr)
344 bool block = d->listView->signalsBlocked();
345 d->listView->blockSignals(true);
346 QModelIndex currentIndex = d->selectedIndex();
347 if (currentIndex.isValid()) {
348 d->model->setData(currentIndex, text);
349 }
350 d->listView->blockSignals(block);
351 Q_EMIT changed();
352 }
353 }
354
355 if (!d->servNewButton) {
356 return;
357 }
358
359 if (!d->lineEdit->hasAcceptableInput()) {
360 d->servNewButton->setEnabled(false);
361 return;
362 }
363
364 if (!d->checkAtEntering) {
365 d->servNewButton->setEnabled(!text.isEmpty());
366 } else {
367 if (text.isEmpty()) {
368 d->servNewButton->setEnabled(false);
369 } else {
370 QStringList list = d->model->stringList();
371 bool enable = !list.contains(text, Qt::CaseSensitive);
372 d->servNewButton->setEnabled(enable);
373 }
374 }
375}
376
377void KEditListWidget::moveItemUp()
378{
379 if (!d->listView->isEnabled()) {
381 return;
382 }
383
384 QModelIndex index = d->selectedIndex();
385 if (index.isValid()) {
386 if (index.row() == 0) {
388 return;
389 }
390
391 QModelIndex aboveIndex = d->model->index(index.row() - 1, index.column());
392
393 QString tmp = d->model->data(aboveIndex, Qt::DisplayRole).toString();
394 d->model->setData(aboveIndex, d->model->data(index, Qt::DisplayRole));
395 d->model->setData(index, tmp);
396
397 d->listView->selectionModel()->select(index, QItemSelectionModel::Deselect);
398 d->listView->selectionModel()->select(aboveIndex, QItemSelectionModel::Select);
399 }
400
401 Q_EMIT changed();
402}
403
404void KEditListWidget::moveItemDown()
405{
406 if (!d->listView->isEnabled()) {
408 return;
409 }
410
411 QModelIndex index = d->selectedIndex();
412 if (index.isValid()) {
413 if (index.row() == d->model->rowCount() - 1) {
415 return;
416 }
417
418 QModelIndex belowIndex = d->model->index(index.row() + 1, index.column());
419
420 QString tmp = d->model->data(belowIndex, Qt::DisplayRole).toString();
421 d->model->setData(belowIndex, d->model->data(index, Qt::DisplayRole));
422 d->model->setData(index, tmp);
423
424 d->listView->selectionModel()->select(index, QItemSelectionModel::Deselect);
425 d->listView->selectionModel()->select(belowIndex, QItemSelectionModel::Select);
426 }
427
428 Q_EMIT changed();
429}
430
431void KEditListWidget::addItem()
432{
433 // when checkAtEntering is true, the add-button is disabled, but this
434 // slot can still be called through Key_Return/Key_Enter. So we guard
435 // against this.
436 if (!d->servNewButton || !d->servNewButton->isEnabled()) {
437 return;
438 }
439
440 QModelIndex currentIndex = d->selectedIndex();
441
442 const QString &currentTextLE = d->lineEdit->text();
443 bool alreadyInList(false);
444 // if we didn't check for dupes at the inserting we have to do it now
445 if (!d->checkAtEntering) {
446 // first check current item instead of dumb iterating the entire list
447 if (currentIndex.isValid()) {
448 if (d->model->data(currentIndex, Qt::DisplayRole).toString() == currentTextLE) {
449 alreadyInList = true;
450 }
451 } else {
452 alreadyInList = d->model->stringList().contains(currentTextLE, Qt::CaseSensitive);
453 }
454 }
455 if (d->servNewButton) {
456 // prevent losing the focus by it being moved outside of this widget
457 // as well as support the user workflow a little by moving the focus
458 // to the lineedit. chances are that users will add some items consecutively,
459 // so this will save a manual focus change, and it is also consistent
460 // to what happens on the click on the Remove button
461 if (d->servNewButton->hasFocus()) {
462 d->lineEdit->setFocus(Qt::OtherFocusReason);
463 }
464 d->servNewButton->setEnabled(false);
465 }
466
467 bool block = d->lineEdit->signalsBlocked();
468 d->lineEdit->blockSignals(true);
469 d->lineEdit->clear();
470 d->lineEdit->blockSignals(block);
471
472 d->listView->selectionModel()->setCurrentIndex(currentIndex, QItemSelectionModel::Deselect);
473
474 if (!alreadyInList) {
475 block = d->listView->signalsBlocked();
476
477 if (currentIndex.isValid()) {
478 d->model->setData(currentIndex, currentTextLE);
479 } else {
480 QStringList lst;
481 lst << currentTextLE;
482 lst << d->model->stringList();
483 d->model->setStringList(lst);
484 }
485 Q_EMIT changed();
486 Q_EMIT added(currentTextLE); // TODO: pass the index too
487 }
488
489 d->updateButtonState();
490}
491
493{
494 QModelIndex selectedIndex = d->selectedIndex();
495 if (selectedIndex.isValid()) {
496 return selectedIndex.row();
497 } else {
498 return -1;
499 }
500}
501
502void KEditListWidget::removeItem()
503{
504 QModelIndex currentIndex = d->selectedIndex();
505 if (!currentIndex.isValid()) {
506 return;
507 }
508
509 if (currentIndex.row() >= 0) {
510 // prevent losing the focus by it being moved outside of this widget
511 // as well as support the user workflow a little by moving the focus
512 // to the lineedit. chances are that users will add some item next,
513 // so this will save a manual focus change,
514 if (d->servRemoveButton && d->servRemoveButton->hasFocus()) {
515 d->lineEdit->setFocus(Qt::OtherFocusReason);
516 }
517
518 QString removedText = d->model->data(currentIndex, Qt::DisplayRole).toString();
519
520 d->model->removeRows(currentIndex.row(), 1);
521
522 d->listView->selectionModel()->clear();
523
524 Q_EMIT changed();
525
526 Q_EMIT removed(removedText);
527 }
528
529 d->updateButtonState();
530}
531
532void KEditListWidget::enableMoveButtons(const QModelIndex &newIndex, const QModelIndex &)
533{
534 int index = newIndex.row();
535
536 // Update the lineEdit when we select a different line.
537 if (currentText() != d->lineEdit->text()) {
538 d->lineEdit->setText(currentText());
539 }
540
541 bool moveEnabled = d->servUpButton && d->servDownButton;
542
543 if (moveEnabled) {
544 if (d->model->rowCount() <= 1) {
545 d->servUpButton->setEnabled(false);
546 d->servDownButton->setEnabled(false);
547 } else if (index == (d->model->rowCount() - 1)) {
548 d->servUpButton->setEnabled(true);
549 d->servDownButton->setEnabled(false);
550 } else if (index == 0) {
551 d->servUpButton->setEnabled(false);
552 d->servDownButton->setEnabled(true);
553 } else {
554 d->servUpButton->setEnabled(true);
555 d->servDownButton->setEnabled(true);
556 }
557 }
558
559 if (d->servRemoveButton) {
560 d->servRemoveButton->setEnabled(true);
561 }
562}
563
565{
566 d->lineEdit->clear();
567 d->model->setStringList(QStringList());
568 Q_EMIT changed();
569}
570
572{
573 QStringList content = d->model->stringList();
574 if (index < 0) {
575 content += list;
576 } else {
577 for (int i = 0, j = index; i < list.count(); ++i, ++j) {
578 content.insert(j, list[i]);
579 }
580 }
581
582 d->model->setStringList(content);
583}
584
585void KEditListWidget::insertItem(const QString &text, int index)
586{
587 QStringList list = d->model->stringList();
588
589 if (index < 0) {
590 list.append(text);
591 } else {
592 list.insert(index, text);
593 }
594
595 d->model->setStringList(list);
596}
597
599{
600 const QStringList list = d->model->stringList();
601
602 return list[index];
603}
604
606{
607 QModelIndex index = d->selectedIndex();
608 if (!index.isValid()) {
609 return QString();
610 } else {
611 return text(index.row());
612 }
613}
614
615QStringList KEditListWidget::items() const
616{
617 return d->model->stringList();
618}
619
621{
622 d->model->setStringList(items);
623}
624
625KEditListWidget::Buttons KEditListWidget::buttons() const
626{
627 return d->buttons;
628}
629
630void KEditListWidget::slotSelectionChanged(const QItemSelection &, const QItemSelection &)
631{
632 d->updateButtonState();
633 QModelIndex index = d->selectedIndex();
634 enableMoveButtons(index, QModelIndex());
635 if (index.isValid()) {
636 d->lineEdit->setFocus(Qt::OtherFocusReason);
637 }
638}
639
641{
642 if (o == d->lineEdit && e->type() == QEvent::KeyPress) {
643 QKeyEvent *keyEvent = (QKeyEvent *)e;
644 if (keyEvent->key() == Qt::Key_Down || keyEvent->key() == Qt::Key_Up) {
645 return ((QObject *)d->listView)->event(e);
646 } else if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) {
647 return true;
648 }
649 }
650
651 return false;
652}
653
654#include "moc_keditlistwidget.cpp"
An editable listbox.
QLineEdit * lineEdit() const
void insertStringList(const QStringList &list, int index=-1)
Inserts a list of elements from the index element If index is negative, the elements will be appended...
void setCheckAtEntering(bool check)
If check is true, after every character you type in the line edit KEditListWidget will enable or disa...
QPushButton * upButton() const
QFlags< Button > Buttons
Stores a combination of Button values.
void clear()
Clears both the listbox and the line edit.
QPushButton * removeButton() const
bool eventFilter(QObject *o, QEvent *e) override
Reimplemented for internal reasons.
void setItems(const QStringList &items)
Clears the listbox and sets the contents to items.
void setCustomEditor(const CustomEditor &editor)
Allows to use a custom editing widget instead of the standard QLineEdit widget.
QPushButton * downButton() const
int currentItem() const
void removed(const QString &text)
This signal is emitted when the user removes a string from the list, the parameter is the removed str...
void insertItem(const QString &text, int index=-1)
Inserts a text element at the index position If index is negative, the element will be appended.
KEditListWidget(QWidget *parent=nullptr)
Create an editable listbox.
void added(const QString &text)
This signal is emitted when the user adds a new string to the list, the parameter is the added string...
QString text(int index) const
void setButtons(Buttons buttons)
Specifies which buttons are visible.
QString currentText() const
QListView * listView() const
QPushButton * addButton() const
KIOCORE_EXPORT QStringList list(const QString &fileClass)
void clicked(bool checked)
QItemSelectionModel * selectionModel() const const
virtual void setModel(QAbstractItemModel *model)
void addLayout(QLayout *layout, int stretch)
void addStretch(int stretch)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
void insertWidget(int index, QWidget *widget, int stretch, Qt::Alignment alignment)
QLineEdit * lineEdit() const const
Type type() const const
QIcon fromTheme(const QString &name)
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
void setContentsMargins(const QMargins &margins)
void returnPressed()
void textChanged(const QString &text)
void append(QList< T > &&value)
qsizetype count() const const
iterator insert(const_iterator before, parameter_type value)
int column() const const
bool isValid() const const
int row() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void installEventFilter(QObject *filterObj)
QObject * parent() const const
QString tr(const char *sourceText, const char *disambiguation, int n)
bool isEmpty() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
CaseSensitive
OtherFocusReason
DisplayRole
Key_Down
virtual bool event(QEvent *event) override
void setFocus()
void setParent(QWidget *parent)
void setTabOrder(QWidget *first, QWidget *second)
void setSizePolicy(QSizePolicy)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 12 2024 11:52:13 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.