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) {
147 d->itemDoubleClicked(item);
148 });
149 connect(sender: d->selectedListWidget, signal: &QListWidget::itemDoubleClicked, context: this, slot: [this](QListWidgetItem *item) {
150 d->itemDoubleClicked(item);
151 });
152 connect(sender: d->availableListWidget, signal: &QListWidget::itemSelectionChanged, context: this, slot: &KActionSelector::setButtonsEnabled);
153 connect(sender: d->selectedListWidget, signal: &QListWidget::itemSelectionChanged, context: this, slot: &KActionSelector::setButtonsEnabled);
154
155 d->availableListWidget->installEventFilter(filterObj: this);
156 d->selectedListWidget->installEventFilter(filterObj: this);
157 setButtonsEnabled();
158}
159
160KActionSelector::~KActionSelector() = default;
161
162// END Constructor/destroctor
163
164// BEGIN Public Methods
165
166QListWidget *KActionSelector::availableListWidget() const
167{
168 return d->availableListWidget;
169}
170
171QListWidget *KActionSelector::selectedListWidget() const
172{
173 return d->selectedListWidget;
174}
175
176void KActionSelector::setButtonIcon(const QString &icon, MoveButton button)
177{
178 switch (button) {
179 case ButtonAdd:
180 d->addIcon = icon;
181 d->btnAdd->setIcon(QIcon::fromTheme(name: icon));
182 break;
183 case ButtonRemove:
184 d->removeIcon = icon;
185 d->btnRemove->setIcon(QIcon::fromTheme(name: icon));
186 break;
187 case ButtonUp:
188 d->upIcon = icon;
189 d->btnUp->setIcon(QIcon::fromTheme(name: icon));
190 break;
191 case ButtonDown:
192 d->downIcon = icon;
193 d->btnDown->setIcon(QIcon::fromTheme(name: icon));
194 break;
195 default:
196 // qCDebug(KWidgetsAddonsLog)<<"KActionSelector::setButtonIcon: DAINBREAD!";
197 break;
198 }
199}
200
201void KActionSelector::setButtonIconSet(const QIcon &iconset, MoveButton button)
202{
203 switch (button) {
204 case ButtonAdd:
205 d->btnAdd->setIcon(iconset);
206 break;
207 case ButtonRemove:
208 d->btnRemove->setIcon(iconset);
209 break;
210 case ButtonUp:
211 d->btnUp->setIcon(iconset);
212 break;
213 case ButtonDown:
214 d->btnDown->setIcon(iconset);
215 break;
216 default:
217 // qCDebug(KWidgetsAddonsLog)<<"KActionSelector::setButtonIconSet: DAINBREAD!";
218 break;
219 }
220}
221
222void KActionSelector::setButtonTooltip(const QString &tip, MoveButton button)
223{
224 switch (button) {
225 case ButtonAdd:
226 d->btnAdd->setText(tip);
227 d->btnAdd->setToolTip(tip);
228 break;
229 case ButtonRemove:
230 d->btnRemove->setText(tip);
231 d->btnRemove->setToolTip(tip);
232 break;
233 case ButtonUp:
234 d->btnUp->setText(tip);
235 d->btnUp->setToolTip(tip);
236 break;
237 case ButtonDown:
238 d->btnDown->setText(tip);
239 d->btnDown->setToolTip(tip);
240 break;
241 default:
242 // qCDebug(KWidgetsAddonsLog)<<"KActionSelector::setButtonToolTip: DAINBREAD!";
243 break;
244 }
245}
246
247void KActionSelector::setButtonWhatsThis(const QString &text, MoveButton button)
248{
249 switch (button) {
250 case ButtonAdd:
251 d->btnAdd->setWhatsThis(text);
252 break;
253 case ButtonRemove:
254 d->btnRemove->setWhatsThis(text);
255 break;
256 case ButtonUp:
257 d->btnUp->setWhatsThis(text);
258 break;
259 case ButtonDown:
260 d->btnDown->setWhatsThis(text);
261 break;
262 default:
263 // qCDebug(KWidgetsAddonsLog)<<"KActionSelector::setButtonWhatsThis: DAINBREAD!";
264 break;
265 }
266}
267
268// END Public Methods
269
270// BEGIN Properties
271
272bool KActionSelector::moveOnDoubleClick() const
273{
274 return d->moveOnDoubleClick;
275}
276
277void KActionSelector::setMoveOnDoubleClick(bool b)
278{
279 d->moveOnDoubleClick = b;
280}
281
282bool KActionSelector::keyboardEnabled() const
283{
284 return d->keyboardEnabled;
285}
286
287void KActionSelector::setKeyboardEnabled(bool b)
288{
289 d->keyboardEnabled = b;
290}
291
292QString KActionSelector::availableLabel() const
293{
294 return d->lAvailable->text();
295}
296
297void KActionSelector::setAvailableLabel(const QString &text)
298{
299 d->lAvailable->setText(text);
300}
301
302QString KActionSelector::selectedLabel() const
303{
304 return d->lSelected->text();
305}
306
307void KActionSelector::setSelectedLabel(const QString &text)
308{
309 d->lSelected->setText(text);
310}
311
312KActionSelector::InsertionPolicy KActionSelector::availableInsertionPolicy() const
313{
314 return d->availableInsertionPolicy;
315}
316
317void KActionSelector::setAvailableInsertionPolicy(InsertionPolicy p)
318{
319 d->availableInsertionPolicy = p;
320}
321
322KActionSelector::InsertionPolicy KActionSelector::selectedInsertionPolicy() const
323{
324 return d->selectedInsertionPolicy;
325}
326
327void KActionSelector::setSelectedInsertionPolicy(InsertionPolicy p)
328{
329 d->selectedInsertionPolicy = p;
330}
331
332bool KActionSelector::showUpDownButtons() const
333{
334 return d->showUpDownButtons;
335}
336
337void KActionSelector::setShowUpDownButtons(bool show)
338{
339 d->showUpDownButtons = show;
340 if (show) {
341 d->btnUp->show();
342 d->btnDown->show();
343 } else {
344 d->btnUp->hide();
345 d->btnDown->hide();
346 }
347}
348
349// END Properties
350
351// BEGIN Public Slots
352
353void KActionSelector::setButtonsEnabled()
354{
355 d->btnAdd->setEnabled(d->selectedRowIndex(lb: d->availableListWidget) > -1);
356 d->btnRemove->setEnabled(d->selectedRowIndex(lb: d->selectedListWidget) > -1);
357 d->btnUp->setEnabled(d->selectedRowIndex(lb: d->selectedListWidget) > 0);
358 d->btnDown->setEnabled(d->selectedRowIndex(lb: d->selectedListWidget) > -1 //
359 && d->selectedRowIndex(lb: d->selectedListWidget) < d->selectedListWidget->count() - 1);
360}
361
362// END Public Slots
363
364// BEGIN Protected
365void KActionSelector::keyPressEvent(QKeyEvent *e)
366{
367 if (!d->keyboardEnabled) {
368 return;
369 }
370 if ((e->modifiers() & Qt::ControlModifier)) {
371 switch (e->key()) {
372 case Qt::Key_Right:
373 d->buttonAddClicked();
374 break;
375 case Qt::Key_Left:
376 d->buttonRemoveClicked();
377 break;
378 case Qt::Key_Up:
379 d->buttonUpClicked();
380 break;
381 case Qt::Key_Down:
382 d->buttonDownClicked();
383 break;
384 default:
385 e->ignore();
386 return;
387 }
388 }
389}
390
391bool KActionSelector::eventFilter(QObject *o, QEvent *e)
392{
393 if (d->keyboardEnabled && e->type() == QEvent::KeyPress) {
394 if ((((QKeyEvent *)e)->modifiers() & Qt::ControlModifier)) {
395 switch (((QKeyEvent *)e)->key()) {
396 case Qt::Key_Right:
397 d->buttonAddClicked();
398 break;
399 case Qt::Key_Left:
400 d->buttonRemoveClicked();
401 break;
402 case Qt::Key_Up:
403 d->buttonUpClicked();
404 break;
405 case Qt::Key_Down:
406 d->buttonDownClicked();
407 break;
408 default:
409 return QWidget::eventFilter(watched: o, event: e);
410 }
411 return true;
412 } else if (QListWidget *lb = qobject_cast<QListWidget *>(object: o)) {
413 switch (((QKeyEvent *)e)->key()) {
414 case Qt::Key_Return:
415 case Qt::Key_Enter:
416 int index = lb->currentRow();
417 if (index < 0) {
418 break;
419 }
420 d->moveItem(item: lb->item(row: index));
421 return true;
422 }
423 }
424 }
425 return QWidget::eventFilter(watched: o, event: e);
426}
427
428// END Protected
429
430// BEGIN Private Slots
431
432void KActionSelectorPrivate::buttonAddClicked()
433{
434 // move all selected items from available to selected listbox
435 const QList<QListWidgetItem *> list = availableListWidget->selectedItems();
436 for (QListWidgetItem *item : list) {
437 availableListWidget->takeItem(row: availableListWidget->row(item));
438 selectedListWidget->insertItem(row: insertionIndex(lb: selectedListWidget, policy: selectedInsertionPolicy), item);
439 selectedListWidget->setCurrentItem(item);
440 Q_EMIT q->added(item);
441 }
442 if (selectedInsertionPolicy == KActionSelector::Sorted) {
443 selectedListWidget->sortItems();
444 }
445 selectedListWidget->setFocus();
446}
447
448void KActionSelectorPrivate::buttonRemoveClicked()
449{
450 // move all selected items from selected to available listbox
451 const QList<QListWidgetItem *> list = selectedListWidget->selectedItems();
452 for (QListWidgetItem *item : list) {
453 selectedListWidget->takeItem(row: selectedListWidget->row(item));
454 availableListWidget->insertItem(row: insertionIndex(lb: availableListWidget, policy: availableInsertionPolicy), item);
455 availableListWidget->setCurrentItem(item);
456 Q_EMIT q->removed(item);
457 }
458 if (availableInsertionPolicy == KActionSelector::Sorted) {
459 availableListWidget->sortItems();
460 }
461 availableListWidget->setFocus();
462}
463
464void KActionSelectorPrivate::buttonUpClicked()
465{
466 int c = selectedRowIndex(lb: selectedListWidget);
467 if (c < 1) {
468 return;
469 }
470 QListWidgetItem *item = selectedListWidget->item(row: c);
471 selectedListWidget->takeItem(row: c);
472 selectedListWidget->insertItem(row: c - 1, item);
473 selectedListWidget->setCurrentItem(item);
474 Q_EMIT q->movedUp(item);
475}
476
477void KActionSelectorPrivate::buttonDownClicked()
478{
479 int c = selectedRowIndex(lb: selectedListWidget);
480 if (c < 0 || c == selectedListWidget->count() - 1) {
481 return;
482 }
483 QListWidgetItem *item = selectedListWidget->item(row: c);
484 selectedListWidget->takeItem(row: c);
485 selectedListWidget->insertItem(row: c + 1, item);
486 selectedListWidget->setCurrentItem(item);
487 Q_EMIT q->movedDown(item);
488}
489
490void KActionSelectorPrivate::itemDoubleClicked(QListWidgetItem *item)
491{
492 if (moveOnDoubleClick) {
493 moveItem(item);
494 }
495}
496
497// END Private Slots
498
499// BEGIN Private Methods
500
501void KActionSelectorPrivate::loadIcons()
502{
503 btnAdd->setIcon(QIcon::fromTheme(name: addIcon));
504 btnRemove->setIcon(QIcon::fromTheme(name: removeIcon));
505 btnUp->setIcon(QIcon::fromTheme(name: upIcon));
506 btnDown->setIcon(QIcon::fromTheme(name: downIcon));
507}
508
509void KActionSelectorPrivate::moveItem(QListWidgetItem *item)
510{
511 QListWidget *lbFrom = item->listWidget();
512 QListWidget *lbTo;
513 if (lbFrom == availableListWidget) {
514 lbTo = selectedListWidget;
515 } else if (lbFrom == selectedListWidget) {
516 lbTo = availableListWidget;
517 } else { //?! somewhat unlikely...
518 return;
519 }
520
521 KActionSelector::InsertionPolicy p = (lbTo == availableListWidget) ? availableInsertionPolicy : selectedInsertionPolicy;
522
523 lbFrom->takeItem(row: lbFrom->row(item));
524 lbTo->insertItem(row: insertionIndex(lb: lbTo, policy: p), item);
525 lbTo->setFocus();
526 lbTo->setCurrentItem(item);
527
528 if (p == KActionSelector::Sorted) {
529 lbTo->sortItems();
530 }
531 if (lbTo == selectedListWidget) {
532 Q_EMIT q->added(item);
533 } else {
534 Q_EMIT q->removed(item);
535 }
536}
537
538int KActionSelectorPrivate::insertionIndex(QListWidget *lb, KActionSelector::InsertionPolicy policy)
539{
540 int index;
541 switch (policy) {
542 case KActionSelector::BelowCurrent:
543 index = lb->currentRow();
544 if (index > -1) {
545 index += 1;
546 }
547 break;
548 case KActionSelector::AtTop:
549 index = 0;
550 break;
551 case KActionSelector::AtBottom:
552 index = lb->count();
553 break;
554 default:
555 index = -1;
556 }
557 return index;
558}
559
560int KActionSelectorPrivate::selectedRowIndex(QListWidget *lb)
561{
562 QList<QListWidgetItem *> list = lb->selectedItems();
563 if (list.isEmpty()) {
564 return -1;
565 }
566 return lb->row(item: list.at(i: 0));
567}
568
569// END Private Methods
570#include "moc_kactionselector.cpp"
571

source code of kwidgetsaddons/src/kactionselector.cpp