You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libcutefish/screen/qt5/outputmodel.cpp

369 lines
12 KiB
C++

#include "outputmodel.h"
#include <KScreen/Mode>
#include <KScreen/Output>
#include <algorithm>
#include <limits>
#include <QtMath>
KScreenOutputModel::KScreenOutputModel(QObject *parent)
: QAbstractListModel(parent)
{
}
int KScreenOutputModel::rowCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : m_outputs.size();
}
KScreen::OutputPtr KScreenOutputModel::outputAt(int row) const
{
if (row < 0 || row >= m_outputs.size()) {
return {};
}
return m_outputs.at(row);
}
QList<KScreenOutputModel::ModeChoice> KScreenOutputModel::modesForOutput(const KScreen::OutputPtr &output) const
{
QList<ModeChoice> modes;
if (!output) {
return modes;
}
const KScreen::ModeList outputModes = output->modes();
for (auto it = outputModes.cbegin(); it != outputModes.cend(); ++it) {
if (!it.value()) {
continue;
}
modes.append({it.key(), it.value()->size(), it.value()->refreshRate()});
}
return modes;
}
QList<QSize> KScreenOutputModel::resolutionsForOutput(const KScreen::OutputPtr &output) const
{
QList<QSize> resolutions;
for (const ModeChoice &mode : modesForOutput(output)) {
if (mode.size.isValid() && !resolutions.contains(mode.size)) {
resolutions.append(mode.size);
}
}
std::sort(resolutions.begin(), resolutions.end(), [](const QSize &first, const QSize &second) {
const qint64 firstArea = qint64(first.width()) * first.height();
const qint64 secondArea = qint64(second.width()) * second.height();
if (firstArea != secondArea) {
return firstArea > secondArea;
}
return first.width() > second.width();
});
return resolutions;
}
QList<float> KScreenOutputModel::refreshRatesForOutput(const KScreen::OutputPtr &output,
const QSize &size) const
{
QList<float> refreshRates;
for (const ModeChoice &mode : modesForOutput(output)) {
if (mode.size != size || mode.refreshRate <= 0) {
continue;
}
bool alreadyListed = false;
for (const float refreshRate : refreshRates) {
if (sameRefreshRate(refreshRate, mode.refreshRate)) {
alreadyListed = true;
break;
}
}
if (!alreadyListed) {
refreshRates.append(mode.refreshRate);
}
}
std::sort(refreshRates.begin(), refreshRates.end());
return refreshRates;
}
QString KScreenOutputModel::outputName(const KScreen::OutputPtr &output)
{
if (!output) {
return QString();
}
if (!output->name().isEmpty()) {
return output->name();
}
if (!output->model().isEmpty()) {
return output->model();
}
if (!output->vendor().isEmpty()) {
return output->vendor();
}
return QStringLiteral("Display");
}
QString KScreenOutputModel::formatResolution(const QSize &size)
{
return QStringLiteral("%1 × %2").arg(size.width()).arg(size.height());
}
QString KScreenOutputModel::formatRefreshRate(float refreshRate)
{
const int roundedRefreshRate = qRound(refreshRate);
if (qAbs(refreshRate - roundedRefreshRate) < 0.01) {
return QString::number(roundedRefreshRate) + QStringLiteral(" Hz");
}
return QString::number(refreshRate, 'f', 2) + QStringLiteral(" Hz");
}
bool KScreenOutputModel::sameRefreshRate(float first, float second)
{
return qAbs(first - second) < 0.01;
}
QVariant KScreenOutputModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
const KScreen::OutputPtr output = outputAt(index.row());
if (!output) {
return QVariant();
}
const KScreen::ModePtr currentMode = output->currentMode();
const QSize currentModeSize = currentMode ? currentMode->size() : output->enforcedModeSize();
const QList<QSize> resolutions = resolutionsForOutput(output);
const QList<float> refreshRates = refreshRatesForOutput(output, currentModeSize);
switch (role) {
case Qt::DisplayRole:
return outputName(output);
case EnabledRole:
return output->isEnabled();
case InternalRole:
return output->type() == KScreen::Output::Panel
|| outputName(output).startsWith(QStringLiteral("eDP"), Qt::CaseInsensitive)
|| outputName(output).startsWith(QStringLiteral("LVDS"), Qt::CaseInsensitive);
case PrimaryRole:
return output->isPrimary();
case SizeRole:
return output->size();
case PositionRole:
case NormalizedPositionRole:
return output->pos();
case AutoRotateRole:
return output->autoRotatePolicy() != KScreen::Output::AutoRotatePolicy::Never;
case AutoRotateOnlyInTabletModeRole:
return output->autoRotatePolicy() == KScreen::Output::AutoRotatePolicy::InTabletMode;
case RotationRole:
return static_cast<int>(output->rotation());
case ScaleRole:
return output->scale();
case ResolutionIndexRole: {
const int resolutionIndex = resolutions.indexOf(currentModeSize);
return resolutionIndex >= 0 ? resolutionIndex : 0;
}
case ResolutionsRole: {
QStringList result;
for (const QSize &resolution : resolutions) {
result.append(formatResolution(resolution));
}
return result;
}
case RefreshRateIndexRole: {
if (!currentMode) {
return 0;
}
for (int i = 0; i < refreshRates.size(); ++i) {
if (sameRefreshRate(refreshRates.at(i), currentMode->refreshRate())) {
return i;
}
}
return 0;
}
case RefreshRatesRole: {
QStringList result;
for (const float refreshRate : refreshRates) {
result.append(formatRefreshRate(refreshRate));
}
return result;
}
case ReplicationSourceModelRole:
return QStringList();
case ReplicationSourceIndexRole:
return -1;
case ReplicasModelRole:
return QVariantList();
default:
return QVariant();
}
}
bool KScreenOutputModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid()) {
return false;
}
const KScreen::OutputPtr output = outputAt(index.row());
if (!output) {
return false;
}
bool changed = false;
switch (role) {
case EnabledRole: {
const bool enabled = value.toBool();
if (!enabled) {
int enabledOutputs = 0;
for (const KScreen::OutputPtr &candidate : m_outputs) {
enabledOutputs += candidate && candidate->isEnabled() ? 1 : 0;
}
if (enabledOutputs <= 1) {
return false;
}
}
if (output->isEnabled() != enabled) {
output->setEnabled(enabled);
changed = true;
}
break;
}
case RotationRole: {
const auto rotation = static_cast<KScreen::Output::Rotation>(value.toInt());
if (rotation != KScreen::Output::None
&& rotation != KScreen::Output::Left
&& rotation != KScreen::Output::Inverted
&& rotation != KScreen::Output::Right) {
return false;
}
if (output->rotation() != rotation) {
output->setRotation(rotation);
changed = true;
}
break;
}
case ResolutionIndexRole: {
const QList<QSize> resolutions = resolutionsForOutput(output);
const int resolutionIndex = value.toInt();
if (resolutionIndex < 0 || resolutionIndex >= resolutions.size()) {
return false;
}
const QSize selectedSize = resolutions.at(resolutionIndex);
const KScreen::ModePtr currentMode = output->currentMode();
const float currentRefreshRate = currentMode ? currentMode->refreshRate() : 0;
QString selectedModeId;
float closestRefreshRateDifference = std::numeric_limits<float>::max();
for (const ModeChoice &mode : modesForOutput(output)) {
if (mode.size != selectedSize) {
continue;
}
const float difference = qAbs(mode.refreshRate - currentRefreshRate);
if (selectedModeId.isEmpty() || difference < closestRefreshRateDifference) {
selectedModeId = mode.id;
closestRefreshRateDifference = difference;
}
}
if (!selectedModeId.isEmpty() && selectedModeId != output->currentModeId()) {
output->setCurrentModeId(selectedModeId);
changed = true;
}
break;
}
case RefreshRateIndexRole: {
const KScreen::ModePtr currentMode = output->currentMode();
const QSize currentSize = currentMode ? currentMode->size() : output->enforcedModeSize();
const QList<float> refreshRates = refreshRatesForOutput(output, currentSize);
const int refreshRateIndex = value.toInt();
if (refreshRateIndex < 0 || refreshRateIndex >= refreshRates.size()) {
return false;
}
const float selectedRefreshRate = refreshRates.at(refreshRateIndex);
QString selectedModeId;
for (const ModeChoice &mode : modesForOutput(output)) {
if (mode.size == currentSize && sameRefreshRate(mode.refreshRate, selectedRefreshRate)) {
selectedModeId = mode.id;
break;
}
}
if (!selectedModeId.isEmpty() && selectedModeId != output->currentModeId()) {
output->setCurrentModeId(selectedModeId);
changed = true;
}
break;
}
case ScaleRole: {
const qreal scale = value.toReal();
if (scale <= 0 || qFuzzyCompare(output->scale(), scale)) {
return false;
}
output->setScale(scale);
changed = true;
break;
}
default:
return false;
}
if (changed) {
emit dataChanged(index, index, {role});
}
return changed;
}
Qt::ItemFlags KScreenOutputModel::flags(const QModelIndex &index) const
{
if (!index.isValid()) {
return Qt::NoItemFlags;
}
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
}
QHash<int, QByteArray> KScreenOutputModel::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractListModel::roleNames();
roles[Qt::DisplayRole] = "display";
roles[EnabledRole] = "enabled";
roles[InternalRole] = "internal";
roles[PrimaryRole] = "primary";
roles[SizeRole] = "size";
roles[PositionRole] = "position";
roles[NormalizedPositionRole] = "normalizedPosition";
roles[AutoRotateRole] = "autoRotate";
roles[AutoRotateOnlyInTabletModeRole] = "autoRotateOnlyInTabletMode";
roles[RotationRole] = "rotation";
roles[ScaleRole] = "scale";
roles[ResolutionIndexRole] = "resolutionIndex";
roles[ResolutionsRole] = "resolutions";
roles[RefreshRateIndexRole] = "refreshRateIndex";
roles[RefreshRatesRole] = "refreshRates";
roles[ReplicationSourceModelRole] = "replicationSourceModel";
roles[ReplicationSourceIndexRole] = "replicationSourceIndex";
roles[ReplicasModelRole] = "replicasModel";
return roles;
}
void KScreenOutputModel::setConfig(const KScreen::ConfigPtr &config)
{
beginResetModel();
m_config = config;
m_outputs.clear();
if (m_config) {
const KScreen::OutputList outputs = m_config->outputs();
for (auto it = outputs.cbegin(); it != outputs.cend(); ++it) {
if (it.value() && it.value()->isConnected()) {
m_outputs.append(it.value());
}
}
}
endResetModel();
}