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: |
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 | |
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(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 | |
160 | KActionSelector::~KActionSelector() = default; |
161 | |
162 | // END Constructor/destroctor |
163 | |
164 | // BEGIN Public Methods |
165 | |
166 | QListWidget *KActionSelector::availableListWidget() const |
167 | { |
168 | return d->availableListWidget; |
169 | } |
170 | |
171 | QListWidget *KActionSelector::selectedListWidget() const |
172 | { |
173 | return d->selectedListWidget; |
174 | } |
175 | |
176 | void 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 | |
201 | void 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 | |
222 | void 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 | |
247 | void 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 | |
272 | bool KActionSelector::moveOnDoubleClick() const |
273 | { |
274 | return d->moveOnDoubleClick; |
275 | } |
276 | |
277 | void KActionSelector::setMoveOnDoubleClick(bool b) |
278 | { |
279 | d->moveOnDoubleClick = b; |
280 | } |
281 | |
282 | bool KActionSelector::keyboardEnabled() const |
283 | { |
284 | return d->keyboardEnabled; |
285 | } |
286 | |
287 | void KActionSelector::setKeyboardEnabled(bool b) |
288 | { |
289 | d->keyboardEnabled = b; |
290 | } |
291 | |
292 | QString KActionSelector::availableLabel() const |
293 | { |
294 | return d->lAvailable->text(); |
295 | } |
296 | |
297 | void KActionSelector::setAvailableLabel(const QString &text) |
298 | { |
299 | d->lAvailable->setText(text); |
300 | } |
301 | |
302 | QString KActionSelector::selectedLabel() const |
303 | { |
304 | return d->lSelected->text(); |
305 | } |
306 | |
307 | void KActionSelector::setSelectedLabel(const QString &text) |
308 | { |
309 | d->lSelected->setText(text); |
310 | } |
311 | |
312 | KActionSelector::InsertionPolicy KActionSelector::availableInsertionPolicy() const |
313 | { |
314 | return d->availableInsertionPolicy; |
315 | } |
316 | |
317 | void KActionSelector::setAvailableInsertionPolicy(InsertionPolicy p) |
318 | { |
319 | d->availableInsertionPolicy = p; |
320 | } |
321 | |
322 | KActionSelector::InsertionPolicy KActionSelector::selectedInsertionPolicy() const |
323 | { |
324 | return d->selectedInsertionPolicy; |
325 | } |
326 | |
327 | void KActionSelector::setSelectedInsertionPolicy(InsertionPolicy p) |
328 | { |
329 | d->selectedInsertionPolicy = p; |
330 | } |
331 | |
332 | bool KActionSelector::showUpDownButtons() const |
333 | { |
334 | return d->showUpDownButtons; |
335 | } |
336 | |
337 | void 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 | |
353 | void 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 |
365 | void 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 | |
391 | bool 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 | |
432 | void 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 | |
448 | void 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 | |
464 | void 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 | |
477 | void 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 | |
490 | void KActionSelectorPrivate::itemDoubleClicked(QListWidgetItem *item) |
491 | { |
492 | if (moveOnDoubleClick) { |
493 | moveItem(item); |
494 | } |
495 | } |
496 | |
497 | // END Private Slots |
498 | |
499 | // BEGIN Private Methods |
500 | |
501 | void 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 | |
509 | void 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 | |
538 | int 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 | |
560 | int 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 | |