Compare commits
10
Commits
7db7dad988
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45fc172fc8 | ||
|
|
db02df7ca2 | ||
|
|
d9008479c1 | ||
|
|
6b9cb32534 | ||
|
|
e59a674ba7 | ||
|
|
b5c43ba55e | ||
|
|
2e460fcb76 | ||
|
|
1a20802dad | ||
|
|
0bd7559961 | ||
|
|
788ca03df8 |
@@ -52,7 +52,7 @@ Every push to main/master branches triggers CI builds to ensure code quality.
|
|||||||
### Manual Release
|
### Manual Release
|
||||||
|
|
||||||
1. Go to Actions tab in GitHub
|
1. Go to Actions tab in GitHub
|
||||||
2. Select "Build and Release Clouck"
|
2. Select "Build and Release DualFloatingClock"
|
||||||
3. Click "Run workflow"
|
3. Click "Run workflow"
|
||||||
4. Enter version number (e.g., v1.0.0)
|
4. Enter version number (e.g., v1.0.0)
|
||||||
5. Click "Run workflow"
|
5. Click "Run workflow"
|
||||||
|
|||||||
@@ -50,5 +50,5 @@ jobs:
|
|||||||
- name: Upload Build Artifact
|
- name: Upload Build Artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: Clouck-${{ runner.os }}-cmake
|
name: DualFloatingClock-${{ runner.os }}-cmake
|
||||||
path: build/Clouck*
|
path: build/DualFloatingClock*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: Build and Release Clouck
|
name: Build and Release DualFloatingClock
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -15,6 +15,10 @@ on:
|
|||||||
required: true
|
required: true
|
||||||
default: "v1.0.0"
|
default: "v1.0.0"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-macos:
|
build-macos:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
@@ -39,43 +43,43 @@ jobs:
|
|||||||
ls -la build/
|
ls -la build/
|
||||||
|
|
||||||
# Look for the .app bundle or executable
|
# Look for the .app bundle or executable
|
||||||
if [ -d "build/Clouck.app" ]; then
|
if [ -d "build/DualFloatingClock.app" ]; then
|
||||||
echo "Found .app bundle"
|
echo "Found .app bundle"
|
||||||
cp -r build/Clouck.app ./
|
cp -r build/DualFloatingClock.app ./
|
||||||
elif [ -f "build/Clouck" ]; then
|
elif [ -f "build/DualFloatingClock" ]; then
|
||||||
echo "Found standalone executable"
|
echo "Found standalone executable"
|
||||||
# Create app bundle
|
# Create app bundle
|
||||||
mkdir -p Clouck.app/Contents/MacOS
|
mkdir -p DualFloatingClock.app/Contents/MacOS
|
||||||
mkdir -p Clouck.app/Contents/Resources
|
mkdir -p DualFloatingClock.app/Contents/Resources
|
||||||
cp build/Clouck Clouck.app/Contents/MacOS/
|
cp build/DualFloatingClock DualFloatingClock.app/Contents/MacOS/
|
||||||
else
|
else
|
||||||
echo "Looking for executable in build directory..."
|
echo "Looking for executable in build directory..."
|
||||||
find build/ -name "Clouck*" -type f
|
find build/ -name "DualFloatingClock*" -type f
|
||||||
# Try to find and copy the first executable
|
# Try to find and copy the first executable
|
||||||
EXECUTABLE=$(find build/ -name "Clouck*" -type f | head -1)
|
EXECUTABLE=$(find build/ -name "DualFloatingClock*" -type f | head -1)
|
||||||
if [ -n "$EXECUTABLE" ]; then
|
if [ -n "$EXECUTABLE" ]; then
|
||||||
echo "Found executable: $EXECUTABLE"
|
echo "Found executable: $EXECUTABLE"
|
||||||
mkdir -p Clouck.app/Contents/MacOS
|
mkdir -p DualFloatingClock.app/Contents/MacOS
|
||||||
mkdir -p Clouck.app/Contents/Resources
|
mkdir -p DualFloatingClock.app/Contents/Resources
|
||||||
cp "$EXECUTABLE" Clouck.app/Contents/MacOS/
|
cp "$EXECUTABLE" DualFloatingClock.app/Contents/MacOS/
|
||||||
else
|
else
|
||||||
echo "ERROR: No Clouck executable found!"
|
echo "ERROR: No DualFloatingClock executable found!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create Info.plist
|
# Create Info.plist
|
||||||
cat > Clouck.app/Contents/Info.plist << EOF
|
cat > DualFloatingClock.app/Contents/Info.plist << EOF
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>Clouck</string>
|
<string>DualFloatingClock</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.clouck.app</string>
|
<string>com.clouck.app</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>Clouck</string>
|
<string>DualFloatingClock</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0.0</string>
|
<string>1.0.0</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
@@ -88,18 +92,18 @@ jobs:
|
|||||||
|
|
||||||
- name: Deploy Qt Dependencies
|
- name: Deploy Qt Dependencies
|
||||||
run: |
|
run: |
|
||||||
if [ -d "Clouck.app" ]; then
|
if [ -d "DualFloatingClock.app" ]; then
|
||||||
macdeployqt Clouck.app -dmg || hdiutil create -volname "Clouck" -srcfolder Clouck.app -ov -format UDZO Clouck.dmg
|
macdeployqt DualFloatingClock.app -dmg || hdiutil create -volname "DualFloatingClock" -srcfolder DualFloatingClock.app -ov -format UDZO DualFloatingClock.dmg
|
||||||
else
|
else
|
||||||
echo "ERROR: Clouck.app not found!"
|
echo "ERROR: DualFloatingClock.app not found!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Upload macOS Artifact
|
- name: Upload macOS Artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: Clouck-macos
|
name: DualFloatingClock-macos
|
||||||
path: Clouck.dmg
|
path: DualFloatingClock.dmg
|
||||||
|
|
||||||
build-windows:
|
build-windows:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
@@ -123,17 +127,17 @@ jobs:
|
|||||||
# Check what was actually built
|
# Check what was actually built
|
||||||
Get-ChildItem -Path "build" -Recurse -Name
|
Get-ChildItem -Path "build" -Recurse -Name
|
||||||
|
|
||||||
New-Item -ItemType Directory -Path Clouck-windows -Force
|
New-Item -ItemType Directory -Path DualFloatingClock-win -Force
|
||||||
Copy-Item "build/Release/Clouck.exe" -Destination "Clouck-windows/" -ErrorAction SilentlyContinue
|
Copy-Item "build/Release/DualFloatingClock.exe" -Destination "DualFloatingClock-win/" -ErrorAction SilentlyContinue
|
||||||
Copy-Item "build/Release/*.dll" -Destination "Clouck-windows/" -ErrorAction SilentlyContinue
|
Copy-Item "build/Release/*.dll" -Destination "DualFloatingClock-win/" -ErrorAction SilentlyContinue
|
||||||
windeployqt --release Clouck-windows/Clouck.exe
|
windeployqt --release DualFloatingClock-win/DualFloatingClock.exe
|
||||||
Compress-Archive -Path Clouck-windows -DestinationPath Clouck-windows.zip
|
Compress-Archive -Path DualFloatingClock-win -DestinationPath DualFloatingClock-win.zip
|
||||||
|
|
||||||
- name: Upload Windows Artifact
|
- name: Upload Windows Artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: Clouck-windows
|
name: DualFloatingClock-win
|
||||||
path: Clouck-windows.zip
|
path: DualFloatingClock-win.zip
|
||||||
|
|
||||||
build-linux:
|
build-linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -165,32 +169,35 @@ jobs:
|
|||||||
ls -la build/
|
ls -la build/
|
||||||
|
|
||||||
# Look for the executable
|
# Look for the executable
|
||||||
EXECUTABLE=$(find build/ -name "Clouck" -type f -executable | head -1)
|
EXECUTABLE=$(find build/ -name "DualFloatingClock" -type f -executable | head -1)
|
||||||
if [ -n "$EXECUTABLE" ]; then
|
if [ -n "$EXECUTABLE" ]; then
|
||||||
echo "Found executable: $EXECUTABLE"
|
echo "Found executable: $EXECUTABLE"
|
||||||
mkdir -p Clouck-linux
|
mkdir -p DualFloatingClock-linux
|
||||||
cp "$EXECUTABLE" Clouck-linux/
|
cp "$EXECUTABLE" DualFloatingClock-linux/
|
||||||
else
|
else
|
||||||
echo "ERROR: No Clouck executable found!"
|
echo "ERROR: No DualFloatingClock executable found!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Copy any Qt libraries
|
# Copy any Qt libraries
|
||||||
find build/ -name "*.so*" -exec cp {} Clouck-linux/ \; 2>/dev/null || true
|
find build/ -name "*.so*" -exec cp {} DualFloatingClock-linux/ \; 2>/dev/null || true
|
||||||
|
|
||||||
# Create tar.gz for distribution
|
# Create tar.gz for distribution
|
||||||
tar -czf Clouck-linux.tar.gz Clouck-linux/
|
tar -czf DualFloatingClock-linux.tar.gz DualFloatingClock-linux/
|
||||||
|
|
||||||
- name: Upload Linux Artifact
|
- name: Upload Linux Artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: Clouck-linux
|
name: DualFloatingClock-linux
|
||||||
path: Clouck-linux.tar.gz
|
path: DualFloatingClock-linux.tar.gz
|
||||||
|
|
||||||
generate-changelog:
|
generate-changelog:
|
||||||
needs: [ build-macos, build-windows, build-linux ]
|
needs: [ build-macos, build-windows, build-linux ]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: always()
|
if: always()
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
outputs:
|
outputs:
|
||||||
changelog: ${{ steps.changelog.outputs.changelog }}
|
changelog: ${{ steps.changelog.outputs.changelog }}
|
||||||
version: ${{ steps.version.outputs.version }}
|
version: ${{ steps.version.outputs.version }}
|
||||||
@@ -220,15 +227,18 @@ jobs:
|
|||||||
echo "$CHANGELOG" >> $GITHUB_OUTPUT
|
echo "$CHANGELOG" >> $GITHUB_OUTPUT
|
||||||
echo "" >> $GITHUB_OUTPUT
|
echo "" >> $GITHUB_OUTPUT
|
||||||
echo "## Downloads" >> $GITHUB_OUTPUT
|
echo "## Downloads" >> $GITHUB_OUTPUT
|
||||||
echo "- **macOS**: Clouck.dmg" >> $GITHUB_OUTPUT
|
echo "- **macOS**: DualFloatingClock.dmg" >> $GITHUB_OUTPUT
|
||||||
echo "- **Windows**: Clouck-windows.zip" >> $GITHUB_OUTPUT
|
echo "- **Windows**: DualFloatingClock-win.zip" >> $GITHUB_OUTPUT
|
||||||
echo "- **Linux**: Clouck-linux.tar.gz" >> $GITHUB_OUTPUT
|
echo "- **Linux**: DualFloatingClock-linux.tar.gz" >> $GITHUB_OUTPUT
|
||||||
echo "EOF" >> $GITHUB_OUTPUT
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
create-release:
|
create-release:
|
||||||
needs: [ build-macos, build-windows, build-linux, generate-changelog ]
|
needs: [ build-macos, build-windows, build-linux, generate-changelog ]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: always()
|
if: always()
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: read
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download All Artifacts
|
- name: Download All Artifacts
|
||||||
@@ -242,40 +252,17 @@ jobs:
|
|||||||
- name: Create Release
|
- name: Create Release
|
||||||
uses: softprops/action-gh-release@v3
|
uses: softprops/action-gh-release@v3
|
||||||
with:
|
with:
|
||||||
name: Clouck ${{ needs.generate-changelog.outputs.version }}
|
name: DualFloatingClock ${{ needs.generate-changelog.outputs.version }}
|
||||||
body: |
|
body: |
|
||||||
Cross-platform clock widget application
|
Cross-platform clock widget application
|
||||||
|
|
||||||
${{ needs.generate-changelog.outputs.changelog }}
|
${{ needs.generate-changelog.outputs.changelog }}
|
||||||
|
|
||||||
## Installation
|
|
||||||
### macOS
|
|
||||||
1. Download Clouck.dmg
|
|
||||||
2. Open the DMG file
|
|
||||||
3. Drag Clouck.app to Applications folder
|
|
||||||
|
|
||||||
### Windows
|
|
||||||
1. Download Clouck-windows.zip
|
|
||||||
2. Extract the ZIP file
|
|
||||||
3. Run Clouck.exe
|
|
||||||
|
|
||||||
### Linux
|
|
||||||
1. Download Clouck-linux.tar.gz
|
|
||||||
2. Extract the tar.gz file
|
|
||||||
3. Run the Clouck executable
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- Always-on-top functionality across all platforms
|
|
||||||
- Fullscreen mode compatibility
|
|
||||||
- Resizable interface
|
|
||||||
- Context menu with customization options
|
|
||||||
- Native platform integration
|
|
||||||
files: |
|
files: |
|
||||||
artifacts/Clouck-macos/Clouck.dmg
|
artifacts/DualFloatingClock-macos/DualFloatingClock.dmg
|
||||||
artifacts/Clouck-windows/Clouck-windows.zip
|
artifacts/DualFloatingClock-win/DualFloatingClock-win.zip
|
||||||
artifacts/Clouck-linux/Clouck-linux.tar.gz
|
artifacts/DualFloatingClock-linux/DualFloatingClock-linux.tar.gz
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
env:
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|||||||
+8
-1
@@ -88,5 +88,12 @@ Icon
|
|||||||
.cmake
|
.cmake
|
||||||
.qtc_clangd
|
.qtc_clangd
|
||||||
.qmake.stash
|
.qmake.stash
|
||||||
build
|
|
||||||
*.dmg
|
*.dmg
|
||||||
|
build
|
||||||
|
test*
|
||||||
|
!tests/
|
||||||
|
!tests/*.cpp
|
||||||
|
|
||||||
|
# AW
|
||||||
|
.aw/
|
||||||
|
CLAUDE.md
|
||||||
|
|||||||
Vendored
+9
-9
@@ -5,20 +5,20 @@
|
|||||||
"includePath": [
|
"includePath": [
|
||||||
"${workspaceFolder}/**",
|
"${workspaceFolder}/**",
|
||||||
"/opt/homebrew/opt/qt/include",
|
"/opt/homebrew/opt/qt/include",
|
||||||
"/opt/homebrew/Cellar/qtbase/6.11.0/lib/QtCore.framework/Headers",
|
"/opt/homebrew/Cellar/qtbase/6.11.1/lib/QtCore.framework/Headers",
|
||||||
"/opt/homebrew/Cellar/qtbase/6.11.0/lib/QtGui.framework/Headers",
|
"/opt/homebrew/Cellar/qtbase/6.11.1/lib/QtGui.framework/Headers",
|
||||||
"/opt/homebrew/Cellar/qtbase/6.11.0/lib/QtWidgets.framework/Headers",
|
"/opt/homebrew/Cellar/qtbase/6.11.1/lib/QtWidgets.framework/Headers",
|
||||||
"/opt/homebrew/Cellar/qttools/6.11.0/include/QtUiPlugin",
|
"/opt/homebrew/Cellar/qttools/6.11.1/include/QtUiPlugin",
|
||||||
"/opt/homebrew/Cellar/qtdeclarative/6.11.0/include/QtQmlDebug"
|
"/opt/homebrew/Cellar/qtdeclarative/6.11.1/include/QtQmlDebug"
|
||||||
],
|
],
|
||||||
"defines": [],
|
"defines": [],
|
||||||
"compilerPath": "/usr/bin/clang++",
|
"compilerPath": "/usr/bin/clang++",
|
||||||
"cStandard": "c17",
|
"cStandard": "c17",
|
||||||
"cppStandard": "c++17",
|
"cppStandard": "c++20",
|
||||||
"intelliSenseMode": "macos-clang-arm64",
|
"intelliSenseMode": "macos-clang-arm64",
|
||||||
"configurationProvider": "kylinideteam.cmake-intellisence",
|
"configurationProvider": "kylinideteam.cmake-intellisence",
|
||||||
"macFrameworkPath": [
|
"macFrameworkPath": [
|
||||||
"/opt/homebrew/Cellar/qtbase/6.11.0/lib",
|
"/opt/homebrew/Cellar/qtbase/6.11.1/lib",
|
||||||
"/System/Library/Frameworks"
|
"/System/Library/Frameworks"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"includePath": [
|
"includePath": [
|
||||||
"${workspaceFolder}/**",
|
"${workspaceFolder}/**",
|
||||||
"${env:SCOOP}\\apps\\msys2-cn\\current\\ucrt64\\include",
|
"${env:SCOOP}\\apps\\msys2-cn\\current\\ucrt64\\include",
|
||||||
"${env:SCOOP}\\apps\\msys2-cn\\current\\ucrt64\\include\\qt"
|
"${env:SCOOP}\\apps\\msys2-cn\\current\\ucrt64\\include\\qt6"
|
||||||
],
|
],
|
||||||
"defines": [
|
"defines": [
|
||||||
"_DEBUG",
|
"_DEBUG",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
],
|
],
|
||||||
"compilerPath": "${env:SCOOP}\\apps\\msys2-cn\\current\\ucrt64\\bin\\g++.exe",
|
"compilerPath": "${env:SCOOP}\\apps\\msys2-cn\\current\\ucrt64\\bin\\g++.exe",
|
||||||
"cStandard": "c17",
|
"cStandard": "c17",
|
||||||
"cppStandard": "c++17",
|
"cppStandard": "c++20",
|
||||||
"intelliSenseMode": "windows-gcc-x64",
|
"intelliSenseMode": "windows-gcc-x64",
|
||||||
"configurationProvider": "kylinideteam.cmake-intellisence"
|
"configurationProvider": "kylinideteam.cmake-intellisence"
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-10
@@ -1,9 +1,11 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
project(Clouck)
|
project(DualFloatingClock)
|
||||||
|
|
||||||
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
|
||||||
@@ -43,33 +47,65 @@ elseif(APPLE)
|
|||||||
list(APPEND HEADERS window_helper_macos.h)
|
list(APPEND HEADERS window_helper_macos.h)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# macOS app icon
|
||||||
|
if(APPLE)
|
||||||
|
set(MACOSX_BUNDLE_ICON_FILE dualfloatingclock.icns)
|
||||||
|
set(APP_ICON_MACOSX ${CMAKE_CURRENT_SOURCE_DIR}/resources/dualfloatingclock.icns)
|
||||||
|
set_source_files_properties(${APP_ICON_MACOSX} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
|
||||||
|
endif()
|
||||||
|
|
||||||
# Create executable
|
# Create executable
|
||||||
qt6_add_executable(Clouck WIN32 MACOSX_BUNDLE
|
qt6_add_executable(DualFloatingClock WIN32 MACOSX_BUNDLE
|
||||||
${SOURCES}
|
${SOURCES}
|
||||||
${HEADERS}
|
${HEADERS}
|
||||||
|
${APP_ICON_MACOSX}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Platform-specific sources and libraries
|
# Platform-specific sources and libraries
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
target_sources(Clouck PRIVATE window_helper_macos.mm)
|
target_sources(DualFloatingClock PRIVATE window_helper_macos.mm)
|
||||||
target_link_libraries(Clouck PRIVATE "-framework AppKit" "-framework CoreGraphics")
|
target_link_libraries(DualFloatingClock PRIVATE "-framework AppKit" "-framework CoreGraphics")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_sources(Clouck PRIVATE window_helper_windows.cpp)
|
target_sources(DualFloatingClock PRIVATE window_helper_windows.cpp)
|
||||||
target_link_libraries(Clouck PRIVATE user32 dwmapi)
|
target_link_libraries(DualFloatingClock PRIVATE user32 dwmapi)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
target_sources(Clouck PRIVATE window_helper_linux.cpp)
|
target_sources(DualFloatingClock PRIVATE window_helper_linux.cpp)
|
||||||
target_link_libraries(Clouck PRIVATE ${X11_LIBRARIES} Xfixes)
|
target_link_libraries(DualFloatingClock PRIVATE ${X11_LIBRARIES} Xfixes)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Link Qt libraries
|
# Link Qt libraries
|
||||||
target_link_libraries(Clouck PRIVATE Qt6::Core Qt6::Widgets)
|
target_link_libraries(DualFloatingClock PRIVATE Qt6::Core Qt6::Widgets)
|
||||||
|
|
||||||
# Set up install
|
# Set up install
|
||||||
install(TARGETS Clouck
|
install(TARGETS DualFloatingClock
|
||||||
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()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Created by and for Qt Creator This file was created for editing the project sources only.
|
# Created by and for Qt Creator This file was created for editing the project sources only.
|
||||||
# You may attempt to use it for building too, by modifying this file here.
|
# You may attempt to use it for building too, by modifying this file here.
|
||||||
|
|
||||||
TARGET = Clouck
|
TARGET = DualFloatingClock
|
||||||
|
|
||||||
QT = core gui widgets
|
QT = core gui widgets
|
||||||
|
|
||||||
-123
@@ -1,123 +0,0 @@
|
|||||||
# Clouck 浮动时钟
|
|
||||||
|
|
||||||
## 功能特性
|
|
||||||
|
|
||||||
✅ **已实现的功能:**
|
|
||||||
|
|
||||||
- **无边框窗口** - 简洁的浮动时钟界面
|
|
||||||
- **拖拽移动** - 鼠标左键拖拽移动窗口位置
|
|
||||||
- **右键菜单** - 丰富的自定义选项
|
|
||||||
- **总在最前** - 始终显示在其他窗口之上
|
|
||||||
- **透明背景** - 支持半透明背景效果
|
|
||||||
- **自定义样式** - 可调整字体颜色、背景颜色、字体大小
|
|
||||||
- **设置持久化** - 自动保存和恢复用户设置
|
|
||||||
- **跨平台支持** - 基于Qt6,支持 macOS/Linux/Windows
|
|
||||||
|
|
||||||
## 使用方法
|
|
||||||
|
|
||||||
### 基本操作
|
|
||||||
|
|
||||||
- **移动时钟**:鼠标左键点击并拖拽
|
|
||||||
- **显示菜单**:右键点击时钟
|
|
||||||
- **退出应用**:右键菜单选择"退出"
|
|
||||||
|
|
||||||
### 自定义设置
|
|
||||||
|
|
||||||
- **字体颜色**:右键菜单 → 设置字体颜色
|
|
||||||
- **背景颜色**:右键菜单 → 设置背景颜色
|
|
||||||
- **字体大小**:右键菜单 → 设置字体大小
|
|
||||||
- **总在最前**:右键菜单 → 勾选/取消"总在最前"
|
|
||||||
- **重置设置**:右键菜单 → 重置设置
|
|
||||||
|
|
||||||
### 构建和运行
|
|
||||||
|
|
||||||
#### 快速构建(推荐):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 一键构建
|
|
||||||
./build.sh
|
|
||||||
|
|
||||||
# 运行应用
|
|
||||||
./build/Clouck.app/Contents/MacOS/Clouck # macOS
|
|
||||||
./build/Clouck # Linux
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 手动构建:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 清理旧的构建文件
|
|
||||||
./clean.sh
|
|
||||||
|
|
||||||
# 生成Makefile
|
|
||||||
qmake Clouck.pro
|
|
||||||
|
|
||||||
# 编译
|
|
||||||
make
|
|
||||||
|
|
||||||
# 运行
|
|
||||||
./build/Clouck.app/Contents/MacOS/Clouck # macOS
|
|
||||||
./build/Clouck # Linux
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 使用CMake构建:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir build && cd build
|
|
||||||
cmake ..
|
|
||||||
make
|
|
||||||
./Clouck
|
|
||||||
```
|
|
||||||
|
|
||||||
### 项目结构
|
|
||||||
|
|
||||||
```text
|
|
||||||
qClouck/
|
|
||||||
├── build/ # 构建输出目录
|
|
||||||
│ ├── Clouck.app/ # macOS应用包
|
|
||||||
│ ├── *.o # 目标文件
|
|
||||||
│ ├── moc_*.cpp # Qt元对象编译器输出
|
|
||||||
│ └── Makefile # 构建文件
|
|
||||||
├── src/ # 源代码(建议)
|
|
||||||
├── build.sh # 一键构建脚本
|
|
||||||
├── clean.sh # 清理脚本
|
|
||||||
├── Clouck.pro # qmake项目文件
|
|
||||||
├── CMakeLists.txt # CMake项目文件
|
|
||||||
└── README.md
|
|
||||||
```
|
|
||||||
|
|
||||||
## 技术特点
|
|
||||||
|
|
||||||
- **Qt6框架**:使用最新的Qt6库
|
|
||||||
- **透明窗口**:支持RGBA透明背景
|
|
||||||
- **定时器更新**:每秒更新时钟显示
|
|
||||||
- **设置管理**:使用QSettings持久化用户偏好
|
|
||||||
- **事件处理**:自定义鼠标事件实现拖拽功能
|
|
||||||
|
|
||||||
## 开发说明
|
|
||||||
|
|
||||||
### 构建输出管理
|
|
||||||
|
|
||||||
- 所有编译生成的文件(.o, moc_*.cpp, Makefile等)都会输出到`build/`目录
|
|
||||||
- 可执行文件位于`build/Clouck.app/Contents/MacOS/Clouck`(macOS)或`build/Clouck`(Linux)
|
|
||||||
- 使用`./clean.sh`可以快速清理所有构建文件
|
|
||||||
- 使用`./build.sh`可以进行完整的一键构建
|
|
||||||
|
|
||||||
### 自定义构建配置
|
|
||||||
|
|
||||||
可以在`Clouck.pro`中修改以下配置:
|
|
||||||
|
|
||||||
- `DESTDIR`:可执行文件输出目录
|
|
||||||
- `OBJECTS_DIR`:目标文件输出目录
|
|
||||||
- `MOC_DIR`:元对象编译器输出目录
|
|
||||||
|
|
||||||
## 更新日志
|
|
||||||
|
|
||||||
### v1.0.0 (2026-04-18)
|
|
||||||
|
|
||||||
- ✨ 初始版本发布
|
|
||||||
- ✨ 基础时钟功能
|
|
||||||
- ✨ 拖拽移动支持
|
|
||||||
- ✨ 右键菜单功能
|
|
||||||
- ✨ 样式自定义
|
|
||||||
- ✨ 设置持久化
|
|
||||||
- ✨ 构建输出目录管理
|
|
||||||
@@ -1,169 +1,61 @@
|
|||||||
# ☁ Clouck
|
# DualFloatingClock
|
||||||
|
|
||||||

|
A menu-bar-first, dual-timezone floating clock for macOS, Windows and Linux — built with Qt6.
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
This project aims to build a cross-platform floating clock based on Qt6.
|
> **Fork provenance:** this project is a fork of [clouck](https://github.com/ivaquero/clouck)
|
||||||
|
> by [ivaquero](https://github.com/ivaquero) — a cross-platform floating clock.
|
||||||
|
> DualFloatingClock extends it with dual time zones, a flat tray menu, glyph outlines,
|
||||||
|
> per-element font sizing and a menu-bar-only presence (no Dock icon on macOS).
|
||||||
|
|
||||||
## Motivation
|
## What it does
|
||||||
|
|
||||||
When coding in full screen mode, I often forget the time.
|
A borderless, always-on-top clock that shows **two configurable time zones**
|
||||||
When on macOS / Linux, the clock on the menu bar is too small to be noticeable, so is the clock on Windows.
|
side by side — each with its own time and city name — plus a menu bar tray icon
|
||||||
|
for quick access to all settings. Designed for people working across time zones
|
||||||
|
who want the second (or third) clock glanceable, not hidden in world-clock widgets.
|
||||||
|
|
||||||

|
## Features
|
||||||
|
|
||||||
## ✨ Features
|
- **Dual time zones** — pick any IANA zone for each clock (`Europe/Bucharest`, `America/Los_Angeles`, …)
|
||||||
|
- **Floating clock** — frameless, always-on-top, drag anywhere, resizable from the corner; visible even in fullscreen spaces (macOS)
|
||||||
|
- **Menu-bar tray** — icon adapts to light/dark mode; flat context menu with every setting (no submenus, no clicking on the clock itself)
|
||||||
|
- **Text outline** — configurable glyph outline (color) under the text fill, so the clock stays readable on any wallpaper
|
||||||
|
- **Per-element fonts** — separate font sizes for the time and the city name (up to 200pt)
|
||||||
|
- **Colors via HTML/hex** — type `#00ff00`, `#80000000` (with alpha) or `transparent`; falls back to the native color picker
|
||||||
|
- **Start at Login** — toggle in the tray menu (macOS LaunchAgent)
|
||||||
|
- **Menu-bar-only on macOS** — no Dock icon, no ⌘Tab entry; lives in the menu bar
|
||||||
|
- **XML config** — persisted in the OS config location (e.g. `~/Library/Preferences/DualFloatingClock/DualFloatingClock/config.xml`)
|
||||||
|
|
||||||
- Cross Platform
|
## Build
|
||||||
- [x] MacOS
|
|
||||||
- [x] Windows
|
|
||||||
- [ ] Linux
|
|
||||||
- [x] **Main Window**
|
|
||||||
- [x] Always On Top
|
|
||||||
- [x] Frameless
|
|
||||||
- [x] Drag and Move
|
|
||||||
- [x] Resizable
|
|
||||||
- [x] Show in Fullscreen Mode
|
|
||||||
- [x] **Style**
|
|
||||||
- [x] Right Click Menu
|
|
||||||
- [x] Set Background Color
|
|
||||||
- [ ] Transparent Background
|
|
||||||
- [x] Set Font Color
|
|
||||||
- [x] Set FontSize
|
|
||||||
- [x] **Tweakable** (XML)
|
|
||||||
- [x] Read Settings
|
|
||||||
- [x] Write Settings
|
|
||||||
- [x] Reset Settings
|
|
||||||
- [ ] **Time Zone**
|
|
||||||
- [ ] Select Time Zone
|
|
||||||
|
|
||||||
## Quick Start
|
Requires Qt6 (Core, Widgets, Test) and CMake ≥ 3.16.
|
||||||
|
|
||||||
### Build and Run
|
|
||||||
|
|
||||||
#### Quick Build (Recommended)
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# One-click build
|
cmake -S . -B build
|
||||||
./build.sh
|
cmake --build build
|
||||||
|
|
||||||
# Run application
|
# macOS — run
|
||||||
./build/flock.app/Contents/MacOS/flock # macOS
|
open build/DualFloatingClock.app
|
||||||
./build/flock # Linux
|
# install to /Applications
|
||||||
|
cp -R build/DualFloatingClock.app /Applications/
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Manual Build
|
qmake is also supported (legacy): see `build.sh` / `DualFloatingClock.pro`.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clean build files
|
cd build && ctest
|
||||||
./clean.sh
|
|
||||||
|
|
||||||
# Generate Makefile
|
|
||||||
qmake flock.pro
|
|
||||||
|
|
||||||
# Build
|
|
||||||
make
|
|
||||||
|
|
||||||
# Run
|
|
||||||
./build/flock.app/Contents/MacOS/flock # macOS
|
|
||||||
./build/flock # Linux
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Using CMake
|
Qt Test suites: `ConfigManagerTest` (XML round-trip, default path), `AutoStartManagerTest` (LaunchAgent contents, enable/disable round-trip).
|
||||||
|
|
||||||
```bash
|
## Usage
|
||||||
mkdir build && cd build
|
|
||||||
cmake ..
|
|
||||||
make
|
|
||||||
./flock
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 GitHub Actions
|
- **Move**: drag the clock · **Resize**: drag the bottom-right corner
|
||||||
|
- **Settings**: right-click the **tray icon** (the clock itself has no menu — dragging-friendly)
|
||||||
This project uses GitHub Actions for automated building and releasing:
|
- Colors accept `#RRGGBB`, `#AARRGGBB` or `transparent`
|
||||||
|
|
||||||
### CI/CD Workflows
|
|
||||||
|
|
||||||
- **CI Build**: Validates builds on all platforms for every push/PR
|
|
||||||
- **Build Qt**: Comprehensive builds using both qmake and CMake
|
|
||||||
- **Release**: Creates distributable packages for tagged releases
|
|
||||||
|
|
||||||
### Download Pre-built Binaries
|
|
||||||
|
|
||||||
Visit the [Releases](https://github.com/ivaquero/clouck/releases) page to download pre-built binaries for:
|
|
||||||
|
|
||||||
- **macOS**: DMG installer with native app bundle
|
|
||||||
- **Windows**: ZIP package with all dependencies
|
|
||||||
- **Linux**: TAR.GZ package with shared libraries
|
|
||||||
|
|
||||||
### Creating a Release
|
|
||||||
|
|
||||||
1. Tag a new version:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git tag v1.0.0
|
|
||||||
git push origin v1.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
2. GitHub Actions will automatically build and create a release with binaries for all platforms
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
The application now uses XML configuration files instead of QSettings. Configuration is stored in `config.xml` with the following options:
|
|
||||||
|
|
||||||
- **FontColor**: Font color in hex format
|
|
||||||
- **BackgroundColor**: Background color with alpha transparency
|
|
||||||
- **FontSize**: Font size in points (8-72)
|
|
||||||
- **AlwaysOnTop**: Window always stays on top
|
|
||||||
- **WindowPosition**: Window position coordinates
|
|
||||||
|
|
||||||
The configuration file is automatically created when you first run the application and make changes to settings.
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
- **Move**: Left-click and drag
|
|
||||||
- **Menu**: Right-click for context menu
|
|
||||||
- **Customize**: Change colors, font size, always-on-top behavior
|
|
||||||
- **Configuration**: Settings are saved to `config.xml` and persist between sessions
|
|
||||||
|
|
||||||
### Project Structure
|
|
||||||
|
|
||||||
```text
|
|
||||||
qflock/
|
|
||||||
├── build/ # Build output directory
|
|
||||||
│ ├── flock.app/ # macOS application bundle
|
|
||||||
│ ├── *.o # Object files
|
|
||||||
│ ├── moc_*.cpp # Qt meta-object compiler output
|
|
||||||
│ └── Makefile # Build file
|
|
||||||
├── config.xml # XML configuration file
|
|
||||||
├── build.sh # One-click build script
|
|
||||||
├── clean.sh # Clean script
|
|
||||||
├── flock.pro # qmake project file
|
|
||||||
├── CMakeLists.txt # CMake project file
|
|
||||||
└── README.md
|
|
||||||
```
|
|
||||||
|
|
||||||
### Build Output Management
|
|
||||||
|
|
||||||
- All compilation files (.o, moc_*.cpp, Makefile, etc.) are output to `build/` directory
|
|
||||||
- Executable files are located at `build/flock.app/Contents/MacOS/flock` (macOS) or `build/flock` (Linux)
|
|
||||||
- Use `./clean.sh` to quickly clean all build files
|
|
||||||
- Use `./build.sh` for complete one-click build
|
|
||||||
|
|
||||||
### Next Steps
|
|
||||||
|
|
||||||
- Time zone selection
|
|
||||||
- Alarm functionality
|
|
||||||
- Enhanced UI/UX
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
- Qt6 (Core, Widgets)
|
|
||||||
- C++17 compatible compiler
|
|
||||||
- CMake 3.16+ (optional, for CMake build)
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
See LICENSE file for details.
|
Same as upstream clouck — see [LICENSE](LICENSE).
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
#include "autostart_manager.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QSaveFile>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QXmlStreamWriter>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const QString launchAgentLabel = QStringLiteral("com.dualfloatingclock.clock");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoStartManager::enableForCurrentApplication()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
return enableForPath(QCoreApplication::applicationFilePath());
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoStartManager::enableForPath(const QString &executablePath)
|
||||||
|
{
|
||||||
|
#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(executablePath).toUtf8());
|
||||||
|
return agentFile.commit();
|
||||||
|
#else
|
||||||
|
Q_UNUSED(executablePath);
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoStartManager::disable()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
const QString agentPath = launchAgentPath();
|
||||||
|
if (agentPath.isEmpty() || !QFile::exists(agentPath))
|
||||||
|
{
|
||||||
|
return true; // already gone — desired state reached
|
||||||
|
}
|
||||||
|
return QFile::remove(agentPath);
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoStartManager::isEnabled()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
return QFile::exists(launchAgentPath());
|
||||||
|
#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");
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#ifndef AUTOSTART_MANAGER_H
|
||||||
|
#define AUTOSTART_MANAGER_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class AutoStartManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static bool enableForCurrentApplication();
|
||||||
|
static bool enableForPath(const QString &executablePath);
|
||||||
|
static bool disable();
|
||||||
|
static bool isEnabled();
|
||||||
|
static QString launchAgentContents(const QString &executablePath);
|
||||||
|
static QString launchAgentPath();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // AUTOSTART_MANAGER_H
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Build script
|
# Build script
|
||||||
|
|
||||||
echo "Starting QFlock build..."
|
echo "Starting DualFloatingClock build..."
|
||||||
|
|
||||||
# Create build directory if it doesn't exist
|
# Create build directory if it doesn't exist
|
||||||
mkdir -p build
|
mkdir -p build
|
||||||
@@ -12,18 +12,18 @@ rm -rf build/*
|
|||||||
|
|
||||||
# Generate Makefile using qmake
|
# Generate Makefile using qmake
|
||||||
echo "Generating Makefile..."
|
echo "Generating Makefile..."
|
||||||
qmake Clouck.pro
|
qmake DualFloatingClock.pro
|
||||||
|
|
||||||
# Build project
|
# Build project
|
||||||
echo "Starting compilation..."
|
echo "Starting compilation..."
|
||||||
make
|
make
|
||||||
|
|
||||||
# Check build result
|
# Check build result
|
||||||
if [ -f "build/Clouck.app/Contents/MacOS/Clouck" ] || [ -f "build/Clouck" ]; then
|
if [ -f "build/DualFloatingClock.app/Contents/MacOS/DualFloatingClock" ] || [ -f "build/DualFloatingClock" ]; then
|
||||||
echo "Build successful!"
|
echo "Build successful!"
|
||||||
echo "Executable location:"
|
echo "Executable location:"
|
||||||
[ -f "build/Clouck.app/Contents/MacOS/Clouck" ] && echo " - build/Clouck.app/Contents/MacOS/Clouck (macOS)"
|
[ -f "build/DualFloatingClock.app/Contents/MacOS/DualFloatingClock" ] && echo " - build/DualFloatingClock.app/Contents/MacOS/DualFloatingClock (macOS)"
|
||||||
[ -f "build/Clouck" ] && echo " - build/Clouck (Linux)"
|
[ -f "build/DualFloatingClock" ] && echo " - build/DualFloatingClock (Linux)"
|
||||||
else
|
else
|
||||||
echo "Build failed!"
|
echo "Build failed!"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Cross-platform build script for Clouck
|
# Cross-platform build script for DualFloatingClock
|
||||||
|
|
||||||
echo "Building Clouck - Cross-platform Clock Application"
|
echo "Building DualFloatingClock - Cross-platform Clock Application"
|
||||||
echo "=================================================="
|
echo "=================================================="
|
||||||
|
|
||||||
# Detect operating system
|
# Detect operating system
|
||||||
@@ -49,19 +49,19 @@ echo ""
|
|||||||
echo "Executable location:"
|
echo "Executable location:"
|
||||||
|
|
||||||
if [ "$OS" == "macOS" ]; then
|
if [ "$OS" == "macOS" ]; then
|
||||||
echo " - build/Clouck.app/Contents/MacOS/Clouck (macOS)"
|
echo " - build/DualFloatingClock.app/Contents/MacOS/DualFloatingClock (macOS)"
|
||||||
elif [ "$OS" == "Windows" ]; then
|
elif [ "$OS" == "Windows" ]; then
|
||||||
echo " - build/Release/Clouck.exe (Windows)"
|
echo " - build/Release/DualFloatingClock.exe (Windows)"
|
||||||
elif [ "$OS" == "Linux" ]; then
|
elif [ "$OS" == "Linux" ]; then
|
||||||
echo " - build/Clouck (Linux)"
|
echo " - build/DualFloatingClock (Linux)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "To run the application:"
|
echo "To run the application:"
|
||||||
if [ "$OS" == "macOS" ]; then
|
if [ "$OS" == "macOS" ]; then
|
||||||
echo " ./build/Clouck.app/Contents/MacOS/Clouck"
|
echo " ./build/DualFloatingClock.app/Contents/MacOS/DualFloatingClock"
|
||||||
elif [ "$OS" == "Windows" ]; then
|
elif [ "$OS" == "Windows" ]; then
|
||||||
echo " ./build/Release/Clouck.exe"
|
echo " ./build/Release/DualFloatingClock.exe"
|
||||||
elif [ "$OS" == "Linux" ]; then
|
elif [ "$OS" == "Linux" ]; then
|
||||||
echo " ./build/Clouck"
|
echo " ./build/DualFloatingClock"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ if [ -d "build" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Delete .app bundle
|
# Delete .app bundle
|
||||||
if [ -d "Clouck.app" ]; then
|
if [ -d "DualFloatingClock.app" ]; then
|
||||||
rm -rf Clouck.app
|
rm -rf DualFloatingClock.app
|
||||||
echo "Clouck.app deleted"
|
echo "DualFloatingClock.app deleted"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Cleanup complete!"
|
echo "Cleanup complete!"
|
||||||
|
|||||||
+338
-30
@@ -2,12 +2,77 @@
|
|||||||
#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 <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)
|
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)
|
||||||
@@ -27,6 +92,7 @@ ClockWidget::ClockWidget(QWidget *parent)
|
|||||||
|
|
||||||
setWindowFlags(flags);
|
setWindowFlags(flags);
|
||||||
setAttribute(Qt::WA_TranslucentBackground);
|
setAttribute(Qt::WA_TranslucentBackground);
|
||||||
|
setAttribute(Qt::WA_StyledBackground); // required for widget-level QSS background/border
|
||||||
setMouseTracking(true); // Enable mouse tracking for resize cursor
|
setMouseTracking(true); // Enable mouse tracking for resize cursor
|
||||||
|
|
||||||
// Set up platform-specific window properties after window creation
|
// Set up platform-specific window properties after window creation
|
||||||
@@ -47,12 +113,18 @@ ClockWidget::~ClockWidget()
|
|||||||
|
|
||||||
void ClockWidget::setupUI()
|
void ClockWidget::setupUI()
|
||||||
{
|
{
|
||||||
m_timeLabel = new QLabel(this);
|
m_primaryTimeLabel = new ClockLabel(this);
|
||||||
m_timeLabel->setAlignment(Qt::AlignCenter);
|
m_primaryLocationLabel = new ClockLabel(this);
|
||||||
|
m_secondaryTimeLabel = new ClockLabel(this);
|
||||||
|
m_secondaryLocationLabel = new ClockLabel(this);
|
||||||
|
|
||||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||||
layout->addWidget(m_timeLabel);
|
layout->addWidget(m_primaryTimeLabel);
|
||||||
|
layout->addWidget(m_primaryLocationLabel);
|
||||||
|
layout->addWidget(m_secondaryTimeLabel);
|
||||||
|
layout->addWidget(m_secondaryLocationLabel);
|
||||||
layout->setContentsMargins(10, 10, 10, 10);
|
layout->setContentsMargins(10, 10, 10, 10);
|
||||||
|
layout->setSpacing(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClockWidget::createMenu()
|
void ClockWidget::createMenu()
|
||||||
@@ -70,9 +142,21 @@ void ClockWidget::createMenu()
|
|||||||
QAction *bgColorAction = new QAction("Set Background Color", this);
|
QAction *bgColorAction = new QAction("Set Background Color", this);
|
||||||
connect(bgColorAction, &QAction::triggered, this, &ClockWidget::changeBackgroundColor);
|
connect(bgColorAction, &QAction::triggered, this, &ClockWidget::changeBackgroundColor);
|
||||||
|
|
||||||
QAction *fontSizeAction = new QAction("Set Font Size", this);
|
QAction *fontSizeAction = new QAction("Set Time Font Size", this);
|
||||||
connect(fontSizeAction, &QAction::triggered, this, &ClockWidget::changeFontSize);
|
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);
|
QAction *resetAction = new QAction("Reset Settings", this);
|
||||||
connect(resetAction, &QAction::triggered, this, &ClockWidget::resetSettings);
|
connect(resetAction, &QAction::triggered, this, &ClockWidget::resetSettings);
|
||||||
|
|
||||||
@@ -83,7 +167,13 @@ void ClockWidget::createMenu()
|
|||||||
m_contextMenu->addSeparator();
|
m_contextMenu->addSeparator();
|
||||||
m_contextMenu->addAction(fontColorAction);
|
m_contextMenu->addAction(fontColorAction);
|
||||||
m_contextMenu->addAction(bgColorAction);
|
m_contextMenu->addAction(bgColorAction);
|
||||||
|
m_contextMenu->addAction(borderColorAction);
|
||||||
|
m_contextMenu->addSeparator();
|
||||||
m_contextMenu->addAction(fontSizeAction);
|
m_contextMenu->addAction(fontSizeAction);
|
||||||
|
m_contextMenu->addAction(locationFontSizeAction);
|
||||||
|
m_contextMenu->addSeparator();
|
||||||
|
m_contextMenu->addAction(primaryTimeZoneAction);
|
||||||
|
m_contextMenu->addAction(secondaryTimeZoneAction);
|
||||||
m_contextMenu->addSeparator();
|
m_contextMenu->addSeparator();
|
||||||
m_contextMenu->addAction(resetAction);
|
m_contextMenu->addAction(resetAction);
|
||||||
m_contextMenu->addAction(quitAction);
|
m_contextMenu->addAction(quitAction);
|
||||||
@@ -173,13 +263,52 @@ void ClockWidget::mouseReleaseEvent(QMouseEvent *event)
|
|||||||
|
|
||||||
void ClockWidget::contextMenuEvent(QContextMenuEvent *event)
|
void ClockWidget::contextMenuEvent(QContextMenuEvent *event)
|
||||||
{
|
{
|
||||||
m_contextMenu->exec(event->globalPos());
|
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()
|
void ClockWidget::updateTime()
|
||||||
{
|
{
|
||||||
QString currentTime = QTime::currentTime().toString("HH:mm:ss");
|
const QTimeZone primaryTimeZone(m_primaryTimeZoneId.toUtf8());
|
||||||
m_timeLabel->setText(currentTime);
|
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()
|
void ClockWidget::toggleAlwaysOnTop()
|
||||||
@@ -207,38 +336,76 @@ void ClockWidget::toggleAlwaysOnTop()
|
|||||||
|
|
||||||
void ClockWidget::changeFontColor()
|
void ClockWidget::changeFontColor()
|
||||||
{
|
{
|
||||||
QColor color = QColorDialog::getColor(m_fontColor, this, "Choose Font Color");
|
const std::optional<QColor> color = pickColor("Font Color", m_fontColor, this);
|
||||||
if (color.isValid())
|
if (color)
|
||||||
{
|
{
|
||||||
m_fontColor = color;
|
m_fontColor = *color;
|
||||||
m_configManager->setFontColor(m_fontColor);
|
m_configManager->setFontColor(m_fontColor);
|
||||||
updateStyleSheet();
|
updateStyleSheet();
|
||||||
|
saveSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClockWidget::changeBackgroundColor()
|
void ClockWidget::changeBackgroundColor()
|
||||||
{
|
{
|
||||||
QColor color = QColorDialog::getColor(m_backgroundColor, this, "Choose Background Color");
|
const std::optional<QColor> color = pickColor("Background Color", m_backgroundColor, this);
|
||||||
if (color.isValid())
|
if (color)
|
||||||
{
|
{
|
||||||
m_backgroundColor = color;
|
m_backgroundColor = *color;
|
||||||
m_configManager->setBackgroundColor(m_backgroundColor);
|
m_configManager->setBackgroundColor(m_backgroundColor);
|
||||||
updateStyleSheet();
|
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()
|
void ClockWidget::changeFontSize()
|
||||||
{
|
{
|
||||||
bool ok;
|
bool ok;
|
||||||
int size = QInputDialog::getInt(this, "Set Font Size", "Font Size:", m_fontSize, 8, 72, 1, &ok);
|
int size = QInputDialog::getInt(this, "Set Time Font Size", "Font Size:", m_fontSize, 8, 200, 1, &ok);
|
||||||
if (ok)
|
if (ok)
|
||||||
{
|
{
|
||||||
m_fontSize = size;
|
m_fontSize = size;
|
||||||
m_configManager->setFontSize(m_fontSize);
|
m_configManager->setFontSize(m_fontSize);
|
||||||
updateStyleSheet();
|
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()
|
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?");
|
||||||
@@ -265,6 +432,28 @@ bool ClockWidget::isInResizeArea(const QPoint &pos) const
|
|||||||
return resizeArea.contains(pos);
|
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()
|
void ClockWidget::loadSettings()
|
||||||
{
|
{
|
||||||
m_configManager = new ConfigManager(this);
|
m_configManager = new ConfigManager(this);
|
||||||
@@ -279,11 +468,24 @@ void ClockWidget::loadSettings()
|
|||||||
// Apply loaded settings
|
// Apply loaded settings
|
||||||
m_fontColor = m_configManager->fontColor();
|
m_fontColor = m_configManager->fontColor();
|
||||||
m_backgroundColor = m_configManager->backgroundColor();
|
m_backgroundColor = m_configManager->backgroundColor();
|
||||||
|
m_borderColor = m_configManager->borderColor();
|
||||||
m_fontSize = m_configManager->fontSize();
|
m_fontSize = m_configManager->fontSize();
|
||||||
|
m_locationFontSize = m_configManager->locationFontSize();
|
||||||
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
|
||||||
move(m_configManager->windowPosition());
|
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());
|
resize(m_configManager->windowSize());
|
||||||
|
|
||||||
// Update always on top checkbox in menu
|
// Update always on top checkbox in menu
|
||||||
@@ -308,10 +510,14 @@ void ClockWidget::saveSettings()
|
|||||||
// Update config manager with current values
|
// Update config manager with current values
|
||||||
m_configManager->setFontColor(m_fontColor);
|
m_configManager->setFontColor(m_fontColor);
|
||||||
m_configManager->setBackgroundColor(m_backgroundColor);
|
m_configManager->setBackgroundColor(m_backgroundColor);
|
||||||
|
m_configManager->setBorderColor(m_borderColor);
|
||||||
m_configManager->setFontSize(m_fontSize);
|
m_configManager->setFontSize(m_fontSize);
|
||||||
|
m_configManager->setLocationFontSize(m_locationFontSize);
|
||||||
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())
|
||||||
@@ -322,19 +528,121 @@ void ClockWidget::saveSettings()
|
|||||||
|
|
||||||
void ClockWidget::updateStyleSheet()
|
void ClockWidget::updateStyleSheet()
|
||||||
{
|
{
|
||||||
QString styleSheet = QString(
|
// Fonts via QSS; glyph fill + outline via ClockLabel::paintEvent.
|
||||||
"QLabel { "
|
const auto labelFontStyleSheet = [](int size) {
|
||||||
"color: %1; "
|
return QString("QLabel { font-family: 'SF Pro Rounded'; font-size: %1pt; background: transparent; }")
|
||||||
"font-size: %2pt; "
|
.arg(size);
|
||||||
"font-weight: bold; "
|
};
|
||||||
"background-color: %3; "
|
|
||||||
"border-radius: 10px; "
|
|
||||||
"padding: 5px; "
|
|
||||||
"}")
|
|
||||||
.arg(m_fontColor.name(),
|
|
||||||
QString::number(m_fontSize),
|
|
||||||
m_backgroundColor.name(QColor::HexArgb));
|
|
||||||
|
|
||||||
m_timeLabel->setStyleSheet(styleSheet);
|
m_primaryTimeLabel->setStyleSheet(labelFontStyleSheet(m_fontSize));
|
||||||
setStyleSheet(QString("background-color: transparent;"));
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-1
@@ -8,9 +8,30 @@
|
|||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
class ConfigManager;
|
class ConfigManager;
|
||||||
|
|
||||||
|
// QLabel that paints its text with an optional outline (border around the
|
||||||
|
// glyphs, not a box). QSS cannot stroke text, so we use a QPainterPath.
|
||||||
|
class ClockLabel : public QLabel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ClockLabel(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
void setDisplayColors(const QColor &textColor, const QColor &outlineColor, qreal outlineWidth);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QColor m_textColor;
|
||||||
|
QColor m_outlineColor;
|
||||||
|
qreal m_outlineWidth;
|
||||||
|
};
|
||||||
|
|
||||||
class ClockWidget : public QWidget
|
class ClockWidget : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -19,18 +40,28 @@ public:
|
|||||||
explicit ClockWidget(QWidget *parent = nullptr);
|
explicit ClockWidget(QWidget *parent = nullptr);
|
||||||
~ClockWidget();
|
~ClockWidget();
|
||||||
|
|
||||||
|
QMenu *menu() const { return m_contextMenu; }
|
||||||
|
|
||||||
|
QString primaryTimeZoneId() const { return m_primaryTimeZoneId; }
|
||||||
|
QString secondaryTimeZoneId() const { return m_secondaryTimeZoneId; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void mousePressEvent(QMouseEvent *event) override;
|
void mousePressEvent(QMouseEvent *event) override;
|
||||||
void mouseMoveEvent(QMouseEvent *event) override;
|
void mouseMoveEvent(QMouseEvent *event) override;
|
||||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||||
void contextMenuEvent(QContextMenuEvent *event) override;
|
void contextMenuEvent(QContextMenuEvent *event) override;
|
||||||
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void updateTime();
|
void updateTime();
|
||||||
void toggleAlwaysOnTop();
|
void toggleAlwaysOnTop();
|
||||||
void changeFontColor();
|
void changeFontColor();
|
||||||
void changeBackgroundColor();
|
void changeBackgroundColor();
|
||||||
|
void changeBorderColor();
|
||||||
void changeFontSize();
|
void changeFontSize();
|
||||||
|
void changeLocationFontSize();
|
||||||
|
void changePrimaryTimeZone();
|
||||||
|
void changeSecondaryTimeZone();
|
||||||
void resetSettings();
|
void resetSettings();
|
||||||
void quitApplication();
|
void quitApplication();
|
||||||
|
|
||||||
@@ -40,8 +71,15 @@ private:
|
|||||||
void loadSettings();
|
void loadSettings();
|
||||||
void saveSettings();
|
void saveSettings();
|
||||||
void updateStyleSheet();
|
void updateStyleSheet();
|
||||||
|
void changeTimeZone(bool primaryClock);
|
||||||
|
QString formattedTime(const QString &timeZoneId) const;
|
||||||
|
QString locationName(const QString &timeZoneId) const;
|
||||||
|
std::optional<QColor> pickColor(const QString &title, const QColor ¤t, QWidget *parent);
|
||||||
|
|
||||||
QLabel *m_timeLabel;
|
ClockLabel *m_primaryTimeLabel;
|
||||||
|
ClockLabel *m_primaryLocationLabel;
|
||||||
|
ClockLabel *m_secondaryTimeLabel;
|
||||||
|
ClockLabel *m_secondaryLocationLabel;
|
||||||
QTimer *m_timer;
|
QTimer *m_timer;
|
||||||
QMenu *m_contextMenu;
|
QMenu *m_contextMenu;
|
||||||
|
|
||||||
@@ -59,11 +97,16 @@ private:
|
|||||||
ConfigManager *m_configManager;
|
ConfigManager *m_configManager;
|
||||||
QColor m_fontColor;
|
QColor m_fontColor;
|
||||||
QColor m_backgroundColor;
|
QColor m_backgroundColor;
|
||||||
|
QColor m_borderColor;
|
||||||
int m_fontSize;
|
int m_fontSize;
|
||||||
|
int m_locationFontSize;
|
||||||
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;
|
||||||
|
void positionAtBottomRight();
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
#ifdef Q_OS_MAC
|
||||||
void setupMacOSWindowProperties();
|
void setupMacOSWindowProperties();
|
||||||
|
|||||||
+8
-6
@@ -1,9 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<QFlockConfig>
|
<DualFloatingClockConfig>
|
||||||
<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>1132,708</WindowPosition>
|
<WindowPosition>1236,784</WindowPosition>
|
||||||
<WindowSize>137,59</WindowSize>
|
<WindowSize>240,160</WindowSize>
|
||||||
</QFlockConfig>
|
<PrimaryTimeZone>Europe/Bucharest</PrimaryTimeZone>
|
||||||
|
<SecondaryTimeZone>America/Los_Angeles</SecondaryTimeZone>
|
||||||
|
</DualFloatingClockConfig>
|
||||||
|
|||||||
+83
-15
@@ -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";
|
||||||
@@ -26,7 +48,8 @@ bool ConfigManager::loadSettings(const QString &filename)
|
|||||||
QXmlStreamReader reader(&file);
|
QXmlStreamReader reader(&file);
|
||||||
|
|
||||||
// Check if it's a valid XML file
|
// Check if it's a valid XML file
|
||||||
if (!reader.readNextStartElement() || reader.name() != QString("QFlockConfig"))
|
if (!reader.readNextStartElement()
|
||||||
|
|| (reader.name() != QString("DualFloatingClockConfig") && reader.name() != QString("ClouckConfig")))
|
||||||
{
|
{
|
||||||
qDebug() << "Invalid config file format";
|
qDebug() << "Invalid config file format";
|
||||||
file.close();
|
file.close();
|
||||||
@@ -77,6 +100,22 @@ 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 if (name == "BorderColor")
|
||||||
|
{
|
||||||
|
m_borderColor = QColor(text);
|
||||||
|
}
|
||||||
|
else if (name == "LocationFontSize")
|
||||||
|
{
|
||||||
|
m_locationFontSize = text.toInt();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
reader.skipCurrentElement();
|
reader.skipCurrentElement();
|
||||||
@@ -96,7 +135,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();
|
||||||
@@ -108,16 +160,20 @@ bool ConfigManager::saveSettings(const QString &filename)
|
|||||||
writer.writeStartDocument();
|
writer.writeStartDocument();
|
||||||
|
|
||||||
// Root element
|
// Root element
|
||||||
writer.writeStartElement("QFlockConfig");
|
writer.writeStartElement("DualFloatingClockConfig");
|
||||||
|
|
||||||
// Font color
|
// Colors: store #RRGGBB when fully opaque, #AARRGGBB when translucent —
|
||||||
writer.writeTextElement("FontColor", m_fontColor.name(QColor::HexArgb));
|
// the XML stays close to what the user typed.
|
||||||
|
const auto colorString = [](const QColor &color) {
|
||||||
|
return color.alpha() == 255 ? color.name(QColor::HexRgb) : color.name(QColor::HexArgb);
|
||||||
|
};
|
||||||
|
writer.writeTextElement("FontColor", colorString(m_fontColor));
|
||||||
|
writer.writeTextElement("BackgroundColor", colorString(m_backgroundColor));
|
||||||
|
writer.writeTextElement("BorderColor", colorString(m_borderColor));
|
||||||
|
|
||||||
// Background color
|
// Font sizes
|
||||||
writer.writeTextElement("BackgroundColor", m_backgroundColor.name(QColor::HexArgb));
|
|
||||||
|
|
||||||
// Font size
|
|
||||||
writer.writeTextElement("FontSize", QString::number(m_fontSize));
|
writer.writeTextElement("FontSize", QString::number(m_fontSize));
|
||||||
|
writer.writeTextElement("LocationFontSize", QString::number(m_locationFontSize));
|
||||||
|
|
||||||
// Always on top
|
// Always on top
|
||||||
writer.writeTextElement("AlwaysOnTop", m_alwaysOnTop ? "true" : "false");
|
writer.writeTextElement("AlwaysOnTop", m_alwaysOnTop ? "true" : "false");
|
||||||
@@ -134,6 +190,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,15 +209,24 @@ 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;
|
||||||
m_windowPosition = QPoint(100, 100);
|
|
||||||
m_windowSize = QSize(200, 80); // Default clock size
|
// Calculate initial position: bottom-right corner of primary screen
|
||||||
|
// 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_windowSize = QSize(240, 160); // Default clock size
|
||||||
|
m_primaryTimeZoneId = QString::fromUtf8(QTimeZone::systemTimeZoneId());
|
||||||
|
m_secondaryTimeZoneId = QStringLiteral("America/Los_Angeles");
|
||||||
|
m_borderColor = QColor(0, 0, 0, 0); // No border by default
|
||||||
|
m_locationFontSize = 12; // Smaller city label under the time
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-3
@@ -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,10 @@ 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; }
|
||||||
|
QColor borderColor() const { return m_borderColor; }
|
||||||
|
int locationFontSize() const { return m_locationFontSize; }
|
||||||
|
|
||||||
// Setters
|
// Setters
|
||||||
void setFontColor(const QColor &color) { m_fontColor = color; }
|
void setFontColor(const QColor &color) { m_fontColor = color; }
|
||||||
@@ -37,12 +43,16 @@ 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; }
|
||||||
|
void setBorderColor(const QColor &color) { m_borderColor = color; }
|
||||||
|
void setLocationFontSize(int size) { m_locationFontSize = size; }
|
||||||
|
|
||||||
// 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 +65,10 @@ 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;
|
||||||
|
QColor m_borderColor;
|
||||||
|
int m_locationFontSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONFIG_MANAGER_H
|
#endif // CONFIG_MANAGER_H
|
||||||
|
|||||||
@@ -1,16 +1,143 @@
|
|||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QAction>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QSettings>
|
||||||
|
#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)
|
||||||
|
{
|
||||||
|
// Render at 16 logical points times the device pixel ratio so the
|
||||||
|
// bitmap is crisp on Retina but occupies exactly one menu bar cell.
|
||||||
|
const qreal devicePixelRatio = qApp->devicePixelRatio();
|
||||||
|
QPixmap pixmap(qRound(trayIconExtent * devicePixelRatio),
|
||||||
|
qRound(trayIconExtent * devicePixelRatio));
|
||||||
|
pixmap.setDevicePixelRatio(devicePixelRatio);
|
||||||
|
pixmap.fill(Qt::transparent);
|
||||||
|
|
||||||
|
QPainter painter(&pixmap);
|
||||||
|
painter.setRenderHint(QPainter::Antialiasing);
|
||||||
|
painter.setPen(QPen(color, 1.5));
|
||||||
|
|
||||||
|
// Draw inside the 16pt coordinate space; center with a small margin.
|
||||||
|
const QPointF center(trayIconExtent / 2.0, trayIconExtent / 2.0);
|
||||||
|
const qreal radius = trayIconExtent / 2.0 - 1.5;
|
||||||
|
painter.drawEllipse(center, radius, radius);
|
||||||
|
painter.drawLine(QPointF(center.x(), center.y() - radius * 0.55), center);
|
||||||
|
painter.drawLine(center, QPointF(center.x() + radius * 0.5, center.y() + radius * 0.3));
|
||||||
|
|
||||||
|
QIcon icon;
|
||||||
|
icon.addPixmap(pixmap, QIcon::Normal, QIcon::Off);
|
||||||
|
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);
|
||||||
|
|
||||||
app.setApplicationName("QFlock");
|
// Menu-bar-only app: no Dock icon, no app switcher entry.
|
||||||
app.setOrganizationName("QFlock");
|
#ifdef Q_OS_MAC
|
||||||
app.setApplicationDisplayName("QFlock");
|
extern void clouck_setActivationPolicyAccessory(); // defined in window_helper_macos.mm
|
||||||
|
clouck_setActivationPolicyAccessory();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
app.setApplicationName("DualFloatingClock");
|
||||||
|
app.setOrganizationName("DualFloatingClock");
|
||||||
|
app.setApplicationDisplayName("DualFloatingClock");
|
||||||
|
|
||||||
ClockWidget clock;
|
ClockWidget clock;
|
||||||
clock.show();
|
clock.show();
|
||||||
|
|
||||||
|
// Start at Login: honor the persisted choice instead of always forcing it.
|
||||||
|
{
|
||||||
|
QSettings autoStartSettings("DualFloatingClock", "DualFloatingClock");
|
||||||
|
const bool startAtLogin = autoStartSettings.value("startAtLogin", true).toBool();
|
||||||
|
if (startAtLogin)
|
||||||
|
{
|
||||||
|
AutoStartManager::enableForCurrentApplication();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AutoStartManager::disable();
|
||||||
|
}
|
||||||
|
autoStartSettings.setValue("startAtLogin", startAtLogin);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStyleHints *styleHints = app.styleHints();
|
||||||
|
QSystemTrayIcon trayIcon(createTrayIcon(trayIconColorForScheme(styleHints->colorScheme())), &app);
|
||||||
|
trayIcon.setToolTip("DualFloatingClock");
|
||||||
|
|
||||||
|
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 *startAtLoginAction = trayMenu.addAction("Start at Login");
|
||||||
|
{
|
||||||
|
QSettings autoStartSettings("DualFloatingClock", "DualFloatingClock");
|
||||||
|
startAtLoginAction->setCheckable(true);
|
||||||
|
startAtLoginAction->setChecked(autoStartSettings.value("startAtLogin", true).toBool());
|
||||||
|
}
|
||||||
|
QObject::connect(startAtLoginAction, &QAction::toggled, &app, [](bool checked) {
|
||||||
|
QSettings autoStartSettings("DualFloatingClock", "DualFloatingClock");
|
||||||
|
autoStartSettings.setValue("startAtLogin", checked);
|
||||||
|
if (checked)
|
||||||
|
{
|
||||||
|
AutoStartManager::enableForCurrentApplication();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AutoStartManager::disable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
trayMenu.addSeparator();
|
||||||
|
|
||||||
|
// Clock settings live directly in the tray menu — no submenu.
|
||||||
|
const QList<QAction *> clockActions = clock.menu()->actions();
|
||||||
|
for (QAction *action : clockActions)
|
||||||
|
{
|
||||||
|
trayMenu.addAction(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
trayMenu.addSeparator();
|
||||||
|
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);
|
||||||
|
|
||||||
|
trayIcon.setContextMenu(&trayMenu);
|
||||||
|
trayIcon.show();
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -1,27 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo "QFlock Clock Application - macOS Fullscreen Always-on-Top Feature Test"
|
|
||||||
echo "======================================================================"
|
|
||||||
echo ""
|
|
||||||
echo "[SUCCESS] Compilation successful! Application build completed"
|
|
||||||
echo "[SUCCESS] macOS fullscreen always-on-top feature integrated"
|
|
||||||
echo ""
|
|
||||||
echo "Feature highlights:"
|
|
||||||
echo "• Basic always-on-top using Qt::WindowStaysOnTopHint"
|
|
||||||
echo "• Native macOS window level with NSWindowLevel"
|
|
||||||
echo "• Support for all desktop spaces (NSWindowCollectionBehaviorCanJoinAllSpaces)"
|
|
||||||
echo "• Fullscreen mode support (NSWindowCollectionBehaviorFullScreenAuxiliary)"
|
|
||||||
echo "• Prevents window focus stealing (Qt::WindowDoesNotAcceptFocus)"
|
|
||||||
echo ""
|
|
||||||
echo "Testing method:"
|
|
||||||
echo "1. Run ./build/Clouck.app/Contents/MacOS/Clouck"
|
|
||||||
echo "2. Open Safari or other apps in fullscreen mode"
|
|
||||||
echo "3. Observe if clock stays on top layer"
|
|
||||||
echo ""
|
|
||||||
echo "Optimized file structure:"
|
|
||||||
echo "• clock_widget.h/cpp - Main clock component"
|
|
||||||
echo "• config_manager.h/cpp - Configuration management"
|
|
||||||
echo "• window_helper_macos.h/mm - macOS native window helper"
|
|
||||||
echo ""
|
|
||||||
echo "Press any key to exit..."
|
|
||||||
read -n 1 -s
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#include <QCoreApplication>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QSize>
|
|
||||||
#include <QPoint>
|
|
||||||
#include "config_manager.h"
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
QCoreApplication app(argc, argv);
|
|
||||||
|
|
||||||
ConfigManager configManager;
|
|
||||||
|
|
||||||
// Set some test values
|
|
||||||
configManager.setWindowPosition(QPoint(100, 200));
|
|
||||||
configManager.setWindowSize(QSize(300, 400));
|
|
||||||
|
|
||||||
// Save to file
|
|
||||||
if (configManager.saveSettings("test_config.xml"))
|
|
||||||
{
|
|
||||||
qDebug() << "Config saved successfully";
|
|
||||||
|
|
||||||
// Read the file content
|
|
||||||
QFile file("test_config.xml");
|
|
||||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
|
|
||||||
{
|
|
||||||
QString content = file.readAll();
|
|
||||||
qDebug() << "File content:";
|
|
||||||
qDebug() << content;
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qDebug() << "Failed to save config";
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Cross-platform test script for Clouck
|
|
||||||
|
|
||||||
echo "Clouck Cross-Platform Test Suite"
|
|
||||||
echo "================================"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Detect operating system
|
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
||||||
OS="macOS"
|
|
||||||
EXECUTABLE="./build/Clouck"
|
|
||||||
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
||||||
OS="Linux"
|
|
||||||
EXECUTABLE="./build/Clouck"
|
|
||||||
elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "win32" ]]; then
|
|
||||||
OS="Windows"
|
|
||||||
EXECUTABLE="./build/Clouck"
|
|
||||||
else
|
|
||||||
echo "Unsupported operating system: $OSTYPE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Detected OS: $OS"
|
|
||||||
echo "Executable: $EXECUTABLE"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Check if executable exists
|
|
||||||
if [ ! -f "$EXECUTABLE" ]; then
|
|
||||||
echo "[ERROR] Executable not found: $EXECUTABLE"
|
|
||||||
echo "Please build the project first using: ./build_cross_platform.sh"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[SUCCESS] Executable found"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Test 1: Basic functionality
|
|
||||||
echo "Test 1: Basic Clock Functionality"
|
|
||||||
echo "---------------------------------"
|
|
||||||
echo "• Starting Clouck application..."
|
|
||||||
echo "• Application should display a clock widget"
|
|
||||||
echo "• Clock should update every second"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Start the application in background
|
|
||||||
$EXECUTABLE &
|
|
||||||
PID=$!
|
|
||||||
sleep 3
|
|
||||||
|
|
||||||
# Check if application is running
|
|
||||||
if ps -p $PID >/dev/null; then
|
|
||||||
echo "[SUCCESS] Application started successfully (PID: $PID)"
|
|
||||||
else
|
|
||||||
echo "[ERROR] Application failed to start"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Test 2: Always-on-Top Feature"
|
|
||||||
echo "------------------------------"
|
|
||||||
if [ "$OS" == "macOS" ]; then
|
|
||||||
echo "• Open Safari or any other application"
|
|
||||||
echo "• Enter fullscreen mode (Control + Command + F)"
|
|
||||||
echo "• Clock should remain visible on top"
|
|
||||||
elif [ "$OS" == "Windows" ]; then
|
|
||||||
echo "• Open any application in fullscreen mode"
|
|
||||||
echo "• Clock should remain visible on top"
|
|
||||||
elif [ "$OS" == "Linux" ]; then
|
|
||||||
echo "• Switch to different workspace"
|
|
||||||
echo "• Clock should appear on all workspaces"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Test 3: Resizable Feature"
|
|
||||||
echo "-------------------------"
|
|
||||||
echo "• Move mouse to bottom-right corner of clock"
|
|
||||||
echo "• Cursor should change to diagonal resize arrow"
|
|
||||||
echo "• Click and drag to resize"
|
|
||||||
echo "• Release mouse to save new size"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Test 4: Context Menu"
|
|
||||||
echo "---------------------"
|
|
||||||
echo "• Right-click on clock to show context menu"
|
|
||||||
echo "• Test various menu options:"
|
|
||||||
echo " - Change color"
|
|
||||||
echo " - Change font"
|
|
||||||
echo " - Toggle always on top"
|
|
||||||
echo " - Toggle click through"
|
|
||||||
echo " - Quit application"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Platform-Specific Features:"
|
|
||||||
echo "---------------------------"
|
|
||||||
if [ "$OS" == "macOS" ]; then
|
|
||||||
echo "• Native macOS window level integration"
|
|
||||||
echo "• Support for all desktop spaces"
|
|
||||||
echo "• Fullscreen mode compatibility"
|
|
||||||
elif [ "$OS" == "Windows" ]; then
|
|
||||||
echo "• Windows native window management"
|
|
||||||
echo "• Taskbar exclusion"
|
|
||||||
echo "• Click-through support"
|
|
||||||
elif [ "$OS" == "Linux" ]; then
|
|
||||||
echo "• X11 window manager integration"
|
|
||||||
echo "• Workspace stickiness"
|
|
||||||
echo "• EWMH (Extended Window Manager Hints) support"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "To stop testing:"
|
|
||||||
echo "• Right-click on clock and select 'Quit'"
|
|
||||||
echo "• Or run: kill $PID"
|
|
||||||
echo ""
|
|
||||||
echo "Test the features now! Press any key when finished..."
|
|
||||||
read -n 1 -s
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Stopping application..."
|
|
||||||
kill $PID 2>/dev/null
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Test completed!"
|
|
||||||
echo ""
|
|
||||||
echo "Configuration file location:"
|
|
||||||
echo "• macOS: ~/Library/Preferences/Clouck/config.xml"
|
|
||||||
echo "• Windows: %APPDATA%\\Clouck\\config.xml"
|
|
||||||
echo "• Linux: ~/.config/Clouck/config.xml"
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo "Testing QFlock Clock Application Fullscreen Always-on-Top Feature..."
|
|
||||||
echo ""
|
|
||||||
echo "Test Steps:"
|
|
||||||
echo "1. Clock application should already be running"
|
|
||||||
echo "2. Open any application (like Safari) and enter fullscreen mode"
|
|
||||||
echo "3. Observe if the clock still displays above the fullscreen application"
|
|
||||||
echo "4. You can use Command+Tab to switch applications, the clock should remain visible"
|
|
||||||
echo ""
|
|
||||||
echo "Press any key to exit test instructions..."
|
|
||||||
read -n 1 -s
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Test complete! If the clock remains visible in fullscreen mode, the feature is working correctly."
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# GitHub Actions Local Testing Script
|
|
||||||
# This script helps test GitHub Actions workflows locally using act
|
|
||||||
|
|
||||||
echo "GitHub Actions Local Testing for Clouck"
|
|
||||||
echo "========================================"
|
|
||||||
|
|
||||||
# Check if act is installed
|
|
||||||
if ! command -v act &> /dev/null; then
|
|
||||||
echo "❌ act is not installed. Installing..."
|
|
||||||
echo "Please install act from: https://github.com/nektos/act"
|
|
||||||
echo "On macOS: brew install act"
|
|
||||||
echo "On Linux: curl https://raw.githubusercontent.com/nektos/act/master/install.sh | bash"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Display available workflows
|
|
||||||
echo "Available workflows:"
|
|
||||||
echo "1. CI Build (ci.yml) - Quick validation builds"
|
|
||||||
echo "2. Build Qt (build.yml) - Comprehensive builds"
|
|
||||||
echo "3. Release (release.yml) - Create releases"
|
|
||||||
echo "4. All workflows"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Function to run workflow
|
|
||||||
run_workflow() {
|
|
||||||
local workflow=$1
|
|
||||||
local event=$2
|
|
||||||
|
|
||||||
echo "Running $workflow with event: $event"
|
|
||||||
act -W .github/workflows/$workflow -e $event --dry-run
|
|
||||||
}
|
|
||||||
|
|
||||||
# Menu
|
|
||||||
read -p "Which workflow do you want to test? (1-4): " choice
|
|
||||||
|
|
||||||
case $choice in
|
|
||||||
1)
|
|
||||||
echo "Testing CI Build workflow..."
|
|
||||||
run_workflow "ci.yml" "push"
|
|
||||||
;;
|
|
||||||
2)
|
|
||||||
echo "Testing Build Qt workflow..."
|
|
||||||
run_workflow "build.yml" "push"
|
|
||||||
;;
|
|
||||||
3)
|
|
||||||
echo "Testing Release workflow..."
|
|
||||||
# Create a mock release event
|
|
||||||
cat > /tmp/release-event.json << EOF
|
|
||||||
{
|
|
||||||
"ref": "refs/tags/v1.0.0",
|
|
||||||
"repository": {
|
|
||||||
"name": "floating-clock",
|
|
||||||
"owner": {
|
|
||||||
"login": "ivaquero"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
run_workflow "release.yml" "/tmp/release-event.json"
|
|
||||||
;;
|
|
||||||
4)
|
|
||||||
echo "Testing all workflows..."
|
|
||||||
act --dry-run
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Invalid choice. Exiting."
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "To run the workflows for real (not dry-run), remove the --dry-run flag"
|
|
||||||
echo "Example: act -W .github/workflows/ci.yml"
|
|
||||||
echo ""
|
|
||||||
echo "For local testing with Docker, you may need to use different images:"
|
|
||||||
echo "act -P ubuntu-latest=nektos/act-environments-ubuntu:18.04"
|
|
||||||
echo "act -P macos-latest=nektos/act-environments-macos:latest"
|
|
||||||
echo "act -P windows-latest=nektos/act-environments-windows:latest"
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo "Testing QFlock Resizable Feature"
|
|
||||||
echo "================================"
|
|
||||||
echo ""
|
|
||||||
echo "1. Starting QFlock application..."
|
|
||||||
./build/Clouck.app/Contents/MacOS/Clouck &
|
|
||||||
PID=$!
|
|
||||||
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "2. Checking if application is running..."
|
|
||||||
if ps -p $PID >/dev/null; then
|
|
||||||
echo "[SUCCESS] Application started successfully (PID: $PID)"
|
|
||||||
else
|
|
||||||
echo "[ERROR] Application failed to start"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "3. Checking configuration file..."
|
|
||||||
if [ -f "config.xml" ]; then
|
|
||||||
echo "[INFO] Configuration file exists"
|
|
||||||
echo "Current configuration:"
|
|
||||||
cat config.xml
|
|
||||||
else
|
|
||||||
echo "[WARNING] Configuration file not found"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "4. Instructions for testing resizable feature:"
|
|
||||||
echo " - Move mouse to bottom-right corner of the clock"
|
|
||||||
echo " - Cursor should change to diagonal resize arrow"
|
|
||||||
echo " - Click and drag to resize"
|
|
||||||
echo " - Release mouse to save new size"
|
|
||||||
echo " - Size will be saved to config.xml"
|
|
||||||
echo ""
|
|
||||||
echo "5. To stop the application:"
|
|
||||||
echo " - Right-click on clock and select 'Quit'"
|
|
||||||
echo " - Or run: kill $PID"
|
|
||||||
echo ""
|
|
||||||
echo "Test the resizable feature now!"
|
|
||||||
echo "Press any key to continue..."
|
|
||||||
read -n 1 -s
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "6. Checking final configuration..."
|
|
||||||
if [ -f "config.xml" ]; then
|
|
||||||
echo "Final configuration:"
|
|
||||||
cat config.xml
|
|
||||||
else
|
|
||||||
echo "Configuration file not found"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Test completed!"
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Build and Run Test
|
|
||||||
|
|
||||||
echo "Building QFlock with XML configuration..."
|
|
||||||
|
|
||||||
# Clean previous build
|
|
||||||
./clean.sh
|
|
||||||
|
|
||||||
# Build the project
|
|
||||||
./build.sh
|
|
||||||
|
|
||||||
# Check if build was successful
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo "Build successful! Testing XML configuration..."
|
|
||||||
|
|
||||||
# Show config file content
|
|
||||||
echo "Config file content:"
|
|
||||||
if [ -f "config.xml" ]; then
|
|
||||||
cat config.xml
|
|
||||||
else
|
|
||||||
echo "No config.xml found (will be created on first run)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "You can now run the application:"
|
|
||||||
echo "./build/flock.app/Contents/MacOS/flock # macOS"
|
|
||||||
echo "./build/flock # Linux"
|
|
||||||
else
|
|
||||||
echo "Build failed!"
|
|
||||||
fi
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#include "autostart_manager.h"
|
||||||
|
|
||||||
|
#include <QTemporaryDir>
|
||||||
|
#include <QtTest>
|
||||||
|
|
||||||
|
class AutoStartManagerTest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void createsRunAtLoadLaunchAgent();
|
||||||
|
void roundTripsPlistRoundTrip();
|
||||||
|
};
|
||||||
|
|
||||||
|
void AutoStartManagerTest::createsRunAtLoadLaunchAgent()
|
||||||
|
{
|
||||||
|
const QString executablePath = "/Applications/DualFloatingClock.app/Contents/MacOS/DualFloatingClock";
|
||||||
|
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/>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoStartManagerTest::roundTripsPlistRoundTrip()
|
||||||
|
{
|
||||||
|
// Disabled-by-default: no plist written when enable is false,
|
||||||
|
// enableForPath + disable must leave no residue.
|
||||||
|
QTemporaryDir home;
|
||||||
|
QVERIFY(home.isValid());
|
||||||
|
qputenv("HOME", home.path().toUtf8());
|
||||||
|
|
||||||
|
QVERIFY(AutoStartManager::disable());
|
||||||
|
QVERIFY(!QFile::exists(AutoStartManager::launchAgentPath()));
|
||||||
|
QVERIFY(!AutoStartManager::isEnabled());
|
||||||
|
|
||||||
|
QVERIFY(AutoStartManager::enableForPath("/Applications/DualFloatingClock.app/Contents/MacOS/DualFloatingClock"));
|
||||||
|
QVERIFY(AutoStartManager::isEnabled());
|
||||||
|
QVERIFY(QFile::exists(AutoStartManager::launchAgentPath()));
|
||||||
|
|
||||||
|
QVERIFY(AutoStartManager::disable());
|
||||||
|
QVERIFY(!AutoStartManager::isEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
QTEST_APPLESS_MAIN(AutoStartManagerTest)
|
||||||
|
|
||||||
|
#include "autostart_manager_test.moc"
|
||||||
@@ -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"
|
||||||
@@ -30,7 +30,15 @@ void MacOSWindowHelper::setupAlwaysOnTopForFullscreen(QWidget *widget)
|
|||||||
|
|
||||||
// Ensure it stays above dock and menu bar
|
// Ensure it stays above dock and menu bar
|
||||||
[nativeWindow setLevel:CGWindowLevelForKey(kCGFloatingWindowLevelKey)];
|
[nativeWindow setLevel:CGWindowLevelForKey(kCGFloatingWindowLevelKey)];
|
||||||
|
|
||||||
|
// Kill the system window shadow — text must float with no halo.
|
||||||
|
[nativeWindow setHasShadow:NO];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void clouck_setActivationPolicyAccessory()
|
||||||
|
{
|
||||||
|
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
|
||||||
|
}
|
||||||
#endif // Q_OS_MAC
|
#endif // Q_OS_MAC
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#endif
|
|
||||||
|
|
||||||
class WindowHelperWindows
|
class WindowHelperWindows
|
||||||
{
|
{
|
||||||
@@ -19,4 +18,6 @@ private:
|
|||||||
static HWND getWindowHandle(QWidget *widget);
|
static HWND getWindowHandle(QWidget *widget);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif // Q_OS_WIN
|
||||||
|
|
||||||
#endif // WINDOW_HELPER_WINDOWS_H
|
#endif // WINDOW_HELPER_WINDOWS_H
|
||||||
|
|||||||
Reference in New Issue
Block a user