gui: close-to-tray option + fix session list disrupting rename
- Add configurable close-to-tray (Settings > System Tray) - QSystemTrayIcon with show/hide toggle and quit action - closeToTray property persisted in theme.conf - Window hides instead of quitting when enabled - Fix session rename being interrupted by state changes - onStateChanged no longer calls refreshSessions() - State updates in-place via sessionStateUpdated signal - Delegates track state locally, preserving rename TextField
This commit is contained in:
parent
1d646f7fab
commit
f45cb8199d
|
|
@ -10,6 +10,13 @@ ApplicationWindow {
|
||||||
height: 600
|
height: 600
|
||||||
visible: true
|
visible: true
|
||||||
|
|
||||||
|
onClosing: {
|
||||||
|
if (theme.closeToTray && systemTrayAvailable) {
|
||||||
|
close.accepted = false
|
||||||
|
root.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
color: palette.window
|
color: palette.window
|
||||||
font.family: theme.fontFamily || Qt.application.font.family
|
font.family: theme.fontFamily || Qt.application.font.family
|
||||||
font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize
|
font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize
|
||||||
|
|
@ -136,6 +143,37 @@ ApplicationWindow {
|
||||||
text: "Reset to system font"
|
text: "Reset to system font"
|
||||||
onClicked: theme.resetFont()
|
onClicked: theme.resetFont()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Close to tray section ---
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 1
|
||||||
|
color: palette.mid
|
||||||
|
Layout.topMargin: 8
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "System Tray"
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
text: "Close to tray instead of quitting"
|
||||||
|
checked: theme.closeToTray
|
||||||
|
onToggled: theme.closeToTray = checked
|
||||||
|
enabled: systemTrayAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: systemTrayAvailable
|
||||||
|
? "Window will hide to the system tray when closed."
|
||||||
|
: "System tray is not available."
|
||||||
|
opacity: 0.5
|
||||||
|
font.pixelSize: Qt.application.font.pixelSize * 0.85
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: theme.closeToTray || !systemTrayAvailable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -206,6 +244,15 @@ ApplicationWindow {
|
||||||
highlighted: modelData.id === dbus.activeSessionId
|
highlighted: modelData.id === dbus.activeSessionId
|
||||||
|
|
||||||
property bool renaming: false
|
property bool renaming: false
|
||||||
|
property string sessionState: modelData.state
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: dbus
|
||||||
|
function onSessionStateUpdated(sessionId, newState) {
|
||||||
|
if (sessionId === modelData.id)
|
||||||
|
sessionDelegate.sessionState = newState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: sessionDelegate.highlighted
|
color: sessionDelegate.highlighted
|
||||||
|
|
@ -221,7 +268,7 @@ ApplicationWindow {
|
||||||
height: 8
|
height: 8
|
||||||
radius: 4
|
radius: 4
|
||||||
color: {
|
color: {
|
||||||
var state = modelData.state
|
var state = sessionDelegate.sessionState
|
||||||
if (state === "idle") return "#4caf50"
|
if (state === "idle") return "#4caf50"
|
||||||
if (state.startsWith("calling")) return "#ff9800"
|
if (state.startsWith("calling")) return "#ff9800"
|
||||||
return "#2196f3"
|
return "#2196f3"
|
||||||
|
|
|
||||||
55
gui/main.cpp
55
gui/main.cpp
|
|
@ -7,6 +7,10 @@
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QFont>
|
#include <QFont>
|
||||||
#include <QFontDatabase>
|
#include <QFontDatabase>
|
||||||
|
#include <QSystemTrayIcon>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QAction>
|
||||||
|
#include <QWindow>
|
||||||
|
|
||||||
#include "olliedbusclient.h"
|
#include "olliedbusclient.h"
|
||||||
#include "thememanager.h"
|
#include "thememanager.h"
|
||||||
|
|
@ -77,6 +81,8 @@ int main(int argc, char *argv[])
|
||||||
engine.rootContext()->setContextProperty("dbus", dbus);
|
engine.rootContext()->setContextProperty("dbus", dbus);
|
||||||
engine.rootContext()->setContextProperty("theme", theme);
|
engine.rootContext()->setContextProperty("theme", theme);
|
||||||
engine.rootContext()->setContextProperty("chatModel", chatModel);
|
engine.rootContext()->setContextProperty("chatModel", chatModel);
|
||||||
|
engine.rootContext()->setContextProperty("systemTrayAvailable",
|
||||||
|
QSystemTrayIcon::isSystemTrayAvailable());
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
const QUrl url(QStringLiteral("qrc:/gui/main.qml"));
|
const QUrl url(QStringLiteral("qrc:/gui/main.qml"));
|
||||||
|
|
@ -91,5 +97,54 @@ int main(int argc, char *argv[])
|
||||||
}, Qt::QueuedConnection);
|
}, Qt::QueuedConnection);
|
||||||
|
|
||||||
engine.load(url);
|
engine.load(url);
|
||||||
|
|
||||||
|
// --- System tray icon (shown when close-to-tray is enabled) ---
|
||||||
|
auto *trayIcon = new QSystemTrayIcon(&app);
|
||||||
|
trayIcon->setIcon(app.windowIcon());
|
||||||
|
trayIcon->setToolTip("Ollie");
|
||||||
|
|
||||||
|
auto *trayMenu = new QMenu();
|
||||||
|
auto *showAction = trayMenu->addAction("Show");
|
||||||
|
trayMenu->addSeparator();
|
||||||
|
auto *quitAction = trayMenu->addAction("Quit");
|
||||||
|
trayIcon->setContextMenu(trayMenu);
|
||||||
|
|
||||||
|
// Toggle window visibility on tray icon activation
|
||||||
|
QObject::connect(trayIcon, &QSystemTrayIcon::activated,
|
||||||
|
[&engine](QSystemTrayIcon::ActivationReason reason) {
|
||||||
|
if (reason != QSystemTrayIcon::Trigger)
|
||||||
|
return;
|
||||||
|
auto roots = engine.rootObjects();
|
||||||
|
if (roots.isEmpty()) return;
|
||||||
|
auto *window = qobject_cast<QWindow *>(roots.first());
|
||||||
|
if (!window) return;
|
||||||
|
if (window->isVisible()) {
|
||||||
|
window->hide();
|
||||||
|
} else {
|
||||||
|
window->show();
|
||||||
|
window->raise();
|
||||||
|
window->requestActivate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(showAction, &QAction::triggered, [&engine]() {
|
||||||
|
auto roots = engine.rootObjects();
|
||||||
|
if (roots.isEmpty()) return;
|
||||||
|
auto *window = qobject_cast<QWindow *>(roots.first());
|
||||||
|
if (!window) return;
|
||||||
|
window->show();
|
||||||
|
window->raise();
|
||||||
|
window->requestActivate();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(quitAction, &QAction::triggered, &app, &QApplication::quit);
|
||||||
|
|
||||||
|
// Show/hide tray icon based on setting
|
||||||
|
auto updateTrayVisibility = [trayIcon, theme]() {
|
||||||
|
trayIcon->setVisible(theme->closeToTray());
|
||||||
|
};
|
||||||
|
updateTrayVisibility();
|
||||||
|
QObject::connect(theme, &ThemeManager::closeToTrayChanged, updateTrayVisibility);
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
49
gui/main.qml
49
gui/main.qml
|
|
@ -10,6 +10,13 @@ ApplicationWindow {
|
||||||
height: 600
|
height: 600
|
||||||
visible: true
|
visible: true
|
||||||
|
|
||||||
|
onClosing: function(close) {
|
||||||
|
if (theme.closeToTray && systemTrayAvailable) {
|
||||||
|
close.accepted = false
|
||||||
|
root.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
color: palette.window
|
color: palette.window
|
||||||
font.family: theme.fontFamily || Qt.application.font.family
|
font.family: theme.fontFamily || Qt.application.font.family
|
||||||
font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize
|
font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize
|
||||||
|
|
@ -137,6 +144,37 @@ ApplicationWindow {
|
||||||
text: "Reset to system font"
|
text: "Reset to system font"
|
||||||
onClicked: theme.resetFont()
|
onClicked: theme.resetFont()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Close to tray section ---
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 1
|
||||||
|
color: palette.mid
|
||||||
|
Layout.topMargin: 8
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "System Tray"
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
text: "Close to tray instead of quitting"
|
||||||
|
checked: theme.closeToTray
|
||||||
|
onToggled: theme.closeToTray = checked
|
||||||
|
enabled: systemTrayAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: systemTrayAvailable
|
||||||
|
? "Window will hide to the system tray when closed."
|
||||||
|
: "System tray is not available."
|
||||||
|
opacity: 0.5
|
||||||
|
font.pixelSize: Qt.application.font.pixelSize * 0.85
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: theme.closeToTray || !systemTrayAvailable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,6 +245,15 @@ ApplicationWindow {
|
||||||
highlighted: modelData.id === dbus.activeSessionId
|
highlighted: modelData.id === dbus.activeSessionId
|
||||||
|
|
||||||
property bool renaming: false
|
property bool renaming: false
|
||||||
|
property string sessionState: modelData.state
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: dbus
|
||||||
|
function onSessionStateUpdated(sessionId, newState) {
|
||||||
|
if (sessionId === modelData.id)
|
||||||
|
sessionDelegate.sessionState = newState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: sessionDelegate.highlighted
|
color: sessionDelegate.highlighted
|
||||||
|
|
@ -222,7 +269,7 @@ ApplicationWindow {
|
||||||
height: 8
|
height: 8
|
||||||
radius: 4
|
radius: 4
|
||||||
color: {
|
color: {
|
||||||
let state = modelData.state
|
let state = sessionDelegate.sessionState
|
||||||
if (state === "idle") return "#4caf50"
|
if (state === "idle") return "#4caf50"
|
||||||
if (state.startsWith("calling")) return "#ff9800"
|
if (state.startsWith("calling")) return "#ff9800"
|
||||||
return "#2196f3"
|
return "#2196f3"
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ ThemeManager::ThemeManager(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_mode("system")
|
, m_mode("system")
|
||||||
, m_fontSize(0)
|
, m_fontSize(0)
|
||||||
|
, m_closeToTray(false)
|
||||||
, m_watcher(nullptr)
|
, m_watcher(nullptr)
|
||||||
{
|
{
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
@ -372,6 +373,22 @@ QStringList ThemeManager::availableStyles() const
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Close-to-tray ---
|
||||||
|
|
||||||
|
bool ThemeManager::closeToTray() const
|
||||||
|
{
|
||||||
|
return m_closeToTray;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThemeManager::setCloseToTray(bool enabled)
|
||||||
|
{
|
||||||
|
if (enabled == m_closeToTray)
|
||||||
|
return;
|
||||||
|
m_closeToTray = enabled;
|
||||||
|
saveSettings();
|
||||||
|
emit closeToTrayChanged();
|
||||||
|
}
|
||||||
|
|
||||||
void ThemeManager::resetFont()
|
void ThemeManager::resetFont()
|
||||||
{
|
{
|
||||||
m_fontFamily.clear();
|
m_fontFamily.clear();
|
||||||
|
|
@ -430,6 +447,8 @@ void ThemeManager::loadSettings()
|
||||||
m_fontFamily = settings.value("Font/family", "").toString();
|
m_fontFamily = settings.value("Font/family", "").toString();
|
||||||
m_fontSize = settings.value("Font/size", 0).toInt();
|
m_fontSize = settings.value("Font/size", 0).toInt();
|
||||||
m_fontStyle = settings.value("Font/style", "").toString();
|
m_fontStyle = settings.value("Font/style", "").toString();
|
||||||
|
|
||||||
|
m_closeToTray = settings.value("General/closeToTray", false).toBool();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -444,4 +463,5 @@ void ThemeManager::saveSettings()
|
||||||
settings.setValue("Font/family", m_fontFamily);
|
settings.setValue("Font/family", m_fontFamily);
|
||||||
settings.setValue("Font/size", m_fontSize);
|
settings.setValue("Font/size", m_fontSize);
|
||||||
settings.setValue("Font/style", m_fontStyle);
|
settings.setValue("Font/style", m_fontStyle);
|
||||||
|
settings.setValue("General/closeToTray", m_closeToTray);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,9 @@ class ThemeManager : public QObject
|
||||||
Q_PROPERTY(QStringList availableFamilies READ availableFamilies CONSTANT)
|
Q_PROPERTY(QStringList availableFamilies READ availableFamilies CONSTANT)
|
||||||
Q_PROPERTY(QStringList availableStyles READ availableStyles NOTIFY fontChanged)
|
Q_PROPERTY(QStringList availableStyles READ availableStyles NOTIFY fontChanged)
|
||||||
|
|
||||||
|
// Close-to-tray
|
||||||
|
Q_PROPERTY(bool closeToTray READ closeToTray WRITE setCloseToTray NOTIFY closeToTrayChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Mode {
|
enum Mode {
|
||||||
System, // Follow KDE system color scheme
|
System, // Follow KDE system color scheme
|
||||||
|
|
@ -91,6 +94,10 @@ public:
|
||||||
QStringList availableFamilies() const;
|
QStringList availableFamilies() const;
|
||||||
QStringList availableStyles() const;
|
QStringList availableStyles() const;
|
||||||
|
|
||||||
|
// Close-to-tray
|
||||||
|
bool closeToTray() const;
|
||||||
|
void setCloseToTray(bool enabled);
|
||||||
|
|
||||||
Q_INVOKABLE void resetFont();
|
Q_INVOKABLE void resetFont();
|
||||||
Q_INVOKABLE QString chooseFolder(const QString &title, const QString &startDir = QString());
|
Q_INVOKABLE QString chooseFolder(const QString &title, const QString &startDir = QString());
|
||||||
|
|
||||||
|
|
@ -101,6 +108,7 @@ signals:
|
||||||
void modeChanged();
|
void modeChanged();
|
||||||
void paletteChanged();
|
void paletteChanged();
|
||||||
void fontChanged();
|
void fontChanged();
|
||||||
|
void closeToTrayChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPalette paletteFromScheme(const QString &path) const;
|
QPalette paletteFromScheme(const QString &path) const;
|
||||||
|
|
@ -116,6 +124,7 @@ private:
|
||||||
QString m_fontFamily;
|
QString m_fontFamily;
|
||||||
int m_fontSize;
|
int m_fontSize;
|
||||||
QString m_fontStyle;
|
QString m_fontStyle;
|
||||||
|
bool m_closeToTray;
|
||||||
QFileSystemWatcher *m_watcher;
|
QFileSystemWatcher *m_watcher;
|
||||||
KConfigWatcher::Ptr m_configWatcher;
|
KConfigWatcher::Ptr m_configWatcher;
|
||||||
static constexpr const char *SCHEMES_DIR = "/usr/share/color-schemes";
|
static constexpr const char *SCHEMES_DIR = "/usr/share/color-schemes";
|
||||||
|
|
|
||||||
|
|
@ -426,7 +426,21 @@ void OllieDBusClient::onStateChanged(const QString &session_id, const QString &n
|
||||||
m_activeState = new_state;
|
m_activeState = new_state;
|
||||||
emit activeStateChanged();
|
emit activeStateChanged();
|
||||||
}
|
}
|
||||||
refreshSessions();
|
|
||||||
|
// Update state in-place without emitting sessionsChanged() — that would
|
||||||
|
// cause the ListView to recreate all delegates, breaking inline rename.
|
||||||
|
// The QML state indicator for the active session uses dbus.activeState
|
||||||
|
// (updated above). For other sessions the dot updates on the next real
|
||||||
|
// refresh (session created/killed/renamed).
|
||||||
|
for (int i = 0; i < m_sessions.size(); ++i) {
|
||||||
|
QVariantMap session = m_sessions[i].toMap();
|
||||||
|
if (session["id"].toString() == session_id) {
|
||||||
|
session["state"] = new_state;
|
||||||
|
m_sessions[i] = session;
|
||||||
|
emit sessionStateUpdated(session_id, new_state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OllieDBusClient::onChatUpdated(const QString &session_id, qlonglong offset, const QString &new_text)
|
void OllieDBusClient::onChatUpdated(const QString &session_id, qlonglong offset, const QString &new_text)
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ signals:
|
||||||
void activeCwdChanged();
|
void activeCwdChanged();
|
||||||
void activeModelChanged();
|
void activeModelChanged();
|
||||||
void activeBackendChanged();
|
void activeBackendChanged();
|
||||||
|
void sessionStateUpdated(const QString &sessionId, const QString &newState);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onSessionCreated(const QString &session_id);
|
void onSessionCreated(const QString &session_id);
|
||||||
|
|
|
||||||
Reference in New Issue