pysfi 0.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pysfi-0.1.5.dist-info/METADATA +107 -0
- pysfi-0.1.5.dist-info/RECORD +19 -0
- pysfi-0.1.5.dist-info/WHEEL +4 -0
- pysfi-0.1.5.dist-info/entry_points.txt +11 -0
- sfi/__init__.py +3 -0
- sfi/alarmclock/__init__.py +0 -0
- sfi/alarmclock/alarmclock.py +367 -0
- sfi/bumpversion/__init__.py +3 -0
- sfi/bumpversion/bumpversion.py +535 -0
- sfi/embedinstall/embedinstall.py +418 -0
- sfi/filedate/__init__.py +0 -0
- sfi/filedate/filedate.py +112 -0
- sfi/makepython/__init__.py +0 -0
- sfi/makepython/makepython.py +310 -0
- sfi/projectparse/projectparse.py +152 -0
- sfi/pyloadergen/pyloadergen.py +995 -0
- sfi/pypacker/fspacker.py +91 -0
- sfi/taskkill/taskkill.py +236 -0
- sfi/which/which.py +74 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pysfi
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: Single File commands for Interactive python.
|
|
5
|
+
Requires-Python: >=3.8
|
|
6
|
+
Requires-Dist: tomli>=2.4.0; python_version < '3.11'
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# pysfi
|
|
10
|
+
|
|
11
|
+
Single File commands for Interactive python.
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
pysfi is a Python project that provides single-file command-line utilities, designed to be lightweight and easy-to-use.
|
|
16
|
+
|
|
17
|
+
## Available Commands
|
|
18
|
+
|
|
19
|
+
- **alarmclk**: Alarm clock functionality
|
|
20
|
+
- **[bumpversion](sfi/bumpversion/README.md)**: Automated version number management tool
|
|
21
|
+
- **embedinstall**: Embed installation utilities
|
|
22
|
+
- **[filedate](sfi/filedate/README.md)**: A file date management tool that normalizes date prefixes in filenames
|
|
23
|
+
- **mkp**: Make Python project utilities
|
|
24
|
+
- **projectparse**: Project parsing and analysis tools
|
|
25
|
+
- **pyloadergen**: Python loader code generation
|
|
26
|
+
- **pypacker**: Python packaging utilities
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Install using uv (recommended)
|
|
32
|
+
uv add pysfi
|
|
33
|
+
|
|
34
|
+
# Or using pip
|
|
35
|
+
pip install pysfi
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Development
|
|
39
|
+
|
|
40
|
+
### Requirements
|
|
41
|
+
|
|
42
|
+
- Python >= 3.8
|
|
43
|
+
- [uv](https://github.com/astral-sh/uv) (recommended) or pip
|
|
44
|
+
|
|
45
|
+
### Development Dependencies
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv pip install -e ".[dev]"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Code Standards
|
|
52
|
+
|
|
53
|
+
The project uses Ruff for code linting and formatting:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Check code
|
|
57
|
+
ruff check .
|
|
58
|
+
|
|
59
|
+
# Format code
|
|
60
|
+
ruff format .
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Project Structure
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pysfi/
|
|
67
|
+
├── pyproject.toml # Main project configuration
|
|
68
|
+
├── README.md
|
|
69
|
+
└── sfi/
|
|
70
|
+
├── __init__.py
|
|
71
|
+
├── alarmclock/ # alarmclk command module
|
|
72
|
+
│ ├── alarmclock.py
|
|
73
|
+
│ ├── pyproject.toml
|
|
74
|
+
│ └── __init__.py
|
|
75
|
+
├── embedinstall/ # embedinstall command module
|
|
76
|
+
│ ├── embedinstall.py
|
|
77
|
+
│ ├── pyproject.toml
|
|
78
|
+
│ └── __init__.py
|
|
79
|
+
├── filedate/ # filedate command module
|
|
80
|
+
│ ├── filedate.py
|
|
81
|
+
│ ├── pyproject.toml
|
|
82
|
+
│ ├── README.md # Detailed documentation
|
|
83
|
+
│ └── __init__.py
|
|
84
|
+
├── makepython/ # mkp command module
|
|
85
|
+
│ ├── makepython.py
|
|
86
|
+
│ ├── pyproject.toml
|
|
87
|
+
│ └── __init__.py
|
|
88
|
+
├── projectparse/ # projectparse command module
|
|
89
|
+
│ ├── projectparse.py
|
|
90
|
+
│ ├── pyproject.toml
|
|
91
|
+
│ └── __init__.py
|
|
92
|
+
├── pyloadergen/ # pyloadergen command module
|
|
93
|
+
│ ├── pyloadergen.py
|
|
94
|
+
│ ├── pyproject.toml
|
|
95
|
+
│ └── __init__.py
|
|
96
|
+
└── pypacker/ # pypacker command module
|
|
97
|
+
├── fspacker.py
|
|
98
|
+
└── pyproject.toml
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT License
|
|
104
|
+
|
|
105
|
+
## Contributing
|
|
106
|
+
|
|
107
|
+
Issues and Pull Requests are welcome!
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
sfi/__init__.py,sha256=3q6-yp52kPMW6F1LAzTb3-GlRA86Ga4qvIJg4iK-OEA,74
|
|
2
|
+
sfi/alarmclock/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
sfi/alarmclock/alarmclock.py,sha256=65G8OyTGpe4oQ2SFerQG1N9PVJ4KxO7WzgsTxpGm4O0,12509
|
|
4
|
+
sfi/bumpversion/__init__.py,sha256=gnn2L3Q-mwVkX175c0tmFtonk-driIiVHB5LRP4Mt_4,85
|
|
5
|
+
sfi/bumpversion/bumpversion.py,sha256=HOyHLaE0sZajrlcVZ8hsim8mPjz77qwQVSo6aIzjMXE,20735
|
|
6
|
+
sfi/embedinstall/embedinstall.py,sha256=N5EbTDdX4bE3W0qHGAwAUuepqFr0sbdZuPI3KWrtuUY,14936
|
|
7
|
+
sfi/filedate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
sfi/filedate/filedate.py,sha256=DpVp26lumE_Lz_4TgqUEX8IxtK3Y6yHSEFV8qJyegyk,3645
|
|
9
|
+
sfi/makepython/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
sfi/makepython/makepython.py,sha256=YKBXTjvugKN_TlFfx-vOudpkZy45VhO0vq89fxo6hFU,10583
|
|
11
|
+
sfi/projectparse/projectparse.py,sha256=Ojg-z4lZEtjEBpJYWyznTgL307N45AxlQKnRkEH0P70,5525
|
|
12
|
+
sfi/pyloadergen/pyloadergen.py,sha256=Erzz1PwrEQcDSCxXG-4DZ-CZavDt6MNv7k3nET5IB9U,32423
|
|
13
|
+
sfi/pypacker/fspacker.py,sha256=3tlS7qiWoH_kOzsp9eSWsQ-SY7-bSTugwfB-HIL69iE,3238
|
|
14
|
+
sfi/taskkill/taskkill.py,sha256=6Aw4afmgfLZcQnvgG_38A1VrwazDrnNdOmY1l4kr0lc,7758
|
|
15
|
+
sfi/which/which.py,sha256=zVIAwZA-pGGngxkkwZ6IxDX3ozVHg7cLSYwYO9FjaIc,2439
|
|
16
|
+
pysfi-0.1.5.dist-info/METADATA,sha256=yll-LofKX8YmpbsO2hbw2hPRRtxngGWlpUjjQwEJt5M,2755
|
|
17
|
+
pysfi-0.1.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
18
|
+
pysfi-0.1.5.dist-info/entry_points.txt,sha256=3wUSD_M7k2pUSq9VXvdYOJ41nUD2fMN0wati4QkDz9I,429
|
|
19
|
+
pysfi-0.1.5.dist-info/RECORD,,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
[console_scripts]
|
|
2
|
+
alarmclk = sfi.alarmclock.alarmclock:main
|
|
3
|
+
bumpversion = sfi.bumpversion.bumpversion:main
|
|
4
|
+
embedinstall = sfi.embedinstall.embedinstall:main
|
|
5
|
+
filedate = sfi.filedate.filedate:main
|
|
6
|
+
mkp = sfi.makepython.makepython:main
|
|
7
|
+
projectparse = sfi.projectparse.projectparse:main
|
|
8
|
+
pyloadergen = sfi.pyloadergen.pyloadergen:main
|
|
9
|
+
pypacker = sfi.pypacker.pypacker:main
|
|
10
|
+
taskk = sfi.taskkill.taskkill:main
|
|
11
|
+
wch = sfi.which.which:main
|
sfi/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import logging
|
|
5
|
+
import random
|
|
6
|
+
import sys
|
|
7
|
+
from datetime import datetime, timedelta, timezone
|
|
8
|
+
from functools import partial
|
|
9
|
+
from typing import ClassVar
|
|
10
|
+
|
|
11
|
+
import qdarkstyle
|
|
12
|
+
from PySide2.QtCore import QSize, Qt, QTime, QTimer
|
|
13
|
+
from PySide2.QtGui import QCloseEvent
|
|
14
|
+
from PySide2.QtWidgets import (
|
|
15
|
+
QApplication,
|
|
16
|
+
QCheckBox,
|
|
17
|
+
QDialog,
|
|
18
|
+
QHBoxLayout,
|
|
19
|
+
QLabel,
|
|
20
|
+
QMainWindow,
|
|
21
|
+
QPushButton,
|
|
22
|
+
QTimeEdit,
|
|
23
|
+
QVBoxLayout,
|
|
24
|
+
QWidget,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__version__ = "0.1.2"
|
|
28
|
+
__build_date__ = "2025-09-16"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AlarmClockConfig:
|
|
32
|
+
"""Alarm clock configuration."""
|
|
33
|
+
|
|
34
|
+
ALARM_CLOCK_TITLE = "Digital Alarm Clock"
|
|
35
|
+
|
|
36
|
+
DIGITAL_FONT: str = "bold italic 81px 'Consolas'"
|
|
37
|
+
DIGITAL_COLOR: str = "#ccee00"
|
|
38
|
+
DIGITAL_BORDER_COLORS: ClassVar[list[str]] = [
|
|
39
|
+
"#00aa00",
|
|
40
|
+
"#eecc00",
|
|
41
|
+
"#aa00aa",
|
|
42
|
+
"#c0e0b0",
|
|
43
|
+
]
|
|
44
|
+
DIGITAL_TIMER_FORMAT: str = "%H:%M:%S"
|
|
45
|
+
DIGITAL_UPDATE_INTERVAL: int = 1000
|
|
46
|
+
|
|
47
|
+
BLINK_TITLE: str = "Alarm Reminder!"
|
|
48
|
+
BLINK_CONTENT: str = "⏰ Time's Up!"
|
|
49
|
+
BLINK_TYPE: str = "color" # Options: 'color' or 'opacity'
|
|
50
|
+
BLINK_BG_COLORS: ClassVar[list[str]] = [
|
|
51
|
+
"#baf1ba",
|
|
52
|
+
"#f8ccc3",
|
|
53
|
+
"#aab4f0",
|
|
54
|
+
"#efaec0",
|
|
55
|
+
]
|
|
56
|
+
BLINK_INTERVAL: ClassVar[int] = 300 # ms
|
|
57
|
+
DELAY_STEPS: ClassVar[list[int]] = [1, 5, 10, 15, 30, 60] # minutes
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
61
|
+
|
|
62
|
+
conf = AlarmClockConfig()
|
|
63
|
+
logger = logging.getLogger(__name__)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class DigitalClock(QLabel):
|
|
67
|
+
"""Cool digital clock display."""
|
|
68
|
+
|
|
69
|
+
def __init__(self, parent: QWidget | None = None) -> None:
|
|
70
|
+
super().__init__(parent)
|
|
71
|
+
|
|
72
|
+
self.setAlignment(Qt.AlignCenter) # type: ignore
|
|
73
|
+
|
|
74
|
+
self._color = conf.DIGITAL_BORDER_COLORS[0]
|
|
75
|
+
|
|
76
|
+
# Timer to update current time
|
|
77
|
+
self._timer = QTimer()
|
|
78
|
+
self._timer.timeout.connect(self.update_time) # type: ignore
|
|
79
|
+
self._timer.start(conf.DIGITAL_UPDATE_INTERVAL) # Update every second
|
|
80
|
+
|
|
81
|
+
self.update_time()
|
|
82
|
+
|
|
83
|
+
def update_time(self) -> None:
|
|
84
|
+
"""Update current time display."""
|
|
85
|
+
current = datetime.now(timezone.utc) + timedelta(hours=8) # Beijing time
|
|
86
|
+
self.setText(current.strftime(conf.DIGITAL_TIMER_FORMAT))
|
|
87
|
+
logger.debug(f"Updated time: {current}")
|
|
88
|
+
|
|
89
|
+
# Add blink effect
|
|
90
|
+
self._color = random.choice(
|
|
91
|
+
[_ for _ in conf.DIGITAL_BORDER_COLORS if _ != self._color],
|
|
92
|
+
)
|
|
93
|
+
self.setStyleSheet(f"""
|
|
94
|
+
font: {conf.DIGITAL_FONT};
|
|
95
|
+
color: {conf.DIGITAL_COLOR};
|
|
96
|
+
background-color: black;
|
|
97
|
+
border: 2px dashed {self._color};
|
|
98
|
+
border-radius: 10px;
|
|
99
|
+
padding: 10px;
|
|
100
|
+
""")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class BlinkDialog(QDialog):
|
|
104
|
+
"""Alarm reminder dialog."""
|
|
105
|
+
|
|
106
|
+
def __init__(self) -> None:
|
|
107
|
+
super().__init__()
|
|
108
|
+
|
|
109
|
+
self.setWindowTitle(conf.BLINK_TITLE)
|
|
110
|
+
self.setModal(True)
|
|
111
|
+
self.setWindowFlags(
|
|
112
|
+
self.windowFlags() | Qt.WindowStaysOnTopHint | Qt.WindowType.Dialog, # type: ignore
|
|
113
|
+
)
|
|
114
|
+
self.setFixedSize(QSize(400, 240))
|
|
115
|
+
|
|
116
|
+
layout = QVBoxLayout()
|
|
117
|
+
msg_label = QLabel(conf.BLINK_CONTENT)
|
|
118
|
+
msg_label.setStyleSheet("""
|
|
119
|
+
color: red;
|
|
120
|
+
font-size: 24px;
|
|
121
|
+
""")
|
|
122
|
+
msg_label.setAlignment(Qt.AlignmentFlag.AlignCenter) # type: ignore
|
|
123
|
+
|
|
124
|
+
close_button = QPushButton("Close Alarm")
|
|
125
|
+
close_button.clicked.connect(self.accept) # type: ignore
|
|
126
|
+
|
|
127
|
+
layout.addWidget(msg_label)
|
|
128
|
+
layout.addWidget(close_button)
|
|
129
|
+
self.setLayout(layout)
|
|
130
|
+
|
|
131
|
+
# Prevent user from closing dialog by other means, ensure button click only
|
|
132
|
+
self.setWindowFlag(Qt.WindowCloseButtonHint, False) # type: ignore
|
|
133
|
+
|
|
134
|
+
# Blink control variables and timer
|
|
135
|
+
self.blink_timer = QTimer(self)
|
|
136
|
+
self.blink_timer.timeout.connect(self.update_blink) # type: ignore
|
|
137
|
+
self.blink_state = False
|
|
138
|
+
self.blink_type = conf.BLINK_TYPE
|
|
139
|
+
|
|
140
|
+
# Initialize style
|
|
141
|
+
self.bg_color = random.choice(conf.BLINK_BG_COLORS)
|
|
142
|
+
self.origin_style = self.styleSheet()
|
|
143
|
+
self.blink_timer.start(conf.BLINK_INTERVAL)
|
|
144
|
+
|
|
145
|
+
def update_blink(self) -> None:
|
|
146
|
+
"""Timer timeout, update blink state."""
|
|
147
|
+
if self.blink_type == "color":
|
|
148
|
+
# Color blink logic
|
|
149
|
+
colors = [_ for _ in conf.BLINK_BG_COLORS[:] if _ != self.bg_color]
|
|
150
|
+
self.setStyleSheet(f"background-color: {random.choice(colors)}")
|
|
151
|
+
elif self.blink_type == "opacity":
|
|
152
|
+
# Opacity blink logic - Note: Some systems may not fully support window opacity
|
|
153
|
+
new_opacity = 0.3 if self.blink_state else 1.0
|
|
154
|
+
self.setWindowOpacity(new_opacity)
|
|
155
|
+
|
|
156
|
+
self.blink_state = not self.blink_state # Toggle state
|
|
157
|
+
|
|
158
|
+
def stop_blinking(self) -> None:
|
|
159
|
+
"""Stop blinking, restore original style."""
|
|
160
|
+
self.blink_timer.stop()
|
|
161
|
+
self.setStyleSheet(self.origin_style) # Restore original style
|
|
162
|
+
self.setWindowOpacity(1.0) # Ensure opacity is restored
|
|
163
|
+
|
|
164
|
+
def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802
|
|
165
|
+
"""Override close event, ensure timer stops."""
|
|
166
|
+
self.stop_blinking()
|
|
167
|
+
super().closeEvent(event)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class AlarmClock(QMainWindow):
|
|
171
|
+
"""Digital alarm clock GUI."""
|
|
172
|
+
|
|
173
|
+
def __init__(self) -> None:
|
|
174
|
+
super().__init__()
|
|
175
|
+
self.setWindowTitle(f"{conf.ALARM_CLOCK_TITLE} v{__version__}")
|
|
176
|
+
self.setGeometry(
|
|
177
|
+
QApplication.desktop().screenGeometry().center().x() - self.width() // 4,
|
|
178
|
+
QApplication.desktop().screenGeometry().center().y() - self.height() // 2,
|
|
179
|
+
self.width(),
|
|
180
|
+
self.height(),
|
|
181
|
+
)
|
|
182
|
+
self.adjustSize()
|
|
183
|
+
|
|
184
|
+
# Set window style
|
|
185
|
+
self.setStyleSheet("""
|
|
186
|
+
QMainWindow {
|
|
187
|
+
background-color: #2b2b2b;
|
|
188
|
+
}
|
|
189
|
+
QLabel {
|
|
190
|
+
color: #ffffff;
|
|
191
|
+
font-size: 14px;
|
|
192
|
+
}
|
|
193
|
+
QPushButton {
|
|
194
|
+
background-color: #3a3a3a;
|
|
195
|
+
color: white;
|
|
196
|
+
border: 1px solid #5a5a5a;
|
|
197
|
+
padding: 8px;
|
|
198
|
+
border-radius: 4px;
|
|
199
|
+
font-size: 14px;
|
|
200
|
+
}
|
|
201
|
+
QPushButton:hover {
|
|
202
|
+
background-color: #4a4a4a;
|
|
203
|
+
}
|
|
204
|
+
QPushButton:disabled {
|
|
205
|
+
background-color: #2a2a2a;
|
|
206
|
+
color: #6a6a6a;
|
|
207
|
+
}
|
|
208
|
+
QCheckBox {
|
|
209
|
+
color: white;
|
|
210
|
+
font-size: 14px;
|
|
211
|
+
}
|
|
212
|
+
QTimeEdit {
|
|
213
|
+
background-color: #3a3a3a;
|
|
214
|
+
color: white;
|
|
215
|
+
border: 1px solid #5a5a5a;
|
|
216
|
+
padding: 5px;
|
|
217
|
+
font-size: 14px;
|
|
218
|
+
}
|
|
219
|
+
""")
|
|
220
|
+
|
|
221
|
+
# Create central widget
|
|
222
|
+
central_widget = QWidget()
|
|
223
|
+
self.setCentralWidget(central_widget)
|
|
224
|
+
|
|
225
|
+
# Create layout
|
|
226
|
+
main_layout = QVBoxLayout()
|
|
227
|
+
main_layout.setSpacing(20)
|
|
228
|
+
central_widget.setLayout(main_layout)
|
|
229
|
+
|
|
230
|
+
# Cool digital clock display
|
|
231
|
+
self.digital_clock = DigitalClock(parent=self)
|
|
232
|
+
main_layout.addWidget(self.digital_clock)
|
|
233
|
+
|
|
234
|
+
# Alarm time setting
|
|
235
|
+
time_layout = QHBoxLayout()
|
|
236
|
+
time_label = QLabel("Alarm Time:")
|
|
237
|
+
time_label.setStyleSheet("color: white; font-size: 16px;")
|
|
238
|
+
self.alarm_time_edit = QTimeEdit()
|
|
239
|
+
self.alarm_time_edit.setDisplayFormat("HH:mm:ss")
|
|
240
|
+
self.alarm_time_edit.setTime(
|
|
241
|
+
QTime.currentTime().addSecs(conf.DELAY_STEPS[0] * 60),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
time_layout.addWidget(time_label)
|
|
245
|
+
time_layout.addWidget(self.alarm_time_edit)
|
|
246
|
+
main_layout.addLayout(time_layout)
|
|
247
|
+
|
|
248
|
+
delay_layout = QHBoxLayout()
|
|
249
|
+
delay_label = QLabel("Delay (min):")
|
|
250
|
+
delay_label.setStyleSheet("color: white; font-size: 16px;")
|
|
251
|
+
delay_layout.addWidget(delay_label)
|
|
252
|
+
for minutes in conf.DELAY_STEPS:
|
|
253
|
+
button = QPushButton(str(minutes))
|
|
254
|
+
button.setStyleSheet("color: white; font-size: 16px;")
|
|
255
|
+
button.clicked.connect(partial(self.set_delay, minutes)) # type: ignore
|
|
256
|
+
delay_layout.addWidget(button)
|
|
257
|
+
main_layout.addLayout(delay_layout)
|
|
258
|
+
|
|
259
|
+
# Repeat option
|
|
260
|
+
self.repeat_checkbox = QCheckBox("Repeat")
|
|
261
|
+
main_layout.addWidget(self.repeat_checkbox)
|
|
262
|
+
|
|
263
|
+
# Control buttons
|
|
264
|
+
button_layout = QHBoxLayout()
|
|
265
|
+
self.set_alarm_button = QPushButton("Set Alarm")
|
|
266
|
+
self.set_alarm_button.clicked.connect(self.set_alarm) # type: ignore
|
|
267
|
+
self.cancel_alarm_button = QPushButton("Cancel Alarm")
|
|
268
|
+
self.cancel_alarm_button.clicked.connect(self.cancel_alarm) # type: ignore
|
|
269
|
+
self.cancel_alarm_button.setEnabled(False)
|
|
270
|
+
button_layout.addWidget(self.set_alarm_button)
|
|
271
|
+
button_layout.addWidget(self.cancel_alarm_button)
|
|
272
|
+
main_layout.addLayout(button_layout)
|
|
273
|
+
|
|
274
|
+
# Status display
|
|
275
|
+
self.status_label = QLabel("Alarm not set")
|
|
276
|
+
self.status_label.setAlignment(Qt.AlignCenter) # type: ignore
|
|
277
|
+
self.status_label.setStyleSheet("color: #aaaaaa; font-size: 16px;")
|
|
278
|
+
main_layout.addWidget(self.status_label)
|
|
279
|
+
|
|
280
|
+
# Alarm timer
|
|
281
|
+
self.alarm_timer = QTimer()
|
|
282
|
+
self.alarm_timer.timeout.connect(self.check_alarm) # type: ignore
|
|
283
|
+
|
|
284
|
+
# Alarm state
|
|
285
|
+
self.alarm_set = False
|
|
286
|
+
self.alarm_time: QTime = QTime() # Explicit type
|
|
287
|
+
|
|
288
|
+
def set_delay(self, minutes: int) -> None:
|
|
289
|
+
"""Set delay alarm."""
|
|
290
|
+
self.alarm_time_edit.setTime(
|
|
291
|
+
QTime.currentTime().addSecs(minutes * 60),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
def set_alarm(self) -> None:
|
|
295
|
+
"""Set alarm."""
|
|
296
|
+
self.alarm_time = self.alarm_time_edit.time()
|
|
297
|
+
self.alarm_set = True
|
|
298
|
+
self.alarm_timer.start(1000) # Check every second
|
|
299
|
+
self.set_alarm_button.setEnabled(False)
|
|
300
|
+
self.cancel_alarm_button.setEnabled(True)
|
|
301
|
+
self.status_label.setText(
|
|
302
|
+
f"Alarm set: {self.alarm_time.toString('HH:mm:ss')}",
|
|
303
|
+
)
|
|
304
|
+
self.status_label.setStyleSheet(
|
|
305
|
+
"color: #00ff00; font-size: 16px; font-weight: bold;",
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
def cancel_alarm(self) -> None:
|
|
309
|
+
"""Cancel alarm."""
|
|
310
|
+
self.alarm_set = False
|
|
311
|
+
self.alarm_timer.stop()
|
|
312
|
+
self.set_alarm_button.setEnabled(True)
|
|
313
|
+
self.cancel_alarm_button.setEnabled(False)
|
|
314
|
+
self.status_label.setText("Alarm cancelled")
|
|
315
|
+
self.status_label.setStyleSheet("color: #aaaaaa; font-size: 16px;")
|
|
316
|
+
|
|
317
|
+
def check_alarm(self) -> None:
|
|
318
|
+
"""Check if alarm time has arrived."""
|
|
319
|
+
if not self.alarm_set:
|
|
320
|
+
return
|
|
321
|
+
|
|
322
|
+
current_time = QTime.currentTime()
|
|
323
|
+
if (
|
|
324
|
+
current_time.hour() == self.alarm_time.hour()
|
|
325
|
+
and current_time.minute() == self.alarm_time.minute()
|
|
326
|
+
and current_time.second() == self.alarm_time.second()
|
|
327
|
+
):
|
|
328
|
+
# Show reminder message
|
|
329
|
+
dialog = BlinkDialog()
|
|
330
|
+
dialog.exec_()
|
|
331
|
+
|
|
332
|
+
self.status_label.setText("⏰ Alarm Rang! ⏰")
|
|
333
|
+
self.status_label.setStyleSheet(
|
|
334
|
+
"color: #ff5555; font-size: 18px; font-weight: bold;",
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# Add blink effect
|
|
338
|
+
self.status_label.setStyleSheet("""
|
|
339
|
+
color: #ff0000;
|
|
340
|
+
font-size: 18px;
|
|
341
|
+
font-weight: bold;
|
|
342
|
+
background-color: #330000;
|
|
343
|
+
border-radius: 5px;
|
|
344
|
+
padding: 5px;
|
|
345
|
+
""")
|
|
346
|
+
|
|
347
|
+
# Cancel alarm if not repeating
|
|
348
|
+
if not self.repeat_checkbox.isChecked():
|
|
349
|
+
self.cancel_alarm()
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def main() -> None:
|
|
353
|
+
parser = argparse.ArgumentParser(
|
|
354
|
+
prog="alarmclock", description="Digital Alarm Clock"
|
|
355
|
+
)
|
|
356
|
+
parser.add_argument("-d", "--debug", action="store_true", help="Enable debug mode")
|
|
357
|
+
parser.add_argument("-v", "--version", action="version", version=f"{__version__}")
|
|
358
|
+
|
|
359
|
+
args = parser.parse_args()
|
|
360
|
+
if args.debug:
|
|
361
|
+
logger.setLevel(logging.DEBUG)
|
|
362
|
+
|
|
363
|
+
app = QApplication(sys.argv)
|
|
364
|
+
app.setStyleSheet(qdarkstyle.load_stylesheet_pyside2())
|
|
365
|
+
window = AlarmClock()
|
|
366
|
+
window.show()
|
|
367
|
+
sys.exit(app.exec_())
|