diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,15 +21,13 @@ include(ECMQtDeclareLoggingCategory) include(FeatureSummary) -find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS QuickWidgets Test) +find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Test) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS DBusAddons Config - ConfigWidgets I18n XmlGui GlobalAccel - WidgetsAddons Declarative IconThemes Plasma diff --git a/common/control.h b/common/control.h --- a/common/control.h +++ b/common/control.h @@ -19,16 +19,20 @@ #include +#include #include class Control { + Q_GADGET public: enum class OutputRetention { Undefined = -1, Global = 0, Individual = 1, }; + Q_ENUM(OutputRetention) + virtual ~Control() = default; @@ -44,6 +48,7 @@ class ControlConfig : public Control { + Q_GADGET public: ControlConfig(KScreen::ConfigPtr config); @@ -69,6 +74,7 @@ class ControlOutput : public Control { + Q_GADGET public: ControlOutput(KScreen::OutputPtr output); diff --git a/kcm/src/utils.h b/common/utils.h rename from kcm/src/utils.h rename to common/utils.h diff --git a/kcm/src/utils.cpp b/common/utils.cpp rename from kcm/src/utils.cpp rename to common/utils.cpp diff --git a/kcm/CMakeLists.txt b/kcm/CMakeLists.txt --- a/kcm/CMakeLists.txt +++ b/kcm/CMakeLists.txt @@ -1,8 +1,39 @@ -add_subdirectory(src) -add_subdirectory(qml) +add_definitions(-DTRANSLATION_DOMAIN=\"kcm_displayconfiguration\") -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/kcm_kscreen.desktop.cmake - ${CMAKE_CURRENT_BINARY_DIR}/kcm_kscreen.desktop - @ONLY) +set(kcm_kscreen_SRCS + config_handler.cpp + kcm.cpp + output_identifier.cpp + output_model.cpp + ${CMAKE_SOURCE_DIR}/common/utils.cpp + ${CMAKE_SOURCE_DIR}/common/control.cpp + ${CMAKE_SOURCE_DIR}/common/globals.cpp +) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kcm_kscreen.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) +ecm_qt_declare_logging_category(kcm_kscreen_SRCS + HEADER + kcm_screen_debug.h + IDENTIFIER + KSCREEN_KCM + CATEGORY_NAME + kscreen.kcm +) + +add_library(kcm_kscreen MODULE ${kcm_kscreen_SRCS}) + +target_link_libraries(kcm_kscreen + KF5::ConfigCore + KF5::CoreAddons + KF5::I18n + KF5::QuickAddons + KF5::Screen +) + +kcoreaddons_desktop_to_json(kcm_kscreen "kcm_kscreen.desktop" SERVICE_TYPES kcmodule.desktop) + +#this desktop file is installed only for retrocompatibility with sycoca +install(FILES kcm_kscreen.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) + +install(TARGETS kcm_kscreen DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms) + +kpackage_install_package(package kcm_kscreen kcms) diff --git a/kcm/Messages.sh b/kcm/Messages.sh --- a/kcm/Messages.sh +++ b/kcm/Messages.sh @@ -1,5 +1,2 @@ #!/usr/bin/env bash -$EXTRACTRC `find . -name '*.ui'` >> rc.cpp -$XGETTEXT `find . -name '*.cpp'` -o $podir/kcm_displayconfiguration.pot -$XGETTEXT `find . -name '*.qml'` -j -L Java -o $podir/kcm_displayconfiguration.pot -rm -f rc.cpp +$XGETTEXT `find . -name "*.cpp" -o -name "*.qml"` -o $podir/kcm_displayconfiguration.pot diff --git a/kcm/config_handler.h b/kcm/config_handler.h new file mode 100644 --- /dev/null +++ b/kcm/config_handler.h @@ -0,0 +1,78 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +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) any later version. + +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 . +*********************************************************************/ +#pragma once + +#include "../common/control.h" + +#include + +#include + +class OutputModel; + +class ConfigHandler : public QObject +{ + Q_OBJECT +public: + explicit ConfigHandler (QObject *parent = nullptr); + ~ConfigHandler() override = default; + + void setConfig(KScreen::ConfigPtr config); + void updateInitialConfig(); + + OutputModel* outputModel() const { + return m_outputs; + } + + QSize normalizeScreen(); + + KScreen::ConfigPtr config() const { + return m_config; + } + + int retention() const; + void setRetention(int retention); + + void writeControl(); + + void checkNeedsSave(); + +Q_SIGNALS: + void outputModelChanged(); + void changed(); + void screenNormalizationUpdate(bool normalized); + void needsSaveChecked(bool need); + void retentionChanged(); + void outputConnect(bool connected); + +private: + void checkScreenNormalization(); + QSize screenSize() const; + Control::OutputRetention getRetention() const; + void primaryOutputSelected(int index); + void primaryOutputChanged(const KScreen::OutputPtr &output); + void initOutput(const KScreen::OutputPtr &output); + + KScreen::ConfigPtr m_config = nullptr; + KScreen::ConfigPtr m_initialConfig; + OutputModel *m_outputs = nullptr; + + std::unique_ptr m_control; + Control::OutputRetention m_initialRetention = Control::OutputRetention:: + Undefined; + QSize m_lastNormalizedScreenSize; +}; diff --git a/kcm/config_handler.cpp b/kcm/config_handler.cpp new file mode 100644 --- /dev/null +++ b/kcm/config_handler.cpp @@ -0,0 +1,260 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +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) any later version. + +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 . +*********************************************************************/ +#include "config_handler.h" + +#include "kcm_screen_debug.h" +#include "output_model.h" + +#include +#include +#include + +#include + +using namespace KScreen; + +ConfigHandler::ConfigHandler(QObject *parent) + : QObject(parent) +{ +} + +void ConfigHandler::setConfig(KScreen::ConfigPtr config) +{ + m_config = config; + m_initialConfig = m_config->clone(); + KScreen::ConfigMonitor::instance()->addConfig(m_config); + m_control.reset(new ControlConfig(config)); + + m_outputs = new OutputModel(this); + connect(m_outputs, &OutputModel::positionChanged, + this, &ConfigHandler::checkScreenNormalization); + connect(m_outputs, &OutputModel::sizeChanged, + this, &ConfigHandler::checkScreenNormalization); + connect(m_outputs, &OutputModel::changed, + this, [this]() { + checkNeedsSave(); + Q_EMIT changed(); + }); + + for (const KScreen::OutputPtr &output : config->outputs()) { + initOutput(output); + } + m_lastNormalizedScreenSize = screenSize(); + m_initialRetention = getRetention(); + Q_EMIT retentionChanged(); + + connect(m_config.data(), &KScreen::Config::outputAdded, + this, [this]() { Q_EMIT outputConnect(true); }); + connect(m_config.data(), &KScreen::Config::outputRemoved, + this, [this]() { Q_EMIT outputConnect(false); }); + connect(m_config.data(), &KScreen::Config::primaryOutputChanged, + this, &ConfigHandler::primaryOutputChanged); + + Q_EMIT outputModelChanged(); +} + +void ConfigHandler::initOutput(const KScreen::OutputPtr &output) +{ + if (output->isConnected()) { + m_outputs->add(output); + } + connect(output.data(), &KScreen::Output::isConnectedChanged, + this, [this, output]() { + Q_EMIT outputConnect(output->isConnected()); + }); +} + +void ConfigHandler::updateInitialConfig() +{ + m_initialRetention = getRetention(); + connect(new GetConfigOperation(), &GetConfigOperation::finished, + this, [this](ConfigOperation *op) { + if (op->hasError()) { + return; + } + m_initialConfig = qobject_cast(op)->config(); + checkNeedsSave(); + }); +} + +void ConfigHandler::checkNeedsSave() +{ + if (m_config->supportedFeatures() & + KScreen::Config::Feature::PrimaryDisplay) { + if (m_config->primaryOutput()->hashMd5() != + m_initialConfig->primaryOutput()->hashMd5() ) { + Q_EMIT needsSaveChecked(true); + return; + } + } + if (m_initialRetention != getRetention()) { + Q_EMIT needsSaveChecked(true); + return; + } + for (const auto &output : m_config->connectedOutputs()) { + const QString hash = output->hashMd5(); + for (const auto &initialOutput : m_initialConfig->outputs()) { + if (hash != initialOutput->hashMd5()) { + continue; + } + bool needsSave = false; + if (output->isEnabled() != initialOutput->isEnabled()) { + needsSave = true; + } + if (output->isEnabled()) { + needsSave |= output->currentModeId() != + initialOutput->currentModeId() + || output->pos() != initialOutput->pos() + || output->scale() != initialOutput->scale() + || output->rotation() != initialOutput->rotation(); + } + if (needsSave) { + Q_EMIT needsSaveChecked(true); + return; + } + break; + } + } + Q_EMIT needsSaveChecked(false); +} + +QSize ConfigHandler::screenSize() const +{ + int width = 0, height = 0; + QSize size; + + for (const auto &output : m_config->connectedOutputs()) { + const int outputRight = output->geometry().right(); + const int outputBottom = output->geometry().bottom(); + + if (outputRight > width) { + width = outputRight; + } + if (outputBottom > height) { + height = outputBottom; + } + } + if (width > 0 && height > 0) { + size = QSize(width, height); + } else { + size = QSize(); + } + return size; +} + +QSize ConfigHandler::normalizeScreen() +{ + if (!m_config) { + return QSize(); + } + bool changed = m_outputs->normalizePositions(); + + const auto currentScreenSize = screenSize(); + changed |= m_lastNormalizedScreenSize != currentScreenSize; + m_lastNormalizedScreenSize = currentScreenSize; + + Q_EMIT screenNormalizationUpdate(true); + return currentScreenSize; +} + +void ConfigHandler::checkScreenNormalization() +{ + const bool normalized = !m_config || + (m_lastNormalizedScreenSize == screenSize() && + m_outputs->positionsNormalized()); + + Q_EMIT screenNormalizationUpdate(normalized); +} + +void ConfigHandler::primaryOutputSelected(int index) +{ + Q_UNUSED(index) + // TODO +} + +void ConfigHandler::primaryOutputChanged(const KScreen::OutputPtr &output) +{ + Q_UNUSED(output) + +} + +Control::OutputRetention ConfigHandler::getRetention() const +{ + using Retention = Control::OutputRetention; + + auto ret = Retention::Undefined; + if (!m_control) { + return ret; + } + const auto outputs = m_config->connectedOutputs(); + if (outputs.isEmpty()) { + return ret; + } + ret = m_control->getOutputRetention(outputs.first()); + + for (const auto &output : outputs) { + const auto outputRet = m_control->getOutputRetention(output); + if (ret != outputRet ) { + // Control file with different retention values per output. + return Retention::Undefined; + } + } + + if (ret == Retention::Undefined) { + // If all outputs have undefined retention, + // this should be displayed as global retention. + return Retention::Global; + } + return ret; +} + +int ConfigHandler::retention() const +{ + return static_cast(getRetention()); +} + +void ConfigHandler::setRetention(int retention) +{ + using Retention = Control::OutputRetention; + + if (!m_control) { + return; + } + if (retention != static_cast(Retention::Global) && + retention != static_cast(Retention::Individual)) { + // We only allow setting to global or individual retention. + return; + } + if (retention == ConfigHandler::retention()) { + return; + } + auto ret = static_cast(retention); + for (const auto &output : m_config->connectedOutputs()) { + m_control->setOutputRetention(output, ret); + } + checkNeedsSave(); + Q_EMIT retentionChanged(); + Q_EMIT changed(); +} + +void ConfigHandler::writeControl() +{ + if (!m_control) { + return; + } + m_control->writeFile(); +} diff --git a/kcm/kcm.h b/kcm/kcm.h new file mode 100644 --- /dev/null +++ b/kcm/kcm.h @@ -0,0 +1,109 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +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) any later version. + +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 . +*********************************************************************/ +#pragma once + +#include + +namespace KScreen +{ +class ConfigOperation; +} + +class ConfigHandler; +class OutputIdentifier; +class OutputModel; + +class KCMKScreen : public KQuickAddons::ConfigModule +{ + Q_OBJECT + + Q_PROPERTY(OutputModel *outputModel READ outputModel + NOTIFY outputModelChanged) + Q_PROPERTY(bool backendError READ backendError + NOTIFY backendErrorChanged) + Q_PROPERTY(bool screenNormalized READ screenNormalized + NOTIFY screenNormalizedChanged) + Q_PROPERTY(bool perOutputScaling READ perOutputScaling + NOTIFY perOutputScalingChanged) + Q_PROPERTY(bool primaryOutputSupported READ primaryOutputSupported + NOTIFY primaryOutputSupportedChanged) + Q_PROPERTY(qreal globalScale READ globalScale WRITE setGlobalScale + NOTIFY globalScaleChanged) + Q_PROPERTY(int outputRetention READ outputRetention WRITE setOutputRetention + NOTIFY outputRetentionChanged) + +public: + explicit KCMKScreen (QObject *parent = nullptr, + const QVariantList &args = QVariantList()); + ~KCMKScreen() override = default; + + void load() override; + void save() override; + void defaults() override; + + OutputModel* outputModel() const; + + Q_INVOKABLE void identifyOutputs(); + + bool backendError() const; + + Q_INVOKABLE QSize normalizeScreen() const; + bool screenNormalized() const; + + bool perOutputScaling() const; + bool primaryOutputSupported() const; + + qreal globalScale() const; + void setGlobalScale(qreal scale); + + int outputRetention() const; + void setOutputRetention(int retention); + + Q_INVOKABLE void forceSave(); + void doSave(bool force); + +Q_SIGNALS: + void backendErrorChanged(); + void outputModelChanged(); + void changed(); + void screenNormalizedChanged(); + void perOutputScalingChanged(); + void primaryOutputSupportedChanged(); + void globalScaleChanged(); + void outputRetentionChanged(); + void dangerousSave(); + void errorOnSave(); + void globalScaleWritten(); + void outputConnect(bool connected); + +private: + void setBackendError(bool error); + void setScreenNormalized(bool normalized); + + void fetchGlobalScale(); + void writeGlobalScale(); + + void configReady(KScreen::ConfigOperation *op); + void continueNeedsSaveCheck(bool needs); + + std::unique_ptr m_outputIdentifier; + std::unique_ptr m_config; + bool m_backendError = false; + bool m_screenNormalized = true; + double m_globalScale = 1.; + double m_initialGlobalScale = 1.; +}; diff --git a/kcm/kcm.cpp b/kcm/kcm.cpp new file mode 100644 --- /dev/null +++ b/kcm/kcm.cpp @@ -0,0 +1,350 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +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) any later version. + +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 . +*********************************************************************/ +#include "kcm.h" + +#include "config_handler.h" +#include "kcm_screen_debug.h" +#include "output_identifier.h" +#include "output_model.h" +#include "../common/control.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +K_PLUGIN_FACTORY_WITH_JSON(KCMDisplayConfigurationFactory, "kcm_kscreen.json", + registerPlugin();) + +using namespace KScreen; + +KCMKScreen::KCMKScreen(QObject *parent, const QVariantList &args) + : KQuickAddons::ConfigModule(parent, args) +{ + qmlRegisterType(); + qmlRegisterType("org.kde.private.kcm.kscreen", + 1, 0, + "Output"); + qmlRegisterUncreatableType("org.kde.private.kcm.kscreen", + 1, 0, "Control", + QStringLiteral("Provides only the OutputRetention enum class")); + Log::instance(); + + KAboutData *about = new KAboutData(QStringLiteral("kcm_kscreen"), + i18n("Display Configuration"), + QStringLiteral(KSCREEN_VERSION), + i18n("Configuration for displays"), + KAboutLicense::GPL, + i18n("Copyright © 2019 Roman Gilg")); + about->addAuthor(i18n("Roman Gilg"), + i18n("Maintainer"), + QStringLiteral("subdiff@gmail.com")); + setAboutData(about); + setButtons(Apply); +} + +void KCMKScreen::configReady(ConfigOperation *op) +{ + qCDebug(KSCREEN_KCM) << "Reading in config now."; + + if (op->hasError()) { + m_config.reset(); + setBackendError(true); + return; + } + m_config->setConfig(qobject_cast(op)->config()); + Q_EMIT perOutputScalingChanged(); + Q_EMIT primaryOutputSupportedChanged(); +} + +void KCMKScreen::forceSave() +{ + doSave(true); +} + +void KCMKScreen::save() +{ + doSave(false); +} + +void KCMKScreen::doSave(bool force) +{ + if (!m_config) { + Q_EMIT errorOnSave(); + return; + } + + auto config = m_config->config(); + + bool atLeastOneEnabledOutput = false; + for (const KScreen::OutputPtr &output : config->outputs()) { + KScreen::ModePtr mode = output->currentMode(); + + atLeastOneEnabledOutput |= output->isEnabled(); + + qCDebug(KSCREEN_KCM) << output->name() << output->id() + << output.data() << "\n" + << " Connected:" << output->isConnected() << "\n" + << " Enabled:" << output->isEnabled() << "\n" + << " Primary:" << output->isPrimary() << "\n" + << " Rotation:" << output->rotation() << "\n" + << " Mode:" + << (mode ? mode->name() : QStringLiteral("unknown")) + << "@" << (mode ? mode->refreshRate() : 0.0) << "Hz" << "\n" + << " Position:" << output->pos().x() << "x" << output->pos().y(); + } + + if (!atLeastOneEnabledOutput && !force) { + Q_EMIT dangerousSave(); + m_config->checkNeedsSave(); + return; + } + + if (!Config::canBeApplied(config)) { + Q_EMIT errorOnSave(); + m_config->checkNeedsSave(); + return; + } + + if (!perOutputScaling()) { + writeGlobalScale(); + } + m_config->writeControl(); + + // Store the current config, apply settings. Block until operation is + // completed, otherwise ConfigModule might terminate before we get to + // execute the Operation. + auto *op = new SetConfigOperation(config); + op->exec(); + + // The 1000ms is a legacy value tested to work for randr having + // enough time to change configuration. + QTimer::singleShot(1000, this, + [this] () { + if (!m_config) { + setNeedsSave(false); + return; + } + m_config->updateInitialConfig(); + } + ); +} + +bool KCMKScreen::backendError() const +{ + return m_backendError; +} + +void KCMKScreen::setBackendError(bool error) +{ + if (m_backendError == error) { + return; + } + m_backendError = error; + Q_EMIT backendErrorChanged(); +} + +OutputModel* KCMKScreen::outputModel() const +{ + if (!m_config) { + return nullptr; + } + return m_config->outputModel(); +} + +void KCMKScreen::identifyOutputs() +{ + if (!m_config || !m_config->config() || m_outputIdentifier) { + return; + } + m_outputIdentifier.reset(new OutputIdentifier(m_config->config(), this)); + connect(m_outputIdentifier.get(), &OutputIdentifier::identifiersFinished, + this, [this]() { + m_outputIdentifier.reset(); + }); + +} + +QSize KCMKScreen::normalizeScreen() const +{ + if (!m_config) { + return QSize(); + } + return m_config->normalizeScreen(); +} + +bool KCMKScreen::screenNormalized() const +{ + return m_screenNormalized; +} + +bool KCMKScreen::perOutputScaling() const +{ + if (!m_config || !m_config->config()) { + return false; + } + return m_config->config()->supportedFeatures().testFlag(Config::Feature:: + PerOutputScaling); +} + +bool KCMKScreen::primaryOutputSupported() const +{ + if (!m_config || !m_config->config()) { + return false; + } + return m_config->config()->supportedFeatures().testFlag(Config::Feature:: + PrimaryDisplay); +} + +void KCMKScreen::setScreenNormalized(bool normalized) +{ + if (m_screenNormalized == normalized) { + return; + } + m_screenNormalized = normalized; + Q_EMIT screenNormalizedChanged(); +} + +void KCMKScreen::defaults() +{ + qCDebug(KSCREEN_KCM) << "Applying defaults."; + load(); +} + +void KCMKScreen::load() +{ + qCDebug(KSCREEN_KCM) << "About to read in config."; + + setBackendError(false); + setNeedsSave(false); + if (!screenNormalized()) { + Q_EMIT screenNormalizedChanged(); + } + fetchGlobalScale(); + + m_config.reset(new ConfigHandler(this)); + Q_EMIT perOutputScalingChanged(); + connect (m_config.get(), &ConfigHandler::outputModelChanged, + this, &KCMKScreen::outputModelChanged); + connect (m_config.get(), &ConfigHandler::outputConnect, + this, [this](bool connected) { + load(); + Q_EMIT outputConnect(connected); + }); + connect (m_config.get(), &ConfigHandler::screenNormalizationUpdate, + this, &KCMKScreen::setScreenNormalized); + connect (m_config.get(), &ConfigHandler::retentionChanged, + this, &KCMKScreen::outputRetentionChanged); + + // This is a queued connection so that we can fire the event from + // within the save() call in case it failed. + connect (m_config.get(), &ConfigHandler::needsSaveChecked, + this, &KCMKScreen::continueNeedsSaveCheck, Qt::QueuedConnection); + + connect (m_config.get(), &ConfigHandler::changed, + this, &KCMKScreen::changed); + + connect(new GetConfigOperation(), &GetConfigOperation::finished, + this, &KCMKScreen::configReady); + + Q_EMIT changed(); +} + +void KCMKScreen::continueNeedsSaveCheck(bool needs) +{ + if (needs || m_globalScale != m_initialGlobalScale) { + setNeedsSave(true); + } else { + setNeedsSave(false); + } +} + +void KCMKScreen::fetchGlobalScale() +{ + const auto config = KSharedConfig::openConfig(QStringLiteral("kdeglobals")); + const qreal scale = config->group("KScreen").readEntry("ScaleFactor", 1.0); + m_initialGlobalScale = scale; + setGlobalScale(scale); +} + +void KCMKScreen::writeGlobalScale() +{ + if (m_initialGlobalScale == m_globalScale) { + return; + } + auto config = KSharedConfig::openConfig(QStringLiteral("kdeglobals")); + config->group("KScreen").writeEntry("ScaleFactor", m_globalScale); + + // TODO: Legacy code does some more stuff: + // * Write ScreenScaleFactors for each output (with the same + // global scale value allways. + // * Execute xrdb command for the global scale value. + // * Write to kcmfonts config the global scale value * 96. + // Question is what's really needed of that anymore. + + m_initialGlobalScale = m_globalScale; + Q_EMIT globalScaleWritten(); +} + +qreal KCMKScreen::globalScale() const +{ + return m_globalScale; +} + +void KCMKScreen::setGlobalScale(qreal scale) +{ + if (qFuzzyCompare(m_globalScale, scale)) { + return; + } + m_globalScale = scale; + if (m_config) { + m_config->checkNeedsSave(); + } else { + continueNeedsSaveCheck(false); + } + Q_EMIT changed(); + Q_EMIT globalScaleChanged(); +} + +int KCMKScreen::outputRetention() const +{ + if (!m_config) { + return -1; + } + return m_config->retention(); +} + +void KCMKScreen::setOutputRetention(int retention) +{ + if (!m_config) { + return; + } + return m_config->setRetention(retention); +} + + +#include "kcm.moc" diff --git a/kcm/kcm_kscreen.desktop b/kcm/kcm_kscreen.desktop new file mode 100644 --- /dev/null +++ b/kcm/kcm_kscreen.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Exec=kcmshell5 kcm_kscreen +Icon=preferences-desktop-display-randr +Type=Service +X-KDE-ServiceTypes=KCModule +X-KDE-Library=kcm_kscreen +X-KDE-ParentApp=kcontrol +X-KDE-System-Settings-Parent-Category=display +X-KDE-Weight=40 + +Name=Displays +Comment=Manage and configure monitors and displays +X-KDE-Keywords=display,monitor,scale,scaling,resolution,orientation,outputs,screen,refresh diff --git a/kcm/kcm_kscreen.desktop.cmake b/kcm/kcm_kscreen.desktop.cmake deleted file mode 100644 --- a/kcm/kcm_kscreen.desktop.cmake +++ /dev/null @@ -1,123 +0,0 @@ -[Desktop Entry] -Exec=kcmshell5 kcm_kscreen -Icon=preferences-desktop-display-randr -Type=Service -X-KDE-ServiceTypes=KCModule -X-KDE-Library=kcm_kscreen -X-KDE-ParentApp=kcontrol -X-KDE-System-Settings-Parent-Category=display -X-KDE-Weight=40 -X-KDE-PluginInfo-Version=@KSCREEN_VERSION@ - -Name=Displays -Name[ca]=Pantalles -Name[ca@valencia]=Pantalles -Name[cs]=Obrazovky -Name[da]=Skærme -Name[de]=Anzeigen -Name[el]=Οθόνες -Name[en_GB]=Displays -Name[es]=Pantallas -Name[et]=Monitorid -Name[eu]=Bistaratzaileak -Name[fi]=Näytöt -Name[fr]=Affichages -Name[gl]=Pantallas -Name[he]=תצוגות -Name[hu]=Megjelenítők -Name[ia]=Monstratores -Name[id]=Displai -Name[it]=Schermi -Name[ko]=디스플레이 -Name[lt]=Ekranai -Name[nl]=Schermen -Name[nn]=Skjermar -Name[pl]=Ekrany -Name[pt]=Ecrãs -Name[pt_BR]=Telas -Name[ru]=Экраны -Name[sk]=Obrazovky -Name[sl]=Zasloni -Name[sr]=Екрани -Name[sr@ijekavian]=Екрани -Name[sr@ijekavianlatin]=Ekrani -Name[sr@latin]=Ekrani -Name[sv]=Bildskärmar -Name[tr]=Ekranlar -Name[uk]=Дисплеї -Name[x-test]=xxDisplaysxx -Name[zh_CN]=显示 -Name[zh_TW]=顯示 -Comment=Manage and configure monitors and displays -Comment[ar]=أدر واضبط الشّاشات والعروض -Comment[bs]=Upravljanje i konfiguracija monitora i ekrana -Comment[ca]=Gestiona i configura els monitors i pantalles -Comment[ca@valencia]=Gestiona i configura els monitors i pantalles -Comment[cs]=Spravovat a nastavit monitory a zobrazení -Comment[da]=Håndtér og indstil monitorer og skærme -Comment[de]=Verwaltung und Einrichtung vom Monitoren und Anzeigen -Comment[el]=Διαχείριση και διαμόρφωση οθονών και απεικονίσεων -Comment[en_GB]=Manage and configure monitors and displays -Comment[es]=Gestionar y configurar monitores y pantallas -Comment[et]=Monitoride ja ekraanide haldamine ja seadistamine -Comment[eu]=Kudeatu eta konfiguratu monitoreak eta bistaratzaileak -Comment[fi]=Näyttöjen asetusten hallinta -Comment[fr]=Gère et configure les moniteurs et les écrans -Comment[gl]=Xestiona a configura os monitores e a resolución -Comment[he]=נהל את הגדרות המסכים והתצוגות שלך -Comment[hu]=Monitorok és kijelzők kezelése és beállítása -Comment[ia]=Gere e configura monitors e monstratores -Comment[id]=Kelola dan konfigurasi monitor dan displai -Comment[it]=Gestisce e configura i monitor e gli schermi -Comment[ko]=모니터와 디스플레이 설정 및 관리 -Comment[lt]=Tvarkyti ir konfigūruoti monitorius ir ekranus -Comment[nb]=Håndter og sett opp monitorer og skjermer -Comment[nl]=Monitoren en schermen beheren en instellen -Comment[nn]=Set opp skjermar og skjermbilete -Comment[pa]=ਮਾਨੀਟਰ ਤੇ ਡਿਸਪਲੇਅ ਦਾ ਪਰਬੰਧ ਤੇ ਸੰਰਚਨਾ ਕਰੋ -Comment[pl]=Zarządzanie i ustawienia ekranów -Comment[pt]=Faz a gestão e configuração dos monitores e ecrãs -Comment[pt_BR]=Gerencia e configura monitores e telas -Comment[ro]=Gestionează și configurează monitoare și afișaje -Comment[ru]=Настройка экранов и видеовыходов -Comment[sk]=Správa a nastavenie monitorov a obrazoviek -Comment[sl]=Upravljajte in nastavljajte zaslone in prikazovalnike -Comment[sr]=Управљање и подешавање монитора̂ и екрана̂ -Comment[sr@ijekavian]=Управљање и подешавање монитора̂ и екрана̂ -Comment[sr@ijekavianlatin]=Upravljanje i podešavanje monitorâ̂ i ekranâ̂ -Comment[sr@latin]=Upravljanje i podešavanje monitorâ̂ i ekranâ̂ -Comment[sv]=Hantera och ställ in bildskärmar -Comment[tr]=Ekranları ve görüntüleri yönet, yapılandır -Comment[uk]=Керування і налаштовування моніторів і дисплеїв -Comment[x-test]=xxManage and configure monitors and displaysxx -Comment[zh_CN]=管理和配置显示和监视器 -Comment[zh_TW]=管理與設定螢幕與顯示 -X-KDE-Keywords=display,monitor,scale,scaling,resolution,orientation,outputs,screen,refresh -X-KDE-Keywords[ca]=pantalla,monitor,escala,escalat,resolució,orientació,sortides,pantalla,refresc -X-KDE-Keywords[ca@valencia]=pantalla,monitor,escala,escalat,resolució,orientació,eixides,pantalla,refresc -X-KDE-Keywords[da]=skærm,monitor,skaler,opløsning,orientering,output,genopfriskning -X-KDE-Keywords[de]=Anzeige,Monitor,Bildschirm,Skalierung,Auflösung,Ausrichtung,Ausgabe,Aktualisieren -X-KDE-Keywords[el]=απεικόνιση,οθόνη,κλίμακα,κλιμάκωση,ανάλυση,προσανατολισμός,έξοδοι,οθόνη,ανανέωση -X-KDE-Keywords[en_GB]=display,monitor,scale,scaling,resolution,orientation,outputs,screen,refresh -X-KDE-Keywords[es]=pantalla,monitor,escala,escalado,resolución,orientación,salidas,refresco -X-KDE-Keywords[eu]=bistaratzailea,monitorea,eskalatu,eskalatze,bereizmen,orientazioa,irteerak,pantaila,freskatu -X-KDE-Keywords[fi]=näyttö,monitori,mittakaava,skaalaus,tarkkuus,suunta,ulostulot,virkistys -X-KDE-Keywords[fr]=affichage,moniteur,échelle,mise à l'échelle,résolution,orientation,sorties,écran,rafraîchissement -X-KDE-Keywords[gl]=display,pantalla,monitor,scale,cambiar de escala,scaling,cambio de escala,resolution,resolución,orientation,orientación,outputs,saída,saídas,pantalla,actualizar,actualización -X-KDE-Keywords[hu]=megjelenítés,monitor,skála,skálázás,felbontás,tájolás,kimenetek,képernyő,frissítés -X-KDE-Keywords[id]=displai,monitor,skala,penyekalaan,resolusi,orientasi,output,layar,segarkan -X-KDE-Keywords[it]=schermo,monitor,scala,riscalatura,risoluzione,orientazione,uscite,schermo,aggiornamento -X-KDE-Keywords[ko]=display,monitor,scale,scaling,resolution,orientation,outputs,screen,refresh,디스플레이,모니터,비율,배율,해상도,출력,방향,주사율,화면 -X-KDE-Keywords[lt]=ekranas,vaizduoklis,monitorius,mastelis,skiriamoji geba,raiška,išvestis,atnaujinti,padėtis,orientacija,išvedimas -X-KDE-Keywords[nl]=scherm,monitor,schaal,schalen,resolutie,oriëntatie,uitvoer,scherm,vernieuwen -X-KDE-Keywords[nn]=skjermbilete,skjerm,skala,skalering,oppløysing,skjermoppløysing,retning,utgang,oppdatering,oppfrisking -X-KDE-Keywords[pl]=wyświetlacz,monitor,skala,skalowanie,rozdzielczość,kierunek,orientacja,wyjścia,ekran,odświeżanie -X-KDE-Keywords[pt]=ecrã,monitor,escala,resolução,orientação,saídas,monitor,actualização -X-KDE-Keywords[pt_BR]=tela,monitor,escala,ajuste,resolução,orientação,saídas,atualizar -X-KDE-Keywords[ru]=display,monitor,scale,scaling,resolution,orientation,outputs,экран,монитор,масштаб,разрешение,ориентация,выводы,частота кадров -X-KDE-Keywords[sk]=displej,monitor,mierka,škálovanie,rozlíšenie,orientácia,výstupy,obrazovka,obnoviť -X-KDE-Keywords[sv]=skärm,bildskärm,skala,skalning,upplösning,orientering,utgångar,uppdatera -X-KDE-Keywords[uk]=display,monitor,scale,scaling,resolution,orientation,outputs,screen,refresh,дисплей,монітор,масштаб,масштабування,роздільність,здатність,орієнтація,виведення,виходи,екран,оновлення -X-KDE-Keywords[x-test]=xxdisplayxx,xxmonitorxx,xxscalexx,xxscalingxx,xxresolutionxx,xxorientationxx,xxoutputsxx,xxscreenxx,xxrefreshxx -X-KDE-Keywords[zh_CN]=display,monitor,scale,scaling,resolution,orientation,outputs,显示,监视器,缩放,分辨率,旋转,方位,输出,屏幕,刷新 -X-KDE-Keywords[zh_TW]=display,monitor,scale,scaling,resolution,orientation,outputs,screen,refresh diff --git a/kcm/output_identifier.h b/kcm/output_identifier.h new file mode 100644 --- /dev/null +++ b/kcm/output_identifier.h @@ -0,0 +1,43 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +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) any later version. + +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 . +*********************************************************************/ +#pragma once + +#include + +#include +#include + +class QQuickView; + +class OutputIdentifier : public QObject +{ + Q_OBJECT + +public: + explicit OutputIdentifier (KScreen::ConfigPtr config, QObject *parent = nullptr); + ~OutputIdentifier() override; + +Q_SIGNALS: + void identifiersFinished(); + +protected: + bool eventFilter(QObject *object, QEvent *event) override; + +private: + QVector m_views; + QTimer m_timer; +}; diff --git a/kcm/output_identifier.cpp b/kcm/output_identifier.cpp new file mode 100644 --- /dev/null +++ b/kcm/output_identifier.cpp @@ -0,0 +1,100 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +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) any later version. + +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 . +*********************************************************************/ +#include "output_identifier.h" + +#include "../common/utils.h" + +#include + +#include +#include +#include +#include + +#define QML_PATH "kpackage/kcms/kcm_kscreen/contents/ui/" + +OutputIdentifier::OutputIdentifier(KScreen::ConfigPtr config, QObject *parent) + : QObject(parent) +{ + + const QString qmlPath = + QStandardPaths::locate(QStandardPaths::GenericDataLocation, + QStringLiteral(QML_PATH + "OutputIdentifier.qml")); + + for (const auto &output : config->connectedOutputs()) { + if (!output->currentMode()) { + continue; + } + + const KScreen::ModePtr mode = output->currentMode(); + + auto *view = new QQuickView(); + + view->setFlags(Qt::X11BypassWindowManagerHint | Qt::FramelessWindowHint); + view->setResizeMode(QQuickView::SizeViewToRootObject); + view->setSource(QUrl::fromLocalFile(qmlPath)); + view->installEventFilter(this); + + QQuickItem *rootObj = view->rootObject(); + if (!rootObj) { + continue; + } + + QSize deviceSize, logicalSize; + if (output->isHorizontal()) { + deviceSize = mode->size(); + } else { + deviceSize = QSize(mode->size().height(), mode->size().width()); + } + if (config->supportedFeatures() & KScreen::Config::Feature::PerOutputScaling) { + // no scale adjustment needed on Wayland + logicalSize = deviceSize; + } else { + logicalSize = deviceSize / view->effectiveDevicePixelRatio(); + } + + rootObj->setProperty("outputName", Utils::outputName(output)); + rootObj->setProperty("modeName", Utils::sizeToString(deviceSize)); + view->setProperty("screenSize", QRect(output->pos(), logicalSize)); + m_views << view; + } + + for (auto *view : m_views) { + view->show(); + } + QTimer::singleShot(2500, this, &OutputIdentifier::identifiersFinished); +} + +OutputIdentifier::~OutputIdentifier() +{ + qDeleteAll(m_views); +} + +bool OutputIdentifier::eventFilter(QObject* object, QEvent* event) +{ + if (event->type() == QEvent::Resize) { + if (m_views.contains(qobject_cast(object))) { + QResizeEvent *e = static_cast(event); + const QRect screenSize = object->property("screenSize").toRect(); + QRect geometry(QPoint(0, 0), e->size()); + geometry.moveCenter(screenSize.center()); + static_cast(object)->setGeometry(geometry); + } + } + return QObject::eventFilter(object, event); +} diff --git a/kcm/output_model.h b/kcm/output_model.h new file mode 100644 --- /dev/null +++ b/kcm/output_model.h @@ -0,0 +1,108 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +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) any later version. + +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 . +*********************************************************************/ +#pragma once + +#include +#include + +#include +#include + +class ConfigHandler; + +class OutputModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum OutputRoles { + EnabledRole = Qt::UserRole + 1, + PrimaryRole, + SizeRole, + PositionRole, + NormalizedPositionRole, + RotationRole, + ScaleRole, + ResolutionIndexRole, + ResolutionsRole, + RefreshRateIndexRole, + RefreshRatesRole + }; + + explicit OutputModel (ConfigHandler *configHandler); + ~OutputModel() override = default; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, + int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, + const QVariant &value, + int role = Qt::EditRole) override; + + void add(const KScreen::OutputPtr &output); + void remove(int outputId); + + bool normalizePositions(); + bool positionsNormalized() const; + +Q_SIGNALS: + void positionChanged(); + void sizeChanged(); + void changed(); + +protected: + QHash roleNames() const override; + +private: + struct Output { + Output() {} + Output(const Output &output) + : ptr(output.ptr) + , pos(output.pos) + {} + Output(KScreen::OutputPtr _ptr, const QPoint &_pos) + : ptr(_ptr) + , pos(_pos) + {} + KScreen::OutputPtr ptr; + QPoint pos; + }; + + void roleChanged(int outputId, OutputRoles role); + void updatePositions(); + void updateOrder(); + QPoint originDelta() const; + + /** + * @brief Snaps moved output to others + * @param output the moved output + * @param dest the desired destination to be adjusted by snapping + */ + void snap(const Output &output, QPoint &dest); + + bool setResolution(int outputIndex, int resIndex); + bool setRefreshRate(int outputIndex, int refIndex); + bool setRotation(int outputIndex, KScreen::Output::Rotation rotation); + int resolutionIndex(const KScreen::OutputPtr &output) const; + int refreshRateIndex(const KScreen::OutputPtr &output) const; + QVariantList resolutionsStrings(const KScreen::OutputPtr &output) const; + QVector resolutions(const KScreen::OutputPtr &output) const; + QVector refreshRates(const KScreen::OutputPtr &output) const; + + QVector m_outputs; + + ConfigHandler *m_config; +}; diff --git a/kcm/output_model.cpp b/kcm/output_model.cpp new file mode 100644 --- /dev/null +++ b/kcm/output_model.cpp @@ -0,0 +1,658 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +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) any later version. + +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 . +*********************************************************************/ +#include "output_model.h" + +#include "config_handler.h" + +#include + +#include + +OutputModel::OutputModel(ConfigHandler *configHandler) + : QAbstractListModel(configHandler) + , m_config(configHandler) +{ + connect(this, &OutputModel::dataChanged, this, &OutputModel::changed); +} + +int OutputModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_outputs.count(); +} + +QVariant OutputModel::data(const QModelIndex &index, int role) const +{ + if (index.row() < 0 || index.row() >= m_outputs.count()) { + return QVariant(); + } + + const KScreen::OutputPtr &output = m_outputs[index.row()].ptr; + switch (role) { + case Qt::DisplayRole: + return output->name(); + case EnabledRole: + return output->isEnabled(); + case PrimaryRole: + return output->isPrimary(); + case SizeRole: + return output->geometry().size(); + case PositionRole: + return m_outputs[index.row()].pos; + case NormalizedPositionRole: + return output->geometry().topLeft(); + case RotationRole: + return output->rotation(); + case ScaleRole: + return output->scale(); + case ResolutionIndexRole: + return resolutionIndex(output); + case ResolutionsRole: + return resolutionsStrings(output); + case RefreshRateIndexRole: + return refreshRateIndex(output); + case RefreshRatesRole: + QVariantList ret; + for (const auto rate : refreshRates(output)) { + ret << i18n("%1 Hz", int(rate + 0.5)); + } + return ret; + } + return QVariant(); +} + +bool OutputModel::setData(const QModelIndex &index, + const QVariant &value, int role) +{ + if (index.row() < 0 || index.row() >= m_outputs.count()) { + return false; + } + + const Output &output = m_outputs[index.row()]; + switch (role) { + case PositionRole: + if (value.canConvert()) { + QPoint val = value.toPoint(); + if (output.pos == val) { + return false; + } + + snap(output, val); + m_outputs[index.row()].pos = val; + updatePositions(); + Q_EMIT positionChanged(); + Q_EMIT dataChanged(index, index, {role}); + return true; + } + break; + case EnabledRole: + if (value.canConvert()) { + bool enable = value.toBool(); + if (output.ptr->isEnabled() == enable) { + return false; + } + output.ptr->setEnabled(enable); + Q_EMIT dataChanged(index, index, {role}); + return true; + } + break; + case PrimaryRole: + if (value.canConvert()) { + bool primary = value.toBool(); + if (output.ptr->isPrimary() == primary) { + return false; + } + m_config->config()->setPrimaryOutput(output.ptr); + Q_EMIT dataChanged(index, index, {role}); + return true; + } + break; + case ResolutionIndexRole: + if (value.canConvert()) { + return setResolution(index.row(), value.toInt()); + } + break; + case RefreshRateIndexRole: + if (value.canConvert()) { + return setRefreshRate(index.row(), value.toInt()); + } + break; + case RotationRole: + if (value.canConvert()) { + return setRotation(index.row(), + value.value()); + } + break; + case ScaleRole: + bool ok; + const qreal scale = value.toReal(&ok); + if (ok && !qFuzzyCompare(output.ptr->scale(), scale)) { + output.ptr->setScale(scale); + Q_EMIT sizeChanged(); + Q_EMIT dataChanged(index, index, {role, SizeRole}); + return true; + } + break; + } + return false; +} + +QHash OutputModel::roleNames() const { + QHash roles = QAbstractItemModel::roleNames(); + roles[EnabledRole] = "enabled"; + roles[PrimaryRole] = "primary"; + roles[SizeRole] = "size"; + roles[PositionRole] = "position"; + roles[NormalizedPositionRole] = "normalizedPosition"; + roles[RotationRole] = "rotation"; + roles[ScaleRole] = "scale"; + roles[ResolutionIndexRole] = "resolutionIndex"; + roles[ResolutionsRole] = "resolutions"; + roles[RefreshRateIndexRole] = "refreshRateIndex"; + roles[RefreshRatesRole] = "refreshRates"; + return roles; +} + +void OutputModel::add(const KScreen::OutputPtr &output) +{ + const int insertPos = m_outputs.count(); + Q_EMIT beginInsertRows(QModelIndex(), insertPos, insertPos); + + int i = 0; + while (i < m_outputs.size()) { + const QPoint pos = m_outputs[i].ptr->pos(); + if (output->pos().x() < pos.x()) { + break; + } + if (output->pos().x() == pos.x() && + output->pos().y() < pos.y()) { + break; + } + i++; + } + // Set the initial non-normalized position to be the normalized + // position plus the current delta. + QPoint pos = output->pos(); + if (!m_outputs.isEmpty()) { + const QPoint delta = m_outputs[0].pos - m_outputs[0].ptr->pos(); + pos = output->pos() + delta; + } + m_outputs.insert(i, Output(output, pos)); + + connect(output.data(), &KScreen::Output::isPrimaryChanged, + this, [this, output](){ + roleChanged(output->id(), {PrimaryRole}); + }); + Q_EMIT endInsertRows(); +} + +void OutputModel::remove(int outputId) +{ + auto it = std::find_if(m_outputs.begin(), m_outputs.end(), + [outputId](const Output &output) { + return output.ptr->id() == outputId; + }); + if (it != m_outputs.end()) { + const int index = it - m_outputs.begin(); + Q_EMIT beginRemoveRows(QModelIndex(), index, index); + m_outputs.erase(it); + Q_EMIT endRemoveRows(); + } +} + +inline +bool refreshRateCompare(float rate1, float rate2) +{ + return qAbs(rate1 - rate2) < 0.5; +} + +bool OutputModel::setResolution(int outputIndex, int resIndex) +{ + const Output &output = m_outputs[outputIndex]; + const auto resolutionList = resolutions(output.ptr); + if (resIndex < 0 || resIndex >= resolutionList.size()) { + return false; + } + const QSize size = resolutionList[resIndex]; + + const float oldRefreshRate = output.ptr->currentMode()->refreshRate(); + const auto modes = output.ptr->modes(); + + auto modeIt = std::find_if(modes.begin(), modes.end(), + [size, oldRefreshRate](const KScreen::ModePtr &mode) { + // TODO: we don't want to compare against old refresh rate if + // refresh rate selection is auto. + return mode->size() == size && + refreshRateCompare(mode->refreshRate(), oldRefreshRate); + }); + + if (modeIt == modes.end()) { + // New resolution does not support previous refresh rate. + // Get the highest one instead. + float bestRefreshRate = 0; + auto it = modes.begin(); + while (it != modes.end()) { + if ((*it)->size() == size && (*it)->refreshRate() > bestRefreshRate) { + modeIt = it; + } + it++; + } + } + Q_ASSERT(modeIt != modes.end()); + + const auto id = (*modeIt)->id(); + if (output.ptr->currentModeId() == id) { + return false; + } + output.ptr->setCurrentModeId(id); + + QModelIndex index = createIndex(outputIndex, 0); + // Calling this directly ignores possible optimization when the + // refresh rate hasn't changed in fact. But that's ok. + Q_EMIT dataChanged(index, index, {ResolutionIndexRole, + SizeRole, + RefreshRateIndexRole}); + Q_EMIT sizeChanged(); + return true; +} + +bool OutputModel::setRefreshRate(int outputIndex, int refIndex) +{ + const Output &output = m_outputs[outputIndex]; + const auto rates = refreshRates(output.ptr); + if (refIndex < 0 || refIndex >= rates.size()) { + return false; + } + const float refreshRate = rates[refIndex]; + + const auto modes = output.ptr->modes(); + const auto oldMode = output.ptr->currentMode(); + + auto modeIt = std::find_if(modes.begin(), modes.end(), + [oldMode, refreshRate](const KScreen::ModePtr &mode) { + // TODO: we don't want to compare against old refresh rate if + // refresh rate selection is auto. + return mode->size() == oldMode->size() && + refreshRateCompare(mode->refreshRate(), refreshRate); + }); + Q_ASSERT(modeIt != modes.end()); + + if (refreshRateCompare(oldMode->refreshRate(), (*modeIt)->refreshRate())) { + // no change + return false; + } + output.ptr->setCurrentModeId((*modeIt)->id()); + QModelIndex index = createIndex(outputIndex, 0); + Q_EMIT dataChanged(index, index, {RefreshRateIndexRole}); + return true; +} + +bool OutputModel::setRotation(int outputIndex, KScreen::Output::Rotation rotation) +{ + const Output &output = m_outputs[outputIndex]; + + if (rotation != KScreen::Output::None + && rotation != KScreen::Output::Left + && rotation != KScreen::Output::Inverted + && rotation != KScreen::Output::Right) { + return false; + } + if (output.ptr->rotation() == rotation) { + return false; + } + output.ptr->setRotation(rotation); + + QModelIndex index = createIndex(outputIndex, 0); + Q_EMIT dataChanged(index, index, {RotationRole, SizeRole}); + Q_EMIT sizeChanged(); + return true; + +} + +int OutputModel::resolutionIndex(const KScreen::OutputPtr &output) const +{ + if (!output->currentMode()) { + return 0; + } + const auto sizes = resolutions(output); + const QSize currentResolution = output->currentMode()->size(); + + const auto it = std::find_if(sizes.begin(), + sizes.end(), + [currentResolution](const QSize &size) { + return size == currentResolution; + }); + if (it == sizes.end()) { + return -1; + } + return it - sizes.begin(); +} + +int OutputModel::refreshRateIndex(const KScreen::OutputPtr &output) const +{ + if (!output->currentMode()) { + return 0; + } + const auto rates = refreshRates(output); + const float currentRate = output->currentMode()->refreshRate(); + + const auto it = std::find_if(rates.begin(), + rates.end(), + [currentRate](float rate) { + return refreshRateCompare(rate, currentRate); + }); + if (it == rates.end()) { + return 0; + } + return it - rates.begin(); +} + +QVariantList OutputModel::resolutionsStrings(const KScreen::OutputPtr &output) const +{ + QVariantList ret; + for (const QSize &size : resolutions(output)) { + const QString text = QString::number(size.width()) + QStringLiteral("x") + + QString::number(size.height()); + ret << text; + } + return ret; +} + +QVector OutputModel::resolutions(const KScreen::OutputPtr &output) const +{ + QVector hits; + + for (const auto &mode : output->modes()) { + const QSize size = mode->size(); + if (!hits.contains(size)) { + hits << size; + } + } + std::sort(hits.begin(), hits.end(), [](const QSize &a, const QSize &b) { + if (a.width() > b.width()) { + return true; + } + if (a.width() == b.width() && a.height() > b.height()) { + return true; + } + return false; + }); + return hits; +} + +QVector OutputModel::refreshRates(const KScreen::OutputPtr + &output) const +{ + QVector hits; + if (!output->currentMode()) { + return hits; + } + const auto baseSize = output->currentMode()->size(); + + for (const auto &mode : output->modes()) { + if (mode->size() != baseSize) { + continue; + } + const float rate = mode->refreshRate(); + if (std::find_if(hits.begin(), hits.end(), + [rate](float r) { + return refreshRateCompare(r, rate); + }) != hits.end()) { + continue; + } + hits << rate; + } + return hits; +} + +void OutputModel::roleChanged(int outputId, OutputRoles role) +{ + for (int i = 0; i < m_outputs.size(); i++) { + Output &output = m_outputs[i]; + if (output.ptr->id() == outputId) { + QModelIndex index = createIndex(i, 0); + Q_EMIT dataChanged(index, index, {role}); + return; + } + } +} + +QPoint OutputModel::originDelta() const +{ + int x = m_outputs[0].pos.x(); + int y = m_outputs[0].pos.y(); + for (int i = 1; i < m_outputs.size(); i++) { + const QPoint &cmp = m_outputs[i].pos; + + if (cmp.x() < x) { + x = cmp.x(); + } + if (cmp.y() < y) { + y = cmp.y(); + } + } + return QPoint(x, y); +} + +void OutputModel::updatePositions() +{ + const QPoint delta = originDelta(); + for (int i = 0; i < m_outputs.size(); i++) { + const auto &out = m_outputs[i]; + const QPoint set = out.pos - delta; + if (out.ptr->pos() != set) { + out.ptr->setPos(set); + QModelIndex index = createIndex(i, 0); + Q_EMIT dataChanged(index, index, {NormalizedPositionRole}); + } + } + updateOrder(); +} + +void OutputModel::updateOrder() +{ + auto order = m_outputs; + std::sort(order.begin(), order.end(), [](const Output &a, const Output &b) { + const int xDiff = b.ptr->pos().x() - a.ptr->pos().x(); + const int yDiff = b.ptr->pos().y() - a.ptr->pos().y(); + if (xDiff > 0) { + return true; + } + if (xDiff == 0 && yDiff > 0) { + return true; + } + return false; + }); + + for (int i = 0; i < order.size(); i++) { + for (int j = 0; j < m_outputs.size(); j++) { + if (order[i].ptr->id() != m_outputs[j].ptr->id()) { + continue; + } + if (i != j) { + beginMoveRows(QModelIndex(), j, j, QModelIndex(), i); + m_outputs.remove(j); + m_outputs.insert(i, order[i]); + endMoveRows(); + } + break; + } + } +} + +bool OutputModel::normalizePositions() +{ + bool changed = false; + for (int i = 0; i < m_outputs.size(); i++) { + auto &output = m_outputs[i]; + if (output.pos == output.ptr->pos()) { + continue; + } + changed = true; + auto index = createIndex(i, 0); + output.pos = output.ptr->pos(); + Q_EMIT dataChanged(index, index, {PositionRole}); + } + return changed; +} + +bool OutputModel::positionsNormalized() const +{ + // There might be slight deviations because of snapping. + return originDelta().manhattanLength() < 5; +} + +const int s_snapArea = 80; + +bool isVerticalClose(const QRect &rect1, const QRect &rect2) +{ + if (rect2.top() - rect1.bottom() > s_snapArea ) { + return false; + } + if (rect1.top() - rect2.bottom() > s_snapArea ) { + return false; + } + return true; +} + +bool snapToRight(const QRect &target, + const QSize &size, + QPoint &dest) +{ + if (qAbs(target.right() - dest.x()) < s_snapArea) { + // In snap zone for left to right snap. + dest.setX(target.right() + 1); + return true; + } + if (qAbs(target.right() - (dest.x() + size.width())) < s_snapArea) { + // In snap zone for right to right snap. + dest.setX(target.right() - size.width()); + return true; + } + return false; +} + +bool snapToLeft(const QRect &target, + const QSize &size, + QPoint &dest) +{ + if (qAbs(target.left() - dest.x()) < s_snapArea) { + // In snap zone for left to left snap. + dest.setX(target.left()); + return true; + } + if (qAbs(target.left() - (dest.x() + size.width())) < s_snapArea) { + // In snap zone for right to left snap. + dest.setX(target.left() - size.width()); + return true; + } + return false; +} + +bool snapToMiddle(const QRect &target, + const QSize &size, + QPoint &dest) +{ + const int outputMid = dest.y() + size.height() / 2; + const int targetMid = target.top() + target.height() / 2; + if (qAbs(targetMid - outputMid) < s_snapArea) { + // In snap zone for middle to middle snap. + dest.setY(targetMid - size.height() / 2); + return true; + } + return false; +} + +bool snapToTop(const QRect &target, + const QSize &size, + QPoint &dest) +{ + if (qAbs(target.top() - dest.y()) < s_snapArea) { + // In snap zone for bottom to top snap. + dest.setY(target.top()); + return true; + } + if (qAbs(target.top() - (dest.y() + size.height())) < s_snapArea) { + // In snap zone for top to top snap. + dest.setY(target.top() - size.height()); + return true; + } + return false; +} + +bool snapToBottom(const QRect &target, + const QSize &size, + QPoint &dest) +{ + if (qAbs(target.bottom() - dest.y()) < s_snapArea) { + // In snap zone for top to bottom snap. + dest.setY(target.bottom() + 1); + return true; + } + if (qAbs(target.bottom() - (dest.y() + size.height())) < s_snapArea) { + // In snap zone for bottom to bottom snap. + dest.setY(target.bottom() - size.height() + 1); + return true; + } + return false; +} + +bool snapVertical(const QRect &target, + const QSize &size, + QPoint &dest) +{ + if (snapToMiddle(target, size, dest)) { + return true; + } + if (snapToBottom(target, size, dest)) { + return true; + } + if (snapToTop(target, size, dest)) { + return true; + } + return false; +} + +void OutputModel::snap(const Output &output, QPoint &dest) +{ + const QSize size = output.ptr->geometry().size(); + for (const Output &out : m_outputs) { + if (out.ptr->id() == output.ptr->id()) { + // Can not snap to itself. + continue; + } + + const QRect target(out.pos, out.ptr->geometry().size()); + + if (!isVerticalClose(target, QRect(dest, size))) { + continue; + } + + // try snap left to right first + if (snapToRight(target, size, dest)) { + snapVertical(target, size, dest); + continue; + } + if (snapToLeft(target, size, dest)) { + snapVertical(target, size, dest); + continue; + } + if (snapVertical(target, size, dest)) { + continue; + } + } +} diff --git a/kcm/package/contents/ui/Output.qml b/kcm/package/contents/ui/Output.qml new file mode 100644 --- /dev/null +++ b/kcm/package/contents/ui/Output.qml @@ -0,0 +1,235 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg +Copyright © 2012 Dan Vratil + +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) any later version. + +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 . +*********************************************************************/ +import QtQuick 2.12 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.3 as Controls +import QtGraphicalEffects 1.0 + +Rectangle { + id: output + + property bool isSelected: root.selectedOutput === model.index + + onIsSelectedChanged: { + if (isSelected) { + z = 89; + } else { + z = 0; + } + } + + function getAbsolutePosition(pos) { + return Qt.point((pos.x - screen.xOffset) * screen.relativeFactor, + (pos.y - screen.yOffset) * screen.relativeFactor) ; + } + + x: model.position.x / screen.relativeFactor + screen.xOffset + y: model.position.y / screen.relativeFactor + screen.yOffset + + width: model.size.width / screen.relativeFactor + height: model.size.height / screen.relativeFactor + + SystemPalette { + id: palette + } + + radius: 4 + color: palette.window + smooth: true + clip: true + + border { + color: isSelected ? palette.highlight : palette.shadow + width: 1 + + Behavior on color { + PropertyAnimation { + duration: 150 + } + } + } + + Item { + anchors.fill: parent + + ColumnLayout { + anchors.centerIn: parent + spacing: units.smallSpacing + width: parent.width + Layout.maximumHeight: parent.height + + Controls.Label { + Layout.fillWidth: true + Layout.margins: units.smallSpacing + + text: model.display + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + color: palette.text + } + + Controls.Label { + Layout.fillWidth: true + Layout.margins: units.smallSpacing + + text: "(" + model.size.width + "x" + model.size.height + ")" + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + color: palette.text + } + } + } + + Rectangle { + id: posLabel + + y: 4 + x: 4 + width: childrenRect.width + 5 + height: childrenRect.height + 2 + radius: 4 + + opacity: model.enabled && + (tapHandler.isLongPressed || dragHandler.active) ? 0.9 : 0.0 + + + color: palette.shadow + + Text { + id: posLabelText + + y: 2 + x: 2 + + text: model.normalizedPosition.x + "," + model.normalizedPosition.y + color: "white" + } + + Behavior on opacity { + PropertyAnimation { + duration: 100; + } + } + } + + Item { + id: orientationPanelContainer + + anchors.fill: output + visible: false + + Rectangle { + id: orientationPanel + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + + height: 10 + color: isSelected ? palette.highlight : palette.shadow + smooth: true + + Behavior on color { + PropertyAnimation { + duration: 150 + } + } + } + } + + states: [ + State { + name: "rot90" + when: model.rotation === 2 + PropertyChanges { + target: orientationPanel + height: undefined + width: 10 + } + AnchorChanges { + target: orientationPanel + anchors.right: undefined + anchors.top: orientationPanelContainer.top + } + }, + State { + name: "rot180" + when: model.rotation === 4 + AnchorChanges { + target: orientationPanel + anchors.top: orientationPanelContainer.top + anchors.bottom: undefined + } + }, + State { + name: "rot270" + when: model.rotation === 8 + PropertyChanges { + target: orientationPanel + height: undefined + width: 10 + } + AnchorChanges { + target: orientationPanel + anchors.left: undefined + anchors.top: orientationPanelContainer.top + } + } + ] + + OpacityMask { + anchors.fill: orientationPanelContainer + source: orientationPanelContainer + maskSource: output + } + + property point dragStartPosition + + TapHandler { + id: tapHandler + property bool isLongPressed: false + + function bindSelectedOutput() { + root.selectedOutput + = Qt.binding(function() { return model.index; }); + } + onPressedChanged: { + if (pressed) { + bindSelectedOutput(); + dragStartPosition = Qt.point(output.x, output.y) + } else { + isLongPressed = false; + } + } + onLongPressed: isLongPressed = true; + longPressThreshold: 0.3 + } + DragHandler { + id: dragHandler + acceptedButtons: Qt.LeftButton + target: null + + onTranslationChanged: { + var newX = dragStartPosition.x + translation.x; + var newY = dragStartPosition.y + translation.y; + model.position = getAbsolutePosition(Qt.point(newX, newY)); + } + } +} + diff --git a/kcm/qml/OutputIdentifier.qml b/kcm/package/contents/ui/OutputIdentifier.qml rename from kcm/qml/OutputIdentifier.qml rename to kcm/package/contents/ui/OutputIdentifier.qml diff --git a/kcm/package/contents/ui/OutputPanel.qml b/kcm/package/contents/ui/OutputPanel.qml new file mode 100644 --- /dev/null +++ b/kcm/package/contents/ui/OutputPanel.qml @@ -0,0 +1,119 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +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) any later version. + +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 . +*********************************************************************/ +import QtQuick 2.9 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.3 as Controls +import org.kde.kirigami 2.4 as Kirigami +import org.kde.private.kcm.kscreen 1.0 as KScreen + +ColumnLayout { + id: outputPanel + property var element: model + + Kirigami.FormLayout { + Controls.Label { +// Kirigami.FormData.label: i18n("Name") + text: element.display + } + + Controls.CheckBox { + text: i18n("Enabled") + checked: element.enabled + onClicked: element.enabled = checked + } + + Controls.CheckBox { + text: i18n("Primary") + checked: element.primary + onClicked: element.primary = checked + visible: kcm.primaryOutputSupported + } + + Controls.ComboBox { + Kirigami.FormData.label: i18n("Resolution:") + model: element.resolutions + currentIndex: element.resolutionIndex !== undefined ? + element.resolutionIndex : -1 + onActivated: element.resolutionIndex = currentIndex + } + + ColumnLayout { + Layout.fillWidth: true + + visible: kcm.perOutputScaling + Kirigami.FormData.label: i18n("Scale:") + Controls.Slider { + id: scaleSlider + + Layout.fillWidth: true + from: 0.5 + to: 3 + stepSize: 0.1 + live: true + value: element.scale + onMoved: element.scale = value + } + Controls.Label { + Layout.alignment: Qt.AlignHCenter + text: scaleSlider.value.toLocaleString(Qt.locale(), "f", 1) + } + } + + Controls.ButtonGroup { + buttons: orientation.children + } + + RowLayout { + id: orientation + Kirigami.FormData.label: i18n("Orientation:") + Layout.fillWidth: true + + RotationButton { + value: 0 + tooltip: i18n("No Rotation") + onClicked: element.rotation = KScreen.Output.None + checked: element.rotation === KScreen.Output.None + } + RotationButton { + value: 90 + tooltip: i18n("90° Clockwise") + onClicked: element.rotation = KScreen.Output.Left + checked: element.rotation === KScreen.Output.Left + } + RotationButton { + value: 180 + tooltip: i18n("Upside Down") + onClicked: element.rotation = KScreen.Output.Inverted + checked: element.rotation === KScreen.Output.Inverted + } + RotationButton { + value: 270 + tooltip: i18n("90° Counterclockwise") + onClicked: element.rotation = KScreen.Output.Right + checked: element.rotation === KScreen.Output.Right + } + } + + Controls.ComboBox { + Kirigami.FormData.label: i18n("Refresh rate:") + model: element.refreshRates + currentIndex: element.refreshRateIndex ? + element.refreshRateIndex : 0 + onActivated: element.refreshRateIndex = currentIndex + } + } +} diff --git a/kcm/package/contents/ui/Panel.qml b/kcm/package/contents/ui/Panel.qml new file mode 100644 --- /dev/null +++ b/kcm/package/contents/ui/Panel.qml @@ -0,0 +1,128 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +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) any later version. + +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 . +*********************************************************************/ +import QtQuick 2.9 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.3 as Controls +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.kirigami 2.4 as Kirigami +import org.kde.private.kcm.kscreen 1.0 as KScreen + +ColumnLayout { + Kirigami.FormLayout { + twinFormLayouts: globalSettingsLayout + Item { + Kirigami.FormData.isSection: true + Kirigami.FormData.label: i18n("Output settings") + + Rectangle { + anchors.fill: parent + opacity: 0.5 + } + } + } + + Controls.SwipeView { + id: panelView + currentIndex: root.selectedOutput + + onCurrentIndexChanged: root.selectedOutput = + Qt.binding(function() { return currentIndex; }); + + Layout.fillWidth: true + + Repeater { + model: kcm.outputModel + OutputPanel {} + } + } + + Controls.PageIndicator { + id: indicator + + Layout.alignment: Qt.AlignHCenter + opacity: count > 1 ? 1 : 0 + + count: panelView.count + currentIndex: panelView.currentIndex + interactive: true + onCurrentIndexChanged: root.selectedOutput = currentIndex + } + + Kirigami.FormLayout { + id: globalSettingsLayout + Layout.fillWidth: true + Layout.topMargin: 20 + Kirigami.Separator { + Layout.fillWidth: true + Kirigami.FormData.isSection: true + } + Item { + Layout.fillWidth: true + Kirigami.FormData.isSection: true + Kirigami.FormData.label: i18n("Arrangement settings") + } + + ColumnLayout { + Layout.fillWidth: true + Kirigami.FormData.label: i18n("Global scale:") + + visible: !kcm.perOutputScaling + + Controls.Slider { + id: globalScaleSlider + + Layout.fillWidth: true + from: 1 + to: 3 + stepSize: 0.1 + live: true + value: kcm.globalScale + onMoved: kcm.globalScale = value + } + Controls.Label { + Layout.alignment: Qt.AlignHCenter + text: globalScaleSlider.value.toLocaleString(Qt.locale(), "f", 1) + } + } + + Controls.ButtonGroup { + buttons: retentionSelector.children + } + + ColumnLayout { + id: retentionSelector + + Kirigami.FormData.label: i18n("Save values of an output:") + Kirigami.FormData.buddyFor: globalRetentionRadio + spacing: Kirigami.Units.smallSpacing + + Controls.RadioButton { + id: globalRetentionRadio + text: i18n("For this and other combination of outputs") + checked: !individualRetentionRadio.checked + onClicked: kcm.outputRetention = KScreen.Control.Global + } + + Controls.RadioButton { + id: individualRetentionRadio + text: i18n("For this specific setup independently of others") + checked: kcm.outputRetention === KScreen.Control.Individual + onClicked: kcm.outputRetention = KScreen.Control.Individual + } + } + } +} diff --git a/kcm/package/contents/ui/RotationButton.qml b/kcm/package/contents/ui/RotationButton.qml new file mode 100644 --- /dev/null +++ b/kcm/package/contents/ui/RotationButton.qml @@ -0,0 +1,60 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +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) any later version. + +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 . +*********************************************************************/ +import QtQuick 2.9 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.4 as Controls + +Item { + id: root + property int value + property alias checked: button.checked + property var tooltip + + signal clicked() + + Layout.fillWidth: true + height: childrenRect.height + + Controls.ToolButton { + id: button + icon.name: "view-preview" + + Controls.ToolTip { + text: tooltip + } + + onClicked: { + if (checked) { + return; + } + + root.clicked(); + screen.resetTotalSize() + } + + implicitWidth: 45 + implicitHeight: 45 + + transform: Rotation { + origin { + x: button.width / 2 + y: button.height / 2 + } + angle: root.value + } + } +} diff --git a/kcm/package/contents/ui/Screen.qml b/kcm/package/contents/ui/Screen.qml new file mode 100644 --- /dev/null +++ b/kcm/package/contents/ui/Screen.qml @@ -0,0 +1,78 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +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) any later version. + +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 . +*********************************************************************/ +import QtQuick 2.9 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.3 as Controls + +Rectangle { + property var outputs + property size totalSize + + function resetTotalSize() { + totalSize = kcm.normalizeScreen(); + } + + onWidthChanged: resetTotalSize() + onHeightChanged: resetTotalSize() + + property real relativeFactor: { + var relativeSize = Qt.size(totalSize.width / (0.6 * width), + totalSize.height / (0.6 * height)); + if (relativeSize.width > relativeSize.height) { + // Available width smaller than height, optimize for width (we have + // '>' because the available width, height is in the denominator). + return relativeSize.width; + } else { + return relativeSize.height; + } + } + + property int xOffset: (width - totalSize.width / relativeFactor) / 2; + property int yOffset: (height - totalSize.height / relativeFactor) / 2; + + implicitHeight: root.height * 0.45 + color: "white" + clip: true + + Row { + z: 90 + anchors { + bottom: parent.bottom + horizontalCenter: parent.horizontalCenter + margins: units.smallSpacing + } + spacing: units.smallSpacing + Controls.Button { + onClicked: kcm.identifyOutputs() + text: i18n("Identify") + focusPolicy: Qt.NoFocus + } + Controls.Button { + enabled: !kcm.screenNormalized + onClicked: resetTotalSize() + text: i18n("Center view") + focusPolicy: Qt.NoFocus + } + } + + Repeater { + model: kcm.outputModel + delegate: Output {} + + onCountChanged: resetTotalSize() + } +} diff --git a/kcm/package/contents/ui/main.qml b/kcm/package/contents/ui/main.qml new file mode 100644 --- /dev/null +++ b/kcm/package/contents/ui/main.qml @@ -0,0 +1,128 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +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) any later version. + +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 . +*********************************************************************/ +import QtQuick 2.9 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.3 as Controls +import org.kde.kirigami 2.4 as Kirigami + +import org.kde.kcm 1.2 as KCM + +KCM.SimpleKCM { + id: root + + implicitWidth: units.gridUnit * 40 + implicitHeight: units.gridUnit * 50 + + property int selectedOutput: 0 + + ColumnLayout { + Kirigami.InlineMessage { + // Note1: There is an implicit height binding loop error on + // first invokation. Seems to be an issue in Kirigami. + // Note2: This should maybe go in header component of the KCM, + // but there seems to be another issue in Kirigami then + // being always hidden. Compare Night Color KCM with + // the same issue. + id: dangerousSaveMsg + + Layout.fillWidth: true + type: Kirigami.MessageType.Warning + text: i18n("Are you sure you want to disable all outputs? " + + "This might render the device unusable.") + showCloseButton: true + + actions: [ + Kirigami.Action { + iconName: "dialog-ok" + text: i18n("Disable all outputs") + onTriggered: { + dangerousSaveMsg.visible = false; + kcm.forceSave(); + } + } + ] + } + Kirigami.InlineMessage { + id: errBackendMsg + Layout.fillWidth: true + type: Kirigami.MessageType.Error + text: i18n("No KScreen backend found. Please check your KScreen installation.") + visible: false + showCloseButton: false + } + Kirigami.InlineMessage { + id: errSaveMsg + Layout.fillWidth: true + type: Kirigami.MessageType.Error + text: i18n("Outputs could not be saved due to error.") + visible: false + showCloseButton: true + } + Kirigami.InlineMessage { + id: scaleMsg + Layout.fillWidth: true + type: Kirigami.MessageType.Positive + text: i18n("New global scale applied. " + + "Change will come into effect after restart.") + visible: false + showCloseButton: true + } + Kirigami.InlineMessage { + id: connectMsg + Layout.fillWidth: true + type: Kirigami.MessageType.Information + visible: false + showCloseButton: true + } + + Connections { + target: kcm + onDangerousSave: dangerousSaveMsg.visible = true; + onErrorOnSave: errSaveMsg.visible = true; + onGlobalScaleWritten: scaleMsg.visible = true; + onOutputConnect: { + if (connected) { + connectMsg.text = i18n("A new output has been added. " + + " Settings have been reloaded."); + } else { + connectMsg.text = i18n("An output has been removed. " + + " Settings have been reloaded."); + } + connectMsg.visible = true; + } + onBackendErrorChanged: errBackendMsg.visible = kcm.backendError + + onChanged: { + dangerousSaveMsg.visible = false; + errSaveMsg.visible = false; + scaleMsg.visible = false; + } + } + + Screen { + id: screen + Layout.fillWidth: true + enabled: kcm.outputModel && !kcm.backendError + outputs: kcm.outputModel + } + + Panel { + enabled: kcm.outputModel && !kcm.backendError + Layout.fillWidth: true + } + } +} diff --git a/kcm/package/metadata.desktop b/kcm/package/metadata.desktop new file mode 100644 --- /dev/null +++ b/kcm/package/metadata.desktop @@ -0,0 +1,18 @@ +[Desktop Entry] +Name=Displays +Comment=Manage and configure monitors and displays + +Icon=preferences-desktop-display-randr +Keywords= +Type=Service +X-KDE-ParentApp= +X-KDE-PluginInfo-Author=Roman Gilg +X-KDE-PluginInfo-Email=subdiff@gmail.com +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-Name=kcm_kscreen +X-KDE-PluginInfo-Version=@KSCREEN_VERSION@ +X-KDE-PluginInfo-Website=https://www.kde.org/plasma-desktop +X-KDE-ServiceTypes=Plasma/Generic +X-Plasma-API=declarativeappletscript + +X-Plasma-MainScript=ui/main.qml diff --git a/kcm/qml/CMakeLists.txt b/kcm/qml/CMakeLists.txt deleted file mode 100644 --- a/kcm/qml/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(QML_FILES - main.qml - Output.qml - OutputIdentifier.qml - Tip.qml -) - -install(FILES ${QML_FILES} DESTINATION ${KDE_INSTALL_DATADIR}/kcm_kscreen/qml) diff --git a/kcm/qml/Output.qml b/kcm/qml/Output.qml deleted file mode 100644 --- a/kcm/qml/Output.qml +++ /dev/null @@ -1,308 +0,0 @@ -/* - Copyright (C) 2012 Dan Vratil - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -import QtQuick 2.1 -import QtGraphicalEffects 1.0 -import org.kde.kscreen 1.0 - -QMLOutput { - - id: root; - - signal clicked(); - signal primaryTriggered(string self); - signal enabledToggled(string self); - signal mousePressed(); - signal mouseReleased(); - - property bool isDragged: monitorMouseArea.drag.active; - property bool isDragEnabled: true; - property bool isToggleButtonVisible: false; - property bool hasMoved: false; - - width: monitorMouseArea.width; - height: monitorMouseArea.height; - - visible: (opacity > 0); - opacity: output.connected ? 1.0 : 0.0; - - - Component.onCompleted: { - root.updateRootProperties(); - } - - SystemPalette { - - id: palette; - } - - MouseArea { - - id: monitorMouseArea; - - width: root.currentOutputWidth * screen.outputScale; - height: root.currentOutputHeight * screen.outputScale - - anchors.centerIn: parent; - - opacity: root.output.enabled ? 1.0 : 0.5; - transformOrigin: Item.Center; - rotation: { - if (output.rotation === KScreenOutput.None) { - return 0; - } else if (output.rotation === KScreenOutput.Left) { - return 270; - } else if (output.rotation === KScreenOutput.Inverted) { - return 180; - } else { - return 90; - } - } - - hoverEnabled: true; - preventStealing: true; - - drag { - target: root.isDragEnabled && !root.isCloneMode ? root : null; - axis: Drag.XandYAxis; - minimumX: 0; - maximumX: screen.maxScreenSize.width - root.width; - minimumY: 0; - maximumY: screen.maxScreenSize.height - root.height; - filterChildren: false; - } - - drag.onActiveChanged: { - /* If the drag is shorter then the animation then make sure - * we won't end up in an inconsistent state */ - if (dragActiveChangedAnimation.running) { - dragActiveChangedAnimation.complete(); - } - - dragActiveChangedAnimation.running = true; - } - - onPressed: root.clicked(); - - /* FIXME: This could be in 'Behavior', but MouseArea had - * some complaints...to tired to investigate */ - PropertyAnimation { - id: dragActiveChangedAnimation; - - target: monitor; - property: "opacity"; - from: monitorMouseArea.drag.active ? 0.7 : 1.0 - to: monitorMouseArea.drag.active ? 1.0 : 0.7 - duration: 100; - easing.type: "OutCubic"; - } - - Behavior on opacity { - PropertyAnimation { - property: "opacity"; - easing.type: "OutCubic"; - duration: 250; - } - } - - Behavior on rotation { - RotationAnimation { - easing.type: "OutCubic" - duration: 250; - direction: RotationAnimation.Shortest; - } - } - - Behavior on width { - PropertyAnimation { - property: "width"; - easing.type: "OutCubic"; - duration: 150; - } - } - - Behavior on height { - PropertyAnimation { - property: "height"; - easing.type: "OutCubic"; - duration: 150; - } - } - - Rectangle { - - id: monitor; - - anchors.fill: parent; - - radius: 4; - color: palette.window; - smooth: true; - clip: true; - - border { - color: root.focus ? palette.highlight : palette.shadow; - width: 1; - - Behavior on color { - PropertyAnimation { - duration: 150; - } - } - } - - Rectangle { - - id: posLabel; - - y: 4; - x: 4; - - width: childrenRect.width + 5; - height: childrenRect.height + 2; - - radius: 4; - - opacity: root.output.enabled && monitorMouseArea.drag.active ? 1.0 : 0.0; - visible: opacity != 0.0; - - color: "#101010"; - - Text { - id: posLabelText; - - text: root.outputX + "," + root.outputY; - - color: "white"; - - y: 2; - x: 2; - } - } - - Item { - y: ((parent.height - orientationPanel.height) / 2) - (implicitHeight / 2) - - anchors { - left: parent.left; - right: parent.right; - leftMargin: 5; - rightMargin: 5; - } - - Text { - id: nameLabel - text: if (root.isCloneMode === true) { - return ""; - } else if (root.output.type !== KScreenOutput.Panel && root.output.edid && root.output.edid.name) { - return root.output.edid.name; - } else { - return ""; - } - - color: palette.text; - font.pixelSize: 10; - - anchors { - horizontalCenter: parent.horizontalCenter; - bottom: labelVendor.top; - topMargin: 5; - } - } - - Text { - id: labelVendor; - text: if (root.isCloneMode) { - return i18n("Unified Outputs"); - } else if (root.output.type === KScreenOutput.Panel) { - return i18n("Laptop Screen"); - } else if (root.output.edid && root.output.edid.vendor) { - return root.output.edid.vendor; - } else { - return root.output.name; - } - - anchors { - verticalCenter: parent.verticalCenter; - left: parent.left; - right: parent.right; - } - horizontalAlignment: Text.AlignHCenter; - - color: palette.text; - font.pixelSize: 14; - elide: Text.ElideRight; - } - - Text { - id: label - text: (labelVendor.text === root.output.name) ? "" : root.output.name - - color: palette.text; - font.pixelSize: 10; - - anchors { - horizontalCenter: parent.horizontalCenter; - top: labelVendor.bottom; - topMargin: 5; - } - } - } - } - Item { - id: orientationPanelContainer; - - anchors.fill: monitor; - - visible: false - - Rectangle { - id: orientationPanel; - - anchors { - left: parent.left; - right: parent.right; - bottom: parent.bottom; - } - - height: 10; - color: root.focus ? palette.highlight : palette.shadow; - smooth: true; - - Behavior on color { - PropertyAnimation { - duration: 150; - } - } - } - } - - OpacityMask { - anchors.fill: orientationPanelContainer; - source: orientationPanelContainer; - maskSource: monitor; - } - } - - Behavior on opacity { - PropertyAnimation { - duration: 200; - easing.type: "OutCubic"; - } - } -} diff --git a/kcm/qml/Tip.qml b/kcm/qml/Tip.qml deleted file mode 100644 --- a/kcm/qml/Tip.qml +++ /dev/null @@ -1,43 +0,0 @@ -import QtQuick 2.1 -import org.kde.plasma.core 2.0 as PlasmaCore - -Item { - - property alias icon: tipIcon.source; - property alias text: tipText.text; - - width: parent.width; - height: units.iconSizes.toolbar; - - opacity: 0.0; - - Behavior on opacity { - PropertyAnimation { - duration: 100; - easing.type: Easing.InOutQuad; - } - } - - PlasmaCore.IconItem { - - id: tipIcon; - - width: units.iconSizes.toolbar; - height: units.iconSizes.toolbar; - - source: "dialog-information"; - } - - Text { - - id: tipText; - - anchors { - left: tipIcon.right; - leftMargin: 5; - verticalCenter: parent.verticalCenter; - } - - color: palette.text; - } -} diff --git a/kcm/qml/main.qml b/kcm/qml/main.qml deleted file mode 100644 --- a/kcm/qml/main.qml +++ /dev/null @@ -1,123 +0,0 @@ -/* - Copyright (C) 2012 Dan Vratil - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -import QtQuick 2.1 -import QtQuick.Controls 1.1 as Controls -import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.kquickcontrols 2.0 -import org.kde.kscreen 1.0 - -Item { - - id: root; - - property variant virtualScreen: null; - - objectName: "root"; - focus: true; - - SystemPalette { - id: palette; - } - - Rectangle { - id: background; - - anchors.fill: parent; - focus: true; - - color: palette.base; - - FocusScope { - - id: outputViewFocusScope; - - anchors.fill: parent; - focus: true; - - QMLScreen { - - id: outputView; - - anchors.fill: parent; - clip: true; - - objectName: "outputView"; - } - } - - Column { - - anchors { - left: parent.left; - right: identifyButton.left; - bottom: parent.bottom; - margins: 5; - } - - spacing: 5; - - Tip { - - id: dragTip; - - icon: "dialog-information"; - text: i18nd("kcm_displayconfiguration", "Tip: Hold Ctrl while dragging a display to disable snapping"); - } - - Tip { - - id: noActiveOutputsWarning; - - icon: "dialog-warning"; - text: i18nd("kcm_displayconfiguration", "Warning: There are no active outputs!"); - } - - Tip { - - id: tooManyActiveOutputs; - objectName: "tooManyActiveOutputs"; - - icon: "dialog-error"; - text: i18ndp("kcm_displayconfiguration", - "Your system only supports up to %1 active screen", - "Your system only supports up to %1 active screens", - virtualScreen ? virtualScreen.maxActiveOutputsCount : 1); - } - } - - - Controls.ToolButton { - - id: identifyButton - objectName: "identifyButton"; - - anchors { - right: parent.right - bottom: parent.bottom - margins: 5 - } - - height: width - width: theme.largeIconSize; - iconName: "kdocumentinfo" - - tooltip: i18nd("kcm_displayconfiguration", "Identify outputs"); - } - } -} diff --git a/kcm/src/CMakeLists.txt b/kcm/src/CMakeLists.txt deleted file mode 100644 --- a/kcm/src/CMakeLists.txt +++ /dev/null @@ -1,52 +0,0 @@ -add_definitions(-DTRANSLATION_DOMAIN=\"kcm_displayconfiguration\") - -include_directories("${CMAKE_CURRENT_SOURCE_DIR}/declarative") - - -set(kcm_kscreen_SRCS - declarative/qmloutput.cpp - declarative/qmloutputcomponent.cpp - declarative/qmlscreen.cpp - - controlpanel.cpp - outputconfig.cpp - unifiedoutputconfig.cpp - resolutionslider.cpp - utils.cpp - widget.cpp - previewwidget.cpp - scalingconfig.cpp - ../../common/globals.cpp - ../../common/control.cpp -) - -ecm_qt_declare_logging_category(kcm_kscreen_SRCS HEADER kcm_screen_debug.h IDENTIFIER KSCREEN_KCM CATEGORY_NAME kscreen.kcm) - - -ki18n_wrap_ui(kcm_kscreen_SRCS kscreen_widget.ui stylepreview.ui scaling.ui) - -add_library(kcm_kscreen MODULE kcm_kscreen.cpp ${kcm_kscreen_SRCS}) - -target_link_libraries(kcm_kscreen - Qt5::QuickWidgets - Qt5::Widgets - KF5::Screen - KF5::I18n - KF5::ConfigCore - KF5::ConfigWidgets - KF5::WidgetsAddons -) - -install(TARGETS kcm_kscreen DESTINATION ${KDE_INSTALL_PLUGINDIR} ) - -add_executable(kcm_testapp kcm_testapp.cpp ${kcm_kscreen_SRCS}) -set_target_properties(kcm_testapp PROPERTIES COMPILE_FLAGS "-DQT_DECLARATIVE_DEBUG") -target_link_libraries(kcm_testapp - Qt5::QuickWidgets - Qt5::Widgets - KF5::CoreAddons - KF5::I18n - KF5::ConfigCore - KF5::Screen - KF5::WidgetsAddons -) diff --git a/kcm/src/controlpanel.h b/kcm/src/controlpanel.h deleted file mode 100644 --- a/kcm/src/controlpanel.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2013 Daniel Vrátil - * - * 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 . - * - */ - -#ifndef CONTROLPANEL_H -#define CONTROLPANEL_H - -#include "../../common/control.h" - -#include - -#include - -class QVBoxLayout; -class OutputConfig; -class UnifiedOutputConfig; - - -#include - -class ControlPanel : public QFrame -{ - Q_OBJECT - - public: - explicit ControlPanel(QWidget *parent = nullptr); - ~ControlPanel() override; - - void setConfig(const KScreen::ConfigPtr &config); - - void setUnifiedOutput(const KScreen::OutputPtr &output); - - void save(); - - public Q_SLOTS: - void activateOutput(const KScreen::OutputPtr &output); - - Q_SIGNALS: - void changed(); - -private Q_SLOTS: - void addOutput(const KScreen::OutputPtr &output); - void removeOutput(int outputId); - - private: - KScreen::ConfigPtr mConfig; - QList mOutputConfigs; - - QVBoxLayout *mLayout; - UnifiedOutputConfig *mUnifiedOutputCfg; - - std::unique_ptr mControlConfig = nullptr; -}; - -#endif // CONTROLPANEL_H diff --git a/kcm/src/controlpanel.cpp b/kcm/src/controlpanel.cpp deleted file mode 100644 --- a/kcm/src/controlpanel.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2013 Daniel Vrátil - * - * 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 . - * - */ - -#include "controlpanel.h" -#include "outputconfig.h" -#include "unifiedoutputconfig.h" -#include "kcm_screen_debug.h" - -#include - -#include - -ControlPanel::ControlPanel(QWidget *parent) - : QFrame(parent) - , mUnifiedOutputCfg(nullptr) -{ - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); - mLayout = new QVBoxLayout(this); -} - -ControlPanel::~ControlPanel() -{ -} - -void ControlPanel::setConfig(const KScreen::ConfigPtr &config) -{ - qDeleteAll(mOutputConfigs); - mOutputConfigs.clear(); - delete mUnifiedOutputCfg; - mUnifiedOutputCfg = nullptr; - - if (mConfig) { - mConfig->disconnect(this); - } - - mConfig = config; - mControlConfig = std::unique_ptr(new ControlConfig(config)); - connect(mConfig.data(), &KScreen::Config::outputAdded, - this, &ControlPanel::addOutput); - connect(mConfig.data(), &KScreen::Config::outputRemoved, - this, &ControlPanel::removeOutput); - - for (const KScreen::OutputPtr &output : mConfig->outputs()) { - addOutput(output); - } -} - -void ControlPanel::addOutput(const KScreen::OutputPtr &output) -{ - OutputConfig *outputCfg = new OutputConfig(this); - outputCfg->setVisible(false); - outputCfg->setShowScaleOption(mConfig->supportedFeatures().testFlag(KScreen::Config::Feature::PerOutputScaling)); - outputCfg->setOutput(output, mControlConfig->getOutputRetention(output)); - connect(outputCfg, &OutputConfig::changed, - this, &ControlPanel::changed); - mLayout->addWidget(outputCfg); - mOutputConfigs << outputCfg; -} - -void ControlPanel::removeOutput(int outputId) -{ - for (OutputConfig *outputCfg : mOutputConfigs) { - if (outputCfg->output()->id() == outputId) { - mOutputConfigs.removeOne(outputCfg); - delete outputCfg; - return; - } - } -} - -void ControlPanel::activateOutput(const KScreen::OutputPtr &output) -{ - // Ignore activateOutput when in unified mode - if (mUnifiedOutputCfg) { - return; - } - - qCDebug(KSCREEN_KCM) << "Activate output" << output->id(); - - Q_FOREACH (OutputConfig *cfg, mOutputConfigs) { - cfg->setVisible(cfg->output()->id() == output->id()); - } -} - -void ControlPanel::setUnifiedOutput(const KScreen::OutputPtr &output) -{ - Q_FOREACH (OutputConfig *config, mOutputConfigs) { - if (!config->output()->isConnected()) { - continue; - } - - config->setVisible(output == nullptr); - } - - if (output.isNull()) { - mUnifiedOutputCfg->deleteLater(); - mUnifiedOutputCfg = nullptr; - } else { - mUnifiedOutputCfg = new UnifiedOutputConfig(mConfig, this); - mUnifiedOutputCfg->setOutput(output); - mUnifiedOutputCfg->setVisible(true); - mLayout->insertWidget(mLayout->count() - 2, mUnifiedOutputCfg); - connect(mUnifiedOutputCfg, &UnifiedOutputConfig::changed, - this, &ControlPanel::changed); - } -} - -void ControlPanel::save() -{ - if (!mControlConfig) { - return; - } - for (const auto outputConfig : mOutputConfigs) { - if (!outputConfig->hasChange()) { - continue; - } - mControlConfig->setOutputRetention(outputConfig->output(), outputConfig->applyRetention()); - } - - if (!mControlConfig->writeFile()) { - // TODO: error handling - } - // TODO: write output controls in the future -} diff --git a/kcm/src/declarative/qmloutput.h b/kcm/src/declarative/qmloutput.h deleted file mode 100644 --- a/kcm/src/declarative/qmloutput.h +++ /dev/null @@ -1,203 +0,0 @@ -/* - Copyright (C) 2013 Daniel Vrátil - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - - -#ifndef QMLOUTPUT_H -#define QMLOUTPUT_H - -#include -#include - - -class QMLScreen; - - -class QMLOutput : public QQuickItem -{ - Q_OBJECT - - Q_PROPERTY(KScreen::Output* output - READ output - NOTIFY outputChanged) - - Q_PROPERTY(KScreen::OutputPtr outputPtr - READ outputPtr - WRITE setOutputPtr - NOTIFY outputChanged) - - Q_PROPERTY(bool isCloneMode - READ isCloneMode - WRITE setIsCloneMode - NOTIFY isCloneModeChanged) - - Q_PROPERTY(QMLScreen* screen - READ screen - WRITE setScreen - NOTIFY screenChanged) - - Q_PROPERTY(QMLOutput* cloneOf - READ cloneOf - WRITE setCloneOf - NOTIFY cloneOfChanged) - - Q_PROPERTY(QMLOutput* leftDockedTo - READ leftDockedTo - WRITE setLeftDockedTo - RESET undockLeft - NOTIFY leftDockedToChanged) - - Q_PROPERTY(QMLOutput* topDockedTo - READ topDockedTo - WRITE setTopDockedTo - RESET undockTop - NOTIFY topDockedToChanged) - - Q_PROPERTY(QMLOutput* rightDockedTo - READ rightDockedTo - WRITE setRightDockedTo - RESET undockRight - NOTIFY rightDockedToChanged) - - Q_PROPERTY(QMLOutput* bottomDockedTo - READ bottomDockedTo - WRITE setBottomDockedTo - RESET undockBottom - NOTIFY bottomDockedToChanged) - - Q_PROPERTY(int currentOutputHeight - READ currentOutputHeight - NOTIFY currentOutputSizeChanged) - - Q_PROPERTY(int currentOutputWidth - READ currentOutputWidth - NOTIFY currentOutputSizeChanged) - - /* Workaround for possible QML bug when calling output.pos.y = VALUE works, - * but output.pos.x = VALUE has no effect */ - Q_PROPERTY(int outputX - READ outputX - WRITE setOutputX - NOTIFY outputXChanged) - - Q_PROPERTY(int outputY - READ outputY - WRITE setOutputY - NOTIFY outputYChanged) - - public: - enum { - ModeRole = Qt::UserRole, - ModeIdRole, - SizeRole, - RefreshRateRole - }; - - explicit QMLOutput(QQuickItem *parent = nullptr); - - KScreen::Output* output() const; // For QML - - KScreen::OutputPtr outputPtr() const; - void setOutputPtr(const KScreen::OutputPtr &output); - - QMLScreen* screen() const; - void setScreen(QMLScreen *screen); - - QMLOutput* leftDockedTo() const; - void setLeftDockedTo(QMLOutput *output); - void undockLeft(); - - QMLOutput* topDockedTo() const; - void setTopDockedTo(QMLOutput *output); - void undockTop(); - - QMLOutput* rightDockedTo() const; - void setRightDockedTo(QMLOutput *output); - void undockRight(); - - QMLOutput* bottomDockedTo() const; - void setBottomDockedTo(QMLOutput *output); - void undockBottom(); - - Q_INVOKABLE bool collidesWithOutput(QObject *other); - Q_INVOKABLE bool maybeSnapTo(QMLOutput *other); - - void setCloneOf(QMLOutput *other); - QMLOutput* cloneOf() const; - - int currentOutputHeight() const; - int currentOutputWidth() const; - - int outputX() const; - void setOutputX(int x); - - int outputY() const; - void setOutputY(int y); - - void setIsCloneMode(bool isCloneMode); - bool isCloneMode() const; - - void dockToNeighbours(); - - public Q_SLOTS: - void updateRootProperties(); - - Q_SIGNALS: - void changed(); - - void moved(const QString &self); - - /* Property notifications */ - void outputChanged(); - void screenChanged(); - void cloneOfChanged(); - void currentOutputSizeChanged(); - - void leftDockedToChanged(); - void topDockedToChanged(); - void rightDockedToChanged(); - void bottomDockedToChanged(); - - void outputYChanged(); - void outputXChanged(); - - void isCloneModeChanged(); - - private Q_SLOTS: - void moved(); - void currentModeIdChanged(); - - private: - /** - * Returns the biggest resolution available assuming it's the preferred one - */ - KScreen::ModePtr bestMode() const; - - KScreen::OutputPtr m_output; - QMLScreen *m_screen; - - QMLOutput *m_cloneOf; - QMLOutput *m_leftDock; - QMLOutput *m_topDock; - QMLOutput *m_rightDock; - QMLOutput *m_bottomDock; - - bool m_isCloneMode; -}; - -#endif // QMLOUTPUT_H - diff --git a/kcm/src/declarative/qmloutput.cpp b/kcm/src/declarative/qmloutput.cpp deleted file mode 100644 --- a/kcm/src/declarative/qmloutput.cpp +++ /dev/null @@ -1,588 +0,0 @@ -/* - Copyright (C) 2012 Dan Vratil - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - - -#include "qmloutput.h" -#include "qmlscreen.h" - -#include - -#include -#include -#include - -const static int sMargin = 0; -const static int sSnapArea = 20; -const static int sSnapAlignArea = 6; - -Q_DECLARE_METATYPE(KScreen::ModePtr) - -bool operator>(const QSize &sizeA, const QSize &sizeB) -{ - return ((sizeA.width() > sizeB.width()) && (sizeA.height() > sizeB.height())); -} - -QMLOutput::QMLOutput(QQuickItem *parent): - QQuickItem(parent), - m_screen(nullptr), - m_cloneOf(nullptr), - m_leftDock(nullptr), - m_topDock(nullptr), - m_rightDock(nullptr), - m_bottomDock(nullptr), - m_isCloneMode(false) -{ - connect(this, &QMLOutput::xChanged, - this, static_cast(&QMLOutput::moved)); - connect(this, &QMLOutput::yChanged, - this, static_cast(&QMLOutput::moved)); -} - -KScreen::Output* QMLOutput::output() const -{ - return m_output.data(); -} - -KScreen::OutputPtr QMLOutput::outputPtr() const -{ - return m_output; -} - -void QMLOutput::setOutputPtr(const KScreen::OutputPtr &output) -{ - Q_ASSERT(m_output.isNull()); - - m_output = output; - Q_EMIT outputChanged(); - - connect(m_output.data(), &KScreen::Output::rotationChanged, - this, &QMLOutput::updateRootProperties); - connect(m_output.data(), &KScreen::Output::currentModeIdChanged, - this, &QMLOutput::currentModeIdChanged); - connect(m_output.data(), &KScreen::Output::scaleChanged, - this, &QMLOutput::currentModeIdChanged); -} - -QMLScreen *QMLOutput::screen() const -{ - return m_screen; -} - -void QMLOutput::setScreen(QMLScreen *screen) -{ - Q_ASSERT(m_screen == nullptr); - - m_screen = screen; - Q_EMIT screenChanged(); -} - -void QMLOutput::setLeftDockedTo(QMLOutput *output) -{ - if (m_leftDock == output) { - return; - } - - m_leftDock = output; - Q_EMIT leftDockedToChanged(); -} - -QMLOutput *QMLOutput::leftDockedTo() const -{ - return m_leftDock; -} - -void QMLOutput::undockLeft() -{ - setLeftDockedTo(nullptr); -} - -void QMLOutput::setTopDockedTo(QMLOutput *output) -{ - if (m_topDock == output) { - return; - } - - m_topDock = output; - Q_EMIT topDockedToChanged(); -} - -QMLOutput *QMLOutput::topDockedTo() const -{ - return m_topDock; -} - -void QMLOutput::undockTop() -{ - setTopDockedTo(nullptr); -} - -void QMLOutput::setRightDockedTo(QMLOutput *output) -{ - if (m_rightDock == output) { - return; - } - - m_rightDock = output; - Q_EMIT rightDockedToChanged(); -} - -QMLOutput *QMLOutput::rightDockedTo() const -{ - return m_rightDock; -} - -void QMLOutput::undockRight() -{ - setRightDockedTo(nullptr); -} - -void QMLOutput::setBottomDockedTo(QMLOutput *output) -{ - if (m_bottomDock == output) { - return; - } - - m_bottomDock = output; - Q_EMIT bottomDockedToChanged(); -} - -QMLOutput *QMLOutput::bottomDockedTo() const -{ - return m_bottomDock; -} - -void QMLOutput::undockBottom() -{ - setBottomDockedTo(nullptr); -} - -void QMLOutput::setCloneOf(QMLOutput* other) -{ - if (m_cloneOf == other) { - return; - } - - m_cloneOf = other; - Q_EMIT cloneOfChanged(); -} - -QMLOutput* QMLOutput::cloneOf() const -{ - return m_cloneOf; -} - -int QMLOutput::currentOutputHeight() const -{ - if (!m_output) { - return 0; - } - - KScreen::ModePtr mode = m_output->currentMode(); - if (!mode) { - if (m_output->isConnected()) { - mode = bestMode(); - if (!mode) { - return 1000; - } - m_output->setCurrentModeId(mode->id()); - } else { - return 1000; - } - } - - return mode->size().height() / m_output->scale(); -} - -int QMLOutput::currentOutputWidth() const -{ - if (!m_output) { - return 0; - } - - KScreen::ModePtr mode = m_output->currentMode(); - if (!mode) { - if (m_output->isConnected()) { - mode = bestMode(); - if (!mode) { - return 1000; - } - m_output->setCurrentModeId(mode->id()); - } else { - return 1000; - } - } - - return mode->size().width() / m_output->scale(); -} - -void QMLOutput::currentModeIdChanged() -{ - if (!m_output) { - return; - } - - if (isCloneMode()) { - const float newWidth = currentOutputWidth() * m_screen->outputScale(); - setX((m_screen->width() - newWidth) / 2); - const float newHeight = currentOutputHeight() * m_screen->outputScale(); - setY((m_screen->height() - newHeight) / 2); - } else { - if (m_rightDock) { - QMLOutput *rightDock = m_rightDock; - float newWidth = currentOutputWidth() * m_screen->outputScale(); - setX(rightDock->x() - newWidth); - setRightDockedTo(rightDock); - } - - if (m_bottomDock) { - QMLOutput *bottomDock = m_bottomDock; - float newHeight = currentOutputHeight() * m_screen->outputScale(); - setY(bottomDock->y() - newHeight); - setBottomDockedTo(bottomDock); - } - } - - Q_EMIT currentOutputSizeChanged(); -} - - -int QMLOutput::outputX() const -{ - return m_output->pos().x(); -} - -void QMLOutput::setOutputX(int x) -{ - if (m_output->pos().rx() == x) { - return; - } - - QPoint pos = m_output->pos(); - pos.setX(x); - m_output->setPos(pos); - Q_EMIT outputXChanged(); -} - -int QMLOutput::outputY() const -{ - return m_output->pos().y(); -} - -void QMLOutput::setOutputY(int y) -{ - if (m_output->pos().ry() == y) { - return; - } - - QPoint pos = m_output->pos(); - pos.setY(y); - m_output->setPos(pos); - Q_EMIT outputYChanged(); -} - -bool QMLOutput::isCloneMode() const -{ - return m_isCloneMode; -} - -void QMLOutput::setIsCloneMode(bool isCloneMode) -{ - if (m_isCloneMode == isCloneMode) { - return; - } - - m_isCloneMode = isCloneMode; - Q_EMIT isCloneModeChanged(); -} - -void QMLOutput::dockToNeighbours() -{ - Q_FOREACH (QMLOutput *otherQmlOutput, m_screen->outputs()) { - if (otherQmlOutput == this) { - continue; - } - - if (!otherQmlOutput->output()->isConnected() || !otherQmlOutput->output()->isEnabled()) { - continue; - } - - const QRect geom = m_output->geometry(); - const QRect otherGeom = otherQmlOutput->output()->geometry(); - - if (geom.left() - 1 == otherGeom.right()) { - setLeftDockedTo(otherQmlOutput); - continue; - } - if (geom.right() + 1 == otherGeom.left()) { - setRightDockedTo(otherQmlOutput); - continue; - } - if (geom.top() - 1 == otherGeom.bottom()) { - setTopDockedTo(otherQmlOutput); - continue; - } - if (geom.bottom() + 1 == otherGeom.top()) { - setBottomDockedTo(otherQmlOutput); - continue; - } - } -} - -KScreen::ModePtr QMLOutput::bestMode() const -{ - if (!m_output) { - return KScreen::ModePtr(); - } - - KScreen::ModeList modes = m_output->modes(); - KScreen::ModePtr bestMode; - Q_FOREACH (const KScreen::ModePtr &mode, modes) { - if (!bestMode || (mode->size() > bestMode->size())) { - bestMode = mode; - } - } - - return bestMode; -} - -bool QMLOutput::collidesWithOutput(QObject *other) -{ - QQuickItem* otherItem = qobject_cast(other); - return boundingRect().intersects(otherItem->boundingRect()); -} - -bool QMLOutput::maybeSnapTo(QMLOutput *other) -{ - qreal centerX = x() + (width() / 2.0); - qreal centerY = y() + (height() / 2.0); - - const qreal x2 = other->x(); - const qreal y2 = other->y(); - const qreal height2 = other->height(); - const qreal width2 = other->width(); - const qreal centerX2 = x2 + (width2 / 2.0); - const qreal centerY2 = y2 + (height2 / 2.0); - - /* left of other */ - if ((x() + width() > x2 - sSnapArea) && (x() + width() < x2 + sSnapArea) && - (y() + height() > y2) && (y() < y2 + height2)) - { - setX(x2 - width() + sMargin); - centerX = x() + (width() / 2.0); - setRightDockedTo(other); - other->setLeftDockedTo(this); - //output.cloneOf = null; - - /* output is snapped to other on left and their - * upper sides are aligned */ - if ((y() < y2 + sSnapAlignArea) && (y() > y2 - sSnapAlignArea)) { - setY(y2); - return true; - } - - /* output is snapped to other on left and they - * are centered */ - if ((centerY < centerY2 + sSnapAlignArea) && (centerY > centerY2 - sSnapAlignArea)) { - setY(centerY2 - (height() / 2.0)); - return true; - } - - /* output is snapped to other on left and their - * bottom sides are aligned */ - if ((y() + height() < y2 + height2 + sSnapAlignArea) && - (y() + height() > y2 + height2 - sSnapAlignArea)) - { - setY(y2 + height2 - height()); - return true; - } - - return true; - } - - /* output is right of other */ - if ((x() > x2 + width2 - sSnapArea) && (x() < x2 + width2 + sSnapArea) && - (y() + height() > y2) && (y() < y2 + height2)) - { - setX(x2 + width2 - sMargin); - centerX = x() + (width() / 2.0); - setLeftDockedTo(other); - other->setRightDockedTo(this); - //output.cloneOf = null; - - /* output is snapped to other on right and their - * upper sides are aligned */ - if ((y() < y2 + sSnapAlignArea) && (y() > y2 - sSnapAlignArea)) { - setY(y2); - return true; - } - - /* output is snapped to other on right and they - * are centered */ - if ((centerY < centerY2 + sSnapAlignArea) && (centerY > centerY2 - sSnapAlignArea)) { - setY(centerY2 - (height() / 2.0)); - return true; - } - - /* output is snapped to other on right and their - * bottom sides are aligned */ - if ((y() + height() < y2 + height2 + sSnapAlignArea) && - (y() + height() > y2 + height2 - sSnapAlignArea)) - { - setY(y2 + height2 - height()); - return true; - } - - return true; - } - - /* output is above other */ - if ((y() + height() > y2 - sSnapArea) && (y() + height() < y2 + sSnapArea) && - (x() + width() > x2) && (x() < x2 + width2)) - { - setY(y2 - height() + sMargin); - centerY = y() + (height() / 2.0); - setBottomDockedTo(other); - other->setTopDockedTo(this); - //output.cloneOf = null; - - /* output is snapped to other on top and their - * left sides are aligned */ - if ((x() < x2 + sSnapAlignArea) && (x() > x2 - sSnapAlignArea)) { - setX(x2); - return true; - } - - /* output is snapped to other on top and they - * are centered */ - if ((centerX < centerX2 + sSnapAlignArea) && (centerX > centerX2 - sSnapAlignArea)) { - setX(centerX2 - (width() / 2.0)); - return true; - } - - /* output is snapped to other on top and their - * right sides are aligned */ - if ((x() + width() < x2 + width2 + sSnapAlignArea) && - (x() + width() > x2 + width2 - sSnapAlignArea)) - { - setX(x2 + width2 - width()); - return true; - } - - return true; - } - - /* output is below other */ - if ((y() > y2 + height2 - sSnapArea) && (y() < y2 + height2 + sSnapArea) && - (x() + width() > x2) && (x() < x2 + width2)) - { - setY(y2 + height2 - sMargin); - centerY = y() + (height() / 2.0); - setTopDockedTo(other); - other->setBottomDockedTo(this); - //output.cloneOf = null; - - /* output is snapped to other on bottom and their - * left sides are aligned */ - if ((x() < x2 + sSnapAlignArea) && (x() > x2 - sSnapAlignArea)) { - setX(x2); - return true; - } - - /* output is snapped to other on bottom and they - * are centered */ - if ((centerX < centerX2 + sSnapAlignArea) && (centerX > centerX2 - sSnapAlignArea)) { - setX(centerX2 - (width() / 2.0)); - return true; - } - - /* output is snapped to other on bottom and their - * right sides are aligned */ - if ((x() + width() < x2 + width2 + sSnapAlignArea) && - (x() + width() > x2 + width2 - sSnapAlignArea)) - { - setX(x2 + width2 - width()); - return true; - } - - return true; - } - - return false; -} - -void QMLOutput::moved() -{ - const QList siblings = screen()->childItems(); - - // First, if we have moved, then unset the "cloneOf" flag - setCloneOf(nullptr); - - disconnect(this, &QMLOutput::xChanged, this, static_cast(&QMLOutput::moved)); - disconnect(this, &QMLOutput::yChanged, this, static_cast(&QMLOutput::moved)); - Q_FOREACH (QQuickItem *sibling, siblings) { - QMLOutput *otherOutput = qobject_cast(sibling); - if (!otherOutput || otherOutput == this) { - continue; - } - - if (!maybeSnapTo(otherOutput)) { - if (m_leftDock == otherOutput) { - m_leftDock->undockRight(); - undockLeft(); - } - if (m_topDock == otherOutput) { - m_topDock->undockBottom(); - undockTop(); - } - if (m_rightDock == otherOutput) { - m_rightDock->undockLeft(); - undockRight(); - } - if (m_bottomDock == otherOutput) { - m_bottomDock->undockTop(); - undockBottom(); - } - } - } - connect(this, &QMLOutput::xChanged, this, static_cast(&QMLOutput::moved)); - connect(this, &QMLOutput::yChanged, this, static_cast(&QMLOutput::moved)); - - Q_EMIT moved(m_output->name()); -} - - -/* Transformation of an item (rotation of the MouseArea) is only visual. - * The coordinates and dimensions are still the same (when you rotated - * 100x500 rectangle by 90 deg, it will still be 100x500, although - * visually it will be 500x100). - * - * This method calculates the real-visual coordinates and dimensions of - * the MouseArea and updates root item to match them. This makes snapping - * work correctly regardless off visual rotation of the output - */ -void QMLOutput::updateRootProperties() -{ - const float transformedWidth = (m_output->isHorizontal() ? currentOutputWidth() : currentOutputHeight()) * m_screen->outputScale(); - const float transformedHeight = (m_output->isHorizontal() ? currentOutputHeight() : currentOutputWidth()) * m_screen->outputScale(); - - const float transformedX = x() + (width() / 2.0) - (transformedWidth / 2.0); - const float transformedY = y() + (height() / 2.0) - (transformedHeight / 2.0); - - setPosition(QPointF(transformedX, transformedY)); - setSize(QSizeF(transformedWidth, transformedHeight)); -} diff --git a/kcm/src/declarative/qmloutputcomponent.h b/kcm/src/declarative/qmloutputcomponent.h deleted file mode 100644 --- a/kcm/src/declarative/qmloutputcomponent.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2013 Daniel Vrátil - * - * 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) any later version. - * - * 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, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#ifndef QMLOUTPUTCOMPONENT_H -#define QMLOUTPUTCOMPONENT_H - -#include - -#include - -class QMLScreen; -class QMLOutput; - -class QMLOutputComponent : public QQmlComponent -{ - Q_OBJECT - - public: - explicit QMLOutputComponent(QQmlEngine *engine, QMLScreen *parent); - ~QMLOutputComponent() override; - - QMLOutput* createForOutput(const KScreen::OutputPtr &output); - - private: - QQmlEngine *m_engine; - -}; - -#endif // QMLOUTPUTCOMPONENT_H diff --git a/kcm/src/declarative/qmloutputcomponent.cpp b/kcm/src/declarative/qmloutputcomponent.cpp deleted file mode 100644 --- a/kcm/src/declarative/qmloutputcomponent.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2013 Daniel Vrátil - * - * 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) any later version. - * - * 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, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#include "qmloutputcomponent.h" -#include "qmloutput.h" -#include "qmlscreen.h" - -#include - -#include -#include - -Q_DECLARE_METATYPE(KScreen::OutputPtr) -Q_DECLARE_METATYPE(QMLScreen*) - -QMLOutputComponent::QMLOutputComponent(QQmlEngine *engine, QMLScreen *parent): - QQmlComponent(engine, parent), - m_engine(engine) -{ - const QString qmlPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kcm_kscreen/qml/Output.qml")); - loadUrl(QUrl::fromLocalFile(qmlPath)); -} - -QMLOutputComponent::~QMLOutputComponent() -{ -} - -QMLOutput* QMLOutputComponent::createForOutput(const KScreen::OutputPtr &output) -{ - QObject *instance = beginCreate(m_engine->rootContext()); - if (!instance) { - qWarning() << errorString(); - return nullptr; - } - - bool success = instance->setProperty("outputPtr", QVariant::fromValue(output)); - Q_ASSERT(success); - success = instance->setProperty("screen", QVariant::fromValue(qobject_cast(parent()))); - Q_ASSERT(success); - Q_UNUSED(success); - - completeCreate(); - - return qobject_cast(instance); -} diff --git a/kcm/src/declarative/qmlscreen.h b/kcm/src/declarative/qmlscreen.h deleted file mode 100644 --- a/kcm/src/declarative/qmlscreen.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2013 Daniel Vrátil - * - * 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) any later version. - * - * 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, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#ifndef QMLSCREEN_H -#define QMLSCREEN_H - -#include - -#include -#include "qmloutput.h" - - -namespace KScreen { -class Output; -class Config; -} - -class QMLScreen : public QQuickItem -{ - Q_OBJECT - - Q_PROPERTY(QSize maxScreenSize - READ maxScreenSize - CONSTANT) - - Q_PROPERTY(int connectedOutputsCount - READ connectedOutputsCount - NOTIFY connectedOutputsCountChanged) - - Q_PROPERTY(int enabledOutputsCount - READ enabledOutputsCount - NOTIFY enabledOutputsCountChanged) - - Q_PROPERTY(float outputScale - READ outputScale - NOTIFY outputScaleChanged) - - public: - explicit QMLScreen(QQuickItem *parent = nullptr); - ~QMLScreen() override; - - int connectedOutputsCount() const; - int enabledOutputsCount() const; - - QMLOutput* primaryOutput() const; - QList outputs() const; - - QSize maxScreenSize() const; - - float outputScale() const; - - KScreen::ConfigPtr config() const; - void setConfig(const KScreen::ConfigPtr &config); - - void updateOutputsPlacement(); - - void setActiveOutput(QMLOutput *output); - - public Q_SLOTS: - void setActiveOutput() { - setActiveOutput(qobject_cast(sender())); - } - - - Q_SIGNALS: - void connectedOutputsCountChanged(); - void enabledOutputsCountChanged(); - - void outputScaleChanged(); - - void focusedOutputChanged(QMLOutput *output); - - private Q_SLOTS: - void addOutput(const KScreen::OutputPtr &output); - void removeOutput(int outputId); - - void outputConnectedChanged(); - void outputEnabledChanged(); - void outputPositionChanged(); - - void viewSizeChanged(); - - private: - void qmlOutputMoved(QMLOutput *qmlOutput); - void updateCornerOutputs(); - void setOutputScale(float scale); - - KScreen::ConfigPtr m_config; - QHash m_outputMap; - QVector m_manuallyMovedOutputs; - int m_connectedOutputsCount = 0; - int m_enabledOutputsCount = 0; - float m_outputScale = 1.0 / 8.0; - - QMLOutput *m_leftmost = nullptr; - QMLOutput *m_topmost = nullptr; - QMLOutput *m_rightmost = nullptr; - QMLOutput *m_bottommost = nullptr; - -}; - -#endif // QMLSCREEN_H diff --git a/kcm/src/declarative/qmlscreen.cpp b/kcm/src/declarative/qmlscreen.cpp deleted file mode 100644 --- a/kcm/src/declarative/qmlscreen.cpp +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Copyright (C) 2013 Daniel Vrátil - * - * 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) any later version. - * - * 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, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#include "qmlscreen.h" -#include "qmloutputcomponent.h" -#include "qmloutput.h" - -#include -#include - -#include -#include - -QMLScreen::QMLScreen(QQuickItem *parent) - : QQuickItem(parent) -{ - connect(this, &QMLScreen::widthChanged, this, &QMLScreen::viewSizeChanged); - connect(this, &QMLScreen::heightChanged, this, &QMLScreen::viewSizeChanged); -} - -QMLScreen::~QMLScreen() -{ - qDeleteAll(m_outputMap); - m_outputMap.clear(); -} - -KScreen::ConfigPtr QMLScreen::config() const -{ - return m_config; -} - -void QMLScreen::setConfig(const KScreen::ConfigPtr &config) -{ - qDeleteAll(m_outputMap); - m_outputMap.clear(); - m_manuallyMovedOutputs.clear(); - m_bottommost = m_leftmost = m_rightmost = m_topmost = nullptr; - m_connectedOutputsCount = 0; - m_enabledOutputsCount = 0; - - if (m_config) { - m_config->disconnect(this); - } - - m_config = config; - connect(m_config.data(), &KScreen::Config::outputAdded, - this, [this](const KScreen::OutputPtr &output) { - addOutput(output); - updateOutputsPlacement(); - }); - connect(m_config.data(), &KScreen::Config::outputRemoved, - this, &QMLScreen::removeOutput); - - for (const KScreen::OutputPtr &output : m_config->outputs()) { - addOutput(output); - } - - updateOutputsPlacement(); - - for (QMLOutput *qmlOutput : m_outputMap) { - if (qmlOutput->output()->isConnected() && qmlOutput->output()->isEnabled()) { - qmlOutput->dockToNeighbours(); - } - } -} - - -void QMLScreen::addOutput(const KScreen::OutputPtr &output) -{ - QMLOutputComponent comp(qmlEngine(this), this); - QMLOutput *qmloutput = comp.createForOutput(output); - if (!qmloutput) { - qWarning() << "Failed to create QMLOutput"; - return; - } - - m_outputMap.insert(output, qmloutput); - - qmloutput->setParentItem(this); - qmloutput->setZ(m_outputMap.count()); - - connect(output.data(), &KScreen::Output::isConnectedChanged, - this, &QMLScreen::outputConnectedChanged); - connect(output.data(), &KScreen::Output::isEnabledChanged, - this, &QMLScreen::outputEnabledChanged); - connect(output.data(), &KScreen::Output::posChanged, - this, &QMLScreen::outputPositionChanged); - connect(qmloutput, &QMLOutput::yChanged, - [this, qmloutput]() { - qmlOutputMoved(qmloutput); - }); - connect(qmloutput, &QMLOutput::xChanged, - [this, qmloutput]() { - qmlOutputMoved(qmloutput); - }); - connect(qmloutput, SIGNAL(clicked()), - this, SLOT(setActiveOutput())); - - qmloutput->updateRootProperties(); -} - -void QMLScreen::removeOutput(int outputId) -{ - for (const KScreen::OutputPtr &output : m_outputMap.keys()) { - if (output->id() == outputId) { - QMLOutput *qmlOutput = m_outputMap.take(output); - qmlOutput->setParentItem(nullptr); - qmlOutput->setParent(nullptr); - qmlOutput->deleteLater(); - return; - } - } -} - - -int QMLScreen::connectedOutputsCount() const -{ - return m_connectedOutputsCount; -} - -int QMLScreen::enabledOutputsCount() const -{ - return m_enabledOutputsCount; -} - -QMLOutput *QMLScreen::primaryOutput() const -{ - Q_FOREACH (QMLOutput *qmlOutput, m_outputMap) { - if (qmlOutput->output()->isPrimary()) { - return qmlOutput; - } - } - - return nullptr; -} - -QList QMLScreen::outputs() const -{ - return m_outputMap.values(); -} - - -void QMLScreen::setActiveOutput(QMLOutput *output) -{ - Q_FOREACH (QMLOutput *qmlOutput, m_outputMap) { - if (qmlOutput->z() > output->z()) { - qmlOutput->setZ(qmlOutput->z() - 1); - } - } - - output->setZ(m_outputMap.count()); - output->setFocus(true); - Q_EMIT focusedOutputChanged(output); -} - -QSize QMLScreen::maxScreenSize() const -{ - return m_config->screen()->maxSize(); -} - -float QMLScreen::outputScale() const -{ - return m_outputScale; -} - -void QMLScreen::outputConnectedChanged() -{ - int connectedCount = 0; - - Q_FOREACH (const KScreen::OutputPtr &output, m_outputMap.keys()) { - if (output->isConnected()) { - ++connectedCount; - } - } - - if (connectedCount != m_connectedOutputsCount) { - m_connectedOutputsCount = connectedCount; - Q_EMIT connectedOutputsCountChanged(); - updateOutputsPlacement(); - } -} - -void QMLScreen::outputEnabledChanged() -{ - const KScreen::OutputPtr output(qobject_cast(sender()), [](void *){}); - if (output->isEnabled()) { - updateOutputsPlacement(); - } - int enabledCount = 0; - - Q_FOREACH (const KScreen::OutputPtr &output, m_outputMap.keys()) { - if (output->isEnabled()) { - ++enabledCount; - } - } - - if (enabledCount == m_enabledOutputsCount) { - m_enabledOutputsCount = enabledCount; - Q_EMIT enabledOutputsCountChanged(); - } -} - -void QMLScreen::outputPositionChanged() -{ - /* TODO: Reposition the QMLOutputs */ -} - -void QMLScreen::qmlOutputMoved(QMLOutput *qmlOutput) -{ - if (qmlOutput->isCloneMode()) { - return; - } - if (!m_manuallyMovedOutputs.contains(qmlOutput)) - m_manuallyMovedOutputs.append(qmlOutput); - - updateCornerOutputs(); - - if (m_leftmost) { - m_leftmost->setOutputX(0); - } - if (m_topmost) { - m_topmost->setOutputY(0); - } - - if (qmlOutput == m_leftmost) { - Q_FOREACH (QMLOutput *other, m_outputMap) { - if (other == m_leftmost) { - continue; - } - - if (!other->output()->isConnected() || !other->output()->isEnabled()) { - continue; - } - - other->setOutputX(float(other->x() - m_leftmost->x()) / outputScale()); - } - } else if (m_leftmost) { - qmlOutput->setOutputX(float(qmlOutput->x() - m_leftmost->x()) / outputScale()); - } - - if (qmlOutput == m_topmost) { - Q_FOREACH (QMLOutput *other, m_outputMap) { - if (other == m_topmost) { - continue; - } - - if (!other->output()->isConnected() || !other->output()->isEnabled()) { - continue; - } - - other->setOutputY(float(other->y() - m_topmost->y()) / outputScale()); - } - } else if (m_topmost) { - qmlOutput->setOutputY(float(qmlOutput->y() - m_topmost->y()) / outputScale()); - } -} - -void QMLScreen::viewSizeChanged() -{ - updateOutputsPlacement(); -} - -void QMLScreen::updateCornerOutputs() -{ - m_leftmost = nullptr; - m_topmost = nullptr; - m_rightmost = nullptr; - m_bottommost = nullptr; - - Q_FOREACH (QMLOutput *output, m_outputMap) { - if (!output->output()->isConnected() || !output->output()->isEnabled()) { - continue; - } - - QMLOutput *other = m_leftmost; - if (!other || output->x() < other->x()) { - m_leftmost = output; - } - - if (!other || output->y() < other->y()) { - m_topmost = output; - } - - if (!other || output->x() + output->width() > other->x() + other->width()) { - m_rightmost = output; - } - - if (!other || output->y() + output->height() > other->y() + other->height()) { - m_bottommost = output; - } - } -} - -void QMLScreen::setOutputScale(float scale) -{ - if (qFuzzyCompare(scale, m_outputScale)) - return; - m_outputScale = scale; - emit outputScaleChanged(); -} - -void QMLScreen::updateOutputsPlacement() -{ - if (width() <= 0) - return; - - QSizeF initialActiveScreenSize; - - Q_FOREACH (QQuickItem *item, childItems()) { - QMLOutput *qmlOutput = qobject_cast(item); - if (!qmlOutput->output()->isConnected() || !qmlOutput->output()->isEnabled()) { - continue; - } - - if (qmlOutput->outputX() + qmlOutput->currentOutputWidth() > initialActiveScreenSize.width()) { - initialActiveScreenSize.setWidth(qmlOutput->outputX() + qmlOutput->currentOutputWidth()); - } - if (qmlOutput->outputY() + qmlOutput->currentOutputHeight() > initialActiveScreenSize.height()) { - initialActiveScreenSize.setHeight(qmlOutput->outputY() + qmlOutput->currentOutputHeight()); - } - } - - auto initialScale = outputScale(); - auto scale = initialScale; - qreal lastX = -1.0; - do { - auto activeScreenSize = initialActiveScreenSize * scale; - - const QPointF offset((width() - activeScreenSize.width()) / 2.0, - (height() - activeScreenSize.height()) / 2.0); - - lastX = -1.0; - qreal lastY = -1.0; - Q_FOREACH (QQuickItem *item, childItems()) { - QMLOutput *qmlOutput = qobject_cast(item); - if (!qmlOutput->output()->isConnected() || !qmlOutput->output()->isEnabled() || - m_manuallyMovedOutputs.contains(qmlOutput)) { - continue; - } - - qmlOutput->blockSignals(true); - qmlOutput->setPosition(QPointF(offset.x() + (qmlOutput->outputX() * scale), - offset.y() + (qmlOutput->outputY() * scale))); - lastX = qMax(lastX, qmlOutput->position().x() + qmlOutput->width() / initialScale * scale); - lastY = qMax(lastY, qmlOutput->position().y()); - qmlOutput->blockSignals(false); - } - - Q_FOREACH (QQuickItem *item, childItems()) { - QMLOutput *qmlOutput = qobject_cast(item); - if (qmlOutput->output()->isConnected() && !qmlOutput->output()->isEnabled() && - !m_manuallyMovedOutputs.contains(qmlOutput)) { - qmlOutput->blockSignals(true); - qmlOutput->setPosition(QPointF(lastX, lastY)); - lastX += qmlOutput->width() / initialScale * scale; - qmlOutput->blockSignals(false); - } - } - // calculate the scale dynamically, so all screens fit to the dialog - if (lastX > width()) { - scale *= 0.8; - } - } while (lastX > width()); - // Use a timer to avoid binding loop on width() - QTimer::singleShot(0, this, [scale, this] { - setOutputScale(scale); - }); -} diff --git a/kcm/src/kcm_kscreen.h b/kcm/src/kcm_kscreen.h deleted file mode 100644 --- a/kcm/src/kcm_kscreen.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - Copyright (C) 2012 Dan Vratil - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef KCM_KSCREEN_H -#define KCM_KSCREEN_H - -#include - -class Widget; -class QHBoxLayout; -namespace KScreen -{ -class ConfigOperation; -} - -class KCMKScreen : public KCModule -{ - Q_OBJECT - - public: - explicit KCMKScreen (QWidget* parent = nullptr, const QVariantList& args = QVariantList()); - ~KCMKScreen() override; - - QSize sizeHint() const override; - - public Q_SLOTS: - void load() override; - void save() override; - void defaults() override; - void changed(); - - private: - void configReady(KScreen::ConfigOperation *op); - - Widget *mKScreenWidget = nullptr; - bool m_blockChanges = false; - QHBoxLayout *mMainLayout = nullptr; - -}; - -#endif // DisplayConfiguration_H diff --git a/kcm/src/kcm_kscreen.cpp b/kcm/src/kcm_kscreen.cpp deleted file mode 100644 --- a/kcm/src/kcm_kscreen.cpp +++ /dev/null @@ -1,188 +0,0 @@ -/* - Copyright (C) 2012 Dan Vratil - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - - -#include "kcm_kscreen.h" -#include "kcm_screen_debug.h" -#include "widget.h" -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - - -#include -#include -#include -#include - -#include - -K_PLUGIN_FACTORY(KCMDisplayConfigurationFactory, registerPlugin();) - -using namespace KScreen; - -Q_DECLARE_METATYPE(KScreen::OutputPtr) -Q_DECLARE_METATYPE(KScreen::ScreenPtr) - -KCMKScreen::KCMKScreen(QWidget* parent, const QVariantList& args) - : KCModule(parent, args) -{ - Log::instance(); - - setButtons(Apply | Default); - - KAboutData* about = - new KAboutData(QStringLiteral("kcm_kscreen"), - i18n("Display Configuration"), - QStringLiteral(KSCREEN_VERSION), i18n("Configuration for displays"), - KAboutLicense::GPL_V2, i18n("(c), 2012-2013 Daniel Vrátil")); - - about->addAuthor(i18n("Daniel Vrátil"), i18n("Maintainer") , QStringLiteral("dvratil@redhat.com")); - setAboutData(about); - - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); -} - -void KCMKScreen::configReady(ConfigOperation* op) -{ - delete mMainLayout; - mMainLayout = new QHBoxLayout(this); - mMainLayout->setContentsMargins(0, 0, 0, 0); - - if (op->hasError()) { - mKScreenWidget = nullptr; - delete mKScreenWidget; - QLabel *errorLabel = new QLabel(this); - mMainLayout->addWidget(errorLabel); - errorLabel->setText(i18n("No kscreen backend found. Please check your kscreen installation.")); - return; - } - - if (!mKScreenWidget) { - mKScreenWidget = new Widget(this); - mMainLayout->addWidget(mKScreenWidget); - QObject::connect(mKScreenWidget, &Widget::changed, - this, &KCMKScreen::changed); - } else { - mMainLayout->addWidget(mKScreenWidget); - } - - mKScreenWidget->setConfig(qobject_cast(op)->config()); -} - -KCMKScreen::~KCMKScreen() -{ -} - -QSize KCMKScreen::sizeHint() const -{ - return QSize(0, 700); -} - -void KCMKScreen::changed() -{ - if (!m_blockChanges) { - KCModule::changed(); - } -} - -void KCMKScreen::save() -{ - qCDebug(KSCREEN_KCM) << "Saving."; - - if (!mKScreenWidget) { - return; - } - - mKScreenWidget->saveControls(); - const KScreen::ConfigPtr &config = mKScreenWidget->currentConfig(); - - bool atLeastOneEnabledOutput = false; - Q_FOREACH(const KScreen::OutputPtr &output, config->outputs()) { - KScreen::ModePtr mode = output->currentMode(); - - if (output->isEnabled()) { - atLeastOneEnabledOutput = true; - } - - qCDebug(KSCREEN_KCM) << output->name() << output->id() << output.data() << "\n" - << " Connected:" << output->isConnected() << "\n" - << " Enabled:" << output->isEnabled() << "\n" - << " Primary:" << output->isPrimary() << "\n" - << " Rotation:" << output->rotation() << "\n" - << " Mode:" << (mode ? mode->name() : QStringLiteral("unknown")) << "@" << (mode ? mode->refreshRate() : 0.0) << "Hz" << "\n" - << " Position:" << output->pos().x() << "x" << output->pos().y(); - } - - if (!atLeastOneEnabledOutput) { - if (KMessageBox::warningYesNo(this, i18n("Are you sure you want to disable all outputs?"), - i18nc("@title:window", "Disable All Outputs"), - KGuiItem(i18n("&Disable All Outputs"), QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))), - KGuiItem(i18n("&Reconfigure"), QIcon::fromTheme(QStringLiteral("dialog-cancel"))), - QString(), KMessageBox::Dangerous) == KMessageBox::No) - { - return; - } - } - - if (!Config::canBeApplied(config)) { - KMessageBox::information(this, - i18n("Sorry, your configuration could not be applied.\n\n" - "Common reasons are that the overall screen size is too big, or you enabled more displays than supported by your GPU."), - i18nc("@title:window", "Unsupported Configuration")); - return; - } - - m_blockChanges = true; - /* Store the current config, apply settings */ - auto *op = new SetConfigOperation(config); - /* Block until the operation is completed, otherwise KCMShell will terminate - * before we get to execute the Operation */ - op->exec(); - - // The 1000ms is a bit "random" here, it's what works on the systems I've tested, but ultimately, this is a hack - // due to the fact that we just can't be sure when xrandr is done changing things, 1000 doesn't seem to get in the way - QTimer::singleShot(1000, this, - [this] () { - m_blockChanges = false; - } - ); -} - -void KCMKScreen::defaults() -{ - qCDebug(KSCREEN_KCM) << "APPLY DEFAULT"; - load(); -} - -void KCMKScreen::load() -{ - qCDebug(KSCREEN_KCM) << "LOAD"; - connect(new GetConfigOperation(), &GetConfigOperation::finished, - this, &KCMKScreen::configReady); -} - -#include "kcm_kscreen.moc" diff --git a/kcm/src/kcm_testapp.cpp b/kcm/src/kcm_testapp.cpp deleted file mode 100644 --- a/kcm/src/kcm_testapp.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - Copyright (C) 2013 Dan Vratil - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include -#include -#include - -#include - -// #include - -#include "widget.h" - -int main(int argc, char **argv) -{ -// QQuickDebuggingEnabler enabler; - QApplication app(argc, argv); - - KAboutData aboutData(QStringLiteral("kcm_testapp"), QStringLiteral("kcm_testapp"), i18n("KCM Test App"), QStringLiteral("1.0"), KAboutLicense::GPL); - - Widget widget; - widget.resize(800, 600); - widget.show(); - - QObject::connect(new KScreen::GetConfigOperation(), &KScreen::GetConfigOperation::finished, - [&](KScreen::ConfigOperation *op) { - widget.setConfig(qobject_cast(op)->config()); - }); - - return app.exec(); -} diff --git a/kcm/src/kscreen_widget.ui b/kcm/src/kscreen_widget.ui deleted file mode 100644 --- a/kcm/src/kscreen_widget.ui +++ /dev/null @@ -1,98 +0,0 @@ - - - KScreenWidget - - - - 0 - 0 - 549 - 591 - - - - - 0 - 0 - - - - - 100 - 100 - - - - - - - - 0 - 0 - - - - QQuickWidget::SizeRootObjectToView - - - - - - - - - Primary display: - - - - - - - QComboBox::AdjustToContents - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Unify Outputs - - - - - - - Scale Display - - - - - - - - - - - - - QQuickWidget - QWidget -
QtQuickWidgets/QQuickWidget
-
-
- - -
diff --git a/kcm/src/outputconfig.h b/kcm/src/outputconfig.h deleted file mode 100644 --- a/kcm/src/outputconfig.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2013 Daniel Vrátil - * - * 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 . - * - */ - -#ifndef OUTPUTCONFIG_H -#define OUTPUTCONFIG_H - -#include "../../common/control.h" - -#include -#include -#include - -#include - -class QCheckBox; -class QGroupBox; -class ResolutionSlider; -class QLabel; - - -class OutputConfig : public QWidget -{ - Q_OBJECT - - public: - explicit OutputConfig(QWidget *parent); - explicit OutputConfig(const KScreen::OutputPtr &output, QWidget *parent = nullptr); - ~OutputConfig() override; - - virtual void setOutput(const KScreen::OutputPtr &output, Control::OutputRetention retention = Control::OutputRetention::Undefined); - KScreen::OutputPtr output() const; - - void setTitle(const QString &title); - void setShowScaleOption(bool showScaleOption); - bool showScaleOption() const; - - bool hasChange() const; - - Control::OutputRetention applyRetention(); - - protected Q_SLOTS: - void slotResolutionChanged(const QSize &size); - void slotRotationChanged(int index); - void slotRefreshRateChanged(int index); - void slotScaleChanged(int index); - - Q_SIGNALS: - void changed(); - - protected: - virtual void initUi(); - - protected: - QLabel *mTitle = nullptr; - KScreen::OutputPtr mOutput; - QCheckBox *mEnabled = nullptr; - ResolutionSlider *mResolution = nullptr; - QComboBox *mRotation = nullptr; - QComboBox *mScale = nullptr; - QComboBox *mRefreshRate = nullptr; - bool mShowScaleOption = false; - - QGroupBox *mRetentionGroupBox = nullptr; - QRadioButton *mGlobalRetentionButton = nullptr; - QRadioButton *mIndividualRetentionButton = nullptr; - - Control::OutputRetention mRetention = Control::OutputRetention::Undefined; - - // TODO: we should do this instead with a separate config being watched - bool mChanged = false; -}; - -#endif // OUTPUTCONFIG_H diff --git a/kcm/src/outputconfig.cpp b/kcm/src/outputconfig.cpp deleted file mode 100644 --- a/kcm/src/outputconfig.cpp +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright 2013 Daniel Vrátil - * - * 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 . - * - */ - -#include "outputconfig.h" -#include "resolutionslider.h" -#include "utils.h" -#include "kcm_screen_debug.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -OutputConfig::OutputConfig(QWidget *parent) - : QWidget(parent) - , mOutput(nullptr) -{ -} - -OutputConfig::OutputConfig(const KScreen::OutputPtr &output, QWidget *parent) - : QWidget(parent) -{ - setOutput(output); -} - -OutputConfig::~OutputConfig() -{ -} - -void OutputConfig::setTitle(const QString& title) -{ - mTitle->setText(title); -} - - -void OutputConfig::initUi() -{ - connect(mOutput.data(), &KScreen::Output::isConnectedChanged, - this, [=]() { - if (!mOutput->isConnected()) { - setVisible(false); - } - }); - - connect(mOutput.data(), &KScreen::Output::isEnabledChanged, - this, [=]() { - mEnabled->setChecked(mOutput->isEnabled()); - }); - - connect(mOutput.data(), &KScreen::Output::rotationChanged, - this, [=]() { - const int index = mRotation->findData(mOutput->rotation()); - mRotation->setCurrentIndex(index); - }); - - connect(mOutput.data(), &KScreen::Output::scaleChanged, - this, [=]() { - const int index = mScale->findData(mOutput->scale()); - mScale->setCurrentIndex(index); - }); - - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - - QVBoxLayout *vbox = new QVBoxLayout(this); - mTitle = new QLabel(this); - mTitle->setAlignment(Qt::AlignHCenter); - vbox->addWidget(mTitle); - - setTitle(Utils::outputName(mOutput)); - - QFormLayout *formLayout = new QFormLayout(); - vbox->addLayout(formLayout); - - mEnabled = new QCheckBox(i18n("Enabled"), this); - mEnabled->setChecked(mOutput->isEnabled()); - connect(mEnabled, &QCheckBox::clicked, - this, [=](bool checked) { - mOutput->setEnabled(checked); - qCDebug(KSCREEN_KCM) << mOutput.data() << mOutput->name() << mOutput->isEnabled(); - mChanged = true; - Q_EMIT changed(); - }); - formLayout->addRow(i18n("Display:"), mEnabled); - - mResolution = new ResolutionSlider(mOutput, this); - connect(mResolution, &ResolutionSlider::resolutionChanged, - this, &OutputConfig::slotResolutionChanged); - formLayout->addRow(i18n("Resolution:"), mResolution); - - mRotation = new QComboBox(this); - QIcon previewIcon = QIcon::fromTheme(QStringLiteral("view-preview")); - QPixmap previewPixmap = previewIcon.pixmap(mRotation->iconSize()); - QIcon previewRotatedCounterClockwise = QIcon(previewPixmap.transformed(QMatrix(0.0, -1.0, 1.0, 0.0, 0.0, 0.0))); - QIcon previewRotatedClockwise = QIcon(previewPixmap.transformed(QMatrix(0.0, 1.0, -1.0, 0.0, 0.0, 0.0))); - QIcon previewRotatedUpSideDown = QIcon(previewPixmap.transformed(QMatrix(-1.0, 0.0, 0.0, -1.0, 0.0, 0.0))); - mRotation->addItem(previewIcon, i18n("No Rotation"), KScreen::Output::None); - mRotation->addItem(previewRotatedClockwise, i18n("90° Clockwise"), KScreen::Output::Right); - mRotation->addItem(previewRotatedUpSideDown, i18n("Upside Down"), KScreen::Output::Inverted); - mRotation->addItem(previewRotatedCounterClockwise, i18n("90° Counterclockwise"), KScreen::Output::Left); - connect(mRotation, static_cast(&QComboBox::activated), - this, &OutputConfig::slotRotationChanged); - mRotation->setCurrentIndex(mRotation->findData(mOutput->rotation())); - - formLayout->addRow(i18n("Orientation:"), mRotation); - - if (mShowScaleOption) { - mScale = new QComboBox(this); - mScale->addItem(i18nc("Scale multiplier, show everything at 1 times normal scale", "1x"), 1); - mScale->addItem(i18nc("Scale multiplier, show everything at 2 times normal scale", "2x"), 2); - connect(mScale, static_cast(&QComboBox::activated), - this, &OutputConfig::slotScaleChanged); - mScale->setCurrentIndex(mScale->findData(mOutput->scale())); - - formLayout->addRow(i18n("Scale:"), mScale); - - formLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum)); - } - - mRefreshRate = new QComboBox(this); - mRefreshRate->addItem(i18n("Auto"), -1); - formLayout->addRow(i18n("Refresh rate:"), mRefreshRate); - slotResolutionChanged(mResolution->currentResolution()); - connect(mRefreshRate, static_cast(&QComboBox::activated), - this, &OutputConfig::slotRefreshRateChanged); - - - mRetentionGroupBox = new QGroupBox(i18n("Retention of values"), this); - mGlobalRetentionButton = new QRadioButton(i18n("Save as new global values for this display."), this); - mIndividualRetentionButton = new QRadioButton(i18n("Save values only for display in this specific configuration."), this); - mIndividualRetentionButton->setChecked(mRetention == Control::OutputRetention::Individual); - mGlobalRetentionButton->setChecked(!mIndividualRetentionButton->isChecked()); - - QVBoxLayout *vbox2 = new QVBoxLayout(mRetentionGroupBox); - vbox2->addWidget(mGlobalRetentionButton); - vbox2->addWidget(mIndividualRetentionButton); - mRetentionGroupBox->setLayout(vbox2); - - vbox->addWidget(mRetentionGroupBox); -} - -void OutputConfig::setOutput(const KScreen::OutputPtr &output, Control::OutputRetention retention) -{ - mOutput = output; - mRetention = retention; - - initUi(); -} - -KScreen::OutputPtr OutputConfig::output() const -{ - return mOutput; -} - - -void OutputConfig::slotResolutionChanged(const QSize &size) -{ - // Ignore disconnected outputs - if (!size.isValid()) { - return; - } - - KScreen::ModePtr selectedMode; - QList modes; - Q_FOREACH (const KScreen::ModePtr &mode, mOutput->modes()) { - if (mode->size() == size) { - modes << mode; - if (!selectedMode || selectedMode->refreshRate() < mode->refreshRate()) { - selectedMode = mode; - } - } - } - - Q_ASSERT(selectedMode); - mOutput->setCurrentModeId(selectedMode->id()); - - // Don't remove the first "Auto" item - prevents ugly flicker of the combobox - // when changing resolution - for (int i = 1; i < mRefreshRate->count(); ++i) { - mRefreshRate->removeItem(i); - } - - for (int i = 0, total = modes.count(); i < total; ++i) { - const KScreen::ModePtr mode = modes.at(i); - mRefreshRate->addItem(i18n("%1 Hz", QLocale().toString(mode->refreshRate(), 'f', 2)), mode->id()); - // If selected refresh rate is other then what we consider the "Auto" value - // - that is it's not the highest resolution - then select it, otherwise - // we stick with "Auto" - if (mode == selectedMode && i > 1) { - // i + 1 since 0 is auto - mRefreshRate->setCurrentIndex(i + 1); - } - } - mChanged = true; - Q_EMIT changed(); -} - -void OutputConfig::slotRotationChanged(int index) -{ - KScreen::Output::Rotation rotation = - static_cast(mRotation->itemData(index).toInt()); - mOutput->setRotation(rotation); - - mChanged = true; - Q_EMIT changed(); -} - -void OutputConfig::slotRefreshRateChanged(int index) -{ - QString modeId; - if (index == 0) { - // Item 0 is "Auto" - "Auto" is equal to highest refresh rate (at least - // that's how I understand it, and since the combobox is sorted in descending - // order, we just pick the second item from top - modeId = mRefreshRate->itemData(1).toString(); - } else { - modeId = mRefreshRate->itemData(index).toString(); - } - mOutput->setCurrentModeId(modeId); - - mChanged = true; - Q_EMIT changed(); -} - -void OutputConfig::slotScaleChanged(int index) -{ - auto scale = mScale->itemData(index).toInt(); - mOutput->setScale(scale); - mChanged = true; - Q_EMIT changed(); -} - -void OutputConfig::setShowScaleOption(bool showScaleOption) -{ - mShowScaleOption = showScaleOption; - if (mOutput) { - initUi(); - } -} - -bool OutputConfig::showScaleOption() const -{ - return mShowScaleOption; -} - -Control::OutputRetention OutputConfig::applyRetention() -{ - if (mIndividualRetentionButton->isChecked()) { - mRetention = Control::OutputRetention::Individual; - } else { - mRetention = Control::OutputRetention::Global; - } - return mRetention; -} - -bool OutputConfig::hasChange() const -{ - return mChanged; -} diff --git a/kcm/src/previewwidget.h b/kcm/src/previewwidget.h deleted file mode 100644 --- a/kcm/src/previewwidget.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2015 David Edmundson - * - * 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 . - * - */ - -#ifndef PREVIEWWIDGET_H -#define PREVIEWWIDGET_H - -#include - -class PreviewWidget : public QLabel -{ - Q_OBJECT -public: - explicit PreviewWidget(QWidget *parent=nullptr); - ~PreviewWidget() override; - void setScale(qreal scale); -public Q_SLOTS: - QPixmap updatePixmapCache(); -private: - qreal pointSizeToPixelSize(qreal pointSize) const; - qreal m_scale; - QWidget *m_internalPreview = nullptr; -}; - -#endif // PREVIEWWIDGET_H diff --git a/kcm/src/previewwidget.cpp b/kcm/src/previewwidget.cpp deleted file mode 100644 --- a/kcm/src/previewwidget.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2015 David Edmundson - * - * 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 . - * - */ - -#include "previewwidget.h" - -#include - -#include "ui_stylepreview.h" - - -PreviewWidget::PreviewWidget(QWidget *parent): - QLabel(parent), - m_scale(1.0), - m_internalPreview(new QWidget) // deliberately no parent, we don't want it to have a screen -{ - Ui::StylePreview ui; - ui.setupUi(m_internalPreview); -} - -PreviewWidget::~PreviewWidget() -{ - delete m_internalPreview; -} - -void PreviewWidget::setScale(qreal scale) -{ - m_scale = scale; - - QFont font; - //take the user's configured point size, and convert it to a pixel size for preview - - font.setPixelSize(pointSizeToPixelSize(font.pointSize())); - m_internalPreview->setFont(font); - - //as we are a hidden widget, we need to force a repaint to update the size hint properly - updatePixmapCache(); - m_internalPreview->resize(sizeHint()); - m_internalPreview->adjustSize(); - - QPixmap preview = updatePixmapCache(); - setPixmap(preview); -} - -qreal PreviewWidget::pointSizeToPixelSize(qreal pointSize) const -{ - //point size is in how many 72ths of an inch it should be, default DPI is 96 - qreal pixelSize = pointSize * 96.0 / 72.0; - - //scale by our new factor - pixelSize *= m_scale; - - return pixelSize / m_scale; //as we are now dealing with pixels it will be scaled up in the paint(), so it needs dividing here -} - -QPixmap PreviewWidget::updatePixmapCache() -{ - QPixmap pixmap(m_internalPreview ->sizeHint() * m_scale); - pixmap.setDevicePixelRatio(m_scale); - QPainter p(&pixmap); - m_internalPreview ->render(&p); - - //render back at whatever the native DPR of the KCM is - pixmap.setDevicePixelRatio(devicePixelRatioF()); - - return pixmap; -} - - diff --git a/kcm/src/resolutionslider.h b/kcm/src/resolutionslider.h deleted file mode 100644 --- a/kcm/src/resolutionslider.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2013 Daniel Vrátil - * - * 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 . - * - */ - -#ifndef RESOLUTIONSLIDER_H -#define RESOLUTIONSLIDER_H - -#include - -#include - -class QSlider; -class QLabel; -class QComboBox; - -class ResolutionSlider : public QWidget -{ - Q_OBJECT - - public: - explicit ResolutionSlider(const KScreen::OutputPtr &output, QWidget *parent = nullptr); - ~ResolutionSlider() override; - - QSize currentResolution() const; - - Q_SIGNALS: - void resolutionChanged(const QSize &size); - - private Q_SLOTS: - void slotValueChanged(int); - void slotOutputModeChanged(); - - private: - void init(); - - KScreen::OutputPtr mOutput; - - QList mModes; - - QLabel *mSmallestLabel = nullptr; - QLabel *mBiggestLabel = nullptr; - QLabel *mCurrentLabel = nullptr; - QSlider *mSlider = nullptr; - QComboBox *mComboBox = nullptr; -}; - -#endif // RESOLUTIONSLIDER_H diff --git a/kcm/src/resolutionslider.cpp b/kcm/src/resolutionslider.cpp deleted file mode 100644 --- a/kcm/src/resolutionslider.cpp +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2013 Daniel Vrátil - * - * 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 . - * - */ - -#include "resolutionslider.h" -#include "utils.h" - -#include -#include -#include -#include - -#include - -#include - -static bool sizeLessThan(const QSize &sizeA, const QSize &sizeB) -{ - return sizeA.width() * sizeA.height() < sizeB.width() * sizeB.height(); -} - -ResolutionSlider::ResolutionSlider(const KScreen::OutputPtr &output, QWidget *parent) - : QWidget(parent) - , mOutput(output) -{ - connect(output.data(), &KScreen::Output::currentModeIdChanged, - this, &ResolutionSlider::slotOutputModeChanged); - connect(output.data(), &KScreen::Output::modesChanged, - this, &ResolutionSlider::init); - - init(); -} - -ResolutionSlider::~ResolutionSlider() -{ -} - -void ResolutionSlider::init() -{ - mModes.clear(); - Q_FOREACH (const KScreen::ModePtr &mode, mOutput->modes()) { - if (mModes.contains(mode->size())) { - continue; - } - - mModes << mode->size(); - } - std::sort(mModes.begin(), mModes.end(), sizeLessThan); - - delete layout(); - delete mSmallestLabel; - mSmallestLabel = nullptr; - delete mBiggestLabel; - mBiggestLabel = nullptr; - delete mCurrentLabel; - mCurrentLabel = nullptr; - delete mSlider; - mSlider = nullptr; - delete mComboBox; - mComboBox = nullptr; - - QGridLayout *layout = new QGridLayout(this); - int margin = layout->margin(); - // Avoid double margins - layout->setContentsMargins(0, 0, 0, 0); - - if (mModes.count() > 15) { - std::reverse(mModes.begin(), mModes.end()); - mComboBox = new QComboBox(this); - mComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); - mComboBox->setEditable(false); - int currentModeIndex = -1; - int preferredModeIndex = -1; - Q_FOREACH (const QSize &size, mModes) { - mComboBox->addItem(Utils::sizeToString(size)); - if (mOutput->currentMode() && (mOutput->currentMode()->size() == size)) { - currentModeIndex = mComboBox->count() - 1; - } else if (mOutput->preferredMode() && (mOutput->preferredMode()->size() == size)) { - preferredModeIndex = mComboBox->count() - 1; - } - } - if (currentModeIndex != -1) { - mComboBox->setCurrentIndex(currentModeIndex); - } else if (preferredModeIndex != -1) { - mComboBox->setCurrentIndex(preferredModeIndex); - } - layout->addWidget(mComboBox, 0, 0, 1, 1); - connect(mComboBox, static_cast(&QComboBox::currentIndexChanged), - this, &ResolutionSlider::slotValueChanged, Qt::UniqueConnection); - - Q_EMIT resolutionChanged(mModes.at(mComboBox->currentIndex())); - } else { - mCurrentLabel = new QLabel(this); - mCurrentLabel->setAlignment(Qt::AlignCenter); - layout->addWidget(mCurrentLabel, 1, 0, 1, 3); - - if (mModes.isEmpty()) { - mCurrentLabel->setText(i18n("No available resolutions")); - } else if (mModes.count() == 1) { - mCurrentLabel->setText(Utils::sizeToString(mModes.first())); - } else { - // No double margins left and right, but they are needed on top and bottom - layout->setContentsMargins(0, margin, 0, margin); - mSlider = new QSlider(Qt::Horizontal, this); - mSlider->setTickInterval(1); - mSlider->setTickPosition(QSlider::TicksBelow); - mSlider->setSingleStep(1); - mSlider->setPageStep(1); - mSlider->setMinimum(0); - mSlider->setMaximum(mModes.size() - 1); - mSlider->setSingleStep(1); - if (mOutput->currentMode()) { - mSlider->setValue(mModes.indexOf(mOutput->currentMode()->size())); - } else if (mOutput->preferredMode()) { - mSlider->setValue(mModes.indexOf(mOutput->preferredMode()->size())); - } else { - mSlider->setValue(mSlider->maximum()); - } - layout->addWidget(mSlider, 0, 1); - connect(mSlider, &QSlider::valueChanged, - this, &ResolutionSlider::slotValueChanged); - - mSmallestLabel = new QLabel(this); - mSmallestLabel->setText(Utils::sizeToString(mModes.first())); - layout->addWidget(mSmallestLabel, 0, 0); - mBiggestLabel = new QLabel(this); - mBiggestLabel->setText(Utils::sizeToString(mModes.last())); - layout->addWidget(mBiggestLabel, 0, 2); - - const auto size = mModes.at(mSlider->value()); - mCurrentLabel->setText(Utils::sizeToString(size)); - Q_EMIT resolutionChanged(size); - } - } -} - -QSize ResolutionSlider::currentResolution() const -{ - if (mModes.isEmpty()) { - return QSize(); - } - - if (mModes.size() < 2) { - return mModes.first(); - } - - if (mSlider) { - return mModes.at(mSlider->value()); - } else { - const int i = mComboBox->currentIndex(); - return i > -1 ? mModes.at(i) : QSize(); - } -} - -void ResolutionSlider::slotOutputModeChanged() -{ - if (!mOutput->currentMode()) { - return; - } - - if (mSlider) { - mSlider->blockSignals(true); - mSlider->setValue(mModes.indexOf(mOutput->currentMode()->size())); - mSlider->blockSignals(false); - } else if (mComboBox) { - mComboBox->blockSignals(true); - mComboBox->setCurrentIndex(mModes.indexOf(mOutput->currentMode()->size())); - mComboBox->blockSignals(false); - } -} - -void ResolutionSlider::slotValueChanged(int value) -{ - const QSize &size = mModes.at(value); - if (mCurrentLabel) { - mCurrentLabel->setText(Utils::sizeToString(size)); - } - - Q_EMIT resolutionChanged(size); -} diff --git a/kcm/src/scaling.ui b/kcm/src/scaling.ui deleted file mode 100644 --- a/kcm/src/scaling.ui +++ /dev/null @@ -1,149 +0,0 @@ - - - Scaling - - - - 0 - 0 - 422 - 185 - - - - - - - Screen Scaling - - - - - - - - - - - Scale: - - - - - - - 10 - - - 30 - - - 1 - - - 5 - - - 10 - - - 10 - - - true - - - Qt::Horizontal - - - QSlider::TicksBelow - - - 10 - - - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - KMessageWidget - QFrame -
kmessagewidget.h
- 1 -
- - PreviewWidget - QWidget -
previewwidget.h
- 1 -
-
- - - - buttonBox - accepted() - Scaling - accept() - - - 269 - 164 - - - 229 - 143 - - - - - buttonBox - rejected() - Scaling - reject() - - - 405 - 164 - - - 421 - 155 - - - - -
diff --git a/kcm/src/scalingconfig.h b/kcm/src/scalingconfig.h deleted file mode 100644 --- a/kcm/src/scalingconfig.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2015 David Edmundson - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef SCALINGCONFIG_H -#define SCALINGCONFIG_H - -#include - -#include - -#include "ui_scaling.h" - -class ScalingConfig : public QDialog -{ - Q_OBJECT -public: - explicit ScalingConfig(const KScreen::OutputList &outputList, QWidget* parent = nullptr); - ~ScalingConfig() override; - -protected: - void accept() override; -private: - void load(); - qreal scaleFactor() const; - int scaleDPI() const; - Ui::Scaling ui; - qreal m_initialScalingFactor = 1.0; - KScreen::OutputList m_outputList; -}; - -#endif // SCALINGCONFIG_H diff --git a/kcm/src/scalingconfig.cpp b/kcm/src/scalingconfig.cpp deleted file mode 100644 --- a/kcm/src/scalingconfig.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2015 David Edmundson - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#include "scalingconfig.h" - -#include -#include -#include -#include - -//we want a scale between 1 and 3.0 in intervals of 0.1 -//slider can only handle ints so goes 10-30 -#define SLIDER_RATIO 10.0 - -ScalingConfig::ScalingConfig(const KScreen::OutputList &outputList, QWidget* parent): - QDialog(parent), - m_outputList(outputList) -{ - - ui.setupUi(this); - - ui.warningWidget->setText(i18n("Scaling changes will come into effect after restart")); - ui.warningWidget->show(); - - - connect(ui.scaleSlider, &QSlider::valueChanged, ui.previewWidget, [this](qreal value) { - ui.previewWidget->setScale(value / SLIDER_RATIO); - }); - connect(ui.scaleSlider, &QSlider::valueChanged, ui.scaleLabel, [this](qreal value) { - ui.scaleLabel->setText(QString::number(value / SLIDER_RATIO)); - }); - - ui.previewWidget->setScale(1); - ui.scaleLabel->setText(QString::number(1)); - - load(); -} - -ScalingConfig::~ScalingConfig() -{ -} - -void ScalingConfig::load() -{ - //we load UI from a config, as rdb value might not be updated yet - auto config = KSharedConfig::openConfig(QStringLiteral("kdeglobals")); - const qreal dpi = config->group("KScreen").readEntry("ScaleFactor", 1.0); - - m_initialScalingFactor = dpi; - ui.scaleSlider->setValue(dpi * SLIDER_RATIO); -} - -void ScalingConfig::accept() -{ - if (qFuzzyCompare(scaleFactor(), m_initialScalingFactor)) { - QDialog::accept(); - return; - } - const qreal scalingFactor = scaleFactor(); - - //save to config - auto config = KSharedConfig::openConfig(QStringLiteral("kdeglobals")); - config->group("KScreen").writeEntry("ScaleFactor", scalingFactor); - - //write env var to be used by startkde.sh to populate the QT_SCREEN_SCALE_FACTORS env var - //we use QT_SCREEN_SCALE_FACTORS as opposed to QT_SCALE_FACTOR as we need to use one that will *NOT* scale fonts according to the scale - //scaling the fonts makes sense if you don't also set a font DPI, but we *need* to set a font DPI for both PlasmaShell which does it's own thing, and for KDE4/GTK2 applications - - QString screenFactors; - foreach (const KScreen::OutputPtr &output, m_outputList) { - screenFactors.append(output->name() + QLatin1Char('=') + QString::number(scalingFactor) + QLatin1Char(';')); - } - config->group("KScreen").writeEntry("ScreenScaleFactors", screenFactors); - - - KConfig fontConfig(QStringLiteral("kcmfonts")); - auto fontConfigGroup = fontConfig.group("General"); - - if (qFuzzyCompare(scalingFactor, 1.0)) { - //if dpi is the default (96) remove the entry rather than setting it - QProcess proc; - proc.start(QStringLiteral("xrdb -quiet -remove -nocpp")); - if (proc.waitForStarted()) { - proc.write(QByteArray("Xft.dpi\n")); - proc.closeWriteChannel(); - proc.waitForFinished(); - } - fontConfigGroup.writeEntry("forceFontDPI", 0); - } else { - QProcess proc; - proc.start(QStringLiteral("xrdb -quiet -merge -nocpp")); - if (proc.waitForStarted()) { - proc.write(QByteArray("Xft.dpi: " + QString::number(scaleDPI()).toLatin1())); - proc.closeWriteChannel(); - proc.waitForFinished(); - } - fontConfigGroup.writeEntry("forceFontDPI", scaleDPI()); - } - - - QDialog::accept(); -} - -int ScalingConfig::scaleDPI() const -{ - return qRound(scaleFactor() * 96.0); -} - -qreal ScalingConfig::scaleFactor() const -{ - return ui.scaleSlider->value() / SLIDER_RATIO; -} - diff --git a/kcm/src/stylepreview.ui b/kcm/src/stylepreview.ui deleted file mode 100644 --- a/kcm/src/stylepreview.ui +++ /dev/null @@ -1,164 +0,0 @@ - - - StylePreview - - - - - - - 1 - 0 - - - - 0 - - - - Tab 1 - - - - - - - - - - Group Box - - - - - - Radio button - - - true - - - - - - - Radio button - - - - - - - QFrame::HLine - - - QFrame::Sunken - - - - - - - Checkbox - - - true - - - false - - - - - - - - - - Qt::Vertical - - - - - - - - - - - 70 - - - - - - - 30 - - - Qt::Horizontal - - - - - - - - - 999999 - - - - - - - Button - - - - - - - - - - Combobox - - - - - - - - Qt::Vertical - - - - - - - - - 19 - - - Qt::Vertical - - - - - - - - - - Tab 2 - - - - - - - - - diff --git a/kcm/src/unifiedoutputconfig.h b/kcm/src/unifiedoutputconfig.h deleted file mode 100644 --- a/kcm/src/unifiedoutputconfig.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2013 Daniel Vrátil - * - * 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 . - * - */ - -#ifndef UNIFIEDOUTPUTCONFIG_H -#define UNIFIEDOUTPUTCONFIG_H - -#include "outputconfig.h" - -namespace KScreen -{ -class Output; -class Config; -} - -class UnifiedOutputConfig : public OutputConfig -{ - Q_OBJECT - public: - explicit UnifiedOutputConfig(const KScreen::ConfigPtr &config, QWidget *parent); - ~UnifiedOutputConfig() override; - - void setOutput(const KScreen::OutputPtr &output, Control::OutputRetention retention = Control::OutputRetention::Undefined) override; - - private Q_SLOTS: - void slotResolutionChanged(const QSize &size); - - private: - void initUi() override; - KScreen::OutputPtr createFakeOutput(); - QString findBestMode(const KScreen::OutputPtr &output, const QSize &size); - - private: - KScreen::ConfigPtr mConfig; - QList mClones; -}; - -#endif // UNIFIEDOUTPUTCONFIG_H diff --git a/kcm/src/unifiedoutputconfig.cpp b/kcm/src/unifiedoutputconfig.cpp deleted file mode 100644 --- a/kcm/src/unifiedoutputconfig.cpp +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2013 Daniel Vrátil - * - * 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 . - * - */ - -#include "unifiedoutputconfig.h" -#include "resolutionslider.h" -#include "utils.h" -#include "kcm_screen_debug.h" - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - - -bool operator<(const QSize &s1, const QSize &s2) -{ - return s1.width() * s1.height() < s2.width() * s2.height(); -} - -template<> -bool qMapLessThanKey(const QSize &s1, const QSize &s2) -{ - return s1 < s2; -} - - -UnifiedOutputConfig::UnifiedOutputConfig(const KScreen::ConfigPtr &config, QWidget *parent) - : OutputConfig(parent) - , mConfig(config) -{ -} - -UnifiedOutputConfig::~UnifiedOutputConfig() -{ -} - -void UnifiedOutputConfig::setOutput(const KScreen::OutputPtr &output, Control::OutputRetention retention) -{ - Q_UNUSED(retention); // TODO: use? - - mOutput = output; - - mClones.clear(); - mClones.reserve(mOutput->clones().count()); - Q_FOREACH (int id, mOutput->clones()) { - mClones << mConfig->output(id); - } - mClones << mOutput; - - OutputConfig::setOutput(output); -} - -void UnifiedOutputConfig::initUi() -{ - QVBoxLayout *vbox = new QVBoxLayout(this); - mTitle = new QLabel(this); - mTitle->setAlignment(Qt::AlignHCenter); - vbox->addWidget(mTitle); - - setTitle(i18n("Unified Outputs")); - - QGridLayout *formLayout = new QGridLayout(); - vbox->addLayout(formLayout); - vbox->addStretch(2); - - KScreen::OutputPtr fakeOutput = createFakeOutput(); - mResolution = new ResolutionSlider(fakeOutput, this); - connect(mResolution, &ResolutionSlider::resolutionChanged, - this, &UnifiedOutputConfig::slotResolutionChanged); - formLayout->addWidget(new QLabel(i18n("Resolution:"), this), 1, 0); - formLayout->addWidget(mResolution, 1, 1); - slotResolutionChanged(mResolution->currentResolution()); - - mRotation = new QComboBox(this); - connect(mRotation, static_cast(&QComboBox::currentIndexChanged), - this, &UnifiedOutputConfig::slotRotationChanged); - mRotation->addItem(QIcon::fromTheme(QStringLiteral("arrow-up")), i18n("Normal"), KScreen::Output::None); - mRotation->addItem(QIcon::fromTheme(QStringLiteral("arrow-right")), i18n("90° Clockwise"), KScreen::Output::Right); - mRotation->addItem(QIcon::fromTheme(QStringLiteral("arrow-down")), i18n("Upside Down"), KScreen::Output::Inverted); - mRotation->addItem(QIcon::fromTheme(QStringLiteral("arrow-left")), i18n("90° Counterclockwise"), KScreen::Output::Left); - formLayout->addWidget(new QLabel(i18n("Orientation:"), this), 2, 0); - formLayout->addWidget(mRotation, 2, 1); - - formLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum), 0, 2, 3, 1); -} - -KScreen::OutputPtr UnifiedOutputConfig::createFakeOutput() -{ - // Find set of common resolutions - QMap commonSizes; - Q_FOREACH (const KScreen::OutputPtr &clone, mClones) { - QList processedSizes; - Q_FOREACH (const KScreen::ModePtr &mode, clone->modes()) { - // Make sure we don't count some modes multiple times because of different - // refresh rates - if (processedSizes.contains(mode->size())) { - continue; - } - - processedSizes << mode->size(); - - if (commonSizes.contains(mode->size())) { - commonSizes[mode->size()]++; - } else { - commonSizes.insert(mode->size(), 1); - } - } - } - - KScreen::OutputPtr fakeOutput(new KScreen::Output); - - // This will give us list of resolution that are shared by all outputs - QList commonResults = commonSizes.keys(mClones.count()); - // If there are no common resolution, fallback to smallest preferred mode - if (commonResults.isEmpty()) { - QSize smallestMode; - Q_FOREACH (const KScreen::OutputPtr &clone, mClones) { - qCDebug(KSCREEN_KCM) << smallestMode << clone->preferredMode()->size(); - if (!smallestMode.isValid() || clone->preferredMode()->size() < smallestMode) { - smallestMode = clone->preferredMode()->size(); - } - } - commonResults << smallestMode; - } - std::sort(commonResults.begin(), commonResults.end()); - - KScreen::ModeList modes; - Q_FOREACH (const QSize &size, commonResults) { - KScreen::ModePtr mode(new KScreen::Mode); - mode->setSize(size); - mode->setId(Utils::sizeToString(size)); - mode->setName(mode->id()); - modes.insert(mode->id(), mode); - } - fakeOutput->setModes(modes); - fakeOutput->setCurrentModeId(Utils::sizeToString(commonResults.last())); - return fakeOutput; -} - -void UnifiedOutputConfig::slotResolutionChanged(const QSize &size) -{ - // Ignore disconnected outputs - if (!size.isValid()) { - return; - } - - Q_FOREACH (const KScreen::OutputPtr &clone, mClones) { - const QString &id = findBestMode(clone, size); - if (id.isEmpty()) { - // FIXME: Error? - return; - } - - clone->setCurrentModeId(id); - } - - Q_EMIT changed(); -} - -QString UnifiedOutputConfig::findBestMode(const KScreen::OutputPtr &output, const QSize &size) -{ - float refreshRate = 0; - QString id; - Q_FOREACH (const KScreen::ModePtr &mode, output->modes()) { - if (mode->size() == size && mode->refreshRate() > refreshRate) { - refreshRate = mode->refreshRate(); - id = mode->id(); - } - } - - return id; -} diff --git a/kcm/src/widget.h b/kcm/src/widget.h deleted file mode 100644 --- a/kcm/src/widget.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2013 Daniel Vrátil - * - * 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) any later version. - * - * 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, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#ifndef WIDGET_H -#define WIDGET_H - -#include -#include - -#include - -class QMLOutput; -class QMLScreen; -class ControlPanel; -class ControlConfig; - -class QQuickView; - -namespace KScreen -{ -class ConfigOperation; -} - -namespace Ui -{ -class KScreenWidget; -} - -class Widget : public QWidget -{ - Q_OBJECT - - public: - explicit Widget(QWidget *parent = nullptr); - ~Widget() override; - - void setConfig(const KScreen::ConfigPtr &config); - KScreen::ConfigPtr currentConfig() const; - - protected: - bool eventFilter(QObject *object, QEvent *event) override; - - Q_SIGNALS: - void changed(); - void saveControls(); - - private Q_SLOTS: - void slotFocusedOutputChanged(QMLOutput *output); - - void slotOutputEnabledChanged(); - void slotOutputConnectedChanged(); - - void slotUnifyOutputs(); - - void slotIdentifyButtonClicked(bool checked = true); - void slotIdentifyOutputs(KScreen::ConfigOperation *op); - void clearOutputIdentifiers(); - - void outputAdded(const KScreen::OutputPtr &output); - void outputRemoved(int outputId); - void primaryOutputSelected(int index); - void primaryOutputChanged(const KScreen::OutputPtr &output); - - private: - void loadQml(); - void resetPrimaryCombo(); - void addOutputToPrimaryCombo(const KScreen::OutputPtr &output); - - KScreen::OutputPtr findOutput(const KScreen::ConfigPtr &config, const QVariantMap &info); - - private: - Ui::KScreenWidget *ui; - QMLScreen *mScreen = nullptr; - KScreen::ConfigPtr mConfig = nullptr; - KScreen::ConfigPtr mPrevConfig = nullptr; - - ControlPanel *mControlPanel = nullptr; - - QList mOutputIdentifiers; - QTimer *mOutputTimer = nullptr; -}; - -#endif // WIDGET_H diff --git a/kcm/src/widget.cpp b/kcm/src/widget.cpp deleted file mode 100644 --- a/kcm/src/widget.cpp +++ /dev/null @@ -1,483 +0,0 @@ -/* - * Copyright (C) 2013 Daniel Vr??til - * - * 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) any later version. - * - * 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, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -#include "widget.h" -#include "controlpanel.h" - -#include - -#include "declarative/qmloutput.h" -#include "declarative/qmlscreen.h" -#include "utils.h" -#include "scalingconfig.h" -#include "../../common/control.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "ui_kscreen_widget.h" - -#define QML_PATH "kcm_kscreen/qml/" - -Widget::Widget(QWidget *parent) - : QWidget(parent) - , ui(new Ui::KScreenWidget()) -{ - qRegisterMetaType(); - - ui->setupUi(this); - ui->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); - connect(ui->primaryCombo, QOverload::of(&QComboBox::currentIndexChanged), - this, &Widget::primaryOutputSelected); - - mControlPanel = new ControlPanel(this); - connect(mControlPanel, &ControlPanel::changed, - this, &Widget::changed); - ui->controlPanelLayout->addWidget(mControlPanel); - - connect(ui->unifyButton, &QPushButton::released, - [this]{ - slotUnifyOutputs(); - }); - - connect(ui->scaleAllOutputsButton, &QPushButton::released, - [this] { - QPointer dialog = new ScalingConfig(mConfig->outputs(), this); - dialog->exec(); - delete dialog; - }); - connect(this, &Widget::saveControls, mControlPanel, &ControlPanel::save); - - mOutputTimer = new QTimer(this); - connect(mOutputTimer, &QTimer::timeout, - this, &Widget::clearOutputIdentifiers); - - loadQml(); -} - -Widget::~Widget() -{ - clearOutputIdentifiers(); - delete ui; -} - -bool Widget::eventFilter(QObject* object, QEvent* event) -{ - if (event->type() == QEvent::Resize) { - if (mOutputIdentifiers.contains(qobject_cast(object))) { - QResizeEvent *e = static_cast(event); - const QRect screenSize = object->property("screenSize").toRect(); - QRect geometry(QPoint(0, 0), e->size()); - geometry.moveCenter(screenSize.center()); - static_cast(object)->setGeometry(geometry); - // Pass the event further - } - } - - return QObject::eventFilter(object, event); -} - - -void Widget::setConfig(const KScreen::ConfigPtr &config) -{ - if (mConfig) { - KScreen::ConfigMonitor::instance()->removeConfig(mConfig); - for (const KScreen::OutputPtr &output : mConfig->outputs()) { - output->disconnect(this); - } - mConfig->disconnect(this); - } - - mConfig = config; - KScreen::ConfigMonitor::instance()->addConfig(mConfig); - resetPrimaryCombo(); - connect(mConfig.data(), &KScreen::Config::outputAdded, - this, &Widget::outputAdded); - connect(mConfig.data(), &KScreen::Config::outputRemoved, - this, &Widget::outputRemoved); - connect(mConfig.data(), &KScreen::Config::primaryOutputChanged, - this, &Widget::primaryOutputChanged); - - mScreen->setConfig(mConfig); - mControlPanel->setConfig(mConfig); - ui->unifyButton->setEnabled(mConfig->outputs().count() > 1); - ui->scaleAllOutputsButton->setVisible(!mConfig->supportedFeatures().testFlag(KScreen::Config::Feature::PerOutputScaling)); - - for (const KScreen::OutputPtr &output : mConfig->outputs()) { - outputAdded(output); - } - - // Select the primary (or only) output by default - QMLOutput *qmlOutput = mScreen->primaryOutput(); - if (qmlOutput) { - mScreen->setActiveOutput(qmlOutput); - } else { - if (!mScreen->outputs().isEmpty()) { - mScreen->setActiveOutput(mScreen->outputs().at(0)); - } - } - - slotOutputEnabledChanged(); -} - -KScreen::ConfigPtr Widget::currentConfig() const -{ - return mConfig; -} - -void Widget::loadQml() -{ - qmlRegisterType("org.kde.kscreen", 1, 0, "QMLOutput"); - qmlRegisterType("org.kde.kscreen", 1, 0, "QMLScreen"); - - qmlRegisterType("org.kde.kscreen", 1, 0, "KScreenOutput"); - qmlRegisterType("org.kde.kscreen", 1, 0, "KScreenEdid"); - qmlRegisterType("org.kde.kscreen", 1, 0, "KScreenMode"); - - //const QString file = QDir::currentPath() + "/main.qml"; - const QString file = QStandardPaths::locate(QStandardPaths::QStandardPaths::GenericDataLocation, QStringLiteral("kcm_kscreen/qml/main.qml")); - ui->quickWidget->setSource(QUrl::fromLocalFile(file)); - - QQuickItem* rootObject = ui->quickWidget->rootObject(); - mScreen = rootObject->findChild(QStringLiteral("outputView")); - if (!mScreen) { - return; - } - connect(mScreen, &QMLScreen::focusedOutputChanged, - this, &Widget::slotFocusedOutputChanged); - connect(rootObject->findChild(QStringLiteral("identifyButton")), SIGNAL(clicked()), - this, SLOT(slotIdentifyButtonClicked())); -} - -void Widget::resetPrimaryCombo() -{ - bool isPrimaryDisplaySupported = mConfig->supportedFeatures().testFlag(KScreen::Config::Feature::PrimaryDisplay); - ui->primaryLabel->setVisible(isPrimaryDisplaySupported); - ui->primaryCombo->setVisible(isPrimaryDisplaySupported); - - // Don't emit currentIndexChanged when resetting - bool blocked = ui->primaryCombo->blockSignals(true); - ui->primaryCombo->clear(); - ui->primaryCombo->addItem(i18n("No Primary Output")); - ui->primaryCombo->blockSignals(blocked); - - if (!mConfig) { - return; - } - - for (auto &output: mConfig->outputs()) { - addOutputToPrimaryCombo(output); - } -} - -void Widget::addOutputToPrimaryCombo(const KScreen::OutputPtr &output) -{ - if (!output->isConnected() || !output->isEnabled()) { - return; - } - ui->primaryCombo->addItem(Utils::outputName(output), output->id()); - if (output->isPrimary()) { - Q_ASSERT(mConfig); - int lastIndex = ui->primaryCombo->count() - 1; - ui->primaryCombo->setCurrentIndex(lastIndex); - } -} - -void Widget::slotFocusedOutputChanged(QMLOutput *output) -{ - mControlPanel->activateOutput(output->outputPtr()); -} - -void Widget::slotOutputEnabledChanged() -{ - resetPrimaryCombo(); - - int enabledOutputsCount = 0; - Q_FOREACH (const KScreen::OutputPtr &output, mConfig->outputs()) { - if (output->isEnabled()) { - ++enabledOutputsCount; - } - if (enabledOutputsCount > 1) { - break; - } - } - ui->unifyButton->setEnabled(enabledOutputsCount > 1); -} - -void Widget::slotOutputConnectedChanged() -{ - resetPrimaryCombo(); -} - -void Widget::slotUnifyOutputs() -{ - QMLOutput *base = mScreen->primaryOutput(); - QList clones; - - if (!base) { - for (QMLOutput *output: mScreen->outputs()) { - if (output->output()->isConnected() && output->output()->isEnabled()) { - base = output; - break; - } - } - - if (!base) { - // WTF? - return; - } - } - - if (base->isCloneMode()) { - setConfig(mPrevConfig); - mPrevConfig.clear(); - - ui->primaryCombo->setEnabled(true); - ui->unifyButton->setText(i18n("Unify Outputs")); - } else { - // Clone the current config, so that we can restore it in case user - // breaks the cloning - mPrevConfig = mConfig->clone(); - - for (QMLOutput *output: mScreen->outputs()) { - if (!output->output()->isConnected()) { - continue; - } - - if (!output->output()->isEnabled()) { - output->setVisible(false); - continue; - } - - if (!base) { - base = output; - } - - output->setOutputX(0); - output->setOutputY(0); - output->output()->setPos(QPoint(0, 0)); - output->output()->setClones(QList()); - - if (base != output) { - clones << output->output()->id(); - output->setCloneOf(base); - output->setVisible(false); - } - } - - base->output()->setClones(clones); - base->setIsCloneMode(true); - - mScreen->updateOutputsPlacement(); - - ui->primaryCombo->setEnabled(false); - mControlPanel->setUnifiedOutput(base->outputPtr()); - ui->unifyButton->setText(i18n("Break Unified Outputs")); - } - - Q_EMIT changed(); -} - -// FIXME: Copy-pasted from KDED's Serializer::findOutput() -KScreen::OutputPtr Widget::findOutput(const KScreen::ConfigPtr &config, const QVariantMap &info) -{ - KScreen::OutputList outputs = config->outputs(); - Q_FOREACH(const KScreen::OutputPtr &output, outputs) { - if (!output->isConnected()) { - continue; - } - - const QString outputId = (output->edid() && output->edid()->isValid()) ? output->edid()->hash() : output->name(); - if (outputId != info[QStringLiteral("id")].toString()) { - continue; - } - - QVariantMap posInfo = info[QStringLiteral("pos")].toMap(); - QPoint point(posInfo[QStringLiteral("x")].toInt(), posInfo[QStringLiteral("y")].toInt()); - output->setPos(point); - output->setPrimary(info[QStringLiteral("primary")].toBool()); - output->setEnabled(info[QStringLiteral("enabled")].toBool()); - output->setRotation(static_cast(info[QStringLiteral("rotation")].toInt())); - - QVariantMap modeInfo = info[QStringLiteral("mode")].toMap(); - QVariantMap modeSize = modeInfo[QStringLiteral("size")].toMap(); - QSize size(modeSize[QStringLiteral("width")].toInt(), modeSize[QStringLiteral("height")].toInt()); - - const KScreen::ModeList modes = output->modes(); - Q_FOREACH(const KScreen::ModePtr &mode, modes) { - if (mode->size() != size) { - continue; - } - if (QString::number(mode->refreshRate()) != modeInfo[QStringLiteral("refresh")].toString()) { - continue; - } - - output->setCurrentModeId(mode->id()); - break; - } - return output; - } - - return KScreen::OutputPtr(); -} - -void Widget::clearOutputIdentifiers() -{ - mOutputTimer->stop(); - qDeleteAll(mOutputIdentifiers); - mOutputIdentifiers.clear(); -} - -void Widget::outputAdded(const KScreen::OutputPtr &output) -{ - connect(output.data(), &KScreen::Output::isConnectedChanged, - this, &Widget::slotOutputConnectedChanged); - connect(output.data(), &KScreen::Output::isEnabledChanged, - this, &Widget::slotOutputEnabledChanged); - connect(output.data(), &KScreen::Output::posChanged, - this, &Widget::changed); - - addOutputToPrimaryCombo(output); -} - -void Widget::outputRemoved(int outputId) -{ - KScreen::OutputPtr output = mConfig->output(outputId); - if (!output.isNull()) { - output->disconnect(this); - } - - const int index = ui->primaryCombo->findData(outputId); - if (index == -1) { - return; - } - - if (index == ui->primaryCombo->currentIndex()) { - // We'll get the actual primary update signal eventually - // Don't emit currentIndexChanged - const bool blocked = ui->primaryCombo->blockSignals(true); - ui->primaryCombo->setCurrentIndex(0); - ui->primaryCombo->blockSignals(blocked); - } - ui->primaryCombo->removeItem(index); -} - -void Widget::primaryOutputSelected(int index) -{ - if (!mConfig) { - return; - } - - const KScreen::OutputPtr newPrimary = index == 0 ? KScreen::OutputPtr() : mConfig->output(ui->primaryCombo->itemData(index).toInt()); - if (newPrimary == mConfig->primaryOutput()) { - return; - } - - mConfig->setPrimaryOutput(newPrimary); - Q_EMIT changed(); -} - -void Widget::primaryOutputChanged(const KScreen::OutputPtr &output) -{ - Q_ASSERT(mConfig); - int index = output.isNull() ? 0 : ui->primaryCombo->findData(output->id()); - if (index == -1 || index == ui->primaryCombo->currentIndex()) { - return; - } - ui->primaryCombo->setCurrentIndex(index); -} - -void Widget::slotIdentifyButtonClicked(bool checked) -{ - Q_UNUSED(checked); - connect(new KScreen::GetConfigOperation(), &KScreen::GetConfigOperation::finished, - this, &Widget::slotIdentifyOutputs); -} - -void Widget::slotIdentifyOutputs(KScreen::ConfigOperation *op) -{ - if (op->hasError()) { - return; - } - - const KScreen::ConfigPtr config = qobject_cast(op)->config(); - - const QString qmlPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(QML_PATH "OutputIdentifier.qml")); - - mOutputTimer->stop(); - clearOutputIdentifiers(); - - /* Obtain the current active configuration from KScreen */ - Q_FOREACH (const KScreen::OutputPtr &output, config->outputs()) { - if (!output->isConnected() || !output->currentMode()) { - continue; - } - - const KScreen::ModePtr mode = output->currentMode(); - - QQuickView *view = new QQuickView(); - - view->setFlags(Qt::X11BypassWindowManagerHint | Qt::FramelessWindowHint); - view->setResizeMode(QQuickView::SizeViewToRootObject); - view->setSource(QUrl::fromLocalFile(qmlPath)); - view->installEventFilter(this); - - QQuickItem *rootObj = view->rootObject(); - if (!rootObj) { - qWarning() << "Failed to obtain root item"; - continue; - } - - QSize deviceSize, logicalSize; - if (output->isHorizontal()) { - deviceSize = mode->size(); - } else { - deviceSize = QSize(mode->size().height(), mode->size().width()); - } - if (config->supportedFeatures() & KScreen::Config::Feature::PerOutputScaling) { - // no scale adjustment needed on Wayland - logicalSize = deviceSize; - } else { - logicalSize = deviceSize / devicePixelRatioF(); - } - - rootObj->setProperty("outputName", Utils::outputName(output)); - rootObj->setProperty("modeName", Utils::sizeToString(deviceSize)); - view->setProperty("screenSize", QRect(output->pos(), logicalSize)); - mOutputIdentifiers << view; - } - - for (QQuickView *view: mOutputIdentifiers) { - view->show(); - } - - mOutputTimer->start(2500); -} diff --git a/kded/CMakeLists.txt b/kded/CMakeLists.txt --- a/kded/CMakeLists.txt +++ b/kded/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kscreen\") -include_directories(${CMAKE_CURRENT_BINARY_DIR}/../ ${CMAKE_SOURCE_DIR}/kcm/src) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/../) set(kscreen_daemon_SRCS daemon.cpp @@ -13,7 +13,7 @@ osdaction.cpp ${CMAKE_SOURCE_DIR}/common/globals.cpp ${CMAKE_SOURCE_DIR}/common/control.cpp - ${CMAKE_SOURCE_DIR}/kcm/src/utils.cpp + ${CMAKE_SOURCE_DIR}/common/utils.cpp ) ecm_qt_declare_logging_category(kscreen_daemon_SRCS HEADER kscreen_daemon_debug.h IDENTIFIER KSCREEN_KDED CATEGORY_NAME kscreen.kded) diff --git a/kded/osd.cpp b/kded/osd.cpp --- a/kded/osd.cpp +++ b/kded/osd.cpp @@ -18,9 +18,11 @@ */ #include "osd.h" -#include "utils.h" + #include "kscreen_daemon_debug.h" +#include "../common/utils.h" + #include #include diff --git a/tests/osd/CMakeLists.txt b/tests/osd/CMakeLists.txt --- a/tests/osd/CMakeLists.txt +++ b/tests/osd/CMakeLists.txt @@ -1,14 +1,13 @@ include_directories( - ${CMAKE_SOURCE_DIR}/kcm/src ${CMAKE_BINARY_DIR}/kded ) add_executable(osdtest main.cpp osdtest.cpp ../../kded/osd.cpp ../../kded/osdmanager.cpp ../../kded/osdaction.cpp - ../../kcm/src/utils.cpp + ../../common/utils.cpp ) target_link_libraries(osdtest Qt5::Core