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 
18 class KActionSelectorPrivate
19 {
20 public:
KActionSelectorPrivate(KActionSelector * qq)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);
slotCurrentChanged(QListWidgetItem *)71     void slotCurrentChanged(QListWidgetItem *)
72     {
73         q->setButtonsEnabled();
74     }
75 };
76 
77 // BEGIN Constructor/destructor
78 
KActionSelector(QWidget * parent)79 KActionSelector::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(0, 0, 0, 0);
95 
96     QVBoxLayout *loAv = new QVBoxLayout();
97     lo->addLayout(loAv);
98     d->lAvailable = new QLabel(tr("&Available:", "@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(loHBtns);
106     loHBtns->addStretch(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(1);
112 
113     QVBoxLayout *loS = new QVBoxLayout();
114     lo->addLayout(loS);
115     d->lSelected = new QLabel(tr("&Selected:", "@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(loVBtns);
123     loVBtns->addStretch(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(1);
131 
132     d->loadIcons();
133 
134     connect(d->btnAdd, &QToolButton::clicked, this, [this]() {
135         d->buttonAddClicked();
136     });
137     connect(d->btnRemove, &QToolButton::clicked, this, [this]() {
138         d->buttonRemoveClicked();
139     });
140     connect(d->btnUp, &QToolButton::clicked, this, [this]() {
141         d->buttonUpClicked();
142     });
143     connect(d->btnDown, &QToolButton::clicked, this, [this]() {
144         d->buttonDownClicked();
145     });
146     connect(d->availableListWidget, &QListWidget::itemDoubleClicked, this, [this] (QListWidgetItem *item) { d->itemDoubleClicked(item); });
147     connect(d->selectedListWidget, &QListWidget::itemDoubleClicked, this, [this] (QListWidgetItem *item) { d->itemDoubleClicked(item); });
148     connect(d->availableListWidget, &QListWidget::itemSelectionChanged, this, &KActionSelector::setButtonsEnabled);
149     connect(d->selectedListWidget, &QListWidget::itemSelectionChanged, this, &KActionSelector::setButtonsEnabled);
150 
151     d->availableListWidget->installEventFilter(this);
152     d->selectedListWidget->installEventFilter(this);
153     setButtonsEnabled();
154 }
155 
156 KActionSelector::~KActionSelector() = default;
157 
158 // END Constructor/destroctor
159 
160 // BEGIN Public Methods
161 
availableListWidget() const162 QListWidget *KActionSelector::availableListWidget() const
163 {
164     return d->availableListWidget;
165 }
166 
selectedListWidget() const167 QListWidget *KActionSelector::selectedListWidget() const
168 {
169     return d->selectedListWidget;
170 }
171 
setButtonIcon(const QString & icon,MoveButton button)172 void KActionSelector::setButtonIcon(const QString &icon, MoveButton button)
173 {
174     switch (button) {
175     case ButtonAdd:
176         d->addIcon = icon;
177         d->btnAdd->setIcon(QIcon::fromTheme(icon));
178         break;
179     case ButtonRemove:
180         d->removeIcon = icon;
181         d->btnRemove->setIcon(QIcon::fromTheme(icon));
182         break;
183     case ButtonUp:
184         d->upIcon = icon;
185         d->btnUp->setIcon(QIcon::fromTheme(icon));
186         break;
187     case ButtonDown:
188         d->downIcon = icon;
189         d->btnDown->setIcon(QIcon::fromTheme(icon));
190         break;
191     default:
192         //     qCDebug(KWidgetsAddonsLog)<<"KActionSelector::setButtonIcon: DAINBREAD!";
193         break;
194     }
195 }
196 
setButtonIconSet(const QIcon & iconset,MoveButton button)197 void 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 
setButtonTooltip(const QString & tip,MoveButton button)218 void 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 
setButtonWhatsThis(const QString & text,MoveButton button)243 void 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 
moveOnDoubleClick() const268 bool KActionSelector::moveOnDoubleClick() const
269 {
270     return d->moveOnDoubleClick;
271 }
272 
setMoveOnDoubleClick(bool b)273 void KActionSelector::setMoveOnDoubleClick(bool b)
274 {
275     d->moveOnDoubleClick = b;
276 }
277 
keyboardEnabled() const278 bool KActionSelector::keyboardEnabled() const
279 {
280     return d->keyboardEnabled;
281 }
282 
setKeyboardEnabled(bool b)283 void KActionSelector::setKeyboardEnabled(bool b)
284 {
285     d->keyboardEnabled = b;
286 }
287 
availableLabel() const288 QString KActionSelector::availableLabel() const
289 {
290     return d->lAvailable->text();
291 }
292 
setAvailableLabel(const QString & text)293 void KActionSelector::setAvailableLabel(const QString &text)
294 {
295     d->lAvailable->setText(text);
296 }
297 
selectedLabel() const298 QString KActionSelector::selectedLabel() const
299 {
300     return d->lSelected->text();
301 }
302 
setSelectedLabel(const QString & text)303 void KActionSelector::setSelectedLabel(const QString &text)
304 {
305     d->lSelected->setText(text);
306 }
307 
availableInsertionPolicy() const308 KActionSelector::InsertionPolicy KActionSelector::availableInsertionPolicy() const
309 {
310     return d->availableInsertionPolicy;
311 }
312 
setAvailableInsertionPolicy(InsertionPolicy p)313 void KActionSelector::setAvailableInsertionPolicy(InsertionPolicy p)
314 {
315     d->availableInsertionPolicy = p;
316 }
317 
selectedInsertionPolicy() const318 KActionSelector::InsertionPolicy KActionSelector::selectedInsertionPolicy() const
319 {
320     return d->selectedInsertionPolicy;
321 }
322 
setSelectedInsertionPolicy(InsertionPolicy p)323 void KActionSelector::setSelectedInsertionPolicy(InsertionPolicy p)
324 {
325     d->selectedInsertionPolicy = p;
326 }
327 
showUpDownButtons() const328 bool KActionSelector::showUpDownButtons() const
329 {
330     return d->showUpDownButtons;
331 }
332 
setShowUpDownButtons(bool show)333 void 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 
setButtonsEnabled()349 void KActionSelector::setButtonsEnabled()
350 {
351     d->btnAdd->setEnabled(d->selectedRowIndex(d->availableListWidget) > -1);
352     d->btnRemove->setEnabled(d->selectedRowIndex(d->selectedListWidget) > -1);
353     d->btnUp->setEnabled(d->selectedRowIndex(d->selectedListWidget) > 0);
354     d->btnDown->setEnabled(d->selectedRowIndex(d->selectedListWidget) > -1 //
355                            && d->selectedRowIndex(d->selectedListWidget) < d->selectedListWidget->count() - 1);
356 }
357 
358 // END Public Slots
359 
360 // BEGIN Protected
keyPressEvent(QKeyEvent * e)361 void 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 
eventFilter(QObject * o,QEvent * e)387 bool 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(o, e);
406             }
407             return true;
408         } else if (QListWidget *lb = qobject_cast<QListWidget *>(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(lb->item(index));
417                 return true;
418             }
419         }
420     }
421     return QWidget::eventFilter(o, e);
422 }
423 
424 // END Protected
425 
426 // BEGIN Private Slots
427 
buttonAddClicked()428 void 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(availableListWidget->row(item));
434         selectedListWidget->insertItem(insertionIndex(selectedListWidget, 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 
buttonRemoveClicked()444 void 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(selectedListWidget->row(item));
450         availableListWidget->insertItem(insertionIndex(availableListWidget, 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 
buttonUpClicked()460 void KActionSelectorPrivate::buttonUpClicked()
461 {
462     int c = selectedRowIndex(selectedListWidget);
463     if (c < 1) {
464         return;
465     }
466     QListWidgetItem *item = selectedListWidget->item(c);
467     selectedListWidget->takeItem(c);
468     selectedListWidget->insertItem(c - 1, item);
469     selectedListWidget->setCurrentItem(item);
470     Q_EMIT q->movedUp(item);
471 }
472 
buttonDownClicked()473 void KActionSelectorPrivate::buttonDownClicked()
474 {
475     int c = selectedRowIndex(selectedListWidget);
476     if (c < 0 || c == selectedListWidget->count() - 1) {
477         return;
478     }
479     QListWidgetItem *item = selectedListWidget->item(c);
480     selectedListWidget->takeItem(c);
481     selectedListWidget->insertItem(c + 1, item);
482     selectedListWidget->setCurrentItem(item);
483     Q_EMIT q->movedDown(item);
484 }
485 
itemDoubleClicked(QListWidgetItem * item)486 void KActionSelectorPrivate::itemDoubleClicked(QListWidgetItem *item)
487 {
488     if (moveOnDoubleClick) {
489         moveItem(item);
490     }
491 }
492 
493 // END Private Slots
494 
495 // BEGIN Private Methods
496 
loadIcons()497 void KActionSelectorPrivate::loadIcons()
498 {
499     btnAdd->setIcon(QIcon::fromTheme(addIcon));
500     btnRemove->setIcon(QIcon::fromTheme(removeIcon));
501     btnUp->setIcon(QIcon::fromTheme(upIcon));
502     btnDown->setIcon(QIcon::fromTheme(downIcon));
503 }
504 
moveItem(QListWidgetItem * item)505 void 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(lbFrom->row(item));
520     lbTo->insertItem(insertionIndex(lbTo, 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 
insertionIndex(QListWidget * lb,KActionSelector::InsertionPolicy policy)534 int 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 
selectedRowIndex(QListWidget * lb)556 int KActionSelectorPrivate::selectedRowIndex(QListWidget *lb)
557 {
558     QList<QListWidgetItem *> list = lb->selectedItems();
559     if (list.isEmpty()) {
560         return -1;
561     }
562     return lb->row(list.at(0));
563 }
564 
565 // END Private Methods
566 #include "moc_kactionselector.cpp"
567