1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2002 Anders Lund <anders.lund@lund.tdcadsl.dk>
4
5 SPDX-License-Identifier: LGPL-2.0-only
6*/
7
8#include "kactionselector.h"
9
10#include <QApplication>
11#include <QHBoxLayout>
12#include <QKeyEvent>
13#include <QLabel>
14#include <QListWidget>
15#include <QToolButton>
16#include <QVBoxLayout>
17
18class KActionSelectorPrivate
19{
20public:
21 KActionSelectorPrivate(KActionSelector *qq)
22 : q(qq)
23 {
24 }
25
26 KActionSelector *const q = nullptr;
27 QListWidget *availableListWidget = nullptr;
28 QListWidget *selectedListWidget = nullptr;
29 QToolButton *btnAdd = nullptr;
30 QToolButton *btnRemove = nullptr;
31 QToolButton *btnUp = nullptr;
32 QToolButton *btnDown = nullptr;
33 QLabel *lAvailable = nullptr;
34 QLabel *lSelected = nullptr;
35 bool moveOnDoubleClick : 1;
36 bool keyboardEnabled : 1;
37 bool showUpDownButtons : 1;
38 QString addIcon, removeIcon, upIcon, downIcon;
39 KActionSelector::InsertionPolicy availableInsertionPolicy, selectedInsertionPolicy;
40
41 /**
42 Move item @p item to the other listbox
43 */
44 void moveItem(QListWidgetItem *item);
45
46 /**
47 loads the icons for the move buttons.
48 */
49 void loadIcons();
50
51 /**
52 @return the index to insert an item into listbox @p lb,
53 given InsertionPolicy @p policy.
54
55 Note that if policy is Sorted, this will return -1.
56 Sort the listbox after inserting the item in that case.
57 */
58 int insertionIndex(QListWidget *lb, KActionSelector::InsertionPolicy policy);
59
60 /**
61 @return the index of the first selected item in listbox @p lb.
62 If no item is selected, it will return -1.
63 */
64 int selectedRowIndex(QListWidget *lb);
65
66 void buttonAddClicked();
67 void buttonRemoveClicked();
68 void buttonUpClicked();
69 void buttonDownClicked();
70 void itemDoubleClicked(QListWidgetItem *item);
71 void slotCurrentChanged(QListWidgetItem *)
72 {
73 q->setButtonsEnabled();
74 }
75};
76
77// BEGIN Constructor/destructor
78
79KActionSelector::KActionSelector(QWidget *parent)
80 : QWidget(parent)
81 , d(new KActionSelectorPrivate(this))
82{
83 d->moveOnDoubleClick = true;
84 d->keyboardEnabled = true;
85 d->addIcon = QLatin1String(QApplication::isRightToLeft() ? "go-previous" : "go-next");
86 d->removeIcon = QLatin1String(QApplication::isRightToLeft() ? "go-next" : "go-previous");
87 d->upIcon = QStringLiteral("go-up");
88 d->downIcon = QStringLiteral("go-down");
89 d->availableInsertionPolicy = Sorted;
90 d->selectedInsertionPolicy = BelowCurrent;
91 d->showUpDownButtons = true;
92
93 QHBoxLayout *lo = new QHBoxLayout(this);
94 lo->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
95
96 QVBoxLayout *loAv = new QVBoxLayout();
97 lo->addLayout(layout: loAv);
98 d->lAvailable = new QLabel(tr(s: "&Available:", c: "@label:listbox"), this);
99 loAv->addWidget(d->lAvailable);
100 d->availableListWidget = new QListWidget(this);
101 loAv->addWidget(d->availableListWidget);
102 d->lAvailable->setBuddy(d->availableListWidget);
103
104 QVBoxLayout *loHBtns = new QVBoxLayout();
105 lo->addLayout(layout: loHBtns);
106 loHBtns->addStretch(stretch: 1);
107 d->btnAdd = new QToolButton(this);
108 loHBtns->addWidget(d->btnAdd);
109 d->btnRemove = new QToolButton(this);
110 loHBtns->addWidget(d->btnRemove);
111 loHBtns->addStretch(stretch: 1);
112
113 QVBoxLayout *loS = new QVBoxLayout();
114 lo->addLayout(layout: loS);
115 d->lSelected = new QLabel(tr(s: "&Selected:", c: "@label:listbox"), this);
116 loS->addWidget(d->lSelected);
117 d->selectedListWidget = new QListWidget(this);
118 loS->addWidget(d->selectedListWidget);
119 d->lSelected->setBuddy(d->selectedListWidget);
120
121 QVBoxLayout *loVBtns = new QVBoxLayout();
122 lo->addLayout(layout: loVBtns);
123 loVBtns->addStretch(stretch: 1);
124 d->btnUp = new QToolButton(this);
125 d->btnUp->setAutoRepeat(true);
126 loVBtns->addWidget(d->btnUp);
127 d->btnDown = new QToolButton(this);
128 d->btnDown->setAutoRepeat(true);
129 loVBtns->addWidget(d->btnDown);
130 loVBtns->addStretch(stretch: 1);
131
132 d->loadIcons();
133
134 connect(sender: d->btnAdd, signal: &QToolButton::clicked, context: this, slot: [this]() {
135 d->buttonAddClicked();
136 });
137 connect(sender: d->btnRemove, signal: &QToolButton::clicked, context: this, slot: [this]() {
138 d->buttonRemoveClicked();
139 });
140 connect(sender: d->btnUp, signal: &QToolButton::clicked, context: this, slot: [this]() {
141 d->buttonUpClicked();
142 });
143 connect(sender: d->btnDown, signal: &QToolButton::clicked, context: this, slot: [this]() {
144 d->buttonDownClicked();
145 });
146 connect(sender: d->availableListWidget, signal: &QListWidget::itemDoubleClicked, context: this, slot: [this] (QListWidgetItem *item) { d->itemDoubleClicked(item); });
147 connect(sender: d->selectedListWidget, signal: &QListWidget::itemDoubleClicked, context: this, slot: [this] (QListWidgetItem *item) { d->itemDoubleClicked(item); });
148 connect(sender: d->availableListWidget, signal: &QListWidget::itemSelectionChanged, context: this, slot: &KActionSelector::setButtonsEnabled);
149 connect(sender: d->selectedListWidget, signal: &QListWidget::itemSelectionChanged, context: this, slot: &KActionSelector::setButtonsEnabled);
150
151 d->availableListWidget->installEventFilter(filterObj: this);
152 d->selectedListWidget->installEventFilter(filterObj: this);
153 setButtonsEnabled();
154}
155
156KActionSelector::~KActionSelector() = default;
157
158// END Constructor/destroctor
159
160// BEGIN Public Methods
161
162QListWidget *KActionSelector::availableListWidget() const
163{
164 return d->availableListWidget;
165}
166
167QListWidget *KActionSelector::selectedListWidget() const
168{
169 return d->selectedListWidget;
170}
171
172void KActionSelector::setButtonIcon(const QString &icon, MoveButton button)
173{
174 switch (button) {
175 case ButtonAdd:
176 d->addIcon = icon;
177 d->btnAdd->setIcon(QIcon::fromTheme(name: icon));
178 break;
179 case ButtonRemove:
180 d->removeIcon = icon;
181 d->btnRemove->setIcon(QIcon::fromTheme(name: icon));
182 break;
183 case ButtonUp:
184 d->upIcon = icon;
185 d->btnUp->setIcon(QIcon::fromTheme(name: icon));
186 break;
187 case ButtonDown:
188 d->downIcon = icon;
189 d->btnDown->setIcon(QIcon::fromTheme(name: icon));
190 break;
191 default:
192 // qCDebug(KWidgetsAddonsLog)<<"KActionSelector::setButtonIcon: DAINBREAD!";
193 break;
194 }
195}
196
197void KActionSelector::setButtonIconSet(const QIcon &iconset, MoveButton button)
198{
199 switch (button) {
200 case ButtonAdd:
201 d->btnAdd->setIcon(iconset);
202 break;
203 case ButtonRemove:
204 d->btnRemove->setIcon(iconset);
205 break;
206 case ButtonUp:
207 d->btnUp->setIcon(iconset);
208 break;
209 case ButtonDown:
210 d->btnDown->setIcon(iconset);
211 break;
212 default:
213 // qCDebug(KWidgetsAddonsLog)<<"KActionSelector::setButtonIconSet: DAINBREAD!";
214 break;
215 }
216}
217
218void KActionSelector::setButtonTooltip(const QString &tip, MoveButton button)
219{
220 switch (button) {
221 case ButtonAdd:
222 d->btnAdd->setText(tip);
223 d->btnAdd->setToolTip(tip);
224 break;
225 case ButtonRemove:
226 d->btnRemove->setText(tip);
227 d->btnRemove->setToolTip(tip);
228 break;
229 case ButtonUp:
230 d->btnUp->setText(tip);
231 d->btnUp->setToolTip(tip);
232 break;
233 case ButtonDown:
234 d->btnDown->setText(tip);
235 d->btnDown->setToolTip(tip);
236 break;
237 default:
238 // qCDebug(KWidgetsAddonsLog)<<"KActionSelector::setButtonToolTip: DAINBREAD!";
239 break;
240 }
241}
242
243void KActionSelector::setButtonWhatsThis(const QString &text, MoveButton button)
244{
245 switch (button) {
246 case ButtonAdd:
247 d->btnAdd->setWhatsThis(text);
248 break;
249 case ButtonRemove:
250 d->btnRemove->setWhatsThis(text);
251 break;
252 case ButtonUp:
253 d->btnUp->setWhatsThis(text);
254 break;
255 case ButtonDown:
256 d->btnDown->setWhatsThis(text);
257 break;
258 default:
259 // qCDebug(KWidgetsAddonsLog)<<"KActionSelector::setButtonWhatsThis: DAINBREAD!";
260 break;
261 }
262}
263
264// END Public Methods
265
266// BEGIN Properties
267
268bool KActionSelector::moveOnDoubleClick() const
269{
270 return d->moveOnDoubleClick;
271}
272
273void KActionSelector::setMoveOnDoubleClick(bool b)
274{
275 d->moveOnDoubleClick = b;
276}
277
278bool KActionSelector::keyboardEnabled() const
279{
280 return d->keyboardEnabled;
281}
282
283void KActionSelector::setKeyboardEnabled(bool b)
284{
285 d->keyboardEnabled = b;
286}
287
288QString KActionSelector::availableLabel() const
289{
290 return d->lAvailable->text();
291}
292
293void KActionSelector::setAvailableLabel(const QString &text)
294{
295 d->lAvailable->setText(text);
296}
297
298QString KActionSelector::selectedLabel() const
299{
300 return d->lSelected->text();
301}
302
303void KActionSelector::setSelectedLabel(const QString &text)
304{
305 d->lSelected->setText(text);
306}
307
308KActionSelector::InsertionPolicy KActionSelector::availableInsertionPolicy() const
309{
310 return d->availableInsertionPolicy;
311}
312
313void KActionSelector::setAvailableInsertionPolicy(InsertionPolicy p)
314{
315 d->availableInsertionPolicy = p;
316}
317
318KActionSelector::InsertionPolicy KActionSelector::selectedInsertionPolicy() const
319{
320 return d->selectedInsertionPolicy;
321}
322
323void KActionSelector::setSelectedInsertionPolicy(InsertionPolicy p)
324{
325 d->selectedInsertionPolicy = p;
326}
327
328bool KActionSelector::showUpDownButtons() const
329{
330 return d->showUpDownButtons;
331}
332
333void KActionSelector::setShowUpDownButtons(bool show)
334{
335 d->showUpDownButtons = show;
336 if (show) {
337 d->btnUp->show();
338 d->btnDown->show();
339 } else {
340 d->btnUp->hide();
341 d->btnDown->hide();
342 }
343}
344
345// END Properties
346
347// BEGIN Public Slots
348
349void KActionSelector::setButtonsEnabled()
350{
351 d->btnAdd->setEnabled(d->selectedRowIndex(lb: d->availableListWidget) > -1);
352 d->btnRemove->setEnabled(d->selectedRowIndex(lb: d->selectedListWidget) > -1);
353 d->btnUp->setEnabled(d->selectedRowIndex(lb: d->selectedListWidget) > 0);
354 d->btnDown->setEnabled(d->selectedRowIndex(lb: d->selectedListWidget) > -1 //
355 && d->selectedRowIndex(lb: d->selectedListWidget) < d->selectedListWidget->count() - 1);
356}
357
358// END Public Slots
359
360// BEGIN Protected
361void KActionSelector::keyPressEvent(QKeyEvent *e)
362{
363 if (!d->keyboardEnabled) {
364 return;
365 }
366 if ((e->modifiers() & Qt::ControlModifier)) {
367 switch (e->key()) {
368 case Qt::Key_Right:
369 d->buttonAddClicked();
370 break;
371 case Qt::Key_Left:
372 d->buttonRemoveClicked();
373 break;
374 case Qt::Key_Up:
375 d->buttonUpClicked();
376 break;
377 case Qt::Key_Down:
378 d->buttonDownClicked();
379 break;
380 default:
381 e->ignore();
382 return;
383 }
384 }
385}
386
387bool KActionSelector::eventFilter(QObject *o, QEvent *e)
388{
389 if (d->keyboardEnabled && e->type() == QEvent::KeyPress) {
390 if ((((QKeyEvent *)e)->modifiers() & Qt::ControlModifier)) {
391 switch (((QKeyEvent *)e)->key()) {
392 case Qt::Key_Right:
393 d->buttonAddClicked();
394 break;
395 case Qt::Key_Left:
396 d->buttonRemoveClicked();
397 break;
398 case Qt::Key_Up:
399 d->buttonUpClicked();
400 break;
401 case Qt::Key_Down:
402 d->buttonDownClicked();
403 break;
404 default:
405 return QWidget::eventFilter(watched: o, event: e);
406 }
407 return true;
408 } else if (QListWidget *lb = qobject_cast<QListWidget *>(object: o)) {
409 switch (((QKeyEvent *)e)->key()) {
410 case Qt::Key_Return:
411 case Qt::Key_Enter:
412 int index = lb->currentRow();
413 if (index < 0) {
414 break;
415 }
416 d->moveItem(item: lb->item(row: index));
417 return true;
418 }
419 }
420 }
421 return QWidget::eventFilter(watched: o, event: e);
422}
423
424// END Protected
425
426// BEGIN Private Slots
427
428void KActionSelectorPrivate::buttonAddClicked()
429{
430 // move all selected items from available to selected listbox
431 const QList<QListWidgetItem *> list = availableListWidget->selectedItems();
432 for (QListWidgetItem *item : list) {
433 availableListWidget->takeItem(row: availableListWidget->row(item));
434 selectedListWidget->insertItem(row: insertionIndex(lb: selectedListWidget, policy: selectedInsertionPolicy), item);
435 selectedListWidget->setCurrentItem(item);
436 Q_EMIT q->added(item);
437 }
438 if (selectedInsertionPolicy == KActionSelector::Sorted) {
439 selectedListWidget->sortItems();
440 }
441 selectedListWidget->setFocus();
442}
443
444void KActionSelectorPrivate::buttonRemoveClicked()
445{
446 // move all selected items from selected to available listbox
447 const QList<QListWidgetItem *> list = selectedListWidget->selectedItems();
448 for (QListWidgetItem *item : list) {
449 selectedListWidget->takeItem(row: selectedListWidget->row(item));
450 availableListWidget->insertItem(row: insertionIndex(lb: availableListWidget, policy: availableInsertionPolicy), item);
451 availableListWidget->setCurrentItem(item);
452 Q_EMIT q->removed(item);
453 }
454 if (availableInsertionPolicy == KActionSelector::Sorted) {
455 availableListWidget->sortItems();
456 }
457 availableListWidget->setFocus();
458}
459
460void KActionSelectorPrivate::buttonUpClicked()
461{
462 int c = selectedRowIndex(lb: selectedListWidget);
463 if (c < 1) {
464 return;
465 }
466 QListWidgetItem *item = selectedListWidget->item(row: c);
467 selectedListWidget->takeItem(row: c);
468 selectedListWidget->insertItem(row: c - 1, item);
469 selectedListWidget->setCurrentItem(item);
470 Q_EMIT q->movedUp(item);
471}
472
473void KActionSelectorPrivate::buttonDownClicked()
474{
475 int c = selectedRowIndex(lb: selectedListWidget);
476 if (c < 0 || c == selectedListWidget->count() - 1) {
477 return;
478 }
479 QListWidgetItem *item = selectedListWidget->item(row: c);
480 selectedListWidget->takeItem(row: c);
481 selectedListWidget->insertItem(row: c + 1, item);
482 selectedListWidget->setCurrentItem(item);
483 Q_EMIT q->movedDown(item);
484}
485
486void KActionSelectorPrivate::itemDoubleClicked(QListWidgetItem *item)
487{
488 if (moveOnDoubleClick) {
489 moveItem(item);
490 }
491}
492
493// END Private Slots
494
495// BEGIN Private Methods
496
497void KActionSelectorPrivate::loadIcons()
498{
499 btnAdd->setIcon(QIcon::fromTheme(name: addIcon));
500 btnRemove->setIcon(QIcon::fromTheme(name: removeIcon));
501 btnUp->setIcon(QIcon::fromTheme(name: upIcon));
502 btnDown->setIcon(QIcon::fromTheme(name: downIcon));
503}
504
505void KActionSelectorPrivate::moveItem(QListWidgetItem *item)
506{
507 QListWidget *lbFrom = item->listWidget();
508 QListWidget *lbTo;
509 if (lbFrom == availableListWidget) {
510 lbTo = selectedListWidget;
511 } else if (lbFrom == selectedListWidget) {
512 lbTo = availableListWidget;
513 } else { //?! somewhat unlikely...
514 return;
515 }
516
517 KActionSelector::InsertionPolicy p = (lbTo == availableListWidget) ? availableInsertionPolicy : selectedInsertionPolicy;
518
519 lbFrom->takeItem(row: lbFrom->row(item));
520 lbTo->insertItem(row: insertionIndex(lb: lbTo, policy: p), item);
521 lbTo->setFocus();
522 lbTo->setCurrentItem(item);
523
524 if (p == KActionSelector::Sorted) {
525 lbTo->sortItems();
526 }
527 if (lbTo == selectedListWidget) {
528 Q_EMIT q->added(item);
529 } else {
530 Q_EMIT q->removed(item);
531 }
532}
533
534int KActionSelectorPrivate::insertionIndex(QListWidget *lb, KActionSelector::InsertionPolicy policy)
535{
536 int index;
537 switch (policy) {
538 case KActionSelector::BelowCurrent:
539 index = lb->currentRow();
540 if (index > -1) {
541 index += 1;
542 }
543 break;
544 case KActionSelector::AtTop:
545 index = 0;
546 break;
547 case KActionSelector::AtBottom:
548 index = lb->count();
549 break;
550 default:
551 index = -1;
552 }
553 return index;
554}
555
556int KActionSelectorPrivate::selectedRowIndex(QListWidget *lb)
557{
558 QList<QListWidgetItem *> list = lb->selectedItems();
559 if (list.isEmpty()) {
560 return -1;
561 }
562 return lb->row(item: list.at(i: 0));
563}
564
565// END Private Methods
566#include "moc_kactionselector.cpp"
567

source code of kwidgetsaddons/src/kactionselector.cpp