- All settings move to flat tray menu; right-click on clock disabled - ClockLabel: custom QPainterPath text rendering with configurable glyph outline (BorderColor) — verified pixel-level offscreen - Fixed glyph positioning bug (baseline math) caught by isolated test - Window auto-fits content (adjustSize) — no more oversized clock - Start at Login: checkable tray item, persisted in QSettings, LaunchAgent enable/disable (TDD: roundTrip test caught disable() returning false when plist already absent) - Color dialog: hex/HTML input + 'transparent' + native picker fallback; opaque colors stored as #RRGGBB - Border: glyph outline instead of box border; background still painted in paintEvent (QSS doesn't render with WA_TranslucentBackground on macOS) - Removed window shadow (setHasShadow:NO), no bold, SF Pro Rounded - Font size dialogs allow up to 200 (was 72) - macOS: icon template 16pt, light/dark auto-adapt; LaunchAgent points to /Applications install
649 lines
20 KiB
C++
649 lines
20 KiB
C++
#include "clock_widget.h"
|
|
#include "config_manager.h"
|
|
#include "window_helper.h"
|
|
#include <QVBoxLayout>
|
|
#include <QDateTime>
|
|
#include <QColorDialog>
|
|
#include <QFontDialog>
|
|
#include <QInputDialog>
|
|
#include <QMessageBox>
|
|
#include <QApplication>
|
|
#include <QScreen>
|
|
#include <QTimeZone>
|
|
#include <QLineEdit>
|
|
#include <QPainter>
|
|
#include <QPainterPath>
|
|
|
|
ClockLabel::ClockLabel(QWidget *parent)
|
|
: QLabel(parent), m_textColor(Qt::white), m_outlineColor(Qt::transparent), m_outlineWidth(0)
|
|
{
|
|
setAlignment(Qt::AlignCenter);
|
|
}
|
|
|
|
void ClockLabel::setDisplayColors(const QColor &textColor, const QColor &outlineColor, qreal outlineWidth)
|
|
{
|
|
m_textColor = textColor;
|
|
m_outlineColor = outlineColor;
|
|
m_outlineWidth = outlineWidth;
|
|
update();
|
|
}
|
|
|
|
void ClockLabel::paintEvent(QPaintEvent *event)
|
|
{
|
|
Q_UNUSED(event);
|
|
|
|
QPainter painter(this);
|
|
painter.setRenderHint(QPainter::Antialiasing);
|
|
painter.setRenderHint(QPainter::TextAntialiasing);
|
|
|
|
const QFontMetricsF metrics(font());
|
|
const QStringList lines = text().split('\n');
|
|
const qreal lineHeight = metrics.height();
|
|
const qreal textBlockHeight = lineHeight * lines.size();
|
|
|
|
// First baseline so the whole block is vertically centered.
|
|
const qreal firstBaseline = (height() - textBlockHeight) / 2.0 + metrics.ascent();
|
|
|
|
for (int i = 0; i < lines.size(); ++i)
|
|
{
|
|
const QString &line = lines.at(i);
|
|
if (line.isEmpty())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const qreal lineWidth = metrics.horizontalAdvance(line);
|
|
QPainterPath linePath;
|
|
linePath.addText(0, 0, font(), line);
|
|
|
|
painter.save();
|
|
painter.translate((width() - lineWidth) / 2.0, firstBaseline + i * lineHeight);
|
|
|
|
// Outline under the fill so the glyphs stay readable on any backdrop.
|
|
if (m_outlineColor.alpha() > 0 && m_outlineWidth > 0)
|
|
{
|
|
painter.setPen(QPen(m_outlineColor, m_outlineWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
|
|
painter.setBrush(Qt::NoBrush);
|
|
painter.drawPath(linePath);
|
|
}
|
|
|
|
painter.setPen(Qt::NoPen);
|
|
painter.setBrush(m_textColor);
|
|
painter.drawPath(linePath);
|
|
painter.restore();
|
|
}
|
|
}
|
|
|
|
ClockWidget::ClockWidget(QWidget *parent)
|
|
: QWidget(parent), m_dragging(false), m_resizing(false), m_resizeBorder(5)
|
|
{
|
|
setupUI();
|
|
createMenu();
|
|
loadSettings();
|
|
|
|
// Set window properties for cross-platform always-on-top
|
|
Qt::WindowFlags flags = Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool;
|
|
|
|
#ifdef Q_OS_MAC
|
|
// On macOS, we need additional flags for proper always-on-top behavior in fullscreen
|
|
flags |= Qt::WindowDoesNotAcceptFocus;
|
|
setAttribute(Qt::WA_ShowWithoutActivating);
|
|
#endif
|
|
|
|
setWindowFlags(flags);
|
|
setAttribute(Qt::WA_TranslucentBackground);
|
|
setAttribute(Qt::WA_StyledBackground); // required for widget-level QSS background/border
|
|
setMouseTracking(true); // Enable mouse tracking for resize cursor
|
|
|
|
// Set up platform-specific window properties after window creation
|
|
WindowHelper::setupAlwaysOnTopForFullscreen(this);
|
|
|
|
// Start timer
|
|
m_timer = new QTimer(this);
|
|
connect(m_timer, &QTimer::timeout, this, &ClockWidget::updateTime);
|
|
m_timer->start(1000);
|
|
|
|
updateTime();
|
|
}
|
|
|
|
ClockWidget::~ClockWidget()
|
|
{
|
|
// Settings are now saved immediately after changes, no need to save here
|
|
}
|
|
|
|
void ClockWidget::setupUI()
|
|
{
|
|
m_primaryTimeLabel = new ClockLabel(this);
|
|
m_primaryLocationLabel = new ClockLabel(this);
|
|
m_secondaryTimeLabel = new ClockLabel(this);
|
|
m_secondaryLocationLabel = new ClockLabel(this);
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout(this);
|
|
layout->addWidget(m_primaryTimeLabel);
|
|
layout->addWidget(m_primaryLocationLabel);
|
|
layout->addWidget(m_secondaryTimeLabel);
|
|
layout->addWidget(m_secondaryLocationLabel);
|
|
layout->setContentsMargins(10, 10, 10, 10);
|
|
layout->setSpacing(2);
|
|
}
|
|
|
|
void ClockWidget::createMenu()
|
|
{
|
|
m_contextMenu = new QMenu(this);
|
|
|
|
QAction *alwaysOnTopAction = new QAction("Always on Top", this);
|
|
alwaysOnTopAction->setCheckable(true);
|
|
alwaysOnTopAction->setChecked(true);
|
|
connect(alwaysOnTopAction, &QAction::triggered, this, &ClockWidget::toggleAlwaysOnTop);
|
|
|
|
QAction *fontColorAction = new QAction("Set Font Color", this);
|
|
connect(fontColorAction, &QAction::triggered, this, &ClockWidget::changeFontColor);
|
|
|
|
QAction *bgColorAction = new QAction("Set Background Color", this);
|
|
connect(bgColorAction, &QAction::triggered, this, &ClockWidget::changeBackgroundColor);
|
|
|
|
QAction *fontSizeAction = new QAction("Set Time Font Size", this);
|
|
connect(fontSizeAction, &QAction::triggered, this, &ClockWidget::changeFontSize);
|
|
|
|
QAction *locationFontSizeAction = new QAction("Set Location Font Size", this);
|
|
connect(locationFontSizeAction, &QAction::triggered, this, &ClockWidget::changeLocationFontSize);
|
|
|
|
QAction *borderColorAction = new QAction("Set Border Color", this);
|
|
connect(borderColorAction, &QAction::triggered, this, &ClockWidget::changeBorderColor);
|
|
|
|
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);
|
|
connect(resetAction, &QAction::triggered, this, &ClockWidget::resetSettings);
|
|
|
|
QAction *quitAction = new QAction("Quit", this);
|
|
connect(quitAction, &QAction::triggered, this, &ClockWidget::quitApplication);
|
|
|
|
m_contextMenu->addAction(alwaysOnTopAction);
|
|
m_contextMenu->addSeparator();
|
|
m_contextMenu->addAction(fontColorAction);
|
|
m_contextMenu->addAction(bgColorAction);
|
|
m_contextMenu->addAction(borderColorAction);
|
|
m_contextMenu->addSeparator();
|
|
m_contextMenu->addAction(fontSizeAction);
|
|
m_contextMenu->addAction(locationFontSizeAction);
|
|
m_contextMenu->addSeparator();
|
|
m_contextMenu->addAction(primaryTimeZoneAction);
|
|
m_contextMenu->addAction(secondaryTimeZoneAction);
|
|
m_contextMenu->addSeparator();
|
|
m_contextMenu->addAction(resetAction);
|
|
m_contextMenu->addAction(quitAction);
|
|
}
|
|
|
|
void ClockWidget::mousePressEvent(QMouseEvent *event)
|
|
{
|
|
if (event->button() == Qt::LeftButton)
|
|
{
|
|
if (isInResizeArea(event->pos()))
|
|
{
|
|
m_resizing = true;
|
|
m_resizeStartPos = event->globalPosition().toPoint();
|
|
m_resizeStartSize = size();
|
|
event->accept();
|
|
}
|
|
else
|
|
{
|
|
m_dragging = true;
|
|
m_dragPosition = event->globalPosition().toPoint() - frameGeometry().topLeft();
|
|
event->accept();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClockWidget::mouseMoveEvent(QMouseEvent *event)
|
|
{
|
|
if (m_resizing && (event->buttons() & Qt::LeftButton))
|
|
{
|
|
QPoint delta = event->globalPosition().toPoint() - m_resizeStartPos;
|
|
QSize newSize = m_resizeStartSize + QSize(delta.x(), delta.y());
|
|
|
|
// Minimum size constraints
|
|
newSize = newSize.expandedTo(QSize(100, 50));
|
|
|
|
resize(newSize);
|
|
event->accept();
|
|
}
|
|
else if (m_dragging && (event->buttons() & Qt::LeftButton))
|
|
{
|
|
move(event->globalPosition().toPoint() - m_dragPosition);
|
|
event->accept();
|
|
}
|
|
else
|
|
{
|
|
// Update cursor shape when hovering over resize area
|
|
if (isInResizeArea(event->pos()))
|
|
{
|
|
setCursor(Qt::SizeFDiagCursor);
|
|
}
|
|
else
|
|
{
|
|
setCursor(Qt::ArrowCursor);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClockWidget::mouseReleaseEvent(QMouseEvent *event)
|
|
{
|
|
if (event->button() == Qt::LeftButton)
|
|
{
|
|
if (m_resizing)
|
|
{
|
|
m_resizing = false;
|
|
// Save the new size and position to config
|
|
if (m_configManager)
|
|
{
|
|
m_configManager->setWindowPosition(pos());
|
|
m_configManager->setWindowSize(size());
|
|
m_configManager->saveSettings();
|
|
}
|
|
event->accept();
|
|
}
|
|
else if (m_dragging)
|
|
{
|
|
m_dragging = false;
|
|
// Save the new position to config
|
|
if (m_configManager)
|
|
{
|
|
m_configManager->setWindowPosition(pos());
|
|
m_configManager->saveSettings();
|
|
}
|
|
event->accept();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClockWidget::contextMenuEvent(QContextMenuEvent *event)
|
|
{
|
|
Q_UNUSED(event);
|
|
// Menu lives in the tray icon; right-click on the clock does nothing
|
|
// so it never interferes with dragging.
|
|
}
|
|
|
|
void ClockWidget::paintEvent(QPaintEvent *event)
|
|
{
|
|
Q_UNUSED(event);
|
|
|
|
// QSS background does not render on macOS with
|
|
// WA_TranslucentBackground, so we paint it manually.
|
|
if (m_backgroundColor.alpha() > 0)
|
|
{
|
|
QPainter painter(this);
|
|
painter.setRenderHint(QPainter::Antialiasing);
|
|
painter.setPen(Qt::NoPen);
|
|
painter.setBrush(m_backgroundColor);
|
|
painter.drawRoundedRect(QRectF(contentsRect()).adjusted(0.5, 0.5, -0.5, -0.5), 10, 10);
|
|
}
|
|
}
|
|
|
|
void ClockWidget::updateTime()
|
|
{
|
|
const QTimeZone primaryTimeZone(m_primaryTimeZoneId.toUtf8());
|
|
if (primaryTimeZone.isValid())
|
|
{
|
|
m_primaryTimeLabel->setText(QDateTime::currentDateTimeUtc().toTimeZone(primaryTimeZone).time().toString("HH:mm"));
|
|
m_primaryLocationLabel->setText(locationName(m_primaryTimeZoneId));
|
|
}
|
|
else
|
|
{
|
|
m_primaryTimeLabel->setText("Invalid time zone");
|
|
m_primaryLocationLabel->setText(m_primaryTimeZoneId);
|
|
}
|
|
|
|
const QTimeZone secondaryTimeZone(m_secondaryTimeZoneId.toUtf8());
|
|
if (secondaryTimeZone.isValid())
|
|
{
|
|
m_secondaryTimeLabel->setText(QDateTime::currentDateTimeUtc().toTimeZone(secondaryTimeZone).time().toString("HH:mm"));
|
|
m_secondaryLocationLabel->setText(locationName(m_secondaryTimeZoneId));
|
|
}
|
|
else
|
|
{
|
|
m_secondaryTimeLabel->setText("Invalid time zone");
|
|
m_secondaryLocationLabel->setText(m_secondaryTimeZoneId);
|
|
}
|
|
}
|
|
|
|
void ClockWidget::toggleAlwaysOnTop()
|
|
{
|
|
m_alwaysOnTop = !m_alwaysOnTop;
|
|
|
|
Qt::WindowFlags flags = windowFlags();
|
|
if (m_alwaysOnTop)
|
|
{
|
|
flags |= Qt::WindowStaysOnTopHint;
|
|
}
|
|
else
|
|
{
|
|
flags &= ~Qt::WindowStaysOnTopHint;
|
|
}
|
|
setWindowFlags(flags);
|
|
show();
|
|
|
|
// Re-apply platform-specific window properties after changing window flags
|
|
if (m_alwaysOnTop)
|
|
{
|
|
WindowHelper::setupAlwaysOnTopForFullscreen(this);
|
|
}
|
|
}
|
|
|
|
void ClockWidget::changeFontColor()
|
|
{
|
|
const std::optional<QColor> color = pickColor("Font Color", m_fontColor, this);
|
|
if (color)
|
|
{
|
|
m_fontColor = *color;
|
|
m_configManager->setFontColor(m_fontColor);
|
|
updateStyleSheet();
|
|
saveSettings();
|
|
}
|
|
}
|
|
|
|
void ClockWidget::changeBackgroundColor()
|
|
{
|
|
const std::optional<QColor> color = pickColor("Background Color", m_backgroundColor, this);
|
|
if (color)
|
|
{
|
|
m_backgroundColor = *color;
|
|
m_configManager->setBackgroundColor(m_backgroundColor);
|
|
updateStyleSheet();
|
|
saveSettings();
|
|
}
|
|
}
|
|
|
|
void ClockWidget::changeBorderColor()
|
|
{
|
|
const std::optional<QColor> color = pickColor("Border Color", m_borderColor, this);
|
|
if (color)
|
|
{
|
|
m_borderColor = *color;
|
|
m_configManager->setBorderColor(m_borderColor);
|
|
updateStyleSheet();
|
|
saveSettings();
|
|
}
|
|
}
|
|
|
|
void ClockWidget::changeFontSize()
|
|
{
|
|
bool ok;
|
|
int size = QInputDialog::getInt(this, "Set Time Font Size", "Font Size:", m_fontSize, 8, 200, 1, &ok);
|
|
if (ok)
|
|
{
|
|
m_fontSize = size;
|
|
m_configManager->setFontSize(m_fontSize);
|
|
updateStyleSheet();
|
|
saveSettings();
|
|
}
|
|
}
|
|
|
|
void ClockWidget::changeLocationFontSize()
|
|
{
|
|
bool ok;
|
|
int size = QInputDialog::getInt(this, "Set Location Font Size", "Font Size:", m_locationFontSize, 6, 200, 1, &ok);
|
|
if (ok)
|
|
{
|
|
m_locationFontSize = size;
|
|
m_configManager->setLocationFontSize(m_locationFontSize);
|
|
updateStyleSheet();
|
|
saveSettings();
|
|
}
|
|
}
|
|
|
|
void ClockWidget::changePrimaryTimeZone()
|
|
{
|
|
changeTimeZone(true);
|
|
}
|
|
|
|
void ClockWidget::changeSecondaryTimeZone()
|
|
{
|
|
changeTimeZone(false);
|
|
}
|
|
|
|
void ClockWidget::resetSettings()
|
|
{
|
|
int ret = QMessageBox::question(this, "Reset Settings", "Are you sure you want to reset all settings?");
|
|
if (ret == QMessageBox::Yes)
|
|
{
|
|
m_configManager->resetToDefaults();
|
|
loadSettings();
|
|
updateStyleSheet();
|
|
// Reset to default size
|
|
resize(m_configManager->windowSize());
|
|
}
|
|
}
|
|
|
|
void ClockWidget::quitApplication()
|
|
{
|
|
qApp->quit();
|
|
}
|
|
|
|
bool ClockWidget::isInResizeArea(const QPoint &pos) const
|
|
{
|
|
// Check if mouse is in the bottom-right corner resize area
|
|
QRect resizeArea(rect().bottomRight() - QPoint(m_resizeBorder * 2, m_resizeBorder * 2),
|
|
QSize(m_resizeBorder * 2, m_resizeBorder * 2));
|
|
return resizeArea.contains(pos);
|
|
}
|
|
|
|
void ClockWidget::positionAtBottomRight()
|
|
{
|
|
// Get primary screen geometry
|
|
QScreen *screen = QApplication::primaryScreen();
|
|
if (!screen)
|
|
{
|
|
// Fallback to default position if no screen available
|
|
move(100, 100);
|
|
return;
|
|
}
|
|
|
|
QRect screenGeometry = screen->availableGeometry();
|
|
QSize windowSize = m_configManager->windowSize();
|
|
|
|
// Calculate position: bottom-right corner with some margin
|
|
int margin = 20; // 20 pixels margin from screen edges
|
|
int x = screenGeometry.right() - windowSize.width() - margin;
|
|
int y = screenGeometry.bottom() - windowSize.height() - margin;
|
|
|
|
move(x, y);
|
|
}
|
|
|
|
void ClockWidget::loadSettings()
|
|
{
|
|
m_configManager = new ConfigManager(this);
|
|
|
|
// Load from XML config file
|
|
if (!m_configManager->loadSettings())
|
|
{
|
|
// If loading fails, config manager already has default values
|
|
qDebug() << "Using default settings";
|
|
}
|
|
|
|
// Apply loaded settings
|
|
m_fontColor = m_configManager->fontColor();
|
|
m_backgroundColor = m_configManager->backgroundColor();
|
|
m_borderColor = m_configManager->borderColor();
|
|
m_fontSize = m_configManager->fontSize();
|
|
m_locationFontSize = m_configManager->locationFontSize();
|
|
m_alwaysOnTop = m_configManager->alwaysOnTop();
|
|
m_primaryTimeZoneId = m_configManager->primaryTimeZoneId();
|
|
m_secondaryTimeZoneId = m_configManager->secondaryTimeZoneId();
|
|
|
|
// Restore window position and size
|
|
QPoint windowPos = m_configManager->windowPosition();
|
|
if (windowPos.x() == -1 && windowPos.y() == -1)
|
|
{
|
|
// First time: position at bottom-right corner
|
|
positionAtBottomRight();
|
|
}
|
|
else
|
|
{
|
|
move(windowPos);
|
|
}
|
|
resize(m_configManager->windowSize());
|
|
|
|
// Update always on top checkbox in menu
|
|
QList<QAction *> actions = m_contextMenu->actions();
|
|
for (QAction *action : actions)
|
|
{
|
|
if (action->text() == QString("Always on Top"))
|
|
{
|
|
action->setChecked(m_alwaysOnTop);
|
|
break;
|
|
}
|
|
}
|
|
|
|
updateStyleSheet();
|
|
}
|
|
|
|
void ClockWidget::saveSettings()
|
|
{
|
|
if (!m_configManager)
|
|
return;
|
|
|
|
// Update config manager with current values
|
|
m_configManager->setFontColor(m_fontColor);
|
|
m_configManager->setBackgroundColor(m_backgroundColor);
|
|
m_configManager->setBorderColor(m_borderColor);
|
|
m_configManager->setFontSize(m_fontSize);
|
|
m_configManager->setLocationFontSize(m_locationFontSize);
|
|
m_configManager->setAlwaysOnTop(m_alwaysOnTop);
|
|
m_configManager->setWindowPosition(pos());
|
|
m_configManager->setWindowSize(size());
|
|
m_configManager->setPrimaryTimeZoneId(m_primaryTimeZoneId);
|
|
m_configManager->setSecondaryTimeZoneId(m_secondaryTimeZoneId);
|
|
|
|
// Save to XML file
|
|
if (!m_configManager->saveSettings())
|
|
{
|
|
qDebug() << "Failed to save settings to config.xml";
|
|
}
|
|
}
|
|
|
|
void ClockWidget::updateStyleSheet()
|
|
{
|
|
// Fonts via QSS; glyph fill + outline via ClockLabel::paintEvent.
|
|
const auto labelFontStyleSheet = [](int size) {
|
|
return QString("QLabel { font-family: 'SF Pro Rounded'; font-size: %1pt; background: transparent; }")
|
|
.arg(size);
|
|
};
|
|
|
|
m_primaryTimeLabel->setStyleSheet(labelFontStyleSheet(m_fontSize));
|
|
m_secondaryTimeLabel->setStyleSheet(labelFontStyleSheet(m_fontSize));
|
|
m_primaryLocationLabel->setStyleSheet(labelFontStyleSheet(m_locationFontSize));
|
|
m_secondaryLocationLabel->setStyleSheet(labelFontStyleSheet(m_locationFontSize));
|
|
|
|
// Shrink/grow the window to fit the text exactly (fonts may have changed).
|
|
adjustSize();
|
|
|
|
// Background is painted manually in paintEvent() — QSS does not render
|
|
// it on macOS with WA_TranslucentBackground. Text colors + outline go
|
|
// through ClockLabel (QSS cannot stroke glyphs).
|
|
const qreal outlineWidth = m_borderColor.alpha() > 0 ? 1.0 : 0.0;
|
|
m_primaryTimeLabel->setDisplayColors(m_fontColor, m_borderColor, outlineWidth);
|
|
m_secondaryTimeLabel->setDisplayColors(m_fontColor, m_borderColor, outlineWidth);
|
|
m_primaryLocationLabel->setDisplayColors(m_fontColor, m_borderColor, outlineWidth * 0.75);
|
|
m_secondaryLocationLabel->setDisplayColors(m_fontColor, m_borderColor, outlineWidth * 0.75);
|
|
update();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
QString ClockWidget::locationName(const QString &timeZoneId) const
|
|
{
|
|
return timeZoneId.section('/', -1).replace('_', ' ');
|
|
}
|
|
|
|
std::optional<QColor> ClockWidget::pickColor(const QString &title, const QColor ¤t, QWidget *parent)
|
|
{
|
|
bool ok = false;
|
|
const QString input = QInputDialog::getText(
|
|
parent, "Set " + title,
|
|
QString("HTML color (#RRGGBB or #AARRGGBB), or type 'transparent':"),
|
|
QLineEdit::Normal,
|
|
current.alpha() == 0 ? QStringLiteral("transparent") : current.name(QColor::HexArgb),
|
|
&ok);
|
|
|
|
if (!ok || input.trimmed().isEmpty())
|
|
{
|
|
return std::nullopt; // cancelled — keep current color
|
|
}
|
|
|
|
const QString normalized = input.trimmed().toLower();
|
|
if (normalized == "transparent")
|
|
{
|
|
return QColor(Qt::transparent);
|
|
}
|
|
|
|
const QColor parsed(normalized);
|
|
if (parsed.isValid())
|
|
{
|
|
return parsed;
|
|
}
|
|
|
|
// Not valid HTML text — fall back to the visual color dialog.
|
|
return QColorDialog::getColor(current, parent, "Choose " + title);
|
|
}
|