Files
RedBear-OS/local/recipes/kde/kf6-kwidgetsaddons/source/src/keditlistwidget.cpp
T
2026-04-14 10:51:06 +01:00

655 lines
19 KiB
C++

/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2000 Alexander Neundorf <neundorf@kde.org>
SPDX-FileCopyrightText: 2000, 2002 Carsten Pfeiffer <pfeiffer@kde.org>
SPDX-FileCopyrightText: 2010 Sebastian Trueg <trueg@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "keditlistwidget.h"
#include <QApplication>
#include <QComboBox>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QLineEdit>
#include <QListView>
#include <QPushButton>
#include <QStringList>
#include <QStringListModel>
#include <QVBoxLayout>
class KEditListWidgetPrivate
{
public:
KEditListWidgetPrivate(KEditListWidget *parent)
: q(parent)
{
}
QListView *listView = nullptr;
QPushButton *servUpButton = nullptr;
QPushButton *servDownButton = nullptr;
QPushButton *servNewButton = nullptr;
QPushButton *servRemoveButton = nullptr;
QLineEdit *lineEdit = nullptr;
QWidget *editingWidget = nullptr;
QVBoxLayout *mainLayout = nullptr;
QVBoxLayout *btnsLayout = nullptr;
QStringListModel *model = nullptr;
bool checkAtEntering;
KEditListWidget::Buttons buttons;
void init(bool check = false, KEditListWidget::Buttons buttons = KEditListWidget::All, QWidget *representationWidget = nullptr);
void setEditor(QLineEdit *lineEdit, QWidget *representationWidget = nullptr);
void updateButtonState();
QModelIndex selectedIndex();
private:
KEditListWidget *const q;
};
void KEditListWidgetPrivate::init(bool check, KEditListWidget::Buttons newButtons, QWidget *representationWidget)
{
checkAtEntering = check;
servNewButton = servRemoveButton = servUpButton = servDownButton = nullptr;
q->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred));
mainLayout = new QVBoxLayout(q);
mainLayout->setContentsMargins(0, 0, 0, 0);
QHBoxLayout *subLayout = new QHBoxLayout;
btnsLayout = new QVBoxLayout;
btnsLayout->addStretch();
model = new QStringListModel(q);
listView = new QListView(q);
listView->setModel(model);
subLayout->addWidget(listView);
subLayout->addLayout(btnsLayout);
mainLayout->addLayout(subLayout);
setEditor(lineEdit, representationWidget);
buttons = KEditListWidget::Buttons();
q->setButtons(newButtons);
q->connect(listView->selectionModel(), &QItemSelectionModel::selectionChanged, q, &KEditListWidget::slotSelectionChanged);
}
void KEditListWidgetPrivate::setEditor(QLineEdit *newLineEdit, QWidget *representationWidget)
{
if (editingWidget != lineEdit && editingWidget != representationWidget) {
delete editingWidget;
}
if (lineEdit != newLineEdit) {
delete lineEdit;
}
lineEdit = newLineEdit ? newLineEdit : new QLineEdit(q);
editingWidget = representationWidget ? representationWidget : lineEdit;
if (representationWidget) {
representationWidget->setParent(q);
}
mainLayout->insertWidget(0, editingWidget); // before subLayout
lineEdit->installEventFilter(q);
q->connect(lineEdit, &QLineEdit::textChanged, q, &KEditListWidget::typedSomething);
q->connect(lineEdit, &QLineEdit::returnPressed, q, &KEditListWidget::addItem);
// maybe supplied lineedit has some text already
q->typedSomething(lineEdit->text());
// fix tab ordering
q->setTabOrder(editingWidget, listView);
QWidget *w = listView;
if (servNewButton) {
q->setTabOrder(w, servNewButton);
w = servNewButton;
}
if (servRemoveButton) {
q->setTabOrder(w, servRemoveButton);
w = servRemoveButton;
}
if (servUpButton) {
q->setTabOrder(w, servUpButton);
w = servUpButton;
}
if (servDownButton) {
q->setTabOrder(w, servDownButton);
w = servDownButton;
}
}
void KEditListWidgetPrivate::updateButtonState()
{
const bool hasSelectedItem = selectedIndex().isValid();
// TODO: merge with enableMoveButtons()
QPushButton *const buttons[3] = {servUpButton, servDownButton, servRemoveButton};
for (QPushButton *button : buttons) {
if (button) {
// keep focus in widget
if (!hasSelectedItem && button->hasFocus()) {
lineEdit->setFocus(Qt::OtherFocusReason);
}
button->setEnabled(hasSelectedItem);
}
}
}
QModelIndex KEditListWidgetPrivate::selectedIndex()
{
QItemSelectionModel *selection = listView->selectionModel();
const QModelIndexList selectedIndexes = selection->selectedIndexes();
if (!selectedIndexes.isEmpty() && selectedIndexes[0].isValid()) {
return selectedIndexes[0];
} else {
return QModelIndex();
}
}
class KEditListWidgetCustomEditorPrivate
{
public:
KEditListWidgetCustomEditorPrivate(KEditListWidget::CustomEditor *qq)
: q(qq)
, representationWidget(nullptr)
, lineEdit(nullptr)
{
}
KEditListWidget::CustomEditor *q;
QWidget *representationWidget;
QLineEdit *lineEdit;
};
KEditListWidget::CustomEditor::CustomEditor()
: d(new KEditListWidgetCustomEditorPrivate(this))
{
}
KEditListWidget::CustomEditor::CustomEditor(QWidget *repWidget, QLineEdit *edit)
: d(new KEditListWidgetCustomEditorPrivate(this))
{
d->representationWidget = repWidget;
d->lineEdit = edit;
}
KEditListWidget::CustomEditor::CustomEditor(QComboBox *combo)
: d(new KEditListWidgetCustomEditorPrivate(this))
{
d->representationWidget = combo;
d->lineEdit = qobject_cast<QLineEdit *>(combo->lineEdit());
Q_ASSERT(d->lineEdit);
}
KEditListWidget::CustomEditor::~CustomEditor() = default;
void KEditListWidget::CustomEditor::setRepresentationWidget(QWidget *repWidget)
{
d->representationWidget = repWidget;
}
void KEditListWidget::CustomEditor::setLineEdit(QLineEdit *edit)
{
d->lineEdit = edit;
}
QWidget *KEditListWidget::CustomEditor::representationWidget() const
{
return d->representationWidget;
}
QLineEdit *KEditListWidget::CustomEditor::lineEdit() const
{
return d->lineEdit;
}
KEditListWidget::KEditListWidget(QWidget *parent)
: QWidget(parent)
, d(new KEditListWidgetPrivate(this))
{
d->init();
}
KEditListWidget::KEditListWidget(const CustomEditor &custom, QWidget *parent, bool checkAtEntering, Buttons buttons)
: QWidget(parent)
, d(new KEditListWidgetPrivate(this))
{
d->lineEdit = custom.lineEdit();
d->init(checkAtEntering, buttons, custom.representationWidget());
}
KEditListWidget::~KEditListWidget() = default;
void KEditListWidget::setCustomEditor(const CustomEditor &editor)
{
d->setEditor(editor.lineEdit(), editor.representationWidget());
}
QListView *KEditListWidget::listView() const
{
return d->listView;
}
QLineEdit *KEditListWidget::lineEdit() const
{
return d->lineEdit;
}
QPushButton *KEditListWidget::addButton() const
{
return d->servNewButton;
}
QPushButton *KEditListWidget::removeButton() const
{
return d->servRemoveButton;
}
QPushButton *KEditListWidget::upButton() const
{
return d->servUpButton;
}
QPushButton *KEditListWidget::downButton() const
{
return d->servDownButton;
}
int KEditListWidget::count() const
{
return int(d->model->rowCount());
}
void KEditListWidget::setButtons(Buttons buttons)
{
if (d->buttons == buttons) {
return;
}
if ((buttons & Add) && !d->servNewButton) {
d->servNewButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add")), tr("&Add", "@action:button"), this);
d->servNewButton->setEnabled(false);
d->servNewButton->show();
connect(d->servNewButton, &QAbstractButton::clicked, this, &KEditListWidget::addItem);
d->btnsLayout->insertWidget(0, d->servNewButton);
} else if ((buttons & Add) == 0 && d->servNewButton) {
delete d->servNewButton;
d->servNewButton = nullptr;
}
if ((buttons & Remove) && !d->servRemoveButton) {
d->servRemoveButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-remove")), tr("&Remove", "@action:button"), this);
d->servRemoveButton->setEnabled(false);
d->servRemoveButton->show();
connect(d->servRemoveButton, &QAbstractButton::clicked, this, &KEditListWidget::removeItem);
d->btnsLayout->insertWidget(1, d->servRemoveButton);
} else if ((buttons & Remove) == 0 && d->servRemoveButton) {
delete d->servRemoveButton;
d->servRemoveButton = nullptr;
}
if ((buttons & UpDown) && !d->servUpButton) {
d->servUpButton = new QPushButton(QIcon::fromTheme(QStringLiteral("arrow-up")), tr("Move &Up", "@action:button"), this);
d->servUpButton->setEnabled(false);
d->servUpButton->show();
connect(d->servUpButton, &QAbstractButton::clicked, this, &KEditListWidget::moveItemUp);
d->servDownButton = new QPushButton(QIcon::fromTheme(QStringLiteral("arrow-down")), tr("Move &Down", "@action:button"), this);
d->servDownButton->setEnabled(false);
d->servDownButton->show();
connect(d->servDownButton, &QAbstractButton::clicked, this, &KEditListWidget::moveItemDown);
d->btnsLayout->insertWidget(2, d->servUpButton);
d->btnsLayout->insertWidget(3, d->servDownButton);
} else if ((buttons & UpDown) == 0 && d->servUpButton) {
delete d->servUpButton;
d->servUpButton = nullptr;
delete d->servDownButton;
d->servDownButton = nullptr;
}
d->buttons = buttons;
}
void KEditListWidget::setCheckAtEntering(bool check)
{
d->checkAtEntering = check;
}
bool KEditListWidget::checkAtEntering()
{
return d->checkAtEntering;
}
void KEditListWidget::typedSomething(const QString &text)
{
if (currentItem() >= 0) {
if (currentText() != d->lineEdit->text()) {
// IMHO changeItem() shouldn't do anything with the value
// of currentItem() ... like changing it or emitting signals ...
// but TT disagree with me on this one (it's been that way since ages ... grrr)
bool block = d->listView->signalsBlocked();
d->listView->blockSignals(true);
QModelIndex currentIndex = d->selectedIndex();
if (currentIndex.isValid()) {
d->model->setData(currentIndex, text);
}
d->listView->blockSignals(block);
Q_EMIT changed();
}
}
if (!d->servNewButton) {
return;
}
if (!d->lineEdit->hasAcceptableInput()) {
d->servNewButton->setEnabled(false);
return;
}
if (!d->checkAtEntering) {
d->servNewButton->setEnabled(!text.isEmpty());
} else {
if (text.isEmpty()) {
d->servNewButton->setEnabled(false);
} else {
QStringList list = d->model->stringList();
bool enable = !list.contains(text, Qt::CaseSensitive);
d->servNewButton->setEnabled(enable);
}
}
}
void KEditListWidget::moveItemUp()
{
if (!d->listView->isEnabled()) {
QApplication::beep();
return;
}
QModelIndex index = d->selectedIndex();
if (index.isValid()) {
if (index.row() == 0) {
QApplication::beep();
return;
}
QModelIndex aboveIndex = d->model->index(index.row() - 1, index.column());
QString tmp = d->model->data(aboveIndex, Qt::DisplayRole).toString();
d->model->setData(aboveIndex, d->model->data(index, Qt::DisplayRole));
d->model->setData(index, tmp);
d->listView->selectionModel()->select(index, QItemSelectionModel::Deselect);
d->listView->selectionModel()->select(aboveIndex, QItemSelectionModel::Select);
}
Q_EMIT changed();
}
void KEditListWidget::moveItemDown()
{
if (!d->listView->isEnabled()) {
QApplication::beep();
return;
}
QModelIndex index = d->selectedIndex();
if (index.isValid()) {
if (index.row() == d->model->rowCount() - 1) {
QApplication::beep();
return;
}
QModelIndex belowIndex = d->model->index(index.row() + 1, index.column());
QString tmp = d->model->data(belowIndex, Qt::DisplayRole).toString();
d->model->setData(belowIndex, d->model->data(index, Qt::DisplayRole));
d->model->setData(index, tmp);
d->listView->selectionModel()->select(index, QItemSelectionModel::Deselect);
d->listView->selectionModel()->select(belowIndex, QItemSelectionModel::Select);
}
Q_EMIT changed();
}
void KEditListWidget::addItem()
{
// when checkAtEntering is true, the add-button is disabled, but this
// slot can still be called through Key_Return/Key_Enter. So we guard
// against this.
if (!d->servNewButton || !d->servNewButton->isEnabled()) {
return;
}
QModelIndex currentIndex = d->selectedIndex();
const QString &currentTextLE = d->lineEdit->text();
bool alreadyInList(false);
// if we didn't check for dupes at the inserting we have to do it now
if (!d->checkAtEntering) {
// first check current item instead of dumb iterating the entire list
if (currentIndex.isValid()) {
if (d->model->data(currentIndex, Qt::DisplayRole).toString() == currentTextLE) {
alreadyInList = true;
}
} else {
alreadyInList = d->model->stringList().contains(currentTextLE, Qt::CaseSensitive);
}
}
if (d->servNewButton) {
// prevent losing the focus by it being moved outside of this widget
// as well as support the user workflow a little by moving the focus
// to the lineedit. chances are that users will add some items consecutively,
// so this will save a manual focus change, and it is also consistent
// to what happens on the click on the Remove button
if (d->servNewButton->hasFocus()) {
d->lineEdit->setFocus(Qt::OtherFocusReason);
}
d->servNewButton->setEnabled(false);
}
bool block = d->lineEdit->signalsBlocked();
d->lineEdit->blockSignals(true);
d->lineEdit->clear();
d->lineEdit->blockSignals(block);
d->listView->selectionModel()->setCurrentIndex(currentIndex, QItemSelectionModel::Deselect);
if (!alreadyInList) {
block = d->listView->signalsBlocked();
if (currentIndex.isValid()) {
d->model->setData(currentIndex, currentTextLE);
} else {
QStringList lst;
lst << currentTextLE;
lst << d->model->stringList();
d->model->setStringList(lst);
}
Q_EMIT changed();
Q_EMIT added(currentTextLE); // TODO: pass the index too
}
d->updateButtonState();
}
int KEditListWidget::currentItem() const
{
QModelIndex selectedIndex = d->selectedIndex();
if (selectedIndex.isValid()) {
return selectedIndex.row();
} else {
return -1;
}
}
void KEditListWidget::removeItem()
{
QModelIndex currentIndex = d->selectedIndex();
if (!currentIndex.isValid()) {
return;
}
if (currentIndex.row() >= 0) {
// prevent losing the focus by it being moved outside of this widget
// as well as support the user workflow a little by moving the focus
// to the lineedit. chances are that users will add some item next,
// so this will save a manual focus change,
if (d->servRemoveButton && d->servRemoveButton->hasFocus()) {
d->lineEdit->setFocus(Qt::OtherFocusReason);
}
QString removedText = d->model->data(currentIndex, Qt::DisplayRole).toString();
d->model->removeRows(currentIndex.row(), 1);
d->listView->selectionModel()->clear();
Q_EMIT changed();
Q_EMIT removed(removedText);
}
d->updateButtonState();
}
void KEditListWidget::enableMoveButtons(const QModelIndex &newIndex, const QModelIndex &)
{
int index = newIndex.row();
// Update the lineEdit when we select a different line.
if (currentText() != d->lineEdit->text()) {
d->lineEdit->setText(currentText());
}
bool moveEnabled = d->servUpButton && d->servDownButton;
if (moveEnabled) {
if (d->model->rowCount() <= 1) {
d->servUpButton->setEnabled(false);
d->servDownButton->setEnabled(false);
} else if (index == (d->model->rowCount() - 1)) {
d->servUpButton->setEnabled(true);
d->servDownButton->setEnabled(false);
} else if (index == 0) {
d->servUpButton->setEnabled(false);
d->servDownButton->setEnabled(true);
} else {
d->servUpButton->setEnabled(true);
d->servDownButton->setEnabled(true);
}
}
if (d->servRemoveButton) {
d->servRemoveButton->setEnabled(true);
}
}
void KEditListWidget::clear()
{
d->lineEdit->clear();
d->model->setStringList(QStringList());
Q_EMIT changed();
}
void KEditListWidget::insertStringList(const QStringList &list, int index)
{
QStringList content = d->model->stringList();
if (index < 0) {
content += list;
} else {
for (int i = 0, j = index; i < list.count(); ++i, ++j) {
content.insert(j, list[i]);
}
}
d->model->setStringList(content);
}
void KEditListWidget::insertItem(const QString &text, int index)
{
QStringList list = d->model->stringList();
if (index < 0) {
list.append(text);
} else {
list.insert(index, text);
}
d->model->setStringList(list);
}
QString KEditListWidget::text(int index) const
{
const QStringList list = d->model->stringList();
return list[index];
}
QString KEditListWidget::currentText() const
{
QModelIndex index = d->selectedIndex();
if (!index.isValid()) {
return QString();
} else {
return text(index.row());
}
}
QStringList KEditListWidget::items() const
{
return d->model->stringList();
}
void KEditListWidget::setItems(const QStringList &items)
{
d->model->setStringList(items);
}
KEditListWidget::Buttons KEditListWidget::buttons() const
{
return d->buttons;
}
void KEditListWidget::slotSelectionChanged(const QItemSelection &, const QItemSelection &)
{
d->updateButtonState();
QModelIndex index = d->selectedIndex();
enableMoveButtons(index, QModelIndex());
if (index.isValid()) {
d->lineEdit->setFocus(Qt::OtherFocusReason);
}
}
bool KEditListWidget::eventFilter(QObject *o, QEvent *e)
{
if (o == d->lineEdit && e->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = (QKeyEvent *)e;
if (keyEvent->key() == Qt::Key_Down || keyEvent->key() == Qt::Key_Up) {
return ((QObject *)d->listView)->event(e);
} else if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) {
return true;
}
}
return false;
}
#include "moc_keditlistwidget.cpp"