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 <mutz@kde.org>
6
7 SPDX-FileCopyrightText: 2013-2024 Laurent Montel <montel@kde.org>
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//********************************************************
28using namespace PimCommon;
29
30class PimCommon::SimpleStringListEditorPrivate
31{
32public:
33 SimpleStringListEditorPrivate() = default;
34
35 [[nodiscard]] 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(i18nc("@action:button", "&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(i18nc("@action:button", "&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(i18nc("@action:button", "&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(i18nc("@action:button", "&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
168SimpleStringListEditor::~SimpleStringListEditor() = default;
169
170void 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
191QStringList SimpleStringListEditor::stringList() const
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
202bool 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
213void 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
257void 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
266void 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
278void SimpleStringListEditor::slotAdd()
279{
280 addNewEntry();
281}
282
283void 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
296QString SimpleStringListEditor::customEntry(const QString &text)
297{
298 Q_UNUSED(text)
299 return {};
300}
301
302void SimpleStringListEditor::slotRemove()
303{
304 const QList<QListWidgetItem *> selectedItems = d->mListBox->selectedItems();
305 if (selectedItems.isEmpty()) {
306 return;
307 }
308 const int answer =
309 KMessageBox::warningTwoActions(this, d->mRemoveDialogLabel, i18nc("@title:window", "Remove"), KStandardGuiItem::remove(), KStandardGuiItem::cancel());
310 if (answer == KMessageBox::ButtonCode::PrimaryAction) {
311 for (QListWidgetItem *item : selectedItems) {
312 delete d->mListBox->takeItem(d->mListBox->row(item));
313 }
314 slotSelectionChanged();
315 Q_EMIT changed();
316 }
317}
318
319QString SimpleStringListEditor::modifyEntry(const QString &text)
320{
321 bool ok = false;
322 QString newText = QInputDialog::getText(this, d->mModifyDialogTitle, d->mModifyDialogLabel, QLineEdit::Normal, text, &ok);
323 Q_EMIT aboutToAdd(newText);
324
325 if (!ok || newText.trimmed().isEmpty() || newText == text) {
326 return {};
327 }
328
329 return newText;
330}
331
332void SimpleStringListEditor::slotModify()
333{
334 QListWidgetItem *item = d->mListBox->currentItem();
335 if (!item) {
336 return;
337 }
338 const QString newText = modifyEntry(item->text());
339 if (!newText.isEmpty()) {
340 item->setText(newText);
341 Q_EMIT changed();
342 }
343}
344
345void SimpleStringListEditor::setRemoveDialogLabel(const QString &removeDialogLabel)
346{
347 d->mRemoveDialogLabel = removeDialogLabel;
348}
349
350void SimpleStringListEditor::setAddDialogLabel(const QString &addDialogLabel)
351{
352 d->mAddDialogLabel = addDialogLabel;
353}
354
355void SimpleStringListEditor::setAddDialogTitle(const QString &str)
356{
357 d->mAddDialogTitle = str;
358}
359
360void SimpleStringListEditor::setModifyDialogTitle(const QString &str)
361{
362 d->mModifyDialogTitle = str;
363}
364
365void SimpleStringListEditor::setModifyDialogLabel(const QString &str)
366{
367 d->mModifyDialogLabel = str;
368}
369
370void SimpleStringListEditor::slotUp()
371{
372 QList<QListWidgetItem *> listWidgetItem = d->selectedItems();
373 if (listWidgetItem.isEmpty()) {
374 return;
375 }
376
377 const int numberOfItem(listWidgetItem.count());
378 const int currentRow = d->mListBox->currentRow();
379 if ((numberOfItem == 1) && (currentRow == 0)) {
380 qCDebug(PIMCOMMON_LOG) << "Called while the _topmost_ filter is selected, ignoring.";
381 return;
382 }
383 bool wasMoved = false;
384
385 for (int i = 0; i < numberOfItem; ++i) {
386 const int posItem = d->mListBox->row(listWidgetItem.at(i));
387 if (posItem == i) {
388 continue;
389 }
390 QListWidgetItem *item = d->mListBox->takeItem(posItem);
391 d->mListBox->insertItem(posItem - 1, item);
392
393 wasMoved = true;
394 }
395 if (wasMoved) {
396 Q_EMIT changed();
397 d->mListBox->setCurrentRow(currentRow - 1);
398 }
399}
400
401void SimpleStringListEditor::slotDown()
402{
403 QList<QListWidgetItem *> listWidgetItem = d->selectedItems();
404 if (listWidgetItem.isEmpty()) {
405 return;
406 }
407
408 const int numberOfElement(d->mListBox->count());
409 const int numberOfItem(listWidgetItem.count());
410 const int currentRow = d->mListBox->currentRow();
411 if ((numberOfItem == 1) && (currentRow == numberOfElement - 1)) {
412 qCDebug(PIMCOMMON_LOG) << "Called while the _last_ filter is selected, ignoring.";
413 return;
414 }
415
416 int j = 0;
417 bool wasMoved = false;
418 for (int i = numberOfItem - 1; i >= 0; --i, j++) {
419 const int posItem = d->mListBox->row(listWidgetItem.at(i));
420 if (posItem == (numberOfElement - 1 - j)) {
421 continue;
422 }
423 QListWidgetItem *item = d->mListBox->takeItem(posItem);
424 d->mListBox->insertItem(posItem + 1, item);
425 wasMoved = true;
426 }
427 if (wasMoved) {
428 Q_EMIT changed();
429 d->mListBox->setCurrentRow(currentRow + 1);
430 }
431}
432
433void SimpleStringListEditor::slotSelectionChanged()
434{
435 const QList<QListWidgetItem *> lstSelectedItems = d->mListBox->selectedItems();
436 const int numberOfItemSelected(lstSelectedItems.count());
437 const bool uniqItemSelected = (numberOfItemSelected == 1);
438 const bool aItemIsSelected = !lstSelectedItems.isEmpty();
439 // if there is one, item will be non-null (ie. true), else 0
440 // (ie. false):
441 if (d->mRemoveButton) {
442 d->mRemoveButton->setEnabled(aItemIsSelected);
443 }
444
445 if (d->mModifyButton) {
446 d->mModifyButton->setEnabled(uniqItemSelected);
447 }
448
449 const int currentIndex = d->mListBox->currentRow();
450
451 const bool allItemSelected = (d->mListBox->count() == numberOfItemSelected);
452 const bool theLast = (currentIndex >= d->mListBox->count() - 1);
453 const bool theFirst = (currentIndex == 0);
454
455 if (d->mCustomButton) {
456 d->mCustomButton->setEnabled(uniqItemSelected);
457 }
458
459 if (d->mUpButton) {
460 d->mUpButton->setEnabled(aItemIsSelected && ((uniqItemSelected && !theFirst) || (!uniqItemSelected)) && !allItemSelected);
461 }
462 if (d->mDownButton) {
463 d->mDownButton->setEnabled(aItemIsSelected && ((uniqItemSelected && !theLast) || (!uniqItemSelected)) && !allItemSelected);
464 }
465}
466
467void SimpleStringListEditor::slotContextMenu(const QPoint &pos)
468{
469 QList<QListWidgetItem *> lstSelectedItems = d->mListBox->selectedItems();
470 const bool hasItemsSelected = !lstSelectedItems.isEmpty();
471 QMenu menu(this);
472 if (d->mAddButton) {
473 QAction *act = menu.addAction(d->mAddButton->text(), this, &SimpleStringListEditor::slotAdd);
474 act->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
475 }
476 if (d->mModifyButton && (lstSelectedItems.count() == 1)) {
477 QAction *act = menu.addAction(d->mModifyButton->text(), this, &SimpleStringListEditor::slotModify);
478 act->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
479 }
480 if (d->mRemoveButton && hasItemsSelected) {
481 menu.addSeparator();
482 QAction *act = menu.addAction(d->mRemoveButton->text(), this, &SimpleStringListEditor::slotRemove);
483 act->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
484 }
485 if (!menu.isEmpty()) {
486 menu.exec(d->mListBox->mapToGlobal(pos));
487 }
488}
489
490QSize SimpleStringListEditor::sizeHint() const
491{
492 // Override height because we want the widget to be tall enough to fit the
493 // button columns, but we want to allow it to be made smaller than list
494 // sizeHint().height()
496 sh.setHeight(d->mButtonLayout->minimumSize().height());
497 return sh;
498}
499
500#include "moc_simplestringlisteditor.cpp"
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 setStringList(const QStringList &strings)
Sets the list of strings displayed to strings.
void appendStringList(const QStringList &strings)
Adds strings to the list of displayed strings.
void aboutToAdd(QString &)
Connected slots can alter the argument to be added or set the argument to QString() to suppress addin...
void setButtonText(ButtonCode button, const QString &text)
Sets the text of button button to text.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
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))
KGuiItem remove()
KGuiItem cancel()
folderdialogacltab.h
void clicked(bool checked)
void setIcon(const QIcon &icon)
QIcon fromTheme(const QString &name)
QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode, const QString &text, bool *ok, Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints)
const_reference at(qsizetype i) const const
qsizetype count() const const
bool isEmpty() const const
void reserve(qsizetype size)
void currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)
QListWidgetItem * item(int row) const const
void itemDoubleClicked(QListWidgetItem *item)
void itemSelectionChanged()
bool isSelected() const const
void setText(const QString &text)
QString text() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void setHeight(int height)
bool isEmpty() const const
QString trimmed() const const
CustomContextMenu
void customContextMenuRequested(const QPoint &pos)
void setSizePolicy(QSizePolicy)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:14:35 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.