KWidgetsAddons

keditlistwidget.cpp
1 /*
2  This file is part of the KDE libraries
3  SPDX-FileCopyrightText: 2000 David Faure <[email protected]>
4  SPDX-FileCopyrightText: 2000 Alexander Neundorf <[email protected]>
5  SPDX-FileCopyrightText: 2000, 2002 Carsten Pfeiffer <[email protected]>
6  SPDX-FileCopyrightText: 2010 Sebastian Trueg <[email protected]>
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 
24 class KEditListWidgetPrivate
25 {
26 public:
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 
50 private:
51  KEditListWidget *const q;
52 };
53 
54 void 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 
85 void 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 
131 void 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()) {
142  lineEdit->setFocus(Qt::OtherFocusReason);
143  }
144  button->setEnabled(hasSelectedItem);
145  }
146  }
147 }
148 
149 QModelIndex 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 
160 class KEditListWidgetCustomEditorPrivate
161 {
162 public:
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 
175 KEditListWidget::CustomEditor::CustomEditor()
176  : d(new KEditListWidgetCustomEditorPrivate(this))
177 {
178 }
179 
180 KEditListWidget::CustomEditor::CustomEditor(QWidget *repWidget, QLineEdit *edit)
181  : d(new KEditListWidgetCustomEditorPrivate(this))
182 {
183  d->representationWidget = repWidget;
184  d->lineEdit = edit;
185 }
186 
187 KEditListWidget::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 
195 KEditListWidget::CustomEditor::~CustomEditor() = default;
196 
197 void KEditListWidget::CustomEditor::setRepresentationWidget(QWidget *repWidget)
198 {
199  d->representationWidget = repWidget;
200 }
201 
202 void KEditListWidget::CustomEditor::setLineEdit(QLineEdit *edit)
203 {
204  d->lineEdit = edit;
205 }
206 
207 QWidget *KEditListWidget::CustomEditor::representationWidget() const
208 {
209  return d->representationWidget;
210 }
211 
212 QLineEdit *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 
224 KEditListWidget::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 
232 KEditListWidget::~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 
332 bool KEditListWidget::checkAtEntering()
333 {
334  return d->checkAtEntering;
335 }
336 
337 void 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 
377 void 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 
404 void 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 
431 void 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 
502 void 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 
532 void 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 
571 void KEditListWidget::insertStringList(const QStringList &list, int index)
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 
585 void 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 
615 QStringList KEditListWidget::items() const
616 {
617  return d->model->stringList();
618 }
619 
621 {
622  d->model->setStringList(items);
623 }
624 
625 KEditListWidget::Buttons KEditListWidget::buttons() const
626 {
627  return d->buttons;
628 }
629 
630 void 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"
QLineEdit * lineEdit() const const
void append(const T &value)
QPushButton * upButton() const
void setParent(QWidget *parent)
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
DisplayRole
CaseSensitive
void setSizePolicy(QSizePolicy)
void removed(const QString &text)
This signal is emitted when the user removes a string from the list, the parameter is the removed str...
Q_EMITQ_EMIT
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...
int count(const T &value) const const
int column() const const
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
void clicked(bool checked)
virtual bool event(QEvent *event) override
QIcon fromTheme(const QString &name)
QListView * listView() const
QPushButton * addButton() const
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
void addStretch(int stretch)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
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.
QPushButton * downButton() const
void setCheckAtEntering(bool check)
If check is true, after every character you type in the line edit KEditListWidget will enable or disa...
OtherFocusReason
QString currentText() const
void setTabOrder(QWidget *first, QWidget *second)
void textChanged(const QString &text)
bool isEmpty() const const
void setButtons(Buttons buttons)
Specifies which buttons are visible.
void clear()
Clears both the listbox and the line edit.
void returnPressed()
QString text(int index) const
void init(KXmlGuiWindow *window, KgDifficulty *difficulty=nullptr)
Key_Down
bool isValid() const const
void insert(int i, const T &value)
QFlags< Button > Buttons
Stores a combination of Button values.
int row() const const
int currentItem() const
QEvent::Type type() const const
bool eventFilter(QObject *o, QEvent *e) override
Reimplemented for internal reasons.
QPushButton * removeButton() const
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.
QLineEdit * lineEdit() const
void addLayout(QLayout *layout, int stretch)
QString tr(const char *sourceText, const char *disambiguation, int n)
void setFocus()
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...
QObject * parent() const const
KEditListWidget(QWidget *parent=nullptr)
Create an editable listbox.
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sat Sep 30 2023 04:01:00 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.