kwin/kcmkwin/kwindecoration/kcm.cpp
Marco Martin 299d23230c move the view at the correct index at startup
Summary:
using the same trick as elsewhere, set the currentIndex
and move the view to currentIndex right at startup
the only way to be sure is onContentHeightChanged
as there are no signals for when "the view has been
populated and settled up"

Test Plan:
the view is at the right state since the first frame shown,
no more jumping around effect

Reviewers: #plasma, davidedmundson

Reviewed By: #plasma, davidedmundson

Subscribers: davidedmundson, plasma-devel, kwin, #kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D4703
2017-02-24 15:49:43 +01:00

438 lines
18 KiB
C++

/*
* Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "kcm.h"
#include "decorationmodel.h"
#include "declarative-plugin/buttonsmodel.h"
#include <config-kwin.h>
// KDE
#include <KConfigGroup>
#include <KPluginFactory>
#include <KSharedConfig>
#include <KDecoration2/DecorationButton>
#include <KNewStuff3/KNS3/DownloadDialog>
#include <kdeclarative/kdeclarative.h>
// Qt
#include <QDBusConnection>
#include <QDBusMessage>
#include <QFontDatabase>
#include <QMenu>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickItem>
#include <QQuickView>
#include <QSortFilterProxyModel>
#include <QStandardPaths>
#include <QVBoxLayout>
K_PLUGIN_FACTORY(KDecorationFactory,
registerPlugin<KDecoration2::Configuration::ConfigurationModule>();
)
Q_DECLARE_METATYPE(KDecoration2::BorderSize)
namespace KDecoration2
{
namespace Configuration
{
static const QString s_pluginName = QStringLiteral("org.kde.kdecoration2");
#if HAVE_BREEZE_DECO
static const QString s_defaultPlugin = QStringLiteral(BREEZE_KDECORATION_PLUGIN_ID);
static const QString s_defaultTheme;
#else
static const QString s_defaultPlugin = QStringLiteral("org.kde.kwin.aurorae");
static const QString s_defaultTheme = QStringLiteral("kwin4_decoration_qml_plastik");
#endif
static const QString s_borderSizeNormal = QStringLiteral("Normal");
static const QString s_ghnsIcon = QStringLiteral("get-hot-new-stuff");
ConfigurationForm::ConfigurationForm(QWidget *parent)
: QWidget(parent)
{
setupUi(this);
}
static bool s_loading = false;
ConfigurationModule::ConfigurationModule(QWidget *parent, const QVariantList &args)
: KCModule(parent, args)
, m_model(new DecorationsModel(this))
, m_proxyModel(new QSortFilterProxyModel(this))
, m_ui(new ConfigurationForm(this))
, m_leftButtons(new Preview::ButtonsModel(QVector<DecorationButtonType>(), this))
, m_rightButtons(new Preview::ButtonsModel(QVector<DecorationButtonType>(), this))
, m_availableButtons(new Preview::ButtonsModel(this))
{
m_proxyModel->setSourceModel(m_model);
m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
connect(m_ui->filter, &QLineEdit::textChanged, m_proxyModel, &QSortFilterProxyModel::setFilterFixedString);
m_quickView = new QQuickView(0);
KDeclarative::KDeclarative kdeclarative;
kdeclarative.setDeclarativeEngine(m_quickView->engine());
kdeclarative.setTranslationDomain(QStringLiteral(TRANSLATION_DOMAIN));
kdeclarative.setupBindings();
qmlRegisterType<QAbstractItemModel>();
QWidget *widget = QWidget::createWindowContainer(m_quickView, this);
QVBoxLayout* layout = new QVBoxLayout(m_ui->view);
layout->setContentsMargins(0,0,0,0);
layout->addWidget(widget);
m_quickView->rootContext()->setContextProperty(QStringLiteral("decorationsModel"), m_proxyModel);
updateColors();
m_quickView->rootContext()->setContextProperty("savedIndex", savedIndex());
m_quickView->rootContext()->setContextProperty("_borderSizesIndex", 3); // 3 is normal
m_quickView->rootContext()->setContextProperty("leftButtons", m_leftButtons);
m_quickView->rootContext()->setContextProperty("rightButtons", m_rightButtons);
m_quickView->rootContext()->setContextProperty("availableButtons", m_availableButtons);
m_quickView->rootContext()->setContextProperty("titleFont", QFontDatabase::systemFont(QFontDatabase::TitleFont));
m_quickView->setResizeMode(QQuickView::SizeRootObjectToView);
m_quickView->setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/kcm_kwindecoration/main.qml"))));
if (m_quickView->status() == QQuickView::Ready) {
auto listView = m_quickView->rootObject()->findChild<QQuickItem*>("listView");
if (listView) {
connect(listView, SIGNAL(currentIndexChanged()), this, SLOT(changed()));
}
}
m_ui->tabWidget->tabBar()->disconnect();
auto setCurrentTab = [this](int index) {
if (index == 0)
m_ui->doubleClickMessage->hide();
m_ui->filter->setVisible(index == 0);
m_ui->knsButton->setVisible(index == 0);
if (auto themeList = m_quickView->rootObject()->findChild<QQuickItem*>("themeList")) {
themeList->setVisible(index == 0);
}
m_ui->borderSizesLabel->setVisible(index == 0);
m_ui->borderSizesCombo->setVisible(index == 0);
m_ui->closeWindowsDoubleClick->setVisible(index == 1);
if (auto buttonLayout = m_quickView->rootObject()->findChild<QQuickItem*>("buttonLayout")) {
buttonLayout->setVisible(index == 1);
}
};
connect(m_ui->tabWidget->tabBar(), &QTabBar::currentChanged, this, setCurrentTab);
setCurrentTab(0);
m_ui->doubleClickMessage->setVisible(false);
m_ui->doubleClickMessage->setText(i18n("Close by double clicking:\n To open the menu, keep the button pressed until it appears."));
m_ui->doubleClickMessage->setCloseButtonVisible(true);
m_ui->borderSizesCombo->setItemData(0, QVariant::fromValue(BorderSize::None));
m_ui->borderSizesCombo->setItemData(1, QVariant::fromValue(BorderSize::NoSides));
m_ui->borderSizesCombo->setItemData(2, QVariant::fromValue(BorderSize::Tiny));
m_ui->borderSizesCombo->setItemData(3, QVariant::fromValue(BorderSize::Normal));
m_ui->borderSizesCombo->setItemData(4, QVariant::fromValue(BorderSize::Large));
m_ui->borderSizesCombo->setItemData(5, QVariant::fromValue(BorderSize::VeryLarge));
m_ui->borderSizesCombo->setItemData(6, QVariant::fromValue(BorderSize::Huge));
m_ui->borderSizesCombo->setItemData(7, QVariant::fromValue(BorderSize::VeryHuge));
m_ui->borderSizesCombo->setItemData(8, QVariant::fromValue(BorderSize::Oversized));
m_ui->knsButton->setIcon(QIcon::fromTheme(s_ghnsIcon));
auto changedSlot = static_cast<void (ConfigurationModule::*)()>(&ConfigurationModule::changed);
connect(m_ui->closeWindowsDoubleClick, &QCheckBox::stateChanged, this, changedSlot);
connect(m_ui->closeWindowsDoubleClick, &QCheckBox::toggled, this,
[this] (bool toggled) {
if (s_loading) {
return;
}
if (toggled)
m_ui->doubleClickMessage->animatedShow();
else
m_ui->doubleClickMessage->animatedHide();
}
);
connect(m_ui->borderSizesCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, [this] (int index) {
auto listView = m_quickView->rootObject()->findChild<QQuickItem*>("listView");
if (listView) {
listView->setProperty("borderSizesIndex", index);
}
changed();
}
);
connect(m_model, &QAbstractItemModel::modelReset, this,
[this] {
const auto &kns = m_model->knsProviders();
m_ui->knsButton->setEnabled(!kns.isEmpty());
if (kns.isEmpty()) {
return;
}
if (kns.count() > 1) {
QMenu *menu = new QMenu(m_ui->knsButton);
for (auto it = kns.begin(); it != kns.end(); ++it) {
QAction *action = menu->addAction(QIcon::fromTheme(s_ghnsIcon), it.value());
action->setData(it.key());
connect(action, &QAction::triggered, this, [this, action] { showKNS(action->data().toString());});
}
m_ui->knsButton->setMenu(menu);
}
}
);
connect(m_ui->knsButton, &QPushButton::clicked, this,
[this] {
const auto &kns = m_model->knsProviders();
if (kns.isEmpty()) {
return;
}
showKNS(kns.firstKey());
}
);
connect(m_leftButtons, &QAbstractItemModel::rowsInserted, this, changedSlot);
connect(m_leftButtons, &QAbstractItemModel::rowsMoved, this, changedSlot);
connect(m_leftButtons, &QAbstractItemModel::rowsRemoved, this, changedSlot);
connect(m_rightButtons, &QAbstractItemModel::rowsInserted, this, changedSlot);
connect(m_rightButtons, &QAbstractItemModel::rowsMoved, this, changedSlot);
connect(m_rightButtons, &QAbstractItemModel::rowsRemoved, this, changedSlot);
QVBoxLayout *l = new QVBoxLayout(this);
l->addWidget(m_ui);
QMetaObject::invokeMethod(m_model, "init", Qt::QueuedConnection);
m_ui->installEventFilter(this);
}
ConfigurationModule::~ConfigurationModule() = default;
void ConfigurationModule::showEvent(QShowEvent *ev)
{
KCModule::showEvent(ev);
}
static const QMap<QString, KDecoration2::BorderSize> s_sizes = QMap<QString, KDecoration2::BorderSize>({
{QStringLiteral("None"), BorderSize::None},
{QStringLiteral("NoSides"), BorderSize::NoSides},
{QStringLiteral("Tiny"), BorderSize::Tiny},
{s_borderSizeNormal, BorderSize::Normal},
{QStringLiteral("Large"), BorderSize::Large},
{QStringLiteral("VeryLarge"), BorderSize::VeryLarge},
{QStringLiteral("Huge"), BorderSize::Huge},
{QStringLiteral("VeryHuge"), BorderSize::VeryHuge},
{QStringLiteral("Oversized"), BorderSize::Oversized}
});
static BorderSize stringToSize(const QString &name)
{
auto it = s_sizes.constFind(name);
if (it == s_sizes.constEnd()) {
// non sense values are interpreted just like normal
return BorderSize::Normal;
}
return it.value();
}
static QString sizeToString(BorderSize size)
{
return s_sizes.key(size, s_borderSizeNormal);
}
static QHash<KDecoration2::DecorationButtonType, QChar> s_buttonNames;
static void initButtons()
{
if (!s_buttonNames.isEmpty()) {
return;
}
s_buttonNames[KDecoration2::DecorationButtonType::Menu] = QChar('M');
s_buttonNames[KDecoration2::DecorationButtonType::ApplicationMenu] = QChar('N');
s_buttonNames[KDecoration2::DecorationButtonType::OnAllDesktops] = QChar('S');
s_buttonNames[KDecoration2::DecorationButtonType::ContextHelp] = QChar('H');
s_buttonNames[KDecoration2::DecorationButtonType::Minimize] = QChar('I');
s_buttonNames[KDecoration2::DecorationButtonType::Maximize] = QChar('A');
s_buttonNames[KDecoration2::DecorationButtonType::Close] = QChar('X');
s_buttonNames[KDecoration2::DecorationButtonType::KeepAbove] = QChar('F');
s_buttonNames[KDecoration2::DecorationButtonType::KeepBelow] = QChar('B');
s_buttonNames[KDecoration2::DecorationButtonType::Shade] = QChar('L');
}
static QString buttonsToString(const QVector<KDecoration2::DecorationButtonType> &buttons)
{
auto buttonToString = [](KDecoration2::DecorationButtonType button) -> QChar {
const auto it = s_buttonNames.constFind(button);
if (it != s_buttonNames.constEnd()) {
return it.value();
}
return QChar();
};
QString ret;
for (auto button : buttons) {
ret.append(buttonToString(button));
}
return ret;
}
static
QVector< KDecoration2::DecorationButtonType > readDecorationButtons(const KConfigGroup &config,
const char *key,
const QVector< KDecoration2::DecorationButtonType > &defaultValue)
{
initButtons();
auto buttonsFromString = [](const QString &buttons) -> QVector<KDecoration2::DecorationButtonType> {
QVector<KDecoration2::DecorationButtonType> ret;
for (auto it = buttons.begin(); it != buttons.end(); ++it) {
for (auto it2 = s_buttonNames.constBegin(); it2 != s_buttonNames.constEnd(); ++it2) {
if (it2.value() == (*it)) {
ret << it2.key();
}
}
}
return ret;
};
return buttonsFromString(config.readEntry(key, buttonsToString(defaultValue)));
}
int ConfigurationModule::savedIndex() const
{
const KConfigGroup config = KSharedConfig::openConfig("kwinrc")->group(s_pluginName);
const QString plugin = config.readEntry("library", s_defaultPlugin);
const QString theme = config.readEntry("theme", s_defaultTheme);
return m_proxyModel->mapFromSource(m_model->findDecoration(plugin, theme)).row();
}
void ConfigurationModule::load()
{
s_loading = true;
const KConfigGroup config = KSharedConfig::openConfig("kwinrc")->group(s_pluginName);
const QString plugin = config.readEntry("library", s_defaultPlugin);
const QString theme = config.readEntry("theme", s_defaultTheme);
m_ui->closeWindowsDoubleClick->setChecked(config.readEntry("CloseOnDoubleClickOnMenu", false));
const QVariant border = QVariant::fromValue(stringToSize(config.readEntry("BorderSize", s_borderSizeNormal)));
m_ui->borderSizesCombo->setCurrentIndex(m_ui->borderSizesCombo->findData(border));
// buttons
const auto &left = readDecorationButtons(config, "ButtonsOnLeft", QVector<KDecoration2::DecorationButtonType >{
KDecoration2::DecorationButtonType::Menu,
KDecoration2::DecorationButtonType::OnAllDesktops
});
while (m_leftButtons->rowCount() > 0) {
m_leftButtons->remove(0);
}
for (auto it = left.begin(); it != left.end(); ++it) {
m_leftButtons->add(*it);
}
const auto &right = readDecorationButtons(config, "ButtonsOnRight", QVector<KDecoration2::DecorationButtonType >{
KDecoration2::DecorationButtonType::ContextHelp,
KDecoration2::DecorationButtonType::Minimize,
KDecoration2::DecorationButtonType::Maximize,
KDecoration2::DecorationButtonType::Close
});
while (m_rightButtons->rowCount() > 0) {
m_rightButtons->remove(0);
}
for (auto it = right.begin(); it != right.end(); ++it) {
m_rightButtons->add(*it);
}
KCModule::load();
s_loading = false;
}
void ConfigurationModule::save()
{
KConfigGroup config = KSharedConfig::openConfig("kwinrc")->group(s_pluginName);
config.writeEntry("CloseOnDoubleClickOnMenu", m_ui->closeWindowsDoubleClick->isChecked());
config.writeEntry("BorderSize", sizeToString(m_ui->borderSizesCombo->currentData().value<BorderSize>()));
if (auto listView = m_quickView->rootObject()->findChild<QQuickItem*>("listView")) {
const int currentIndex = listView->property("currentIndex").toInt();
if (currentIndex != -1) {
const QModelIndex index = m_proxyModel->index(currentIndex, 0);
if (index.isValid()) {
config.writeEntry("library", index.data(Qt::UserRole + 4).toString());
const QString theme = index.data(Qt::UserRole +5).toString();
if (theme.isEmpty()) {
config.deleteEntry("theme");
} else {
config.writeEntry("theme", theme);
}
}
}
}
config.writeEntry("ButtonsOnLeft", buttonsToString(m_leftButtons->buttons()));
config.writeEntry("ButtonsOnRight", buttonsToString(m_rightButtons->buttons()));
config.sync();
KCModule::save();
// Send signal to all kwin instances
QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KWin"),
QStringLiteral("org.kde.KWin"),
QStringLiteral("reloadConfig"));
QDBusConnection::sessionBus().send(message);
}
void ConfigurationModule::defaults()
{
if (auto listView = m_quickView->rootObject()->findChild<QQuickItem*>("listView")) {
const QModelIndex index = m_proxyModel->mapFromSource(m_model->findDecoration(s_defaultPlugin));
listView->setProperty("currentIndex", index.isValid() ? index.row() : -1);
}
m_ui->borderSizesCombo->setCurrentIndex(m_ui->borderSizesCombo->findData(QVariant::fromValue(stringToSize(s_borderSizeNormal))));
m_ui->closeWindowsDoubleClick->setChecked(false);
KCModule::defaults();
}
void ConfigurationModule::showKNS(const QString &config)
{
QPointer<KNS3::DownloadDialog> downloadDialog = new KNS3::DownloadDialog(config, this);
if (downloadDialog->exec() == QDialog::Accepted && !downloadDialog->changedEntries().isEmpty()) {
auto listView = m_quickView->rootObject()->findChild<QQuickItem*>("listView");
QString selectedPluginName;
QString selectedThemeName;
if (listView) {
const QModelIndex index = m_proxyModel->index(listView->property("currentIndex").toInt(), 0);
if (index.isValid()) {
selectedPluginName = index.data(Qt::UserRole + 4).toString();
selectedThemeName = index.data(Qt::UserRole + 5).toString();
}
}
m_model->init();
if (!selectedPluginName.isEmpty()) {
const QModelIndex index = m_model->findDecoration(selectedPluginName, selectedThemeName);
const QModelIndex proxyIndex = m_proxyModel->mapFromSource(index);
if (listView) {
listView->setProperty("currentIndex", proxyIndex.isValid() ? proxyIndex.row() : -1);
}
}
}
delete downloadDialog;
}
bool ConfigurationModule::eventFilter(QObject *watched, QEvent *e)
{
if (watched != m_ui) {
return false;
}
if (e->type() == QEvent::PaletteChange) {
updateColors();
}
return false;
}
void ConfigurationModule::updateColors()
{
m_quickView->rootContext()->setContextProperty("backgroundColor", m_ui->palette().color(QPalette::Active, QPalette::Window));
m_quickView->rootContext()->setContextProperty("highlightColor", m_ui->palette().color(QPalette::Active, QPalette::Highlight));
m_quickView->rootContext()->setContextProperty("baseColor", m_ui->palette().color(QPalette::Active, QPalette::Base));
}
}
}
#include "kcm.moc"