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) { 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 | |
156 | KActionSelector::~KActionSelector() = default; |
157 | |
158 | // END Constructor/destroctor |
159 | |
160 | // BEGIN Public Methods |
161 | |
162 | QListWidget *KActionSelector::availableListWidget() const |
163 | { |
164 | return d->availableListWidget; |
165 | } |
166 | |
167 | QListWidget *KActionSelector::selectedListWidget() const |
168 | { |
169 | return d->selectedListWidget; |
170 | } |
171 | |
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(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 | |
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 | |
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 | |
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 | |
268 | bool KActionSelector::moveOnDoubleClick() const |
269 | { |
270 | return d->moveOnDoubleClick; |
271 | } |
272 | |
273 | void KActionSelector::setMoveOnDoubleClick(bool b) |
274 | { |
275 | d->moveOnDoubleClick = b; |
276 | } |
277 | |
278 | bool KActionSelector::keyboardEnabled() const |
279 | { |
280 | return d->keyboardEnabled; |
281 | } |
282 | |
283 | void KActionSelector::setKeyboardEnabled(bool b) |
284 | { |
285 | d->keyboardEnabled = b; |
286 | } |
287 | |
288 | QString KActionSelector::availableLabel() const |
289 | { |
290 | return d->lAvailable->text(); |
291 | } |
292 | |
293 | void KActionSelector::setAvailableLabel(const QString &text) |
294 | { |
295 | d->lAvailable->setText(text); |
296 | } |
297 | |
298 | QString KActionSelector::selectedLabel() const |
299 | { |
300 | return d->lSelected->text(); |
301 | } |
302 | |
303 | void KActionSelector::setSelectedLabel(const QString &text) |
304 | { |
305 | d->lSelected->setText(text); |
306 | } |
307 | |
308 | KActionSelector::InsertionPolicy KActionSelector::availableInsertionPolicy() const |
309 | { |
310 | return d->availableInsertionPolicy; |
311 | } |
312 | |
313 | void KActionSelector::setAvailableInsertionPolicy(InsertionPolicy p) |
314 | { |
315 | d->availableInsertionPolicy = p; |
316 | } |
317 | |
318 | KActionSelector::InsertionPolicy KActionSelector::selectedInsertionPolicy() const |
319 | { |
320 | return d->selectedInsertionPolicy; |
321 | } |
322 | |
323 | void KActionSelector::setSelectedInsertionPolicy(InsertionPolicy p) |
324 | { |
325 | d->selectedInsertionPolicy = p; |
326 | } |
327 | |
328 | bool KActionSelector::showUpDownButtons() const |
329 | { |
330 | return d->showUpDownButtons; |
331 | } |
332 | |
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 | |
349 | void 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 |
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 | |
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(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 | |
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(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 | |
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(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 | |
460 | void 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 | |
473 | void 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 | |
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 | |
497 | void 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 | |
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(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 | |
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 | |
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(item: list.at(i: 0)); |
563 | } |
564 | |
565 | // END Private Methods |
566 | #include "moc_kactionselector.cpp" |
567 | |