diff --git a/.clangd b/.clangd deleted file mode 100644 index cb4c484..0000000 --- a/.clangd +++ /dev/null @@ -1,26 +0,0 @@ -CompileFlags: - Add: - [ - -Wno-documentation, - -Wno-missing-prototypes, - -std=c++17, - --include-directory=/opt/homebrew/include, - --include-directory=/opt/homebrew/include/QtGui, - --include-directory=/opt/homebrew/include/QtCore, - --include-directory=/opt/homebrew/include/QtWidgets, - ] - -Diagnostics: - ClangTidy: - Add: - [ - performance-*, - bugprone-*, - modernize-*, - clang-analyzer-*, - readability-identifier*, - ] - CheckOptions: - readability-identifier-naming.VariableCase: camelCase - - Suppress: [-Wdeprecated-declarations] diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 808d45e..d094de3 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -1,6 +1,36 @@ { - "includePath": "/opt/homebrew/include", - "compilerPath": "/usr/bin/clang++", - "cStandard": "c11", - "cppStandard": "c++17" + "configurations": [ + { + "name": "macos-conda-qt", + "includePath": [ + "${workspaceFolder}/**", + "${env:HOMEBREW_CELLAR}/qt/6.11.0/include/**", + ], + "defines": [], + "compilerPath": "/usr/bin/clang++", + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "macos-clang-arm64", + "configurationProvider": "kylinideteam.cmake-intellisence" + }, + { + "name": "win-conda-qt", + "includePath": [ + "${workspaceFolder}/**", + "${env:SCOOP}\\apps\\msys2-cn\\current\\ucrt64\\include", + "${env:SCOOP}\\apps\\msys2-cn\\current\\ucrt64\\include\\qt" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "compilerPath": "${env:SCOOP}\\apps\\msys2-cn\\current\\ucrt64\\bin\\g++.exe", + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "windows-gcc-x64", + "configurationProvider": "kylinideteam.cmake-intellisence" + } + ], + "version": 4 } diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..74acc45 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,58 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Qt6", + "type": "cppdbg", + "request": "launch", + "targetArchitecture": "arm64", + "program": "${fileDirname}/${fileBasenameNoExtension}", + "args": [], + "stopAtEntry": false, + "externalConsole": false, + "cwd": "${fileDirname}", + "environment": [ + { + "name": "DYLD_LIBRARY_PATH", + "value": "${env:HOMEBREW_CELLAR}/qt/6.11.0/lib" + } + ], + "MIMode": "lldb", + "internalConsoleOptions": "openOnSessionStart" + }, + { + "name": "Debug Qt6 on Windows", + "type": "cppdbg", + "request": "launch", + "targetArchitecture": "x86_64", + "program": "${fileDirname}/${fileBasenameNoExtension}.exe", + "args": [], + "stopAtEntry": false, + "externalConsole": false, + "cwd": "${fileDirname}", + "MIMode": "gdb", + "miDebuggerPath": "${env:SCOOP}\\apps\\msys2-cn\\current\\ucrt64\\bin\\gdb.exe", + "internalConsoleOptions": "openOnSessionStart", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": false + } + ] + }, + { + "name": "C/C++: clang++ build and debug active file", + "type": "cppdbg", + "request": "launch", + "program": "${fileDirname}/${fileBasenameNoExtension}", + "args": [], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "lldb", + "preLaunchTask": "Build Qt6" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..af766c4 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,78 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "cppbuild", + "label": "Build Qt6", + "command": "/usr/bin/clang++", + "args": [ + "-g", + "${file}", + "-o", + "${fileDirname}/${fileBasenameNoExtension}", + "-I", + "${env:HOMEBREW_CELLAR}/qtbase/6.11.0/include", + "-L", + "${env:HOMEBREW_CELLAR}/qtbase/6.11.0/lib", + "-lQt6Core", + "-lQt6Gui", + "-lQt6Widgets" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": "build" + }, + { + "type": "cppbuild", + "label": "Build Qt6 on Windows", + "command": "${env:SCOOP}\\apps\\msys2-cn\\current\\ucrt64\\bin\\g++.exe", + "args": [ + "-fdiagnostics-color=always", + "-g", + "${file}", + "-o", + "${fileDirname}/${fileBasenameNoExtension}.exe", + "-I", + "${env:SCOOP}\\apps\\msys2-cn\\current\\ucrt64\\include", + "-I", + "${env:SCOOP}\\apps\\msys2-cn\\current\\ucrt64\\include\\qt6", + "-L", + "${env:SCOOP}\\apps\\msys2-cn\\current\\ucrt64\\lib", + "-lQt6Core", + "-lQt6Gui", + "-lQt6Widgets" + ], + "options": { + "cwd": "${env:SCOOP}\\apps\\msys2-cn\\current\\ucrt64\\bin" + }, + "problemMatcher": [ + "$gcc" + ], + "group": "build" + }, + { + "type": "cppbuild", + "label": "C/C++: g++.exe build active file", + "command": "${env:SCOOP}\\apps\\msys2-cn\\current\\ucrt64\\bin\\g++.exe", + "args": [ + "-fdiagnostics-color=always", + "-g", + "${file}", + "-o", + "${fileDirname}\\${fileBasenameNoExtension}.exe" + ], + "options": { + "cwd": "${env:SCOOP}\\apps\\msys2-cn\\current\\ucrt64\\bin" + }, + "problemMatcher": [ + "$gcc" + ], + "group": "build", + "detail": "Task generated by Debugger." + } + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 907e569..e3ad736 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,10 @@ if(${CMAKE_VERSION} VERSION_LESS 3.12) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) endif() -project(clock) +project(FloatingClock) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) @@ -13,18 +16,45 @@ set(CMAKE_CXX_STANDARD 17 CACHE STRING "v") set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -set(CMAKE_PREFIX_PATH "/opt/homebrew/opt/qt") -find_package(Qt6 COMPONENTS Core Gui Widgets REQUIRED) +# Homebrew Qt6 路径 (自动检测或手动设置) +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + # 检测架构 + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64") + set(QT6_PATH "/opt/homebrew/opt/qt" CACHE PATH "Qt6 installation path") + else() + set(QT6_PATH "/usr/local/opt/qt" CACHE PATH "Qt6 installation path") + endif() -include_directories(.) + # 添加到 CMake 搜索路径 + list(APPEND CMAKE_PREFIX_PATH "${QT6_PATH}/lib/cmake") +endif() -set (SRCS - flock.cpp - flock.h - main.cpp +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Xml) + +add_executable(${CMAKE_PROJECT_NAME} WIN32 MACOSX_BUNDLE main.cpp) + +set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE ) -add_executable(${CMAKE_PROJECT_NAME} ${SRCS}) +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE Qt6 Qt6::Core + Qt6::Gui + Qt6::Widgets + Qt6::Xml +) -target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets) +# macOS 特定设置 +if(APPLE) + set_target_properties(FloatingClock PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/Info.plist.in + ) + + # 修复运行时库路径 + set_target_properties(FloatingClock PROPERTIES + INSTALL_RPATH "${QT6_PATH}/lib" + BUILD_WITH_INSTALL_RPATH TRUE + ) +endif() diff --git a/clock.py b/clock.py deleted file mode 100644 index 25babd6..0000000 --- a/clock.py +++ /dev/null @@ -1,166 +0,0 @@ -import sys - -from PySide6.QtCore import QPoint, QSettings, Qt, QTime, QTimer -from PySide6.QtGui import QAction, QFont, QMouseEvent -from PySide6.QtWidgets import QApplication, QLabel, QMenu, QVBoxLayout, QWidget - - -class Clock(QWidget): - def __init__(self, parent=None) -> None: - super().__init__(parent) - - # geometry of Mmin window (x, y, width, height) - self.setGeometry(1100, 800, 180, 60) - - # hide frame - self.setWindowFlag(Qt.WindowStaysOnTopHint, True) - self.setWindowFlag(Qt.FramelessWindowHint, True) - self.setAttribute(Qt.WA_TranslucentBackground) - - # mouse & menu - self.pressed = False - self.oldPos = QPoint(0, 0) - self.setContextMenuPolicy(Qt.CustomContextMenu) - self.customContextMenuRequested.connect(self.mouseRightMenu) - - # clock object - clock = QTimer(self) - clock.timeout.connect(self.showTime) - clock.start(1000) # update the clock per second - self.show() - - self.settings = QSettings(f"{sys.argv[0]}/../config.ini", QSettings.IniFormat) - font = QFont() - font.setFamily(self.settings.value("USER/FONT_FAMILY")) - font.setPointSize(int(self.settings.value("USER/FONT_SIZE"))) - - # load settings - self.settings = QSettings(f"{sys.argv[0]}/../config.ini", QSettings.IniFormat) - - # # font object - self.font = QFont() - self.font.setFamily(self.settings.value("DEFAULT/FONT_FAMILY")) - self.font_size_step = int(self.settings.value("USER/FONT_SIZE_STEP")) - - # label object - self.label = QLabel() - self.label.setAlignment(Qt.AlignCenter) - - # layout - layout = QVBoxLayout(self, spacing=0) - layout.addWidget(self.label) - self.setLayout(layout) - - self.reloadUI() - - def reloadUI(self): - self.font_size = int(self.settings.value("USER/FONT_SIZE")) - self.font.setPointSize(self.font_size) - self.label.setFont(self.font) - self.label.setStyleSheet( - f"color: {self.settings.value('USER/FONT_COLOR')}; " - f"background-color: {self.settings.value('USER/BG_COLOR')}" - ) - - def resetUI(self): - default_bg_color = self.settings.value("DEFAULT/BG_COLOR") - default_font_color = self.settings.value("DEFAULT/FONT_COLOR") - default_font_size = self.settings.value("DEFAULT/FONT_SIZE") - - self.settings.setValue("USER/BG_COLOR", default_bg_color) - self.settings.setValue("USER/FONT_COLOR", default_font_color) - self.settings.setValue("USER/FONT_SIZE", default_font_size) - - self.reloadUI() - - def increaseFontSize(self): - self.font_size += self.font_size_step - self.changeFontSize() - - def decreaseFontSize(self): - self.font_size -= self.font_size_step - self.changeFontSize() - - def changeFontSize(self): - font = self.label.font() - font.setPointSize(self.font_size) - self.label.setFont(font) - - def setFontColor(self, color: str) -> None: - self.settings.setValue("USER/FONT_COLOR", color) - self.reloadUI() - - def setBackgroundColor(self, color: str) -> None: - self.settings.setValue("USER/BG_COLOR", color) - self.reloadUI() - - def mousePressEvent(self, event: QMouseEvent) -> None: - self.pressed = True - self.oldPos = event.pos() - super().mousePressEvent(event) - - def mouseMoveEvent(self, event: QMouseEvent) -> None: - if self.pressed: - self.move(self.pos() + (event.pos() - self.oldPos)) - super().mouseMoveEvent(event) - - def mouseReleaseEvent(self, event: QMouseEvent) -> None: - self.pressed = False - self.oldPos = event.pos() - super().mouseReleaseEvent(event) - - def mouseRightMenu(self, pos) -> None: - menu = QMenu(self) - - # font size - font_size_menu = menu.addMenu("font size") - larger_action = QAction("larger", self) - smaller_action = QAction("smaller", self) - - larger_action.triggered.connect(self.increaseFontSize) - smaller_action.triggered.connect(self.decreaseFontSize) - - font_size_menu.addAction(larger_action) - font_size_menu.addAction(smaller_action) - - ft_colors = ["none", "blue", "orange", "green", "red", "yellow", "white"] - bg_colors = ["none", "blue", "orange", "green", "red", "yellow", "white"] - - # font color - font_color_menu = menu.addMenu("font color") - font_color_actions = [ - QAction(color, self, triggered=lambda c=color: self.setFontColor(c)) - for color in ft_colors - ] - font_color_menu.addActions(font_color_actions) - - # background color - bg_color_menu = menu.addMenu("bg color") - bg_color_actions = [ - QAction(color, self, triggered=lambda c=color: self.setBackgroundColor(c)) - for color in bg_colors - ] - bg_color_menu.addActions(bg_color_actions) - - # reset - reset_action = QAction("reset", self) - reset_action.triggered.connect(self.resetUI) - menu.addAction(reset_action) - - # quit - quit_action = QAction("quit", self) - quit_action.triggered.connect(self.close) - menu.addAction(quit_action) - - menu.exec(self.mapToGlobal(pos)) - - def showTime(self): - current_time = QTime.currentTime().toString("hh:mm") - self.label.setText(current_time) - - -if __name__ == "__main__": - app = QApplication(sys.argv) - clock = Clock() - clock.show() # show all the widgets - app.exit(app.exec()) # start the app diff --git a/config.ini b/config.ini deleted file mode 100644 index 2cf1b50..0000000 --- a/config.ini +++ /dev/null @@ -1,13 +0,0 @@ -[DEFAULT] -BG_COLOR=NONE -FONT_COLOR=blue -FONT_FAMILY=Arial -FONT_SIZE=50 -FONT_SIZE_STEP=10 - -[USER] -BG_COLOR=NONE -FONT_COLOR=blue -FONT_FAMILY=Arial -FONT_SIZE=50 -FONT_SIZE_STEP=10 diff --git a/config.xml b/config.xml new file mode 100644 index 0000000..ff103a3 --- /dev/null +++ b/config.xml @@ -0,0 +1,41 @@ + + + + + + 350 + 180 + -1 + -1 + 20 + 2 + #333333 + + + + + 96 + Arial + true + false + #ff6600 + + + + + #1a000000 + + + + + + + + 30 + 4 + 4 + #80000000 + + diff --git a/flock.cpp b/flock.cpp deleted file mode 100644 index dfb5a97..0000000 --- a/flock.cpp +++ /dev/null @@ -1,176 +0,0 @@ -#include "flock.h" - -Clock::Clock(QWidget *parent) : QWidget{parent}, pressed(false) { - // geometry of Main Window (x, y, width, height) - setGeometry(1100, 800, 180, 60); - - // hide frame - setWindowFlag(Qt::WindowStaysOnTopHint, true); - setWindowFlag(Qt::FramelessWindowHint, true); - setAttribute(Qt::WA_TranslucentBackground); - - // setFixedSize(this->width(), this->height()); - setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint & - ~Qt::WindowMinimizeButtonHint); - - // clock object - QTimer *clock = new QTimer(this); - connect(clock, &QTimer::timeout, this, &Clock::showTime); - clock->start(1000); // update the clock per second - show(); - - settings = new QSettings( - QString("%1/config.ini").arg(QCoreApplication::applicationDirPath()), - QSettings::IniFormat); - - font = QFont(); - font.setFamily(settings->value("USER/FONT_FAMILY").toString()); - font.setPointSize(settings->value("USER/FONT_SIZE").toInt()); - - // load settings - settings = new QSettings(QString("%1/../floating-clock/config.ini") - .arg(QCoreApplication::applicationDirPath()), - QSettings::IniFormat); - - // font object - font = QFont(); - font.setFamily(settings->value("DEFAULT/FONT_FAMILY").toString()); - font_size_step = settings->value("USER/FONT_SIZE_STEP").toInt(); - - // label object - label = new QLabel(); - label->setAlignment(Qt::AlignCenter); - - // layout - QVBoxLayout *layout = new QVBoxLayout(this); - layout->addWidget(label); - setLayout(layout); - - reloadUI(); -} - -void Clock::showTime() { - QString current_time = QTime::currentTime().toString("hh:mm"); - label->setText(current_time); -} - -void Clock::reloadUI() { - font_size = settings->value("USER/FONT_SIZE").toInt(); - font.setPointSize(font_size); - label->setFont(font); - label->setStyleSheet(QString("color: %1; background-color: %2") - .arg(settings->value("USER/FONT_COLOR").toString(), - settings->value("USER/BG_COLOR").toString())); -} - -void Clock::resetUI() { - QString default_bg_color = settings->value("DEFAULT/BG_COLOR").toString(); - QString default_font_color = settings->value("DEFAULT/FONT_COLOR").toString(); - QString default_font_size = settings->value("DEFAULT/FONT_SIZE").toString(); - - settings->setValue("USER/BG_COLOR", default_bg_color); - settings->setValue("USER/FONT_COLOR", default_font_color); - settings->setValue("USER/FONT_SIZE", default_font_size); - - reloadUI(); -} - -void Clock::increaseFontSize() { - font_size += font_size_step; - changeFontSize(); -} - -void Clock::decreaseFontSize() { - font_size -= font_size_step; - changeFontSize(); -} - -void Clock::changeFontSize() { - font.setPointSize(font_size); - label->setFont(font); -} - -void Clock::setFontColor(const QString &color) { - settings->setValue("USER/FONT_COLOR", color); - reloadUI(); -} - -void Clock::setBackgroundColor(const QString &color) { - settings->setValue("USER/BG_COLOR", color); - reloadUI(); -} - -void Clock::mousePressEvent(QMouseEvent *event) { - pressed = true; - oldPos = event->pos(); - QWidget::mousePressEvent(event); -} - -void Clock::mouseMoveEvent(QMouseEvent *event) { - if (pressed) { - move(pos() + (event->pos() - oldPos)); - } - QWidget::mouseMoveEvent(event); -} - -void Clock::mouseReleaseEvent(QMouseEvent *event) { - pressed = false; - oldPos = event->pos(); - QWidget::mouseReleaseEvent(event); -} - -void Clock::contextMenuEvent(QContextMenuEvent *event) { - QMenu *menu = new QMenu(); - - // menu->setFixedWidth(100); - - // font size - QMenu *font_size_menu = menu->addMenu("font size"); - QAction *larger_action = new QAction("larger", this); - QAction *smaller_action = new QAction("smaller", this); - - connect(larger_action, &QAction::triggered, this, &Clock::increaseFontSize); - connect(smaller_action, &QAction::triggered, this, &Clock::decreaseFontSize); - - font_size_menu->addAction(larger_action); - font_size_menu->addAction(smaller_action); - - QStringList ft_colors = {"none", "blue", "orange", "green", - "red", "yellow", "white"}; - QStringList bg_colors = {"none", "blue", "orange", "green", - "red", "yellow", "white"}; - - // font color - QMenu *font_color_menu = menu->addMenu("font color"); - QList font_color_actions; - for (const QString &color : ft_colors) { - QAction *action = new QAction(color, this); - connect(action, &QAction::triggered, [=]() { setFontColor(color); }); - font_color_actions.append(action); - } - font_color_menu->addActions(font_color_actions); - - // background color - QMenu *bg_color_menu = menu->addMenu("bg color"); - QList bg_color_actions; - for (const QString &color : bg_colors) { - QAction *action = new QAction(color, this); - connect(action, &QAction::triggered, [=]() { setBackgroundColor(color); }); - bg_color_actions.append(action); - } - bg_color_menu->addActions(bg_color_actions); - - // reset - QAction *reset_action = new QAction("reset", this); - connect(reset_action, &QAction::triggered, this, &Clock::resetUI); - menu->addAction(reset_action); - - // quit - QAction *quit_action = new QAction("quit", this); - connect(quit_action, &QAction::triggered, this, &Clock::close); - menu->addAction(quit_action); - - // menu.exec(mapToGlobal(pos)); - menu->exec(event->globalPos()); - delete menu; -} diff --git a/flock.h b/flock.h deleted file mode 100644 index 79240f5..0000000 --- a/flock.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef CLOCK_H -#define CLOCK_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class Clock : public QWidget { - Q_OBJECT -public: - explicit Clock(QWidget *parent = nullptr); - void reloadUI(); - void resetUI(); - void increaseFontSize(); - void decreaseFontSize(); - void changeFontSize(); - void setFontColor(const class QString &color); - void setBackgroundColor(const class QString &color); - -protected: - void mousePressEvent(QMouseEvent *event) override; - void mouseMoveEvent(QMouseEvent *event) override; - void mouseReleaseEvent(QMouseEvent *event) override; - void mouseRightMenu(const QPoint &pos); - -signals: - -public slots: - void showTime(); - -private: - bool pressed; - QPoint oldPos; - QSettings *settings; - QFont font; - int font_size_step; - int font_size; - QLabel *label; - - void contextMenuEvent(QContextMenuEvent *e) override; -}; - -#endif // CLOCK_H diff --git a/main.cpp b/main.cpp index 6a18f6b..b23f80f 100644 --- a/main.cpp +++ b/main.cpp @@ -1,10 +1,626 @@ #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include "flock.h" +class FloatingClock : public QWidget +{ + Q_OBJECT -auto main(int argc, char *argv[]) -> int { +public: + FloatingClock(QWidget *parent = nullptr) : QWidget(parent) + { + // 先加载XML配置(在创建UI之前) + loadXmlConfig(); + + // 无边框、置顶、透明背景 + setWindowFlags(Qt::FramelessWindowHint | + Qt::WindowStaysOnTopHint | + Qt::Tool | + Qt::WindowDoesNotAcceptFocus); + setAttribute(Qt::WA_TranslucentBackground); + + // 创建时间标签 + timeLabel = new QLabel(this); + timeLabel->setAlignment(Qt::AlignCenter); + + updateStyle(); + + // 布局 + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(timeLabel); + layout->setContentsMargins(20, 20, 20, 20); + setLayout(layout); + + // 添加阴影效果 + QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(this); + shadow->setBlurRadius(shadowBlurRadius); + shadow->setColor(shadowColor); + shadow->setOffset(shadowOffsetX, shadowOffsetY); + timeLabel->setGraphicsEffect(shadow); + + // 定时器更新 + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &FloatingClock::updateTime); + timer->start(1000); + updateTime(); + + // 加载用户设置(覆盖XML默认值) + loadUserSettings(); + + // 调整大小 + resize(windowWidth, windowHeight); + + // 如果位置未设置,居中显示 + if (windowPosX < 0 || windowPosY < 0) + { + moveToCenter(); + } + else + { + move(windowPosX, windowPosY); + } + } + +protected: + void mousePressEvent(QMouseEvent *event) override + { + if (event->button() == Qt::LeftButton) + { + dragging = true; + dragPos = event->globalPosition().toPoint() - frameGeometry().topLeft(); + event->accept(); + } + else if (event->button() == Qt::RightButton) + { + showContextMenu(event->globalPosition().toPoint()); + } + } + + void mouseMoveEvent(QMouseEvent *event) override + { + if (dragging && (event->buttons() & Qt::LeftButton)) + { + move(event->globalPosition().toPoint() - dragPos); + event->accept(); + } + } + + void mouseReleaseEvent(QMouseEvent *event) override + { + if (event->button() == Qt::LeftButton) + { + dragging = false; + saveUserSettings(); + } + } + + void paintEvent(QPaintEvent *) override + { + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + // 绘制半透明背景 + if (bgColor.alpha() > 0) + { + QPainterPath path; + path.addRoundedRect(rect(), borderRadius, borderRadius); + painter.fillPath(path, bgColor); + + // 绘制边框 + if (borderWidth > 0) + { + QPen pen(borderColor); + pen.setWidth(borderWidth); + painter.setPen(pen); + painter.drawPath(path); + } + } + } + +private slots: + void updateTime() + { + QTime time = QTime::currentTime(); + QString text = time.toString(timeFormat); + timeLabel->setText(text); + } + + void changeFontSize() + { + bool ok; + int size = QInputDialog::getInt(this, "字体大小", "请输入字体大小:", + fontSize, 6, 300, 1, &ok); + if (ok) + { + fontSize = size; + updateStyle(); + saveUserSettings(); + } + } + + void changeFontColor() + { + QColor color = QColorDialog::getColor(fontColor, this, "选择字体颜色", + QColorDialog::ShowAlphaChannel); + if (color.isValid()) + { + fontColor = color; + updateStyle(); + saveUserSettings(); + } + } + + void changeBgColor() + { + QColor color = QColorDialog::getColor(bgColor, this, "选择背景颜色", + QColorDialog::ShowAlphaChannel); + if (color.isValid()) + { + bgColor = color; + update(); + saveUserSettings(); + } + } + + void resetSettings() + { + // 重新加载XML默认配置 + loadXmlConfig(); + updateStyle(); + update(); + moveToCenter(); + saveUserSettings(); + } + + void quitApp() + { + qApp->quit(); + } + +private: + void showContextMenu(const QPoint &pos) + { + QMenu menu(this); + menu.setStyleSheet(R"( + QMenu { + background-color: #f0f0f0; + border: 1px solid #cccccc; + border-radius: 8px; + padding: 5px; + } + QMenu::item { + padding: 8px 25px; + border-radius: 4px; + } + QMenu::item:selected { + background-color: #e0e0e0; + } + QMenu::item::right-arrow { + image: none; + width: 10px; + height: 10px; + border-left: 6px solid #333; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + margin-right: 5px; + } + )"); + + QMenu *fontMenu = menu.addMenu("▶ 字体大小"); + for (int size : {36, 48, 64, 72, 96, 120}) + { + QAction *action = fontMenu->addAction(QString::number(size)); + connect(action, &QAction::triggered, this, [this, size]() + { + fontSize = size; + updateStyle(); + saveUserSettings(); }); + } + fontMenu->addSeparator(); + QAction *customSize = fontMenu->addAction("自定义..."); + connect(customSize, &QAction::triggered, this, &FloatingClock::changeFontSize); + + QMenu *colorMenu = menu.addMenu("▶ 字体颜色"); + QList> colors = { + {"蓝色", QColor(0, 0, 255)}, + {"红色", QColor(255, 0, 0)}, + {"绿色", QColor(0, 128, 0)}, + {"黑色", QColor(0, 0, 0)}, + {"白色", QColor(255, 255, 255)}, + {"橙色", QColor(255, 165, 0)}, + {"紫色", QColor(128, 0, 128)}}; + for (auto &c : colors) + { + QPixmap pixmap(16, 16); + pixmap.fill(c.second); + QAction *action = colorMenu->addAction(QIcon(pixmap), c.first); + connect(action, &QAction::triggered, this, [this, color = c.second]() mutable + { + fontColor = color; + updateStyle(); + saveUserSettings(); }); + } + colorMenu->addSeparator(); + QAction *customColor = colorMenu->addAction("自定义..."); + connect(customColor, &QAction::triggered, this, &FloatingClock::changeFontColor); + + QMenu *bgMenu = menu.addMenu("▶ 背景颜色"); + QList> bgColors = { + {"透明", QColor(255, 255, 255, 0)}, + {"白色半透明", QColor(255, 255, 255, 180)}, + {"黑色半透明", QColor(0, 0, 0, 180)}, + {"灰色半透明", QColor(128, 128, 128, 180)}, + {"蓝色半透明", QColor(0, 100, 200, 180)}, + {"自定义...", QColor()}}; + for (auto &c : bgColors) + { + if (c.first == "自定义...") + { + QAction *action = bgMenu->addAction(c.first); + connect(action, &QAction::triggered, this, &FloatingClock::changeBgColor); + } + else + { + QPixmap pixmap(16, 16); + pixmap.fill(c.second); + QAction *action = bgMenu->addAction(QIcon(pixmap), c.first); + connect(action, &QAction::triggered, this, [this, color = c.second]() mutable + { + bgColor = color; + update(); + saveUserSettings(); }); + } + } + + menu.addSeparator(); + QAction *resetAction = menu.addAction("重置"); + connect(resetAction, &QAction::triggered, this, &FloatingClock::resetSettings); + + menu.addSeparator(); + QAction *quitAction = menu.addAction("退出"); + connect(quitAction, &QAction::triggered, this, &FloatingClock::quitApp); + + menu.exec(pos); + } + + void updateStyle() + { + QFont font(fontFamily, fontSize, fontWeight ? QFont::Bold : QFont::Normal); + if (fontItalic) + font.setItalic(true); + timeLabel->setFont(font); + + QString style = QString("color: %1;").arg(fontColor.name()); + timeLabel->setStyleSheet(style); + } + + void moveToCenter() + { + QScreen *screen = QApplication::primaryScreen(); + QRect geo = screen->geometry(); + move((geo.width() - width()) / 2, (geo.height() - height()) / 2); + } + + // 加载XML配置文件 + void loadXmlConfig() + { + QString xmlPath = QApplication::applicationDirPath() + "/clock.xml"; + + // 如果XML不存在,创建默认配置 + if (!QFile::exists(xmlPath)) + { + createDefaultXml(xmlPath); + } + + QFile file(xmlPath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + setDefaultValues(); + return; + } + + QDomDocument doc; + QString errorMsg; + int errorLine, errorColumn; + + if (!doc.setContent(&file, &errorMsg, &errorLine, &errorColumn)) + { + qWarning() << "XML解析错误:" << errorMsg << "行:" << errorLine << "列:" << errorColumn; + setDefaultValues(); + file.close(); + return; + } + file.close(); + + QDomElement root = doc.documentElement(); + if (root.tagName() != "ClockConfig") + { + qWarning() << "XML根节点错误"; + setDefaultValues(); + return; + } + + // 解析Window节点 + QDomElement window = root.firstChildElement("Window"); + if (!window.isNull()) + { + windowWidth = getXmlInt(window, "Width", 300); + windowHeight = getXmlInt(window, "Height", 150); + windowPosX = getXmlInt(window, "PosX", -1); + windowPosY = getXmlInt(window, "PosY", -1); + borderRadius = getXmlInt(window, "BorderRadius", 15); + borderWidth = getXmlInt(window, "BorderWidth", 0); + borderColor = parseColor(getXmlString(window, "BorderColor", "#cccccc")); + } + + // 解析Font节点 + QDomElement font = root.firstChildElement("Font"); + if (!font.isNull()) + { + fontSize = getXmlInt(font, "Size", 72); + fontFamily = getXmlString(font, "Family", "Microsoft YaHei"); + fontWeight = getXmlBool(font, "Bold", true); + fontItalic = getXmlBool(font, "Italic", false); + fontColor = parseColor(getXmlString(font, "Color", "#0000ff")); + } + + // 解析Background节点 + QDomElement background = root.firstChildElement("Background"); + if (!background.isNull()) + { + bgColor = parseColor(getXmlString(background, "Color", "#00ffffff")); + } + + // 解析Time节点 + QDomElement time = root.firstChildElement("Time"); + if (!time.isNull()) + { + timeFormat = getXmlString(time, "Format", "HH:mm"); + } + + // 解析Shadow节点 + QDomElement shadow = root.firstChildElement("Shadow"); + if (!shadow.isNull()) + { + shadowBlurRadius = getXmlInt(shadow, "BlurRadius", 20); + shadowOffsetX = getXmlInt(shadow, "OffsetX", 2); + shadowOffsetY = getXmlInt(shadow, "OffsetY", 2); + shadowColor = parseColor(getXmlString(shadow, "Color", "#64000000")); + } + } + + // 创建默认XML文件 + void createDefaultXml(const QString &path) + { + QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) + { + setDefaultValues(); + return; + } + + QTextStream stream(&file); + stream.setEncoding(QStringConverter::Utf8); + + stream << "\n" + << "\n" + << "\n" + << " \n" + << " \n" + << " 300\n" + << " 150\n" + << " -1 \n" + << " -1\n" + << " 15\n" + << " 0 \n" + << " #cccccc\n" + << " \n" + << "\n" + << " \n" + << " \n" + << " 72\n" + << " Microsoft YaHei\n" + << " true\n" + << " false\n" + << " #0000ff \n" + << " \n" + << "\n" + << " \n" + << " \n" + << " #00ffffff \n" + << " \n" + << "\n" + << " \n" + << " \n" + << "\n" + << " \n" + << " \n" + << " 20\n" + << " 2\n" + << " 2\n" + << " #64000000 \n" + << " \n" + << "\n"; + + file.close(); + } + + // 设置默认值 + void setDefaultValues() + { + windowWidth = 300; + windowHeight = 150; + windowPosX = -1; + windowPosY = -1; + borderRadius = 15; + borderWidth = 0; + borderColor = QColor("#cccccc"); + + fontSize = 72; + fontFamily = "Microsoft YaHei"; + fontWeight = true; + fontItalic = false; + fontColor = QColor("#0000ff"); + + bgColor = QColor("#00ffffff"); + timeFormat = "HH:mm"; + + shadowBlurRadius = 20; + shadowOffsetX = 2; + shadowOffsetY = 2; + shadowColor = QColor("#64000000"); + } + + // XML辅助函数 + int getXmlInt(const QDomElement &parent, const QString &tagName, int defaultValue) + { + QDomElement elem = parent.firstChildElement(tagName); + if (elem.isNull()) + return defaultValue; + return elem.text().toInt(); + } + + bool getXmlBool(const QDomElement &parent, const QString &tagName, bool defaultValue) + { + QDomElement elem = parent.firstChildElement(tagName); + if (elem.isNull()) + return defaultValue; + QString text = elem.text().toLower(); + return text == "true" || text == "1" || text == "yes"; + } + + QString getXmlString(const QDomElement &parent, const QString &tagName, const QString &defaultValue) + { + QDomElement elem = parent.firstChildElement(tagName); + if (elem.isNull()) + return defaultValue; + return elem.text(); + } + + // 加载用户设置(覆盖XML默认值) + void loadUserSettings() + { + QSettings settings("FloatingClock", "Settings"); + if (settings.contains("fontSize")) + { + fontSize = settings.value("fontSize").toInt(); + } + if (settings.contains("fontColor")) + { + fontColor = settings.value("fontColor").value(); + } + if (settings.contains("bgColor")) + { + bgColor = settings.value("bgColor").value(); + } + if (settings.contains("pos")) + { + QPoint pos = settings.value("pos").toPoint(); + if (!pos.isNull()) + move(pos); + } + } + + // 保存用户设置 + void saveUserSettings() + { + QSettings settings("FloatingClock", "Settings"); + settings.setValue("fontSize", fontSize); + settings.setValue("fontColor", fontColor); + settings.setValue("bgColor", bgColor); + settings.setValue("pos", pos()); + } + + // 解析颜色字符串 (#RGB, #RRGGBB, #ARGB, #AARRGGBB) + QColor parseColor(const QString &str) + { + QString s = str.trimmed(); + if (s.startsWith("#")) + s = s.mid(1); + + bool ok; + uint val = s.toUInt(&ok, 16); + if (!ok) + return QColor(); + + switch (s.length()) + { + case 3: // #RGB + return QColor( + ((val >> 8) & 0xF) * 17, + ((val >> 4) & 0xF) * 17, + (val & 0xF) * 17); + case 6: // #RRGGBB + return QColor((val >> 16) & 0xFF, (val >> 8) & 0xFF, val & 0xFF); + case 8: // #AARRGGBB + return QColor( + (val >> 16) & 0xFF, + (val >> 8) & 0xFF, + val & 0xFF, + (val >> 24) & 0xFF); + default: + return QColor(); + } + } + + QLabel *timeLabel; + + // XML配置项 + int windowWidth, windowHeight; + int windowPosX, windowPosY; + int borderRadius; + int borderWidth; + QColor borderColor; + + int fontSize; + QString fontFamily; + bool fontWeight; + bool fontItalic; + QColor fontColor; + + QColor bgColor; + QString timeFormat; + + int shadowBlurRadius; + int shadowOffsetX, shadowOffsetY; + QColor shadowColor; + + bool dragging = false; + QPoint dragPos; +}; + +#include "main.moc" + +int main(int argc, char *argv[]) +{ QApplication app(argc, argv); - Clock clock; + // 高分屏适配 + app.setAttribute(Qt::AA_EnableHighDpiScaling, true); + app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); + + FloatingClock clock; + clock.show(); + return app.exec(); } diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 9137a62..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -pyside6