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