This commit is contained in:
wheaney 2026-01-15 13:45:41 -08:00
parent 538782b585
commit 7eab1b5ba1
5 changed files with 153 additions and 4 deletions

View File

@ -17,6 +17,11 @@
<max>230</max> <max>230</max>
<label>All Displays Distance</label> <label>All Displays Distance</label>
</entry> </entry>
<entry name="measurement_units" type="String">
<default>cm</default>
<label>Measurement units</label>
<description>Units for displaying distances in the KCM UI. Valid values: "cm" or "in".</description>
</entry>
<entry name="ZoomOnFocusEnabled" type="Bool"> <entry name="ZoomOnFocusEnabled" type="Bool">
<default>false</default> <default>false</default>
<label>Zoom on Focus Enabled</label> <label>Zoom on Focus Enabled</label>
@ -30,7 +35,7 @@
<description>How far apart the displays are visually (not logically)</description> <description>How far apart the displays are visually (not logically)</description>
</entry> </entry>
<entry name="DisplaySize" type="Int"> <entry name="DisplaySize" type="Int">
<default>100</default> <default>97</default>
<min>10</min> <min>10</min>
<max>250</max> <max>250</max>
<label>Display Size</label> <label>Display Size</label>

View File

@ -48,6 +48,8 @@
#include <QFile> #include <QFile>
#include <QDir> #include <QDir>
#include <QJsonDocument> #include <QJsonDocument>
#include <QLocale>
#include <QSignalBlocker>
#include <cmath> #include <cmath>
#include <algorithm> #include <algorithm>
@ -201,6 +203,27 @@ BreezyDesktopEffectConfig::BreezyDesktopEffectConfig(QObject *parent, const KPlu
ui.setupUi(widget()); ui.setupUi(widget());
addConfig(BreezyDesktopConfig::self(), widget()); addConfig(BreezyDesktopConfig::self(), widget());
// Advanced tab: measurement units selector (stored as "cm" or "in")
if (ui.comboMeasurementUnits) {
ui.comboMeasurementUnits->clear();
ui.comboMeasurementUnits->addItem(i18n("Centimeters (cm)"), QStringLiteral("cm"));
ui.comboMeasurementUnits->addItem(i18n("Inches (in)"), QStringLiteral("in"));
{
QSignalBlocker b(ui.comboMeasurementUnits);
const QString saved = KConfigGroup(BreezyDesktopConfig::self()->sharedConfig(), QLatin1String(EFFECT_GROUP))
.readEntry(QStringLiteral("measurement_units"), QStringLiteral("cm"));
const int idx = ui.comboMeasurementUnits->findData(saved);
ui.comboMeasurementUnits->setCurrentIndex(idx >= 0 ? idx : 0);
}
connect(ui.comboMeasurementUnits, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int) {
if (m_updatingFromConfig) return;
applyDistanceLabelFormatters();
save();
});
}
// One-time check if the KWin effect backend is actually loaded. If not, disable UI early. // One-time check if the KWin effect backend is actually loaded. If not, disable UI early.
checkEffectLoaded(); checkEffectLoaded();
@ -394,6 +417,8 @@ BreezyDesktopEffectConfig::BreezyDesktopEffectConfig(QObject *parent, const KPlu
lookAheadOverrideSlider->setValueText(-1, i18n("Default")); lookAheadOverrideSlider->setValueText(-1, i18n("Default"));
} }
applyDistanceLabelFormatters();
renderVirtualDisplays(dbusListVirtualDisplays()); renderVirtualDisplays(dbusListVirtualDisplays());
m_virtualDisplayPollTimer.setInterval(15000); m_virtualDisplayPollTimer.setInterval(15000);
@ -431,6 +456,14 @@ void BreezyDesktopEffectConfig::save()
m_updatingFromConfig = true; m_updatingFromConfig = true;
updateConfigFromUi(); updateConfigFromUi();
BreezyDesktopConfig::self()->save(); BreezyDesktopConfig::self()->save();
// Store measurement_units explicitly (snake_case key) without depending on KConfigXT accessor naming.
{
KConfigGroup grp(BreezyDesktopConfig::self()->sharedConfig(), QLatin1String(EFFECT_GROUP));
grp.writeEntry(QStringLiteral("measurement_units"), measurementUnitsFromUi());
grp.sync();
}
KCModule::save(); KCModule::save();
ui.kcfg_FocusedDisplayDistance->setEnabled( ui.kcfg_FocusedDisplayDistance->setEnabled(
ui.kcfg_ZoomOnFocusEnabled->isChecked() || ui.SmoothFollowEnabled->isChecked()); ui.kcfg_ZoomOnFocusEnabled->isChecked() || ui.SmoothFollowEnabled->isChecked());
@ -472,11 +505,29 @@ void BreezyDesktopEffectConfig::updateUiFromConfig()
ui.kcfg_FocusedDisplayDistance->setEnabled( ui.kcfg_FocusedDisplayDistance->setEnabled(
ui.kcfg_ZoomOnFocusEnabled->isChecked() || ui.SmoothFollowEnabled->isChecked()); ui.kcfg_ZoomOnFocusEnabled->isChecked() || ui.SmoothFollowEnabled->isChecked());
ui.kcfg_SmoothFollowThreshold->setValue(BreezyDesktopConfig::self()->smoothFollowThreshold()); ui.kcfg_SmoothFollowThreshold->setValue(BreezyDesktopConfig::self()->smoothFollowThreshold());
if (ui.comboMeasurementUnits) {
QSignalBlocker b(ui.comboMeasurementUnits);
const QString saved = KConfigGroup(BreezyDesktopConfig::self()->sharedConfig(), QLatin1String(EFFECT_GROUP))
.readEntry(QStringLiteral("measurement_units"), QStringLiteral("cm"));
const int idx = ui.comboMeasurementUnits->findData(saved);
ui.comboMeasurementUnits->setCurrentIndex(idx >= 0 ? idx : 0);
}
applyDistanceLabelFormatters();
} }
void BreezyDesktopEffectConfig::updateUiFromDefaultConfig() void BreezyDesktopEffectConfig::updateUiFromDefaultConfig()
{ {
ui.shortcutsEditor->allDefault(); ui.shortcutsEditor->allDefault();
if (ui.comboMeasurementUnits) {
QSignalBlocker b(ui.comboMeasurementUnits);
const int idx = ui.comboMeasurementUnits->findData(QStringLiteral("cm"));
ui.comboMeasurementUnits->setCurrentIndex(idx >= 0 ? idx : 0);
}
applyDistanceLabelFormatters();
} }
void BreezyDesktopEffectConfig::updateUnmanagedState() void BreezyDesktopEffectConfig::updateUnmanagedState()
@ -648,6 +699,11 @@ void BreezyDesktopEffectConfig::pollDriverState()
auto stateJson = stateJsonOpt.value(); auto stateJson = stateJsonOpt.value();
m_connectedDeviceBrand = stateJson.value(QStringLiteral("connected_device_brand")).toString(); m_connectedDeviceBrand = stateJson.value(QStringLiteral("connected_device_brand")).toString();
m_connectedDeviceModel = stateJson.value(QStringLiteral("connected_device_model")).toString(); m_connectedDeviceModel = stateJson.value(QStringLiteral("connected_device_model")).toString();
m_connectedDeviceFullDistanceCm = stateJson.value(QStringLiteral("connected_device_full_distance_cm")).toDouble(0.0);
m_connectedDeviceFullSizeCm = stateJson.value(QStringLiteral("connected_device_full_size_cm")).toDouble(0.0);
m_connectedDevicePoseHasPosition = stateJson.value(QStringLiteral("connected_device_pose_has_position")).toBool(false);
applyDistanceLabelFormatters();
const bool smoothFollow = smoothFollowEnabled(stateJsonOpt); const bool smoothFollow = smoothFollowEnabled(stateJsonOpt);
if (ui.SmoothFollowEnabled->isChecked() != smoothFollow) { if (ui.SmoothFollowEnabled->isChecked() != smoothFollow) {
@ -711,6 +767,46 @@ void BreezyDesktopEffectConfig::pollDriverState()
m_driverStateInitialized = true; m_driverStateInitialized = true;
} }
QString BreezyDesktopEffectConfig::measurementUnitsFromUi() const
{
if (!ui.comboMeasurementUnits) return QStringLiteral("cm");
const QString v = ui.comboMeasurementUnits->currentData().toString();
if (v == QLatin1String("in")) return QStringLiteral("in");
return QStringLiteral("cm");
}
void BreezyDesktopEffectConfig::applyDistanceLabelFormatters()
{
auto *focused = ui.kcfg_FocusedDisplayDistance;
auto *all = ui.kcfg_AllDisplaysDistance;
if (!focused || !all) return;
// Only apply the unit conversion labels when the driver reports positional tracking.
if (!m_connectedDevicePoseHasPosition) {
focused->clearValueToDisplayStringFn();
all->clearValueToDisplayStringFn();
return;
}
const double fullCm = static_cast<double>(m_connectedDeviceFullDistanceCm);
const QString units = measurementUnitsFromUi();
const QLocale loc;
LabeledSlider::ValueToDisplayStringFn fn = [fullCm, units, loc](int raw) -> QString {
if (fullCm <= 0.0) return QString();
const double ratio = static_cast<double>(raw) / 100.0; // slider uses a 2-decimal fixed-point scale
const double cm = ratio * fullCm;
if (units == QLatin1String("in")) {
const double inches = cm / 2.54;
return loc.toString(inches, 'f', 1) + QStringLiteral(" in");
}
return loc.toString(cm, 'f', 0) + QStringLiteral(" cm");
};
focused->setValueToDisplayStringFn(fn);
all->setValueToDisplayStringFn(fn);
}
double BreezyDesktopEffectConfig::neckSaverHorizontalMultiplier(std::optional<QJsonObject> configJsonOpt) double BreezyDesktopEffectConfig::neckSaverHorizontalMultiplier(std::optional<QJsonObject> configJsonOpt)
{ {
if (!configJsonOpt) return 1.0; if (!configJsonOpt) return 1.0;

View File

@ -28,6 +28,9 @@ public Q_SLOTS:
void defaults() override; void defaults() override;
private: private:
QString measurementUnitsFromUi() const;
void applyDistanceLabelFormatters();
void updateDriverEnabled(); void updateDriverEnabled();
void updateMultitapEnabled(); void updateMultitapEnabled();
void updateSmoothFollowEnabled(); void updateSmoothFollowEnabled();
@ -73,8 +76,9 @@ private:
int m_smoothFollowThreshold = 1; int m_smoothFollowThreshold = 1;
QString m_connectedDeviceBrand; QString m_connectedDeviceBrand;
QString m_connectedDeviceModel; QString m_connectedDeviceModel;
int m_connectedDeviceFullDistanceCm = 0; float m_connectedDeviceFullDistanceCm = 0.0;
int m_connectedDeviceFullSizeCm = 0; float m_connectedDeviceFullSizeCm = 0.0;
bool m_connectedDevicePoseHasPosition = false;
QTimer m_statePollTimer; // periodic driver state polling QTimer m_statePollTimer; // periodic driver state polling
QTimer m_virtualDisplayPollTimer; // periodic virtual display list polling QTimer m_virtualDisplayPollTimer; // periodic virtual display list polling
bool m_licenseLoading = false; bool m_licenseLoading = false;

View File

@ -159,11 +159,14 @@
</item> </item>
<item row="6" column="1"> <item row="6" column="1">
<widget class="LabeledSlider" name="kcfg_DisplaySize"> <widget class="LabeledSlider" name="kcfg_DisplaySize">
<property name="decimalShift">
<double>2</double>
</property>
<property name="tickPosition"> <property name="tickPosition">
<enum>QSlider::NoTicks</enum> <enum>QSlider::NoTicks</enum>
</property> </property>
<property name="tickStartOffset"> <property name="tickStartOffset">
<double>10</double> <double>5</double>
</property> </property>
<property name="tickInterval"> <property name="tickInterval">
<double>25</double> <double>25</double>
@ -631,6 +634,17 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="13" column="0">
<widget class="QLabel" name="labelMeasurementUnits">
<property name="text">
<string>Measurement units:</string>
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="QComboBox" name="comboMeasurementUnits">
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tabLicenseDetails"> <widget class="QWidget" name="tabLicenseDetails">

View File

@ -5,6 +5,7 @@
#include <QPainterPath> #include <QPainterPath>
#include <QStyleOptionSlider> #include <QStyleOptionSlider>
#include <algorithm> // for std::max #include <algorithm> // for std::max
#include <functional>
#include <QtCore/QMap> #include <QtCore/QMap>
#include <QtCore/QString> #include <QtCore/QString>
#include <QStringList> #include <QStringList>
@ -33,6 +34,8 @@ class LabeledSlider : public QSlider {
// Example: minimum=0, tickInterval=20, tickStartOffset=10 -> labels at 10,30,50,... // Example: minimum=0, tickInterval=20, tickStartOffset=10 -> labels at 10,30,50,...
Q_PROPERTY(int tickStartOffset READ tickStartOffset WRITE setTickStartOffset) Q_PROPERTY(int tickStartOffset READ tickStartOffset WRITE setTickStartOffset)
public: public:
using ValueToDisplayStringFn = std::function<QString(int)>;
explicit LabeledSlider(QWidget *parent = nullptr) explicit LabeledSlider(QWidget *parent = nullptr)
: QSlider(Qt::Horizontal, parent) : QSlider(Qt::Horizontal, parent)
{ {
@ -74,6 +77,24 @@ public:
QMap<int, QString> valueTexts() const { return m_valueTexts; } QMap<int, QString> valueTexts() const { return m_valueTexts; }
// Optional custom formatter for displayed values.
// If set, it is consulted for values without an explicit setValueText() override.
// Returning a null QString (QString()) falls back to the built-in formatting.
void setValueToDisplayStringFn(ValueToDisplayStringFn fn) {
m_valueToDisplayStringFn = std::move(fn);
updateGeometry();
update();
}
void clearValueToDisplayStringFn() {
if (!m_valueToDisplayStringFn) return;
m_valueToDisplayStringFn = nullptr;
updateGeometry();
update();
}
bool hasValueToDisplayStringFn() const { return static_cast<bool>(m_valueToDisplayStringFn); }
int decimalShift() const { return m_decimalShift; } int decimalShift() const { return m_decimalShift; }
void setDecimalShift(int shift) { void setDecimalShift(int shift) {
// clamp to sensible range // clamp to sensible range
@ -193,6 +214,14 @@ private:
if (it != m_valueTexts.constEnd()) { if (it != m_valueTexts.constEnd()) {
return *it; return *it;
} }
if (m_valueToDisplayStringFn) {
QString formatted = m_valueToDisplayStringFn(raw);
if (!formatted.isNull()) {
return formatted;
}
}
if (m_decimalShift == 0) { if (m_decimalShift == 0) {
return QString::number(raw); return QString::number(raw);
} }
@ -210,6 +239,7 @@ private:
int m_decimalShift = 0; // display-only decimal shift int m_decimalShift = 0; // display-only decimal shift
int m_tickStartOffset = 0; // label positions start offset relative to minimum int m_tickStartOffset = 0; // label positions start offset relative to minimum
QMap<int, QString> m_valueTexts; // optional text overrides for specific values QMap<int, QString> m_valueTexts; // optional text overrides for specific values
ValueToDisplayStringFn m_valueToDisplayStringFn; // optional custom formatter
private: private:
int labelInterval() const { int labelInterval() const {
int ti = tickInterval(); int ti = tickInterval();