Pimcommon

simplestringlisteditor.cpp
1 /*
2  simplestringlisteditor.cpp
3 
4  This file is part of KMail, the KDE mail client.
5  SPDX-FileCopyrightText: 2001 Marc Mutz <[email protected]>
6 
7  SPDX-FileCopyrightText: 2013-2023 Laurent Montel <[email protected]>
8 
9  SPDX-License-Identifier: GPL-2.0-or-later
10 */
11 
12 #include "simplestringlisteditor.h"
13 
14 #include "pimcommon_debug.h"
15 #include <KLocalizedString>
16 #include <KMessageBox>
17 #include <QHBoxLayout>
18 #include <QIcon>
19 #include <QInputDialog>
20 #include <QListWidget>
21 #include <QMenu>
22 #include <QPushButton>
23 #include <QVBoxLayout>
24 
25 //********************************************************
26 // SimpleStringListEditor
27 //********************************************************
28 using namespace PimCommon;
29 
30 class PimCommon::SimpleStringListEditorPrivate
31 {
32 public:
33  SimpleStringListEditorPrivate() = default;
34 
35  Q_REQUIRED_RESULT QList<QListWidgetItem *> selectedItems() const
36  {
37  QList<QListWidgetItem *> listWidgetItem;
38  const int numberOfFilters = mListBox->count();
39  for (int i = 0; i < numberOfFilters; ++i) {
40  if (mListBox->item(i)->isSelected()) {
41  listWidgetItem << mListBox->item(i);
42  }
43  }
44  return listWidgetItem;
45  }
46 
47  QListWidget *mListBox = nullptr;
48  QPushButton *mAddButton = nullptr;
49  QPushButton *mRemoveButton = nullptr;
50  QPushButton *mModifyButton = nullptr;
51  QPushButton *mUpButton = nullptr;
52  QPushButton *mDownButton = nullptr;
53  QPushButton *mCustomButton = nullptr;
54  QVBoxLayout *mButtonLayout = nullptr;
55  QString mAddDialogLabel = i18n("New entry:");
56  QString mAddDialogTitle = i18n("New Value");
57  QString mModifyDialogTitle = i18n("New Value");
58  QString mModifyDialogLabel = i18n("New entry:");
59  QString mRemoveDialogLabel = i18n("Do you want to remove selected text?");
60 };
61 
63  ButtonCode buttons,
64  const QString &addLabel,
65  const QString &removeLabel,
66  const QString &modifyLabel,
67  const QString &addDialogLabel)
68  : QWidget(parent)
69  , d(new SimpleStringListEditorPrivate)
70 {
71  setAddDialogLabel(addDialogLabel);
73  auto hlay = new QHBoxLayout(this);
74  hlay->setContentsMargins({});
75 
76  d->mListBox = new QListWidget(this);
77 
78  d->mListBox->setContextMenuPolicy(Qt::CustomContextMenu);
79  connect(d->mListBox, &QListWidget::customContextMenuRequested, this, &SimpleStringListEditor::slotContextMenu);
80 
81  d->mListBox->setSelectionMode(QAbstractItemView::ExtendedSelection);
82  hlay->addWidget(d->mListBox, 1);
83 
84  if (buttons == None) {
85  qCDebug(PIMCOMMON_LOG) << "SimpleStringListBox called with no buttons."
86  "Consider using a plain QListBox instead!";
87  }
88 
89  d->mButtonLayout = new QVBoxLayout(); // inherits spacing
90  hlay->addLayout(d->mButtonLayout);
91 
92  if (buttons & Add) {
93  if (addLabel.isEmpty()) {
94  d->mAddButton = new QPushButton(i18n("&Add..."), this);
95  } else {
96  d->mAddButton = new QPushButton(addLabel, this);
97  }
98  d->mAddButton->setAutoDefault(false);
99  d->mButtonLayout->addWidget(d->mAddButton);
100  connect(d->mAddButton, &QPushButton::clicked, this, &SimpleStringListEditor::slotAdd);
101  }
102 
103  if (buttons & Modify) {
104  if (modifyLabel.isEmpty()) {
105  d->mModifyButton = new QPushButton(i18n("&Modify..."), this);
106  } else {
107  d->mModifyButton = new QPushButton(modifyLabel, this);
108  }
109  d->mModifyButton->setAutoDefault(false);
110  d->mModifyButton->setEnabled(false); // no selection yet
111  d->mButtonLayout->addWidget(d->mModifyButton);
112  connect(d->mModifyButton, &QPushButton::clicked, this, &SimpleStringListEditor::slotModify);
113  connect(d->mListBox, &QListWidget::itemDoubleClicked, this, &SimpleStringListEditor::slotModify);
114  }
115 
116  if (buttons & Remove) {
117  if (removeLabel.isEmpty()) {
118  d->mRemoveButton = new QPushButton(i18n("&Remove"), this);
119  } else {
120  d->mRemoveButton = new QPushButton(removeLabel, this);
121  }
122  d->mRemoveButton->setAutoDefault(false);
123  d->mRemoveButton->setEnabled(false); // no selection yet
124  d->mButtonLayout->addWidget(d->mRemoveButton);
125  connect(d->mRemoveButton, &QPushButton::clicked, this, &SimpleStringListEditor::slotRemove);
126  }
127 
128  if (buttons & Up) {
129  if (!(buttons & Down)) {
130  qCDebug(PIMCOMMON_LOG) << "Are you sure you want to use an Up button"
131  "without a Down button??";
132  }
133  d->mUpButton = new QPushButton(QString(), this);
134  d->mUpButton->setIcon(QIcon::fromTheme(QStringLiteral("go-up")));
135  d->mUpButton->setAutoDefault(false);
136  d->mUpButton->setEnabled(false); // no selection yet
137  d->mButtonLayout->addWidget(d->mUpButton);
138  connect(d->mUpButton, &QPushButton::clicked, this, &SimpleStringListEditor::slotUp);
139  }
140 
141  if (buttons & Down) {
142  if (!(buttons & Up)) {
143  qCDebug(PIMCOMMON_LOG) << "Are you sure you want to use a Down button"
144  "without an Up button??";
145  }
146  d->mDownButton = new QPushButton(QString(), this);
147  d->mDownButton->setIcon(QIcon::fromTheme(QStringLiteral("go-down")));
148  d->mDownButton->setAutoDefault(false);
149  d->mDownButton->setEnabled(false); // no selection yet
150  d->mButtonLayout->addWidget(d->mDownButton);
151  connect(d->mDownButton, &QPushButton::clicked, this, &SimpleStringListEditor::slotDown);
152  }
153 
154  if (buttons & Custom) {
155  d->mCustomButton = new QPushButton(i18n("&Customize..."), this);
156  d->mCustomButton->setAutoDefault(false);
157  d->mCustomButton->setEnabled(false); // no selection yet
158  d->mButtonLayout->addWidget(d->mCustomButton);
159  connect(d->mCustomButton, &QPushButton::clicked, this, &SimpleStringListEditor::slotCustomize);
160  }
161 
162  d->mButtonLayout->addStretch(1); // spacer
163 
164  connect(d->mListBox, &QListWidget::currentItemChanged, this, &SimpleStringListEditor::slotSelectionChanged);
165  connect(d->mListBox, &QListWidget::itemSelectionChanged, this, &SimpleStringListEditor::slotSelectionChanged);
166 }
167 
168 SimpleStringListEditor::~SimpleStringListEditor() = default;
169 
170 void SimpleStringListEditor::setUpDownAutoRepeat(bool b)
171 {
172  if (d->mUpButton) {
173  d->mUpButton->setAutoRepeat(b);
174  }
175  if (d->mDownButton) {
176  d->mDownButton->setAutoRepeat(b);
177  }
178 }
179 
181 {
182  d->mListBox->clear();
183  d->mListBox->addItems(strings);
184 }
185 
187 {
188  d->mListBox->addItems(strings);
189 }
190 
192 {
193  QStringList result;
194  const int numberOfItem(d->mListBox->count());
195  result.reserve(numberOfItem);
196  for (int i = 0; i < numberOfItem; ++i) {
197  result << (d->mListBox->item(i)->text());
198  }
199  return result;
200 }
201 
202 bool SimpleStringListEditor::containsString(const QString &str)
203 {
204  const int numberOfItem(d->mListBox->count());
205  for (int i = 0; i < numberOfItem; ++i) {
206  if (d->mListBox->item(i)->text() == str) {
207  return true;
208  }
209  }
210  return false;
211 }
212 
213 void SimpleStringListEditor::setButtonText(ButtonCode button, const QString &text)
214 {
215  switch (button) {
216  case Add:
217  if (!d->mAddButton) {
218  break;
219  }
220  d->mAddButton->setText(text);
221  return;
222  case Remove:
223  if (!d->mRemoveButton) {
224  break;
225  }
226  d->mRemoveButton->setText(text);
227  return;
228  case Modify:
229  if (!d->mModifyButton) {
230  break;
231  }
232  d->mModifyButton->setText(text);
233  return;
234  case Custom:
235  if (!d->mCustomButton) {
236  break;
237  }
238  d->mCustomButton->setText(text);
239  return;
240  case Up:
241  case Down:
242  qCDebug(PIMCOMMON_LOG) << "SimpleStringListEditor: Cannot change text of"
243  "Up and Down buttons: they don't contains text!";
244  return;
245  default:
246  if (button & All) {
247  qCDebug(PIMCOMMON_LOG) << "No such button!";
248  } else {
249  qCDebug(PIMCOMMON_LOG) << "Can only set text for one button at a time!";
250  }
251  return;
252  }
253 
254  qCDebug(PIMCOMMON_LOG) << "The requested button has not been created!";
255 }
256 
257 void SimpleStringListEditor::addNewEntry()
258 {
259  bool ok = false;
260  const QString newEntry = QInputDialog::getText(this, d->mAddDialogTitle, d->mAddDialogLabel, QLineEdit::Normal, QString(), &ok);
261  if (ok && !newEntry.trimmed().isEmpty()) {
262  insertNewEntry(newEntry);
263  }
264 }
265 
266 void SimpleStringListEditor::insertNewEntry(const QString &entry)
267 {
268  QString newEntry = entry;
269  // let the user verify the string before adding
270  Q_EMIT aboutToAdd(newEntry);
271  if (!newEntry.isEmpty() && !containsString(newEntry)) {
272  d->mListBox->addItem(newEntry);
273  slotSelectionChanged();
274  Q_EMIT changed();
275  }
276 }
277 
278 void SimpleStringListEditor::slotAdd()
279 {
280  addNewEntry();
281 }
282 
283 void SimpleStringListEditor::slotCustomize()
284 {
285  QListWidgetItem *item = d->mListBox->currentItem();
286  if (!item) {
287  return;
288  }
289  const QString newText = customEntry(item->text());
290  if (!newText.isEmpty()) {
291  item->setText(newText);
292  Q_EMIT changed();
293  }
294 }
295 
296 QString SimpleStringListEditor::customEntry(const QString &text)
297 {
298  Q_UNUSED(text)
299  return {};
300 }
301 
302 void SimpleStringListEditor::slotRemove()
303 {
304  const QList<QListWidgetItem *> selectedItems = d->mListBox->selectedItems();
305  if (selectedItems.isEmpty()) {
306  return;
307  }
308  const int answer = KMessageBox::warningTwoActions(this, d->mRemoveDialogLabel, i18n("Remove"), KStandardGuiItem::remove(), KStandardGuiItem::cancel());
309  if (answer == KMessageBox::ButtonCode::PrimaryAction) {
310  for (QListWidgetItem *item : selectedItems) {
311  delete d->mListBox->takeItem(d->mListBox->row(item));
312  }
313  slotSelectionChanged();
314  Q_EMIT changed();
315  }
316 }
317 
318 QString SimpleStringListEditor::modifyEntry(const QString &text)
319 {
320  bool ok = false;
321  QString newText = QInputDialog::getText(this, d->mModifyDialogTitle, d->mModifyDialogLabel, QLineEdit::Normal, text, &ok);
322  Q_EMIT aboutToAdd(newText);
323 
324  if (!ok || newText.trimmed().isEmpty() || newText == text) {
325  return {};
326  }
327 
328  return newText;
329 }
330 
331 void SimpleStringListEditor::slotModify()
332 {
333  QListWidgetItem *item = d->mListBox->currentItem();
334  if (!item) {
335  return;
336  }
337  const QString newText = modifyEntry(item->text());
338  if (!newText.isEmpty()) {
339  item->setText(newText);
340  Q_EMIT changed();
341  }
342 }
343 
344 void SimpleStringListEditor::setRemoveDialogLabel(const QString &removeDialogLabel)
345 {
346  d->mRemoveDialogLabel = removeDialogLabel;
347 }
348 
349 void SimpleStringListEditor::setAddDialogLabel(const QString &addDialogLabel)
350 {
351  d->mAddDialogLabel = addDialogLabel;
352 }
353 
354 void SimpleStringListEditor::setAddDialogTitle(const QString &str)
355 {
356  d->mAddDialogTitle = str;
357 }
358 
359 void SimpleStringListEditor::setModifyDialogTitle(const QString &str)
360 {
361  d->mModifyDialogTitle = str;
362 }
363 
364 void SimpleStringListEditor::setModifyDialogLabel(const QString &str)
365 {
366  d->mModifyDialogLabel = str;
367 }
368 
369 void SimpleStringListEditor::slotUp()
370 {
371  QList<QListWidgetItem *> listWidgetItem = d->selectedItems();
372  if (listWidgetItem.isEmpty()) {
373  return;
374  }
375 
376  const int numberOfItem(listWidgetItem.count());
377  const int currentRow = d->mListBox->currentRow();
378  if ((numberOfItem == 1) && (currentRow == 0)) {
379  qCDebug(PIMCOMMON_LOG) << "Called while the _topmost_ filter is selected, ignoring.";
380  return;
381  }
382  bool wasMoved = false;
383 
384  for (int i = 0; i < numberOfItem; ++i) {
385  const int posItem = d->mListBox->row(listWidgetItem.at(i));
386  if (posItem == i) {
387  continue;
388  }
389  QListWidgetItem *item = d->mListBox->takeItem(posItem);
390  d->mListBox->insertItem(posItem - 1, item);
391 
392  wasMoved = true;
393  }
394  if (wasMoved) {
395  Q_EMIT changed();
396  d->mListBox->setCurrentRow(currentRow - 1);
397  }
398 }
399 
400 void SimpleStringListEditor::slotDown()
401 {
402  QList<QListWidgetItem *> listWidgetItem = d->selectedItems();
403  if (listWidgetItem.isEmpty()) {
404  return;
405  }
406 
407  const int numberOfElement(d->mListBox->count());
408  const int numberOfItem(listWidgetItem.count());
409  const int currentRow = d->mListBox->currentRow();
410  if ((numberOfItem == 1) && (currentRow == numberOfElement - 1)) {
411  qCDebug(PIMCOMMON_LOG) << "Called while the _last_ filter is selected, ignoring.";
412  return;
413  }
414 
415  int j = 0;
416  bool wasMoved = false;
417  for (int i = numberOfItem - 1; i >= 0; --i, j++) {
418  const int posItem = d->mListBox->row(listWidgetItem.at(i));
419  if (posItem == (numberOfElement - 1 - j)) {
420  continue;
421  }
422  QListWidgetItem *item = d->mListBox->takeItem(posItem);
423  d->mListBox->insertItem(posItem + 1, item);
424  wasMoved = true;
425  }
426  if (wasMoved) {
427  Q_EMIT changed();
428  d->mListBox->setCurrentRow(currentRow + 1);
429  }
430 }
431 
432 void SimpleStringListEditor::slotSelectionChanged()
433 {
434  const QList<QListWidgetItem *> lstSelectedItems = d->mListBox->selectedItems();
435  const int numberOfItemSelected(lstSelectedItems.count());
436  const bool uniqItemSelected = (numberOfItemSelected == 1);
437  const bool aItemIsSelected = !lstSelectedItems.isEmpty();
438  // if there is one, item will be non-null (ie. true), else 0
439  // (ie. false):
440  if (d->mRemoveButton) {
441  d->mRemoveButton->setEnabled(aItemIsSelected);
442  }
443 
444  if (d->mModifyButton) {
445  d->mModifyButton->setEnabled(uniqItemSelected);
446  }
447 
448  const int currentIndex = d->mListBox->currentRow();
449 
450  const bool allItemSelected = (d->mListBox->count() == numberOfItemSelected);
451  const bool theLast = (currentIndex >= d->mListBox->count() - 1);
452  const bool theFirst = (currentIndex == 0);
453 
454  if (d->mCustomButton) {
455  d->mCustomButton->setEnabled(uniqItemSelected);
456  }
457 
458  if (d->mUpButton) {
459  d->mUpButton->setEnabled(aItemIsSelected && ((uniqItemSelected && !theFirst) || (!uniqItemSelected)) && !allItemSelected);
460  }
461  if (d->mDownButton) {
462  d->mDownButton->setEnabled(aItemIsSelected && ((uniqItemSelected && !theLast) || (!uniqItemSelected)) && !allItemSelected);
463  }
464 }
465 
466 void SimpleStringListEditor::slotContextMenu(const QPoint &pos)
467 {
468  QList<QListWidgetItem *> lstSelectedItems = d->mListBox->selectedItems();
469  const bool hasItemsSelected = !lstSelectedItems.isEmpty();
470  QMenu menu(this);
471  if (d->mAddButton) {
472  QAction *act = menu.addAction(d->mAddButton->text(), this, &SimpleStringListEditor::slotAdd);
473  act->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
474  }
475  if (d->mModifyButton && (lstSelectedItems.count() == 1)) {
476  QAction *act = menu.addAction(d->mModifyButton->text(), this, &SimpleStringListEditor::slotModify);
477  act->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
478  }
479  if (d->mRemoveButton && hasItemsSelected) {
480  menu.addSeparator();
481  QAction *act = menu.addAction(d->mRemoveButton->text(), this, &SimpleStringListEditor::slotRemove);
482  act->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
483  }
484  if (!menu.isEmpty()) {
485  menu.exec(d->mListBox->mapToGlobal(pos));
486  }
487 }
488 
489 QSize SimpleStringListEditor::sizeHint() const
490 {
491  // Override height because we want the widget to be tall enough to fit the
492  // button columns, but we want to allow it to be made smaller than list
493  // sizeHint().height()
494  QSize sh = QWidget::sizeHint();
495  sh.setHeight(d->mButtonLayout->minimumSize().height());
496  return sh;
497 }
QString text() const const
void setSizePolicy(QSizePolicy)
Q_EMITQ_EMIT
folderdialogacltab.h
void customContextMenuRequested(const QPoint &pos)
int count(const T &value) const const
QString trimmed() const const
void clicked(bool checked)
QIcon fromTheme(const QString &name)
void currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)
CustomContextMenu
QStringList stringList() const
Retrieves the current list of strings.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void reserve(int alloc)
KGuiItem cancel()
void setIcon(const QIcon &icon)
QString i18n(const char *text, const TYPE &arg...)
bool isEmpty() const const
const T & at(int i) const const
void appendStringList(const QStringList &strings)
Adds strings to the list of displayed strings.
void setStringList(const QStringList &strings)
Sets the list of strings displayed to strings.
QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode, const QString &text, bool *ok, Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints)
bool isEmpty() const const
KGuiItem remove()
ButtonCode warningTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Options(Notify|Dangerous))
void setButtonText(ButtonCode button, const QString &text)
Sets the text of button button to text.
SimpleStringListEditor(QWidget *parent=nullptr, ButtonCode buttons=Unsorted, const QString &addLabel=QString(), const QString &removeLabel=QString(), const QString &modifyLabel=QString(), const QString &addDialogLabel=QString())
Constructor.
void itemDoubleClicked(QListWidgetItem *item)
void aboutToAdd(QString &)
Connected slots can alter the argument to be added or set the argument to QString() to suppress addin...
void itemSelectionChanged()
void setText(const QString &text)
void setHeight(int height)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sun Feb 5 2023 04:08:45 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.