feat: dual time zones, autostart, tray icon, config relocation, tests

- Dual clock (primary/secondary time zone) with IANA picker, persisted in XML
- AutoStartManager: macOS LaunchAgent (com.clouck.clock, RunAtLoad)
- Tray icon: template image, light/dark adaptation, toggle + quit menu
- ConfigManager: default path moved to QStandardPaths::AppConfigLocation
- Qt Test suite (ConfigManager, AutoStartManager) wired into CTest
- GitHub origin renamed to upstream; private Gitea repo as origin
This commit is contained in:
2026-08-19 21:09:06 +03:00
parent e59a674ba7
commit 6b9cb32534
12 changed files with 448 additions and 20 deletions
+6
View File
@@ -91,3 +91,9 @@ Icon
*.dmg *.dmg
build build
test* test*
!tests/
!tests/*.cpp
# AW
.aw/
CLAUDE.md
+28
View File
@@ -4,6 +4,8 @@ project(Clouck)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(CTest)
# Find Qt6 # Find Qt6
find_package(Qt6 REQUIRED COMPONENTS Core Widgets) find_package(Qt6 REQUIRED COMPONENTS Core Widgets)
@@ -23,12 +25,14 @@ qt6_standard_project_setup()
# Sources # Sources
set(SOURCES set(SOURCES
main.cpp main.cpp
autostart_manager.cpp
clock_widget.cpp clock_widget.cpp
config_manager.cpp config_manager.cpp
window_helper.cpp window_helper.cpp
) )
set(HEADERS set(HEADERS
autostart_manager.h
clock_widget.h clock_widget.h
config_manager.h config_manager.h
window_helper.h window_helper.h
@@ -73,3 +77,27 @@ install(TARGETS Clouck
RUNTIME DESTINATION bin RUNTIME DESTINATION bin
BUNDLE DESTINATION bin BUNDLE DESTINATION bin
) )
if(BUILD_TESTING)
find_package(Qt6 REQUIRED COMPONENTS Test)
qt6_add_executable(ConfigManagerTest
tests/config_manager_test.cpp
config_manager.cpp
config_manager.h
)
target_include_directories(ConfigManagerTest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(ConfigManagerTest PRIVATE Qt6::Core Qt6::Gui Qt6::Test)
add_test(NAME ConfigManagerTest COMMAND ConfigManagerTest)
qt6_add_executable(AutoStartManagerTest
tests/autostart_manager_test.cpp
autostart_manager.cpp
autostart_manager.h
)
target_include_directories(AutoStartManagerTest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(AutoStartManagerTest PRIVATE Qt6::Core Qt6::Test)
add_test(NAME AutoStartManagerTest COMMAND AutoStartManagerTest)
endif()
+79
View File
@@ -0,0 +1,79 @@
#include "autostart_manager.h"
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QSaveFile>
#include <QStandardPaths>
#include <QXmlStreamWriter>
namespace
{
const QString launchAgentLabel = QStringLiteral("com.clouck.clock");
}
bool AutoStartManager::enableForCurrentApplication()
{
#ifdef Q_OS_MAC
const QString agentPath = launchAgentPath();
if (agentPath.isEmpty())
{
return false;
}
const QFileInfo agentFileInfo(agentPath);
if (!QDir().mkpath(agentFileInfo.absolutePath()))
{
return false;
}
QSaveFile agentFile(agentPath);
if (!agentFile.open(QIODevice::WriteOnly | QIODevice::Text))
{
return false;
}
agentFile.write(launchAgentContents(QCoreApplication::applicationFilePath()).toUtf8());
return agentFile.commit();
#else
return false;
#endif
}
QString AutoStartManager::launchAgentContents(const QString &executablePath)
{
QString contents;
QXmlStreamWriter writer(&contents);
writer.setAutoFormatting(true);
writer.writeStartDocument();
writer.writeDTD("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
writer.writeStartElement("plist");
writer.writeAttribute("version", "1.0");
writer.writeStartElement("dict");
writer.writeTextElement("key", "Label");
writer.writeTextElement("string", launchAgentLabel);
writer.writeTextElement("key", "ProgramArguments");
writer.writeStartElement("array");
writer.writeTextElement("string", executablePath);
writer.writeEndElement();
writer.writeTextElement("key", "RunAtLoad");
writer.writeEmptyElement("true");
writer.writeEndElement();
writer.writeEndElement();
writer.writeEndDocument();
return contents;
}
QString AutoStartManager::launchAgentPath()
{
const QString homePath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
if (homePath.isEmpty())
{
return QString();
}
return QDir(homePath).filePath("Library/LaunchAgents/" + launchAgentLabel + ".plist");
}
+16
View File
@@ -0,0 +1,16 @@
#ifndef AUTOSTART_MANAGER_H
#define AUTOSTART_MANAGER_H
#include <QString>
class AutoStartManager
{
public:
static bool enableForCurrentApplication();
static QString launchAgentContents(const QString &executablePath);
private:
static QString launchAgentPath();
};
#endif // AUTOSTART_MANAGER_H
+94 -7
View File
@@ -2,13 +2,14 @@
#include "config_manager.h" #include "config_manager.h"
#include "window_helper.h" #include "window_helper.h"
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QTime> #include <QDateTime>
#include <QColorDialog> #include <QColorDialog>
#include <QFontDialog> #include <QFontDialog>
#include <QInputDialog> #include <QInputDialog>
#include <QMessageBox> #include <QMessageBox>
#include <QApplication> #include <QApplication>
#include <QScreen> #include <QScreen>
#include <QTimeZone>
ClockWidget::ClockWidget(QWidget *parent) ClockWidget::ClockWidget(QWidget *parent)
: QWidget(parent), m_dragging(false), m_resizing(false), m_resizeBorder(5) : QWidget(parent), m_dragging(false), m_resizing(false), m_resizeBorder(5)
@@ -48,11 +49,14 @@ ClockWidget::~ClockWidget()
void ClockWidget::setupUI() void ClockWidget::setupUI()
{ {
m_timeLabel = new QLabel(this); m_primaryTimeLabel = new QLabel(this);
m_timeLabel->setAlignment(Qt::AlignCenter); m_secondaryTimeLabel = new QLabel(this);
m_primaryTimeLabel->setAlignment(Qt::AlignCenter);
m_secondaryTimeLabel->setAlignment(Qt::AlignCenter);
QVBoxLayout *layout = new QVBoxLayout(this); QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(m_timeLabel); layout->addWidget(m_primaryTimeLabel);
layout->addWidget(m_secondaryTimeLabel);
layout->setContentsMargins(10, 10, 10, 10); layout->setContentsMargins(10, 10, 10, 10);
} }
@@ -74,6 +78,12 @@ void ClockWidget::createMenu()
QAction *fontSizeAction = new QAction("Set Font Size", this); QAction *fontSizeAction = new QAction("Set Font Size", this);
connect(fontSizeAction, &QAction::triggered, this, &ClockWidget::changeFontSize); connect(fontSizeAction, &QAction::triggered, this, &ClockWidget::changeFontSize);
QAction *primaryTimeZoneAction = new QAction("Set First Time Zone", this);
connect(primaryTimeZoneAction, &QAction::triggered, this, &ClockWidget::changePrimaryTimeZone);
QAction *secondaryTimeZoneAction = new QAction("Set Second Time Zone", this);
connect(secondaryTimeZoneAction, &QAction::triggered, this, &ClockWidget::changeSecondaryTimeZone);
QAction *resetAction = new QAction("Reset Settings", this); QAction *resetAction = new QAction("Reset Settings", this);
connect(resetAction, &QAction::triggered, this, &ClockWidget::resetSettings); connect(resetAction, &QAction::triggered, this, &ClockWidget::resetSettings);
@@ -86,6 +96,9 @@ void ClockWidget::createMenu()
m_contextMenu->addAction(bgColorAction); m_contextMenu->addAction(bgColorAction);
m_contextMenu->addAction(fontSizeAction); m_contextMenu->addAction(fontSizeAction);
m_contextMenu->addSeparator(); m_contextMenu->addSeparator();
m_contextMenu->addAction(primaryTimeZoneAction);
m_contextMenu->addAction(secondaryTimeZoneAction);
m_contextMenu->addSeparator();
m_contextMenu->addAction(resetAction); m_contextMenu->addAction(resetAction);
m_contextMenu->addAction(quitAction); m_contextMenu->addAction(quitAction);
} }
@@ -179,8 +192,8 @@ void ClockWidget::contextMenuEvent(QContextMenuEvent *event)
void ClockWidget::updateTime() void ClockWidget::updateTime()
{ {
QString currentTime = QTime::currentTime().toString("HH:mm:ss"); m_primaryTimeLabel->setText(formattedTime(m_primaryTimeZoneId));
m_timeLabel->setText(currentTime); m_secondaryTimeLabel->setText(formattedTime(m_secondaryTimeZoneId));
} }
void ClockWidget::toggleAlwaysOnTop() void ClockWidget::toggleAlwaysOnTop()
@@ -240,6 +253,16 @@ void ClockWidget::changeFontSize()
} }
} }
void ClockWidget::changePrimaryTimeZone()
{
changeTimeZone(true);
}
void ClockWidget::changeSecondaryTimeZone()
{
changeTimeZone(false);
}
void ClockWidget::resetSettings() void ClockWidget::resetSettings()
{ {
int ret = QMessageBox::question(this, "Reset Settings", "Are you sure you want to reset all settings?"); int ret = QMessageBox::question(this, "Reset Settings", "Are you sure you want to reset all settings?");
@@ -304,6 +327,8 @@ void ClockWidget::loadSettings()
m_backgroundColor = m_configManager->backgroundColor(); m_backgroundColor = m_configManager->backgroundColor();
m_fontSize = m_configManager->fontSize(); m_fontSize = m_configManager->fontSize();
m_alwaysOnTop = m_configManager->alwaysOnTop(); m_alwaysOnTop = m_configManager->alwaysOnTop();
m_primaryTimeZoneId = m_configManager->primaryTimeZoneId();
m_secondaryTimeZoneId = m_configManager->secondaryTimeZoneId();
// Restore window position and size // Restore window position and size
QPoint windowPos = m_configManager->windowPosition(); QPoint windowPos = m_configManager->windowPosition();
@@ -344,6 +369,8 @@ void ClockWidget::saveSettings()
m_configManager->setAlwaysOnTop(m_alwaysOnTop); m_configManager->setAlwaysOnTop(m_alwaysOnTop);
m_configManager->setWindowPosition(pos()); m_configManager->setWindowPosition(pos());
m_configManager->setWindowSize(size()); m_configManager->setWindowSize(size());
m_configManager->setPrimaryTimeZoneId(m_primaryTimeZoneId);
m_configManager->setSecondaryTimeZoneId(m_secondaryTimeZoneId);
// Save to XML file // Save to XML file
if (!m_configManager->saveSettings()) if (!m_configManager->saveSettings())
@@ -357,6 +384,7 @@ void ClockWidget::updateStyleSheet()
QString styleSheet = QString( QString styleSheet = QString(
"QLabel { " "QLabel { "
"color: %1; " "color: %1; "
"font-family: 'SF Pro Rounded'; "
"font-size: %2pt; " "font-size: %2pt; "
"font-weight: bold; " "font-weight: bold; "
"background-color: %3; " "background-color: %3; "
@@ -367,6 +395,65 @@ void ClockWidget::updateStyleSheet()
QString::number(m_fontSize), QString::number(m_fontSize),
m_backgroundColor.name(QColor::HexArgb)); m_backgroundColor.name(QColor::HexArgb));
m_timeLabel->setStyleSheet(styleSheet); m_primaryTimeLabel->setStyleSheet(styleSheet);
m_secondaryTimeLabel->setStyleSheet(styleSheet);
setStyleSheet(QString("background-color: transparent;")); setStyleSheet(QString("background-color: transparent;"));
} }
void ClockWidget::changeTimeZone(bool primaryClock)
{
QStringList timeZoneIds;
for (const QByteArray &timeZoneId : QTimeZone::availableTimeZoneIds())
{
timeZoneIds.append(QString::fromUtf8(timeZoneId));
}
const QString currentTimeZoneId = primaryClock ? m_primaryTimeZoneId : m_secondaryTimeZoneId;
const int currentIndex = timeZoneIds.indexOf(currentTimeZoneId);
bool accepted = false;
const QString selectedTimeZoneId = QInputDialog::getItem(
this,
primaryClock ? "Set First Time Zone" : "Set Second Time Zone",
"Time Zone:",
timeZoneIds,
currentIndex < 0 ? 0 : currentIndex,
false,
&accepted);
if (!accepted)
{
return;
}
if (primaryClock)
{
m_primaryTimeZoneId = selectedTimeZoneId;
m_configManager->setPrimaryTimeZoneId(selectedTimeZoneId);
}
else
{
m_secondaryTimeZoneId = selectedTimeZoneId;
m_configManager->setSecondaryTimeZoneId(selectedTimeZoneId);
}
if (!m_configManager->saveSettings())
{
qDebug() << "Failed to save time zone settings to config.xml";
}
updateTime();
}
QString ClockWidget::formattedTime(const QString &timeZoneId) const
{
const QTimeZone timeZone(timeZoneId.toUtf8());
if (!timeZone.isValid())
{
qWarning() << "Invalid time zone configured:" << timeZoneId;
return QString("Invalid time zone\n%1").arg(timeZoneId);
}
const QDateTime timeInZone = QDateTime::currentDateTimeUtc().toTimeZone(timeZone);
const QString cityName = QString::fromUtf8(timeZone.id()).section('/', -1).replace('_', ' ');
return QString("%1\n%2")
.arg(timeInZone.time().toString("HH:mm"), cityName);
}
+8 -1
View File
@@ -31,6 +31,8 @@ private slots:
void changeFontColor(); void changeFontColor();
void changeBackgroundColor(); void changeBackgroundColor();
void changeFontSize(); void changeFontSize();
void changePrimaryTimeZone();
void changeSecondaryTimeZone();
void resetSettings(); void resetSettings();
void quitApplication(); void quitApplication();
@@ -40,8 +42,11 @@ private:
void loadSettings(); void loadSettings();
void saveSettings(); void saveSettings();
void updateStyleSheet(); void updateStyleSheet();
void changeTimeZone(bool primaryClock);
QString formattedTime(const QString &timeZoneId) const;
QLabel *m_timeLabel; QLabel *m_primaryTimeLabel;
QLabel *m_secondaryTimeLabel;
QTimer *m_timer; QTimer *m_timer;
QMenu *m_contextMenu; QMenu *m_contextMenu;
@@ -61,6 +66,8 @@ private:
QColor m_backgroundColor; QColor m_backgroundColor;
int m_fontSize; int m_fontSize;
bool m_alwaysOnTop; bool m_alwaysOnTop;
QString m_primaryTimeZoneId;
QString m_secondaryTimeZoneId;
// Helper methods // Helper methods
bool isInResizeArea(const QPoint &pos) const; bool isInResizeArea(const QPoint &pos) const;
+5 -3
View File
@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ClouckConfig> <ClouckConfig>
<FontColor>#ffffffff</FontColor> <FontColor>#ff00ff00</FontColor>
<BackgroundColor>#96000000</BackgroundColor> <BackgroundColor>#00000000</BackgroundColor>
<FontSize>24</FontSize> <FontSize>24</FontSize>
<AlwaysOnTop>true</AlwaysOnTop> <AlwaysOnTop>true</AlwaysOnTop>
<WindowPosition>1236,784</WindowPosition> <WindowPosition>1236,784</WindowPosition>
<WindowSize>137,59</WindowSize> <WindowSize>240,160</WindowSize>
<PrimaryTimeZone>Europe/Bucharest</PrimaryTimeZone>
<SecondaryTimeZone>America/Los_Angeles</SecondaryTimeZone>
</ClouckConfig> </ClouckConfig>
+55 -6
View File
@@ -1,6 +1,10 @@
#include "config_manager.h" #include "config_manager.h"
#include <QDir>
#include <QFile> #include <QFile>
#include <QFileInfo>
#include <QDebug> #include <QDebug>
#include <QStandardPaths>
#include <QTimeZone>
ConfigManager::ConfigManager(QObject *parent) ConfigManager::ConfigManager(QObject *parent)
: QObject(parent) : QObject(parent)
@@ -8,9 +12,27 @@ ConfigManager::ConfigManager(QObject *parent)
setDefaultValues(); setDefaultValues();
} }
QString ConfigManager::defaultSettingsPath()
{
const QString configDirectory = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
if (configDirectory.isEmpty())
{
qWarning() << "No application configuration directory is available";
return QString();
}
return QDir(configDirectory).filePath("config.xml");
}
bool ConfigManager::loadSettings(const QString &filename) bool ConfigManager::loadSettings(const QString &filename)
{ {
QFile file(filename); const QString settingsPath = filename.isEmpty() ? defaultSettingsPath() : filename;
if (settingsPath.isEmpty())
{
return false;
}
QFile file(settingsPath);
if (!file.exists()) if (!file.exists())
{ {
qDebug() << "Config file does not exist, using defaults"; qDebug() << "Config file does not exist, using defaults";
@@ -77,6 +99,14 @@ bool ConfigManager::loadSettings(const QString &filename)
m_windowSize = QSize(width, height); m_windowSize = QSize(width, height);
} }
} }
else if (name == "PrimaryTimeZone")
{
m_primaryTimeZoneId = text;
}
else if (name == "SecondaryTimeZone")
{
m_secondaryTimeZoneId = text;
}
else else
{ {
reader.skipCurrentElement(); reader.skipCurrentElement();
@@ -96,7 +126,20 @@ bool ConfigManager::loadSettings(const QString &filename)
bool ConfigManager::saveSettings(const QString &filename) bool ConfigManager::saveSettings(const QString &filename)
{ {
QFile file(filename); const QString settingsPath = filename.isEmpty() ? defaultSettingsPath() : filename;
if (settingsPath.isEmpty())
{
return false;
}
const QFileInfo fileInfo(settingsPath);
if (!QDir().mkpath(fileInfo.absolutePath()))
{
qDebug() << "Cannot create configuration directory:" << fileInfo.absolutePath();
return false;
}
QFile file(settingsPath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
{ {
qDebug() << "Cannot open config file for writing:" << file.errorString(); qDebug() << "Cannot open config file for writing:" << file.errorString();
@@ -134,6 +177,9 @@ bool ConfigManager::saveSettings(const QString &filename)
.arg(m_windowSize.height()); .arg(m_windowSize.height());
writer.writeTextElement("WindowSize", sizeStr); writer.writeTextElement("WindowSize", sizeStr);
writer.writeTextElement("PrimaryTimeZone", m_primaryTimeZoneId);
writer.writeTextElement("SecondaryTimeZone", m_secondaryTimeZoneId);
// Close root element // Close root element
writer.writeEndElement(); writer.writeEndElement();
@@ -150,13 +196,14 @@ void ConfigManager::resetToDefaults()
bool ConfigManager::configExists(const QString &filename) const bool ConfigManager::configExists(const QString &filename) const
{ {
return QFile::exists(filename); const QString settingsPath = filename.isEmpty() ? defaultSettingsPath() : filename;
return !settingsPath.isEmpty() && QFile::exists(settingsPath);
} }
void ConfigManager::setDefaultValues() void ConfigManager::setDefaultValues()
{ {
m_fontColor = QColor(255, 255, 255); // White m_fontColor = QColor(0, 255, 0);
m_backgroundColor = QColor(0, 0, 0, 150); // Semi-transparent black m_backgroundColor = QColor(0, 0, 0, 0);
m_fontSize = 24; m_fontSize = 24;
m_alwaysOnTop = true; m_alwaysOnTop = true;
@@ -164,5 +211,7 @@ void ConfigManager::setDefaultValues()
// Position will be set properly when widget is shown for the first time // Position will be set properly when widget is shown for the first time
m_windowPosition = QPoint(-1, -1); // Special value indicating "not set yet" m_windowPosition = QPoint(-1, -1); // Special value indicating "not set yet"
m_windowSize = QSize(200, 80); // Default clock size m_windowSize = QSize(240, 160); // Default clock size
m_primaryTimeZoneId = QString::fromUtf8(QTimeZone::systemTimeZoneId());
m_secondaryTimeZoneId = QStringLiteral("America/Los_Angeles");
} }
+11 -3
View File
@@ -16,11 +16,13 @@ class ConfigManager : public QObject
public: public:
explicit ConfigManager(QObject *parent = nullptr); explicit ConfigManager(QObject *parent = nullptr);
static QString defaultSettingsPath();
// Load settings from XML file // Load settings from XML file
bool loadSettings(const QString &filename = "config.xml"); bool loadSettings(const QString &filename = QString());
// Save settings to XML file // Save settings to XML file
bool saveSettings(const QString &filename = "config.xml"); bool saveSettings(const QString &filename = QString());
// Getters // Getters
QColor fontColor() const { return m_fontColor; } QColor fontColor() const { return m_fontColor; }
@@ -29,6 +31,8 @@ public:
bool alwaysOnTop() const { return m_alwaysOnTop; } bool alwaysOnTop() const { return m_alwaysOnTop; }
QPoint windowPosition() const { return m_windowPosition; } QPoint windowPosition() const { return m_windowPosition; }
QSize windowSize() const { return m_windowSize; } QSize windowSize() const { return m_windowSize; }
QString primaryTimeZoneId() const { return m_primaryTimeZoneId; }
QString secondaryTimeZoneId() const { return m_secondaryTimeZoneId; }
// Setters // Setters
void setFontColor(const QColor &color) { m_fontColor = color; } void setFontColor(const QColor &color) { m_fontColor = color; }
@@ -37,12 +41,14 @@ public:
void setAlwaysOnTop(bool onTop) { m_alwaysOnTop = onTop; } void setAlwaysOnTop(bool onTop) { m_alwaysOnTop = onTop; }
void setWindowPosition(const QPoint &pos) { m_windowPosition = pos; } void setWindowPosition(const QPoint &pos) { m_windowPosition = pos; }
void setWindowSize(const QSize &size) { m_windowSize = size; } void setWindowSize(const QSize &size) { m_windowSize = size; }
void setPrimaryTimeZoneId(const QString &timeZoneId) { m_primaryTimeZoneId = timeZoneId; }
void setSecondaryTimeZoneId(const QString &timeZoneId) { m_secondaryTimeZoneId = timeZoneId; }
// Reset to default values // Reset to default values
void resetToDefaults(); void resetToDefaults();
// Check if config file exists // Check if config file exists
bool configExists(const QString &filename = "config.xml") const; bool configExists(const QString &filename = QString()) const;
private: private:
// Default values // Default values
@@ -55,6 +61,8 @@ private:
bool m_alwaysOnTop; bool m_alwaysOnTop;
QPoint m_windowPosition; QPoint m_windowPosition;
QSize m_windowSize; QSize m_windowSize;
QString m_primaryTimeZoneId;
QString m_secondaryTimeZoneId;
}; };
#endif // CONFIG_MANAGER_H #endif // CONFIG_MANAGER_H
+80
View File
@@ -1,6 +1,45 @@
#include <QApplication> #include <QApplication>
#include <QAction>
#include <QMenu>
#include <QPainter>
#include <QStyleHints>
#include <QSystemTrayIcon>
#include "autostart_manager.h"
#include "clock_widget.h" #include "clock_widget.h"
namespace
{
// Standard macOS menu bar icon extent (points). Matches surrounding icons.
constexpr int trayIconExtent = 16;
QIcon createTrayIcon(const QColor &color)
{
QPixmap pixmap(trayIconExtent, trayIconExtent);
pixmap.setDevicePixelRatio(qApp->devicePixelRatio());
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QPen(color, 1.5));
painter.drawEllipse(QPointF(8, 8), 5.5, 5.5);
painter.drawLine(QPointF(8, 4.5), QPointF(8, 8));
painter.drawLine(QPointF(8, 8), QPointF(10.5, 9.5));
QIcon icon(pixmap);
#ifdef Q_OS_MAC
// Template image: the system tints it black/white to match the
// active appearance, like native menu bar icons.
icon.setIsMask(true);
#endif
return icon;
}
QColor trayIconColorForScheme(Qt::ColorScheme scheme)
{
return scheme == Qt::ColorScheme::Dark ? QColor(Qt::white) : QColor(Qt::black);
}
}
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
QApplication app(argc, argv); QApplication app(argc, argv);
@@ -12,5 +51,46 @@ int main(int argc, char *argv[])
ClockWidget clock; ClockWidget clock;
clock.show(); clock.show();
AutoStartManager::enableForCurrentApplication();
const QStyleHints *styleHints = app.styleHints();
QSystemTrayIcon trayIcon(createTrayIcon(trayIconColorForScheme(styleHints->colorScheme())), &app);
trayIcon.setToolTip("Clouck");
QObject::connect(styleHints, &QStyleHints::colorSchemeChanged, &trayIcon,
[&trayIcon](Qt::ColorScheme scheme) {
trayIcon.setIcon(createTrayIcon(trayIconColorForScheme(scheme)));
});
QMenu trayMenu;
QAction *toggleClockAction = trayMenu.addAction("Turn Clock Off");
QAction *quitAction = trayMenu.addAction("Quit");
const auto toggleClock = [&clock, toggleClockAction]() {
if (clock.isVisible())
{
clock.hide();
toggleClockAction->setText("Turn Clock On");
return;
}
clock.show();
clock.raise();
toggleClockAction->setText("Turn Clock Off");
};
QObject::connect(toggleClockAction, &QAction::triggered, &app, toggleClock);
QObject::connect(quitAction, &QAction::triggered, &app, &QApplication::quit);
QObject::connect(&trayIcon, &QSystemTrayIcon::activated, &app,
[toggleClock](QSystemTrayIcon::ActivationReason reason) {
if (reason == QSystemTrayIcon::Trigger)
{
toggleClock();
}
});
trayIcon.setContextMenu(&trayMenu);
trayIcon.show();
return app.exec(); return app.exec();
} }
+26
View File
@@ -0,0 +1,26 @@
#include "autostart_manager.h"
#include <QtTest>
class AutoStartManagerTest : public QObject
{
Q_OBJECT
private slots:
void createsRunAtLoadLaunchAgent();
};
void AutoStartManagerTest::createsRunAtLoadLaunchAgent()
{
const QString executablePath = "/Applications/Clouck.app/Contents/MacOS/Clouck";
const QString contents = AutoStartManager::launchAgentContents(executablePath);
QVERIFY(contents.contains("<key>ProgramArguments</key>"));
QVERIFY(contents.contains(executablePath));
QVERIFY(contents.contains("<key>RunAtLoad</key>"));
QVERIFY(contents.contains("<true/>"));
}
QTEST_APPLESS_MAIN(AutoStartManagerTest)
#include "autostart_manager_test.moc"
+40
View File
@@ -0,0 +1,40 @@
#include "config_manager.h"
#include <QDir>
#include <QTemporaryDir>
#include <QtTest>
class ConfigManagerTest : public QObject
{
Q_OBJECT
private slots:
void roundTripsBothClockTimeZones();
void usesAnAbsoluteDefaultSettingsPath();
};
void ConfigManagerTest::roundTripsBothClockTimeZones()
{
QTemporaryDir directory;
QVERIFY(directory.isValid());
const QString filename = directory.filePath("config.xml");
ConfigManager writer;
writer.setPrimaryTimeZoneId("Europe/Bucharest");
writer.setSecondaryTimeZoneId("America/Los_Angeles");
QVERIFY(writer.saveSettings(filename));
ConfigManager reader;
QVERIFY(reader.loadSettings(filename));
QCOMPARE(reader.primaryTimeZoneId(), QString("Europe/Bucharest"));
QCOMPARE(reader.secondaryTimeZoneId(), QString("America/Los_Angeles"));
}
void ConfigManagerTest::usesAnAbsoluteDefaultSettingsPath()
{
QVERIFY(QDir::isAbsolutePath(ConfigManager::defaultSettingsPath()));
}
QTEST_APPLESS_MAIN(ConfigManagerTest)
#include "config_manager_test.moc"