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

KDE's Doxygen guidelines are available online.