From 442ffb9c2a3b9ae4ba133b60690e033b10cdaa3b Mon Sep 17 00:00:00 2001 From: Wolfmyths Date: Sun, 7 Jan 2024 20:23:05 -0500 Subject: [PATCH 1/4] V2.0 - Rewrite --- .github/workflows/main.yml | 5 +- .gitignore | 4 + FUNDING.yml | 13 + README.md | 50 +- createPyExe.bat | 14 + requirements.txt | 10 +- src/python/checkVersion.py | 58 + src/python/constants.py | 36 + src/python/dataParser.py | 37 + src/python/guide.html | 4 +- src/python/main.py | 1698 +--------------------------- src/python/main.spec | 4 +- src/python/notify.py | 60 + src/python/notify.wav | Bin 121388 -> 0 bytes src/python/saveConfig.py | 132 +++ src/python/style.py | 223 ++-- src/python/widgets/addTimer.py | 480 ++++++++ src/python/widgets/central.py | 94 ++ src/python/widgets/guide.py | 95 ++ src/python/widgets/mainWindow.py | 136 +++ src/python/widgets/options.py | 235 ++++ src/python/widgets/staticTimers.py | 220 ++++ src/python/widgets/stopwatch.py | 156 +++ src/python/widgets/sysTrayIcon.py | 24 + src/python/widgets/toolbar.py | 70 ++ src/python/widgets/trayMenu.py | 57 + src/python/widgets/updateAlert.py | 46 + 27 files changed, 2131 insertions(+), 1830 deletions(-) create mode 100644 FUNDING.yml create mode 100644 createPyExe.bat create mode 100644 src/python/checkVersion.py create mode 100644 src/python/constants.py create mode 100644 src/python/dataParser.py create mode 100644 src/python/notify.py delete mode 100644 src/python/notify.wav create mode 100644 src/python/saveConfig.py create mode 100644 src/python/widgets/addTimer.py create mode 100644 src/python/widgets/central.py create mode 100644 src/python/widgets/guide.py create mode 100644 src/python/widgets/mainWindow.py create mode 100644 src/python/widgets/options.py create mode 100644 src/python/widgets/staticTimers.py create mode 100644 src/python/widgets/stopwatch.py create mode 100644 src/python/widgets/sysTrayIcon.py create mode 100644 src/python/widgets/toolbar.py create mode 100644 src/python/widgets/trayMenu.py create mode 100644 src/python/widgets/updateAlert.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5657a4a..6467b30 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,10 +46,7 @@ jobs: name: Genshin-Stopwatch-Assets-${{ matrix.os }} path: | ${{env.working-directory}}Genshin* - ${{env.working-directory}}config.ini - ${{env.working-directory}}save.txt - ${{env.working-directory}}icon.ico - + release-asset: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index ab9ae67..7ea08fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ # Images for README.md img/ +# Save Data +config.ini +save.txt + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/FUNDING.yml b/FUNDING.yml new file mode 100644 index 0000000..ec45590 --- /dev/null +++ b/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: Wolfmyths +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/README.md b/README.md index 7ba65ab..8b541b0 100644 --- a/README.md +++ b/README.md @@ -7,29 +7,28 @@ ![CSharp](https://img.shields.io/badge/CSharp(soon!)-.NET_7-purple) ![HTML](https://img.shields.io/badge/HTML-4-orange) -![Desktop Framework](https://img.shields.io/badge/Desktop_Framework-PyQt5-green) +![Desktop Framework](https://img.shields.io/badge/Desktop_Framework-PySide6-green) ![Mobile Framework](https://img.shields.io/badge/Mobile_Framework(soon!)-.NET_Maui-purple) -![Windows](https://img.shields.io/badge/Windows-Supported-green) -![MacOS](https://img.shields.io/badge/MacOS-Pre--Release-blue) -![Linux](https://img.shields.io/badge/Linux-Pre--Release-blue) -![iOS](https://img.shields.io/badge/iOS-TBD-lightgray) -![Android](https://img.shields.io/badge/Android-TBD-lightgray) +![OS](https://img.shields.io/badge/OS-Win|Mac|Linux|Android(TBD)|iOS(TBD)-blue) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/C0C4MJZS9) # FAQ Table of Contents -* [What does Genshin Stopwatch do?](#what-does-genshin-stopwatch-do) -* [How are my stopwatches saved?](#how-are-my-stopwatches-saved) -* [What platforms is this compatible with?](#what-platforms-is-this-compatible-with) -* [It doesn't work!](#it-doesnt-work) -* [Does this program run on system startup?](#does-this-program-run-on-system-startup) - + [Windows](#on-windows) - + [MacOS](#on-macos) - + [Linux](#on-linux) -* [Future Plans?](#future-plans) -* [Contributing](#contributing) -* [Credits](#credits) +- [Genshin Stopwatch](#genshin-stopwatch) + - [A program to help keep track of Genshin Impact's time gates.](#a-program-to-help-keep-track-of-genshin-impacts-time-gates) +- [FAQ Table of Contents](#faq-table-of-contents) + - [What does Genshin Stopwatch do?](#what-does-genshin-stopwatch-do) + - [How are my stopwatches saved?](#how-are-my-stopwatches-saved) + - [What platforms is this compatible with?](#what-platforms-is-this-compatible-with) + - [It doesn't work!](#it-doesnt-work) + - [Does this program run on system startup?](#does-this-program-run-on-system-startup) + - [On Windows:](#on-windows) + - [On MacOS:](#on-macos) + - [On Linux:](#on-linux) + - [Future Plans?](#future-plans) + - [Contributing](#contributing) + - [Credits](#credits) ## What does Genshin Stopwatch do? @@ -52,9 +51,9 @@ notes = Hilichurl Camps ## What platforms is this compatible with? -At the moment only Windows machines can run this program. +Win 10 and up, MacOS, and Linux versions are available. -**There are plans to support Linux and MacOS, please see the latest [pre release](https://github.com/Wolfmyths/Genshin-Stopwatch/releases/tag/V1.5.5-pre) for QA testing and [issue #26](https://github.com/Wolfmyths/Genshin-Stopwatch/issues/26)** +MacOS/Linux versions are not tested so if there's any issues please submit an issue **Android and iOS is also underway! Release TBD** @@ -98,10 +97,6 @@ Every distro is different so you have to do this research on your own, sorry. ## Future Plans? -+ Restore Apprise Module support if they add more compatibility for Windows - - *This means the notification center will be used again* -+ Touch up on the UI a tiny bit for more clarity -+ MacOS/Linux Support *In Pre-Release Stage!* + Background pictures to choose from for the timers? + Translations? *(Not sure if this is necessary but if people want it I will make an effort)* + Mobile version? *In development! Release is still TBD* @@ -119,14 +114,10 @@ For more information and background knowledge of the program, see [CONTRIBUTING. + [Contributors!](https://github.com/Wolfmyths/Genshin-Stopwatch/graphs/contributors) Without you guys I wouldn't have gotten as far into this project as I would have on my own.
You guys have taught me a lot. ❤️ -+ [PyQt5](https://pypi.org/project/PyQt5/) for creating an open source easy-to-use framework. ++ [PySide6](https://www.qt.io/qt-for-python) for creating an open source easy-to-use framework. + [Pyinstaller](https://pypi.org/project/pyinstaller/) for creating a way to change python programs into an exe. -+ [Playsound](https://pypi.org/project/playsound/1.2.2/) for creating a cross platform module to easily play sound files. - -+ [Itemize](https://freesound.org/people/Scrampunk/sounds/345297/) for creating this sound that is used for notifications - + Color Pallets - [Timeless](https://lospec.com/palette-list/timeless) AKA Dark by Archer on lospec.com - [Light](https://www.color-hex.com/color-palette/106748) by wcburgess on color-hex.com @@ -139,5 +130,8 @@ For more information and background knowledge of the program, see [CONTRIBUTING. - [Koukasita](https://lospec.com/palette-list/koukasita) AKA Geo by namida on lospec.com + *(Previously used)* + - [PyQt5](https://pypi.org/project/PyQt5/) for creating an open source easy-to-use framework. + - [Playsound](https://pypi.org/project/playsound/1.2.2/) for creating a cross platform module to easily play sound files. + - [Itemize](https://freesound.org/people/Scrampunk/sounds/345297/) for creating this sound that is used for notifications - [Apprise](https://pypi.org/project/apprise/) for creating an all-in-one notification module. - [Win10Toast](https://pypi.org/project/win10toast/) for creating an easy way to implement windows notifications. diff --git a/createPyExe.bat b/createPyExe.bat new file mode 100644 index 0000000..c07d08e --- /dev/null +++ b/createPyExe.bat @@ -0,0 +1,14 @@ +@echo on + +echo Setting Variables +set disFolder="Genshin Stopwatch" +set Txt="requirements.txt" +set Spec="./src/python/main.spec" + +echo Installing Dependencies +pip install -r %Txt% + +echo Running Pyinstaller +pyinstaller --clean %Spec% --distpath ./%disFolder% + +echo Installation Finished! \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 6327392..e8c32c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -pyinstaller==5.13.0 -PyQt5==5.15.9 -PyQt5_sip==12.12.1 -playsound==1.2.2 -requests==2.31.0 +PySide6==6.6.0 +semantic-version==2.10.0 + +# External use +pyinstaller==6.2.0 \ No newline at end of file diff --git a/src/python/checkVersion.py b/src/python/checkVersion.py new file mode 100644 index 0000000..b6147dd --- /dev/null +++ b/src/python/checkVersion.py @@ -0,0 +1,58 @@ +import json + +from PySide6.QtCore import QObject, QUrl, Signal +from PySide6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply + +from semantic_version import Version + +from constants import VERSION + +class checkUpdate(QObject): + ''' + Instancing this object will run a series of + functions to get the latest Myth Mod Manager version. + + `checkUpdate` will delete itself after it's finished. + ''' + + updateDetected = Signal(str, str) + upToDate = Signal() + error = Signal() + + def __init__(self) -> None: + super().__init__() + + link = 'https://api.github.com/repos/Wolfmyths/Myth-Mod-Manager/releases' + + network = QNetworkAccessManager(self) + request = QNetworkRequest(QUrl(link)) + + self.reply = network.get(request) + self.reply.finished.connect(self.__reply_handler) + + def __reply_handler(self) -> None: + reply: QNetworkReply = self.sender() + + if reply.error() == QNetworkReply.NetworkError.NoError: + self.__checkVersion() + else: + self.error.emit() + self.deleteLater() + + def __checkVersion(self) -> None: + reply: QNetworkReply = self.sender() + + try: + data: dict = json.loads(reply.readAll().data().decode()) + except Exception as e: + self.error.emit() + return + + latestVersion = Version.coerce(data['tag_name']) + + if latestVersion > VERSION: + self.updateDetected.emit(latestVersion, data['body']) + else: + self.upToDate.emit() + + self.deleteLater() diff --git a/src/python/constants.py b/src/python/constants.py new file mode 100644 index 0000000..15185a0 --- /dev/null +++ b/src/python/constants.py @@ -0,0 +1,36 @@ +import os +import sys +from datetime import timedelta +from enum import StrEnum + +from semantic_version import Version + +class TimeFormats(StrEnum): + Static_Timer = '%Y-%m-%d %I:%M:%S' + Finished_Date = '%B %d @ %I:%M %p' + Saved_Date = '%Y-%m-%d %H:%M:%S' + + +# Used for debugging, flag to see if program is ran through the main.py script or an exe +# This is used for when dealing with files that are packaged within the exe such as guide.html +IS_SCRIPT: bool = not getattr(sys, 'frozen', False) + +# Loading paths, using `os.curdir` instead of `os.dirname(__file__)` because the file dir is in a temp dir +ROOT = os.path.abspath(os.path.dirname(__file__)) if not IS_SCRIPT else os.path.join(os.path.abspath(os.getcwd()), 'src', 'python') +CONFIG = os.path.abspath('config.ini') +SAVEFILE = os.path.abspath('save.txt') +ICON = os.path.join(ROOT, 'icon.ico') +GUIDE = os.path.join(ROOT, 'guide.html') + +# Date Values +ZERO = timedelta(days=0, hours=0, seconds=0) +ONE = timedelta(seconds=1) + +# Program Info +VERSION = Version(major=2, minor=0, patch=0) +PROGRAM_NAME = 'Genshin Stopwatch' + +# Object Names +MAIN_WINDOW = 'mw' +SYS_TRAY = 'System Tray' +TOOLBAR = 'tb' diff --git a/src/python/dataParser.py b/src/python/dataParser.py new file mode 100644 index 0000000..bcf9bc1 --- /dev/null +++ b/src/python/dataParser.py @@ -0,0 +1,37 @@ +from configparser import ConfigParser +from typing import Self +from enum import StrEnum, auto +import os + +from constants import SAVEFILE + +class StopwatchDataKeys(StrEnum): + time_object = 'name' + time_finished = 'time finished' + time_original_duration = 'time original duration' + border_color = 'border color' + notes = auto() + +class dataParser(ConfigParser): + def __init__(self): + super().__init__() + + # Create save file if it doesn't exist + if not os.path.exists(SAVEFILE): + with open(SAVEFILE, 'w+') as f: + pass + + + self.read(SAVEFILE) + + def __new__(cls) -> Self: + + if not hasattr(cls, 'instance'): + + cls.instance = super(dataParser, cls).__new__(cls) + + return cls.instance + + def save(self): + with open(SAVEFILE, 'w') as f: + self.write(f) diff --git a/src/python/guide.html b/src/python/guide.html index 682189a..978051e 100644 --- a/src/python/guide.html +++ b/src/python/guide.html @@ -1,4 +1,4 @@ - + @@ -172,7 +172,7 @@

Trust rank is how much can XP can be stored before collecting.
  • - Adeptal energy is how fast you can earn the XP. (Adeptal energy a score of your teapot and how much is placed within it. It also determines your status.) + Adeptal energy is a score of your teapot and how much is placed within it. It also determines your status and how fast you can earn XP.
  • diff --git a/src/python/main.py b/src/python/main.py index 321ff5a..da5261e 100644 --- a/src/python/main.py +++ b/src/python/main.py @@ -1,1697 +1,37 @@ +import PySide6.QtGui as qtg +import PySide6.QtWidgets as qtw -from datetime import datetime, timedelta, date -import os -from configparser import ConfigParser -import requests -import webbrowser -from random import choice as randChoice - -from playsound import playsound - -import math -from PyQt5.QtCore import QPoint, QTimer, Qt, QSize -import PyQt5.QtGui as qtg -import PyQt5.QtWidgets as qtw - -from style import StyleManager - -class NotificationPanel(qtw.QWidget): - pendingNotify = [] - - def __init__(self) -> None: - super().__init__() - - self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowDoesNotAcceptFocus) - self.setObjectName('NotificationPanel') - self.setStyleSheet(styles.getStyleSheet('notify')) - - self.setWindowTitle('Stopwatch Notify') - self.setWindowIcon(qtg.QIcon(icon_path)) - - # Get screen geometry - screenDim = qtw.QDesktopWidget().screenGeometry() - availableScreenDim = qtw.QDesktopWidget().availableGeometry() - - # Size - - # Set the width to 20% of the screen width - panelWidth = availableScreenDim.width() * 0.20 - - # Set the height to 25% of the panel width - panelHeight = panelWidth * 0.25 - self.setFixedSize(QSize(math.floor(panelWidth), math.floor(panelHeight))) - - # Position - x = availableScreenDim.width() - self.width() - y = 2 * availableScreenDim.height() - screenDim.height() - self.height() - self.move(x,y) - - # Setup - self.windowLayout = qtw.QVBoxLayout() - self.windowLayout.setSpacing(0) - self.windowLayout.setContentsMargins(0,0,0,0) - - # Window Frame - self.windowFrameLayout = qtw.QHBoxLayout() - self.windowFrameLayout.setSpacing(0) - self.windowFrameLayout.setContentsMargins(0,0,0,0) - - self.windowFrame = qtw.QFrame(self) - self.windowFrame.setObjectName('windowFrame') - - # Title Label - self.titleLabel = qtw.QLabel('Genshin Stopwatch', self.windowFrame) - self.titleLabel.setContentsMargins(10,0,0,0) - self.titleLabel.setObjectName('title') - - self.windowFrameLayout.addWidget(self.titleLabel) - - # Open Window Button - - self.openButton = qtw.QPushButton('Open', self.windowFrame) - self.openButton.setToolTip('Open Genshin Stopwatch') - self.titleLabel.setContentsMargins(10,0,10,0) - self.openButton.clicked.connect(lambda: self.Stopwatch_Clicked()) - - self.windowFrameLayout.addWidget(self.openButton) - - # Close Button - self.closeButton = qtw.QPushButton('x', self.windowFrame) - self.closeButton.setMinimumSize(50, 30) - self.closeButton.clicked.connect(lambda: self.hide()) - - self.windowFrameLayout.addWidget(self.closeButton, alignment=Qt.AlignmentFlag.AlignRight) - - self.windowFrame.setLayout(self.windowFrameLayout) - - # Central Widget - - self.centralWidget = qtw.QFrame(self) - self.centralWidget.setObjectName('centralWidget') - - self.centralWidgetLayout = qtw.QVBoxLayout() - self.centralWidgetLayout.setContentsMargins(0,0,0,0) - - # Message Label - self.messageLabel = qtw.QLabel(self.centralWidget) - self.messageLabel.setContentsMargins(10,0,0,0) - self.messageLabel.setObjectName('message') - - self.centralWidgetLayout.addWidget(self.messageLabel) - - self.centralWidget.setLayout(self.centralWidgetLayout) - - # Setting up Notification Panel Layout - - self.windowLayout.addWidget(self.windowFrame) - - self.windowLayout.addWidget(self.centralWidget) - - self.setLayout(self.windowLayout) - - def Stopwatch_Clicked(self): - - self.hide() - - # Activating focus on the window - mw.activateWindow() - - if not mw.isVisible(): - mw.show() - - def Notify(self, message: str, mute: bool = False) -> None: - - # If desktop notifications are false then return - if not config['OPTIONS'].getboolean('desktop notifications', fallback=True): - return - - self.pendingNotify.append(message) - - # If the notification panel is visible, that means there is a notification already in place - if not self.isVisible(): - - self.messageLabel.setText(f'{message} has finished!') - self.show() - - else: - - self.messageLabel.setText(f'Multiple timers have finished!\n{", ".join(self.pendingNotify)}') - - sound = os.path.join(root_path, 'notify.wav') - - # This function is async because of the 2nd param - # The only instance mute will be true is when the app is loading notifications on startup so the user doesn't get spammed - if not mute: - playsound(sound, False) - - def hideEvent(self, a0: qtg.QHideEvent) -> None: - - self.pendingNotify.clear() - - return super().hideEvent(a0) - - -class addTimer(qtw.QDockWidget): - def __init__(self, parent=None | qtw.QMainWindow): - super().__init__(parent) - - self.setWindowTitle('Add Timer') - self.setObjectName('addTimerDockWidget') - self.setAllowedAreas(Qt.RightDockWidgetArea) - self.setFeatures(self.DockWidgetClosable) - - # Central Frame - self.centralFrame = qtw.QFrame(self) - - # Vertical Box Layout - verticalLayout = qtw.QVBoxLayout() - self.centralFrame.setLayout(verticalLayout) - - # Topic Frame - self.topicFrame = qtw.QFrame(self.centralFrame) - verticalLayout.addWidget(self.topicFrame) - - # Form Layout (For Topic Frame) - self.formLayout = qtw.QFormLayout() - self.formLayout.setVerticalSpacing(15) - self.formLayout.setRowWrapPolicy(qtw.QFormLayout.WrapAllRows) - self.topicFrame.setLayout(self.formLayout) - - - # Topic data - self.topicSelectionDict = { - - '': { - 'durations': tuple(['Nothing Selected']) - }, - - 'Stamina': { - 'durations': 'Stamina' - }, - - 'Parametric Transformer': { - 'durations': tuple(['7 Days']) - }, - - 'Respawns': { - 'durations': ('12 Hours', '1 Day', '2 Days', '3 Days') - }, - - 'Expedition': { - 'durations': ('4 Hours', '8 Hours', '12 Hours', '20 Hours') - }, - - 'Teapot Gardening/Construction': { - 'durations': ('12 Hours', '14 Hours', '16 Hours', '70 Hours') - }, - - 'Realm Currency': { - 'durations': ('Bare-Bones', 'Humble Abode', 'Cozy', 'Queen-Size', 'Elegant', 'Exquisite', 'Extradordinary', 'Stately', 'Luxury', 'Fit for a king') - }, - - 'Realm Companionship XP': { - 'durations' : ('0 - 2999 (2/hr)', '3000 - 5999 (3/hr)', '6000 - 11999 (4/hr)', '12000+ (5/hr)') - }, - - 'Fishing': { - 'durations': ('1 Day', '3 Days') - }, - - 'Custom': { - 'durations': 'Custom' - } - - } - - # Topic Selection Row - self.topicLabel = qtw.QLabel('Timed Object:') - self.topicDropDown = qtw.QComboBox() - self.topicDropDown.setFocusPolicy(Qt.NoFocus) - self.topicDropDown.addItems( [x for x in self.topicSelectionDict.keys()] ) - self.topicDropDown.currentTextChanged.connect(lambda topic : self.dropDownSelected(topic)) - - self.formLayout.addRow(self.topicLabel, self.topicDropDown) - - # Duration Row - self.durationLabel = qtw.QLabel('Duration:') - self.durationDropDown = qtw.QComboBox() - self.durationDropDown.addItem('Nothing Selected') - self.durationDropDown.setFocusPolicy(Qt.NoFocus) - - self.formLayout.addRow(self.durationLabel, self.durationDropDown) - - # Values for when the player has characters that have a 25% time discount - # {'4 Hours': 3} - self.expeditionTwentyFivePercentOffDict = {k:v for (k,v) in zip(self.topicSelectionDict['Expedition']['durations'], ('3 Hours', '6 Hours', '9 Hours', '15 Hours'))} - - self.expeditionLabel = qtw.QLabel('Using a 25% Time Reduction Character') - self.expeditionCheckBox = qtw.QCheckBox() - self.expeditionCheckBox.setChecked(config['QOL'].getboolean('expedition_checkbox', fallback=True)) - - self.formLayout.addRow(self.expeditionLabel, self.expeditionCheckBox) - - # Realm Currency Level (Hidden by default, see show/hide events) - # {Trust Rank : Realm Currency Storage Limit} - self.rCLevelValues = { - '1' : 300, - '2' : 300, - '3' : 900, - '4' : 1200, - '5' : 1400, - '6' : 1600, - '7' : 1800, - '8' : 2000, - '9' : 2200, - '10' : 2400 - } - - # Dict of Realm Statuses and their corresponding income rates - # {status:rate/hr} - - self.rCRateValuesDict = {k:v for (k,v) in zip(self.topicSelectionDict['Realm Currency']['durations'], (4, 8, 12, 16, 20, 22, 24, 26, 28, 30))} - - # Dict of Adeptal Energy thresholds and their corresponding income rates for companionship XP - # {adeptal energy range:rate/hr} - - self.rCFriendshipValuesDict = {k:v for (k,v) in zip(self.topicSelectionDict['Realm Companionship XP']['durations'], range(2, 6)) } - - # Line edit w/ label (Hidden by default, see show/hide events) - - self.lineEditLabel1 = qtw.QLabel() - self.lineEditLabel1.setObjectName('lineEditLabel1') - self.lineEdit1 = qtw.QLineEdit() - self.lineEdit1.setObjectName('lineEdit1') - - self.formLayout.addRow(self.lineEditLabel1, self.lineEdit1) - - # Line edit w/ label (Hidden by default, see show/hide events) - - self.lineEditLabel2 = qtw.QLabel() - self.lineEditLabel2.setObjectName('lineEditLabel2') - self.lineEdit2 = qtw.QLineEdit() - self.lineEdit2.setObjectName('lineEdit2') - - self.formLayout.addRow(self.lineEditLabel2, self.lineEdit2) - - # Line edit w/ label (Hidden by default, see show/hide events) - - self.lineEditLabel3 = qtw.QLabel() - self.lineEditLabel3.setObjectName('lineEditLabel3') - self.lineEdit3 = qtw.QLineEdit() - self.lineEdit3.setObjectName('lineEdit3') - - self.formLayout.addRow(self.lineEditLabel3, self.lineEdit3) - - # Name Row - self.nameLabel = qtw.QLabel('Name (Optional):') - self.nameLineEdit = qtw.QLineEdit() - - self.formLayout.addRow(self.nameLabel, self.nameLineEdit) - - self.colorsDict: list[str] = list(styles.getStopwatchColors().keys()) - - # Color Row - self.outlineColorLabel = qtw.QLabel('Border Color:') - self.outlineColorDropDown = qtw.QComboBox() - self.outlineColorDropDown.setFocusPolicy(Qt.NoFocus) - self.outlineColorDropDown.addItems([x.title() for x in self.colorsDict]) - - self.formLayout.addRow(self.outlineColorLabel, self.outlineColorDropDown) - - # Button - self.startTimerButton = qtw.QPushButton('Start Timer') - self.startTimerButton.clicked.connect(lambda: self.startStopWatch()) - verticalLayout.addWidget(self.startTimerButton, alignment=Qt.AlignTop) - - self.setWidget(self.centralFrame) - - def hideAll(self): - for widget in (self.lineEdit1, - self.lineEdit2, - self.lineEdit3, - self.lineEditLabel1, - self.lineEditLabel2, - self.lineEditLabel3, - self.durationDropDown, - self.durationLabel, - self.expeditionCheckBox, - self.expeditionLabel): - - widget.hide() - - def showRealmCurrency(self): - # Show realm currency elements - - self.lineEditLabel1.show() - self.lineEdit1.show() - - def showCustom(self): - # Show custom elements - - self.lineEditLabel1.show() - self.lineEdit1.show() - - self.lineEditLabel2.show() - self.lineEdit2.show() - - self.lineEditLabel3.show() - self.lineEdit3.show() - - def showNormalDurations(self): - - self.durationLabel.show() - self.durationDropDown.show() - - def showExpedition(self): - - self.durationLabel.show() - self.durationDropDown.show() - - self.expeditionLabel.show() - self.expeditionCheckBox.show() - - def dropDownSelected(self, topic: str): - selectedTopic = self.topicSelectionDict[topic]['durations'] - - self.durationDropDown.clear() - - #Hiding all elements - self.hideAll() - - # Clearning line edits to prevent text from carrying over - self.lineEdit1.clear(), self.lineEdit2.clear(), self.lineEdit3.clear() - - match topic: - - case 'Expedition': - - self.showExpedition() - self.durationDropDown.addItems(selectedTopic) - - case 'Realm Currency': - - self.showNormalDurations() - self.durationLabel.setText('Realm Status:') - self.durationDropDown.addItems(selectedTopic) - - self.showRealmCurrency() - self.lineEditLabel1.setText('Realm Trust Level (1-10)') - - case 'Realm Companionship XP': - - self.showNormalDurations() - self.durationLabel.setText('Adeptal Energy:') - self.durationDropDown.addItems(selectedTopic) - - self.showRealmCurrency() - self.lineEditLabel1.setText('Realm Trust Level (1-10)') - - case 'Stamina': - - self.lineEditLabel1.setText('Current Stamina (Max 160)') - self.lineEditLabel1.show() - self.lineEdit1.show() - - self.lineEditLabel2.setText('Desired stamina (Max 160)') - self.lineEditLabel2.show() - self.lineEdit2.setText(config['QOL'].get('desiredStamina', fallback='160')) - self.lineEdit2.show() - - case 'Custom': - - self.showCustom() - self.lineEditLabel1.setText('Days:') - self.lineEditLabel2.setText('Hours:') - self.lineEditLabel3.setText('Minutes:') - - case _: - - self.durationLabel.setText('Duration:') - - if self.durationLabel.isHidden(): - - self.showNormalDurations() - - self.durationDropDown.addItems(selectedTopic) - - - def startStopWatch(self): - ''' Fired when the addTimer button is pressed. This gathers then transforms the current data suitable for the stopwatch's parameters and begins the stopwatch ''' - - # Get the selected time object and outline color - - timeObject = self.topicDropDown.currentText() - - # self.colorsDict is excluding index 0 because that would be the 'random' key value in the dict - color: str = randChoice(self.colorsDict[1:]) if self.outlineColorDropDown.currentText() == 'Random' else self.outlineColorDropDown.currentText() - - days: int = 0 - hours: int = 0 - minutes: int = 0 - - percentToMinutes: callable[[str], str] = lambda rate: round( (float(rate) / 100) * 60 ) - calculateDuration: callable[[int, int], str] = lambda maxStorage, rate: str(round(maxStorage/rate, 2)).split('.') - - # Match the selected time object - match timeObject: - - case 'Expedition': - - duration: str = self.durationDropDown.currentText() - - hours = duration if not self.expeditionCheckBox.isChecked() else self.expeditionTwentyFivePercentOffDict[duration] - - hours = int(hours.split()[0]) - - case 'Realm Currency': - - try: - # Check if the input is in the valid range - if int(self.lineEdit1.text()) not in range(1, 11): - raise ValueError - except ValueError: - return self.lineEdit1.setText('Error: Invalid Input') - # Rerieve maximum storage and rate values - maxStorage: int = self.rCLevelValues[self.lineEdit1.text()] - - rate: str = self.durationDropDown.currentText() - duration = self.rCRateValuesDict[rate] - - duration: list[str] = calculateDuration(maxStorage, duration) - - hours = int(duration[0]) - minutes = percentToMinutes(duration[1]) - - case 'Realm Companionship XP': - - try: - # Check if the input is in the valid range - if int(self.lineEdit1.text()) not in range(1, 11): - raise ValueError - except ValueError: - return self.lineEdit1.setText('Error: Invalid Input') - # Rerieve maximum storage and rate values - - maxStorage = int(self.lineEdit1.text()) * 50 - - rate: str = self.durationDropDown.currentText() - duration = self.rCFriendshipValuesDict[rate] - - duration = calculateDuration(maxStorage, duration) - - hours = int(duration[0]) - minutes = percentToMinutes(duration[1]) - - case 'Custom': - # Retrieve input values for days, hours, and minutes - days: str|int = self.lineEdit1.text() if self.lineEdit1.text().isdigit() else 0 - hours: str|int = self.lineEdit2.text() if self.lineEdit2.text().isdigit() else 0 - minutes: str|int = self.lineEdit3.text() if self.lineEdit3.text().isdigit() else 0 - # Convert values to positive integers - days = abs(int(days)) - hours = abs(int(hours)) - minutes = abs(int(minutes)) - - case 'Stamina': - - # Get inputs - amountOfStamina: str = self.lineEdit1.text() - desiredStamina: str = self.lineEdit2.text() - - # Checking for valid inputs - if amountOfStamina.isdigit() == False or int(amountOfStamina) > 160 or int(amountOfStamina) < 0: - return self.lineEdit1.setText('Error: Invalid Input') - - elif desiredStamina.isdigit() == False or int(desiredStamina) > 160 or int(desiredStamina) < 0: - return self.lineEdit2.setText('Error: Invalid Input') - - # Program remembers how much stamina the user wants - config['QOL']['desiredStamina'] = desiredStamina - saveConfig() - - # Example: (160 - 20 = 140), user needs 140 stamina until 160, stamina takes 8 minutes to regen 1 stamina - minutes = (int(desiredStamina) - int(amountOfStamina)) * 8 - - # Case: default - case _: - - duration: str = self.durationDropDown.currentText() - - # Check if no duration is selected - if duration == 'Nothing Selected': - return - - duration = duration.split() - - if duration[1] == 'Days' or duration[1] == 'Day': - duration = int(duration[0]) * 24 - else: - duration = int(duration[0]) - - - hours = duration - - # Create a timedelta object with the calculated duration - duration = timedelta(days=days, hours=hours, minutes=minutes) - - # Get the text from the nameLineEdit and use it as the name if it is not empty, otherwise use timeObject - nameText = self.nameLineEdit.text() - name = nameText if len(nameText) > 0 else timeObject - - # Find the central widget and call the addStopWatch method with the relevant parameters - centralWidget_: centralWidget = self.parent().findChild(qtw.QWidget, 'centralWidget') - - centralWidget_.addStopWatch(timeObject, duration, name, duration, color) - - - def hideEvent(self, a0: qtg.QHideEvent) -> None: - # Check if the parent widget is hidden - - if self.parent().isHidden(): - a0.ignore() - - else: - - parent: qtw.QMainWindow = self.parent() - toolbar: qtw.QToolBar = parent.findChild(qtw.QToolBar) - # Uncheck the addtimerButton - - addtimerButton: qtw.QAction = toolbar.findChild(qtw.QAction, 'addTimerButton') - - addtimerButton.setChecked(False) - - if not parent.isMinimized(): - - config['QOL']['addtimer open on startup'] = 'False' - saveConfig() - - return super().hideEvent(a0) - - def showEvent(self, a0: qtg.QShowEvent) -> None: - - parent: qtw.QMainWindow = self.parent() - toolbar: qtw.QToolBar = parent.findChild(qtw.QToolBar) - addtimerButton: qtw.QAction = toolbar.findChild(qtw.QAction, 'addTimerButton') - # Check the addtimerButton - - addtimerButton.setChecked(True) - - self.dropDownSelected(self.topicDropDown.currentText()) - # Check if the parent widget is minimized - if not parent.isMinimized(): - - config['QOL']['addtimer open on startup'] = 'True' - - saveConfig() - # Call the base class's showEvent method - - return super().showEvent(a0) - -class optionsDock(qtw.QDockWidget): - def __init__(self, parent=None | qtw.QMainWindow): - super().__init__(parent) - - self.setWindowTitle('Options') - self.setObjectName('optionsDockWidget') - self.setAllowedAreas(Qt.RightDockWidgetArea) - self.setFeatures(self.DockWidgetClosable) - - # Central Frame - self.centralFrame = qtw.QFrame(self) - - # Vertical Box Layout - verticalLayout = qtw.QVBoxLayout() - self.centralFrame.setLayout(verticalLayout) - - # Topic Frame - self.topicFrame = qtw.QFrame(self.centralFrame) - verticalLayout.addWidget(self.topicFrame) - - # Form Layout (For Topic Frame) - self.formLayout = qtw.QFormLayout() - self.formLayout.setVerticalSpacing(20) - self.formLayout.setHorizontalSpacing(10) - self.topicFrame.setLayout(self.formLayout) - - # Checkbox - self.appOnCloseCheckbox = qtw.QCheckBox() - self.appOnCloseCheckbox.setChecked(config['OPTIONS'].getboolean('shutdown app on close', fallback=False)) - self.appOnCloseCheckbox.clicked.connect(lambda: self.settingChanged('true')) - self.appOnCloseCheckbox.setToolTip('The app will close and not run in the background.') - # Form Row - self.formLayout.addRow('Shutdown app on close: ', self.appOnCloseCheckbox) - - # Checkbox - self.showOnOpenCheckbox = qtw.QCheckBox() - self.showOnOpenCheckbox.setChecked(config['OPTIONS'].getboolean('show on startup', fallback=True)) - self.showOnOpenCheckbox.clicked.connect(lambda: self.settingChanged('true')) - self.showOnOpenCheckbox.setToolTip('Program will automatically show or hide when starting.') - # Form Row - self.formLayout.addRow('Show on app start: ', self.showOnOpenCheckbox) - - self.updateCheckBox = qtw.QCheckBox() - self.updateCheckBox.setChecked(config['OPTIONS'].getboolean('checkVersionOnStartup', fallback=True)) - self.updateCheckBox.clicked.connect(lambda: self.settingChanged('true')) - self.updateCheckBox.setToolTip('When the program starts it will go online to check and see if your client is up-to-date') - self.formLayout.addRow('Check for update on startup: ', self.updateCheckBox) - - # Checkbox - self.notifyCheckbox = qtw.QCheckBox() - self.notifyCheckbox.setChecked(config['OPTIONS'].getboolean('desktop notifications', fallback=True)) - self.notifyCheckbox.clicked.connect(lambda: self.settingChanged('true')) - self.notifyCheckbox.setToolTip('A windows desktop notification will appear when a stopwatch finishes.') - # Form Row - self.formLayout.addRow('Desktop notifications: ', self.notifyCheckbox) - - # Checkbox - self.staticDailyNotifyCheckbox = qtw.QCheckBox() - self.staticDailyNotifyCheckbox.setChecked(config['OPTIONS'].getboolean('dailyResetNotify', fallback=False)) - self.staticDailyNotifyCheckbox.clicked.connect(lambda: self.settingChanged('true')) - self.staticDailyNotifyCheckbox.setToolTip('Will recieve a notification when there is a daily reset.') - # Form Row - self.formLayout.addRow('Daily Reset Notifications: ', self.staticDailyNotifyCheckbox) - - # Checkbox - self.staticWeeklyNotifyCheckbox = qtw.QCheckBox() - self.staticWeeklyNotifyCheckbox.setChecked(config['OPTIONS'].getboolean('weeklyResetNotify', fallback=False)) - self.staticWeeklyNotifyCheckbox.clicked.connect(lambda: self.settingChanged('true')) - self.staticWeeklyNotifyCheckbox.setToolTip('Will recieve a notification when there is a daily reset.') - # Form Row - self.formLayout.addRow('Weekly Reset Notifications: ', self.staticWeeklyNotifyCheckbox) - - # Dropdown - self.colorPallet = qtw.QComboBox() - self.colorPallet.addItems([x.title() for x in styles.getColorPallets()]) - self.colorPallet.setCurrentText(config.get('OPTIONS', 'color pallet', fallback='dark').title()) - self.colorPallet.currentTextChanged.connect(lambda: self.settingChanged('true')) - self.colorPallet.setFocusPolicy(Qt.NoFocus) - # Form Row - self.formLayout.addRow('Color Scheme: ', self.colorPallet) - - # Apply Settings Button - self.applyButton = qtw.QPushButton('Apply', self) - self.applyButton.setObjectName('applySettingsButton') - self.applyButton.clicked.connect(lambda: self.applySettings()) - self.applyButton.clicked.connect(lambda: self.settingChanged("false")) - self.applyButton.setProperty('unsavedChanges', "false") - verticalLayout.addWidget(self.applyButton, alignment=Qt.AlignTop) - - # Check for updates button - self.updateButton = qtw.QPushButton('Check for updates', self) - self.updateButton.setObjectName('checkUpdateButton') - self.updateButton.clicked.connect(lambda: self.checkUpdate(button=True)) - verticalLayout.addWidget(self.updateButton, alignment=Qt.AlignTop) - - self.setWidget(self.centralFrame) - - def settingChanged(self, boolean: str): - - # Change the property of the applyButton to signal the user a setting's been changed - self.applyButton.setProperty('unsavedChanges', f"{boolean}") - - self.style().polish(self.applyButton) - - def checkUpdate(self, button: bool = False) -> None: - - # Will create a dialog window if there's a version update unless specified not to - if version < version_check and config['OPTIONS'].getboolean('checkVersionOnStart', fallback=True): - - updateNotify: qtw.QDialog = UpdateAlert(self) - - updateNotify.exec() - - return - - # Checking if the function was started by the "check for update button" - if button: - self.updateButton.setText('On latest version ^_^') - QTimer.singleShot(3000, lambda: self.updateButton.setText('Check for updates')) - - def applySettings(self): - # Create a dictionary with updated configuration settings - - updatedConfig = { - 'shutdown app on close' : str(self.appOnCloseCheckbox.isChecked()), - 'show on startup' : str(self.showOnOpenCheckbox.isChecked()), - 'desktop notifications' : str(self.notifyCheckbox.isChecked()), - 'color pallet' : self.colorPallet.currentText().lower(), - 'checkversiononstartup' : str(self.updateCheckBox.isChecked()), - 'dailyResetNotify' : str(self.staticDailyNotifyCheckbox.isChecked()), - 'weeklyResetNotify' : str(self.staticWeeklyNotifyCheckbox.isChecked()) - } - - # Checking to see if the user wants to change the color scheme - if self.colorPallet.currentText() != config.get('OPTIONS', 'color pallet', fallback='dark'): - - styles.changeColorPallet(updatedConfig['color pallet']) - - for stopwatch in mw.central.findChildren(qtw.QFrame): - - # Identifying a stopwatch - if len(stopwatch.objectName()) == 13: - # Updating the stylesheet again to change border color - styles.changeStopwatchBorderColor(stopwatch.property('border-color')) - - # Change stopwatch stylesheet - stopwatch.setStyleSheet(styles.getStyleSheet('stopwatch')) - mw.central.style().polish(stopwatch) - - # Change global stylesheet - app.setStyleSheet(styles.getStyleSheet('app')) - app.style().polish(app) - - # Change Notification Panel Stylesheet - notify.setStyleSheet(styles.getStyleSheet('notify')) - notify.style().polish(notify) - - config['OPTIONS'] = updatedConfig - - saveConfig() - self.settingChanged(False) - - def hideEvent(self, a0: qtg.QHideEvent) -> None: - # Check if the parent widget is hidden - - if self.parent().isHidden(): - a0.ignore() - - else: - - parent: qtw.QMainWindow = self.parent() - toolbar: qtw.QToolBar = parent.findChild(qtw.QToolBar) - optionsButton: qtw.QAction = toolbar.findChild(qtw.QAction, 'optionsButton') - - optionsButton.setChecked(False) - - if not parent.isMinimized(): - # Update the configuration setting 'settings open on startup' to 'False' - config['QOL']['settings open on startup'] = 'False' - - # Save the updated configuration - saveConfig() - - return super().hideEvent(a0) - - def showEvent(self, a0: qtg.QShowEvent) -> None: - # Get references to the parent widget, toolbar, and optionsButton - - parent: qtw.QMainWindow = self.parent() - toolbar: qtw.QToolBar = parent.findChild(qtw.QToolBar) - optionsButton: qtw.QAction = toolbar.findChild(qtw.QAction, 'optionsButton') - # Set the optionsButton as checked - - optionsButton.setChecked(True) - - # Update the configuration setting 'settings open on startup' to 'True' - if not parent.isMinimized(): - - config['QOL']['settings open on startup'] = 'True' - saveConfig() - return super().showEvent(a0) - -class Guide(qtw.QDockWidget): - def __init__(self, parent=None | qtw.QMainWindow): - super().__init__(parent) - - self.setWindowTitle('Guide') - self.setObjectName('guideDockWidget') - self.setAllowedAreas(Qt.RightDockWidgetArea) - self.setFeatures(self.DockWidgetClosable) - - # Central Frame - self.centralFrame: qtw.QFrame = qtw.QFrame(self) - - # Vertical Box Layout - verticalLayout: qtw.QVBoxLayout = qtw.QVBoxLayout() - self.centralFrame.setLayout(verticalLayout) - - self.textFile: qtw.QTextBrowser = qtw.QTextBrowser(self.centralFrame) - self.textFile.setReadOnly(True) - - # Reading html guide - # The guide is packaged into the .exe so it is always updated - try: - - with open(os.path.join(root_path, 'guide.html'), 'r') as html: - - - self.textFile.setHtml(html.read()) - - except Exception as e: - print(e) - - self.textFile.setHtml( - f''' -

    Error. Could not find Guide:

    -

    - {e} - ''') - - verticalLayout.addWidget(self.textFile) - - self.setWidget(self.centralFrame) - - def hideEvent(self, a0: qtg.QHideEvent) -> None: - # Check if the parent widget is hidden - - if self.parent().isHidden(): - a0.ignore() - - else: - - parent: qtw.QMainWindow = self.parent() - toolbar: qtw.QToolBar = parent.findChild(qtw.QToolBar) - # Uncheck the guideButton - - guideButton: qtw.QAction = toolbar.findChild(qtw.QAction, 'guideButton') - - guideButton.setChecked(False) - - if not parent.isMinimized(): - - config['QOL']['guide open on startup'] = 'False' - saveConfig() - - return super().hideEvent(a0) - - def showEvent(self, a0: qtg.QShowEvent) -> None: - - parent: qtw.QMainWindow = self.parent() - toolbar: qtw.QToolBar = parent.findChild(qtw.QToolBar) - guideButton: qtw.QAction = toolbar.findChild(qtw.QAction, 'guideButton') - # Check the guideButton - - guideButton.setChecked(True) - - # Check if the parent widget is minimized - if not parent.isMinimized(): - - config['QOL']['guide open on startup'] = 'True' - - saveConfig() - # Call the base class's showEvent method - - return super().showEvent(a0) - - -class staticTimers(qtw.QDockWidget): - def __init__(self, parent=None | qtw.QMainWindow): - super().__init__(parent) - - self.setWindowTitle('Static Timers') - self.setObjectName('staticTimersDockWidget') - self.setAllowedAreas(Qt.RightDockWidgetArea) - self.setFeatures(self.DockWidgetClosable) - - # Checking to see if 'STATIC_TIMERS' is a valid section in config.ini - try: - config['STATIC_TIMERS'].keys() - except KeyError: - config.add_section('STATIC_TIMERS') - saveConfig() - - # Instancing the deadlines that may or may have not been reached to make room for the new deadlines - # This is for checkNotify() - self.oldDeadlines: dict[str:str] = {k:v for (k, v) in config['STATIC_TIMERS'].items()} - - # Central Frame - self.centralFrame: qtw.QFrame = qtw.QFrame(self) - - # Form Layout - formlayout: qtw.QFormLayout = qtw.QFormLayout() - self.centralFrame.setLayout(formlayout) - - # Daily reset timer - self.dailyReset = qtw.QLabel(self, text='Loading...') - self.dailyReset.setObjectName('dailyResetTimer') - formlayout.addRow(qtw.QLabel(self, text='Daily Reset: '), self.dailyReset) - - # Weekly reset timer - self.weeklyReset = qtw.QLabel(self, text='Loading...') - self.weeklyReset.setObjectName('weeklyResetTimer') - formlayout.addRow(qtw.QLabel(self, text='Weekly Reset: '), self.weeklyReset) - - # Server's UTC offsets - self.serverTimezones: dict[str:int|float] = { - 'Asia' : 8, - 'EU' : 1, - 'NA' : -5 - } - - # Dropdown - self.chooseServerDropDown = qtw.QComboBox(self) - self.chooseServerDropDown.addItems(list(self.serverTimezones.keys())) - self.chooseServerDropDown.setCurrentText(config['QOL'].get('gameServer', fallback='NA')) - self.chooseServerDropDown.currentTextChanged.connect(lambda server: self.changeServer(server)) - formlayout.addRow('Server: ', self.chooseServerDropDown) - - self.setWidget(self.centralFrame) - - # Defining variables for timers - - self.dailyQTimer = QTimer(self) - self.weeklyQTimer = QTimer(self) - - self.one = timedelta(seconds=1) - self.zero = timedelta(seconds=0) - - self.serverChange = False - - # Start Static Timers - self.staticTimerStart() - - def staticTimerStart(self): - - self.serverChange = False - - # Update perferred server setting - self.selectedServer: str = config['QOL'].get('gameServer', fallback='NA') - - # Define today's date and time - self.today = datetime.today().replace(microsecond=0) - - ### Daily Timer - - # Calculating the deadline of the daily reset with their choice of server - self.dailyDeadline = datetime(year=self.today.year, month=self.today.month, day=self.today.day, hour=9) + timedelta(hours=self.serverTimezones[self.selectedServer]) - - # If the program starts after the reset time happened, add a day to the deadline - if self.dailyDeadline < self.today: - - self.dailyDeadline += timedelta(days=1) - - config['STATIC_TIMERS']['dailydeadline'] = datetime.strftime(self.dailyDeadline, '%Y-%m-%d %I:%M:%S') - - self.difference = self.dailyDeadline - self.today - - ### Weekly Timer - - # Calculating how many days left until Monday - # date.weekday starts at 0 - dayOfTheWeek = date.weekday(self.today) - self.weeklyDayDifference: int = 7 - dayOfTheWeek - - # Calculating the deadline of the weekly reset with their choice of server - # Creating datetime object, hour 9 is the time for UTC+0 for weekly reset - self.weeklyDeadline = datetime(year=self.today.year, month=self.today.month, day=self.today.day, hour=9) - - # Adding UTC Offset depepending on server selected - self.weeklyDeadline += timedelta(hours=self.serverTimezones[self.selectedServer]) - - # Adding days left until upcoming Sunday - self.weeklyDeadline += timedelta(days=self.weeklyDayDifference) - - config['STATIC_TIMERS']['weeklydeadline'] = datetime.strftime(self.weeklyDeadline, '%Y-%m-%d %I:%M:%S') - - self.weeklyDifference = self.weeklyDeadline - self.today - - saveConfig() - - self.dailyResetTimer() - self.weeklyResetTimer() - - # Renabling server selection - self.chooseServerDropDown.setEnabled(True) - - - def dailyResetTimer(self): - - if self.difference > self.zero: - - self.difference -= self.one - - self.dailyReset.setText(str(self.difference)) - - # Returns when the user changes the server to prevent timer being called multiple times - if not self.serverChange: - self.dailyQTimer.singleShot(1000, lambda: self.dailyResetTimer()) - else: - return - - else: - - today = datetime.today() - - self.dailyDeadline += timedelta(days=1) - config['STATIC_TIMERS']['dailyDeadline'] = datetime.strftime(self.weeklyDeadline, '%Y-%m-%d %I:%M:%S') - saveConfig() - - self.difference = self.dailyDeadline - today - - self.dailyQTimer.singleShot(1000, lambda: self.dailyResetTimer()) - - if config['OPTIONS'].getboolean('dailyResetNotify', fallback=False): - - notify.Notify('Daily Reset') - - def weeklyResetTimer(self): - - if self.weeklyDifference > self.zero: - - self.weeklyDifference -= self.one - - # Used formatted string because you only need to see the days since `daily reset` tells you the time - self.weeklyReset.setText(f'{self.weeklyDifference.days} Day(s) left') - - # Returns when the user changes the server to prevent timer being called multiple times - if not self.serverChange: - self.weeklyQTimer.singleShot(1000, lambda: self.weeklyResetTimer()) - else: - return - - else: - - today = datetime.today() - - self.weeklyDeadline += timedelta(days=7) - config['STATIC_TIMERS']['weeklyDeadline'] = datetime.strftime(self.weeklyDeadline, '%Y-%m-%d %I:%M:%S') - saveConfig() - - self.weeklyDifference = self.weeklyDeadline - today - - self.weeklyQTimer.singleShot(1000, lambda: self.weeklyResetTimer()) - - if config['OPTIONS'].getboolean('weeklyResetNotify', fallback=False): - - notify.Notify('Weekly Reset') - - def changeServer(self, server: str): - - # Prevent the selection from being spammed which would break the timers - self.chooseServerDropDown.setEnabled(False) - - - config['QOL']['gameServer'] = server - saveConfig() - - self.serverChange = True - - QTimer.singleShot(1000, lambda: self.staticTimerStart()) - - def checkNotify(self): - '''Checking to see if resets happened while the program was shutdown''' - - static_timers: list[str] = [] - - for name, date in self.oldDeadlines.items(): - - # Getting notification setting bool - notification: bool = config['OPTIONS'].getboolean('{0}ResetNotify'.format(name.split('deadline')[0]), fallback=False) - - # If date isn't valid then skip to the next iteration - try: - deadline: datetime = datetime.strptime(date, '%Y-%m-%d %I:%M:%S') - except ValueError as e: - print(e) - continue - - # If time has passed the static timer's deadline and if the notification settings are set to true - if deadline < self.today and notification: - - - static_timers.append(name.split('deadline')[0].title()) - - # Empty lists return false - if not static_timers: - return - - else: - notify.Notify(", ".join(static_timers)) - - - def hideEvent(self, a0: qtg.QHideEvent) -> None: - # Check if the parent widget is hidden - - if self.parent().isHidden(): - a0.ignore() - - else: - - parent: qtw.QMainWindow = self.parent() - toolbar: qtw.QToolBar = parent.findChild(qtw.QToolBar) - staticTimersButton: qtw.QAction = toolbar.findChild(qtw.QAction, 'staticTimersButton') - - staticTimersButton.setChecked(False) - - if not parent.isMinimized(): - # Update the configuration setting 'settings open on startup' to 'False' - config['QOL']['static timers open on startup'] = 'False' - - # Save the updated configuration - saveConfig() - - return super().hideEvent(a0) - - def showEvent(self, a0: qtg.QShowEvent) -> None: - # Get references to the parent widget, toolbar, and optionsButton - - parent: qtw.QMainWindow = self.parent() - toolbar: qtw.QToolBar = parent.findChild(qtw.QToolBar) - staticTimersButton: qtw.QAction = toolbar.findChild(qtw.QAction, 'staticTimersButton') - # Set the optionsButton as checked - - staticTimersButton.setChecked(True) - - # Update the configuration setting 'settings open on startup' to 'True' - if not parent.isMinimized(): - - config['QOL']['static timers open on startup'] = 'True' - saveConfig() - return super().showEvent(a0) - - -class toolbar(qtw.QToolBar): - def __init__(self, parent=None | qtw.QMainWindow): - super().__init__(parent) - - # Set floatable and movable to False - self.setFloatable(False) - self.setMovable(False) - - self.layout().setSpacing(20) - - # Add Timer Button - self.addTimerButton = qtw.QAction('Add Timer', self) - self.addTimerButton.setObjectName('addTimerButton') - self.addTimerButton.setCheckable(True) - self.addTimerButton.setChecked(config['QOL'].getboolean('addtimer open on startup', fallback=True)) - self.addTimerButton.triggered.connect(lambda: self.button_Clicked('addTimerDockWidget', self.addTimerButton.isChecked() ) ) - self.addAction(self.addTimerButton) - - # Options Button - self.optionsButton = qtw.QAction('Options', self) - self.optionsButton.setObjectName('optionsButton') - self.optionsButton.setCheckable(True) - self.optionsButton.setChecked(config['QOL'].getboolean('settings open on startup', fallback=False)) - self.optionsButton.triggered.connect(lambda: self.button_Clicked('optionsDockWidget', self.optionsButton.isChecked() ) ) - self.addAction(self.optionsButton) - - # Guide Button - self.guideButton = qtw.QAction('Guide', self) - self.guideButton.setObjectName('guideButton') - self.guideButton.setCheckable(True) - self.guideButton.setChecked(config['QOL'].getboolean('guide open on startup', fallback=False)) - self.guideButton.triggered.connect(lambda: self.button_Clicked('guideDockWidget', self.guideButton.isChecked())) - self.addAction(self.guideButton) - - # Static Timers Button - self.staticButton = qtw.QAction('Static Timers', self) - self.staticButton.setObjectName('staticTimersButton') - self.staticButton.setCheckable(True) - self.staticButton.setChecked(config['QOL'].getboolean('static timers open on startup', fallback=False)) - self.staticButton.triggered.connect(lambda: self.button_Clicked('staticTimersDockWidget', self.staticButton.isChecked())) - self.addAction(self.staticButton) - - def button_Clicked(self, dockObjectName: str, buttonisChecked: bool): - # Find the QDockWidget based on the dockObjectName - - addDockWidget: qtw.QDockWidget = self.parent().findChild(qtw.QDockWidget, dockObjectName) - - if buttonisChecked: - # If the button is checked, show the QDockWidget - - addDockWidget.show() - - else: - # If the button is not checked, hide the QDockWidget - - addDockWidget.hide() - - -class centralWidget(qtw.QWidget): - def __init__(self, parent=None | qtw.QMainWindow): - super().__init__(parent) - # Create the layout for the central widget - - self.setObjectName('centralWidget') - self.scrollAreaLayout = qtw.QHBoxLayout(self) - self.scrollAreaLayout.setContentsMargins(0,0,0,0) - self.verticalLayout = qtw.QVBoxLayout() - # Create the scroll area - - self.scrollArea = qtw.QScrollArea() - self.scrollArea.setWidgetResizable(True) - self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.scrollArea.setContentsMargins(0,0,0,0) - # Create the widget to hold the scroll area contents - - self.scrollAreaWidgetContents = qtw.QWidget() - self.scrollAreaWidgetContents.setContentsMargins(0,0,0,0) - self.scrollAreaWidgetContents.setLayout(self.verticalLayout) - # Set the scroll area widget - - self.scrollArea.setWidget(self.scrollAreaWidgetContents) - self.scrollAreaLayout.addWidget(self.scrollArea) - - def addStopWatch(self, timeObject: str, duration: timedelta, name: str, startDuration: timedelta, color: str, notepadContents: str = '', index: int | None = None, save: bool = True) -> None: - # Create a QFrame to hold the stopwatch - - styles.changeStopwatchBorderColor(color) - color = styles.getStopwatchColor(color) - - self.frame = qtw.QFrame(self.scrollAreaWidgetContents) - self.frame.setStyleSheet(styles.getStyleSheet('stopwatch')) - - # Set the object name of the frame using its ID - id_ = str(id(self.frame)) - self.frame.setObjectName(id_) - self.frame.setMaximumHeight(500) - - parent: qtw.QFrame = self.findChild(qtw.QFrame, id_) - # Set the border color property of the frame - - parent.setProperty('border-color', color) - # Extract the start duration in days and minutes - - startDurationDays = int( str(startDuration).split(':')[0].split()[0] ) - startDurationMinutes = int( str(startDuration).split(':')[1].split(':')[0] ) - # Adjust the start duration minutes to have two digits - - if len(str(startDurationMinutes)) == 1: - startDurationMinutes = str(startDurationMinutes) + '0' - # Set the original duration property of the frame - - if startDuration >= timedelta(hours=24): # startDuration gives the days but not in total hours, needed so that loadSaveData() can work - parent.setProperty('originalDuration', f'{startDurationDays * 24}:{startDurationMinutes}:00') - - else: - # Otherwise, set it directly as the startDuration - parent.setProperty('originalDuration', startDuration) - - - frameLayout = qtw.QGridLayout() - frameLayout.setContentsMargins(20,20,20,20) - # Create a QLabel for the name - - nameLabel = qtw.QLabel(name, self.frame) - nameLabel.setObjectName('nameLabel') - frameLayout.addWidget(nameLabel, 0, 0, alignment=Qt.AlignTop) - # Create a delete button - - deleteButton = qtw.QPushButton('X', self.frame) - deleteButton.setMinimumSize(40,40) - deleteButton.clicked.connect(lambda: self.deleteTimer(id_)) - frameLayout.addWidget(deleteButton, 0, 2, alignment=Qt.AlignRight) - # Create a QLabel for the countdown time - - countDown = qtw.QLabel('00:00:00', self.frame) - countDown.setObjectName('CountDownLabel') - frameLayout.addWidget(countDown, 1, 0, 1, 3, alignment=Qt.AlignCenter) - # Create a reset button - - resetButton = qtw.QPushButton('Reset Timer', self.frame) - resetButton.setObjectName('resetButton') - resetButton.setMinimumSize(120,60) - resetButton.setMaximumSize(240,60) - resetButton.clicked.connect(lambda: self.resetTimer(timeObject, name, startDuration, id_, color, notes=notepad.toPlainText())) - frameLayout.addWidget(resetButton, 2,0, alignment=Qt.AlignBottom) - - # Create a QLabel for the finished date - finishedDate = datetime.now() + duration - finishedDateLabel = qtw.QLabel(f'Finished on: {finishedDate.strftime("%B %d @ %I:%M %p")}') - finishedDateLabel.setObjectName('finishedDateLabel') - frameLayout.addWidget(finishedDateLabel, 2, 1, alignment=Qt.AlignCenter) - - # Create a QTextEdit for notes - notepad = qtw.QTextEdit(self.frame) - notepad.setPlaceholderText('Notes go here') - notepad.setText(notepadContents) - notepad.setMinimumSize(300, 100) - notepad.setMaximumSize(400, 200) - notepad.anchorAt(QPoint(0,0)) - - frameLayout.addWidget(notepad, 2, 2) - - # Set the layout for the frame - self.frame.setLayout(frameLayout) - - # Index is only given when a timer is resetTimer() is called - if index != None: - self.verticalLayout.insertWidget(index, self.frame) - else: - self.verticalLayout.addWidget(self.frame) - - self.frame.show() - - - currentTime = datetime.today() - - finishedTime = currentTime + duration - - parent.setProperty('finishedTime', datetime.strftime(finishedTime, '%Y-%m-%d %H:%M:%S')) - - difference = finishedTime - currentTime - - # Find the reset button and count down label - resetButton: qtw.QPushButton = parent.findChild(qtw.QPushButton, 'resetButton') - countDownLabel: qtw.QLabel = parent.findChild(qtw.QLabel, 'CountDownLabel') - - zero = timedelta(days=0, hours=0, seconds=0) - one = timedelta(seconds=1) - - # Create a QTimer for updating the countdown label - QTimer_ = QTimer(parent) - QTimer_.setObjectName('QTimer') - - def countDownTimer(self: qtw.QWidget, difference: datetime) -> None: - - try: - - difference -= one - - if difference > zero: - - # Update the countdown label with the new difference - countDownLabel.setText(str(difference)) - - # Schedule the next update of the countdown timer after 1 second - QTimer_.singleShot(1000, lambda: countDownTimer(self, difference)) - - else: - countDownLabel.setText('00:00:00') - countDownLabel.setProperty('finished', "true") - parent.style().polish(countDownLabel) - - notify.Notify(name) - - # If timer is deleted, will traceback a runtime error because the function is trying to locate objects that don't exist anymore - except RuntimeError: - return - - # If the time difference is already below 0 before countDownTimer starts - # Don't bother running the function and just send the notification - if difference <= zero: - - # If the program is set to show on startup then theres no point of showing a notification - if not config['OPTIONS'].getboolean('show on startup', fallback=True): - notify.Notify(name, True) - - else: - - countDownTimer(self, difference) - - if save: - self.parent().saveData() - - def deleteTimer(self, id_: str): - self.findChild(qtw.QFrame, id_).deleteLater() - - # There is a 1ms delay to call saveData() so that the deleteLater() method can finish, otherwise saveData() won't save anything - QTimer.singleShot(1, lambda: self.parent().saveData()) - - def resetTimer(self, timeObject: str, name: str, startDuration: timedelta, id: str, color: str, notes: str = ''): - # Find the frame associated with the given ID - frame: qtw.QFrame = self.findChild(qtw.QFrame, id) - - index = self.verticalLayout.indexOf(frame) - - self.findChild(qtw.QFrame, id).deleteLater() - - currentTime = datetime.today() - - difference = (currentTime + startDuration) - currentTime - - self.addStopWatch(timeObject, difference, name, startDuration, color, notepadContents=notes, index=index) - - - -class window(qtw.QMainWindow): - def __init__(self): - super(window, self).__init__() - - self.toolBar = toolbar(self) - self.addToolBar(self.toolBar) - - self.central = centralWidget(self) - self.setCentralWidget(self.central) - - self.dockWidgetAddTimer = addTimer(self) - self.addDockWidget(Qt.RightDockWidgetArea, self.dockWidgetAddTimer) - self.dockWidgetAddTimer.setVisible(config['QOL'].getboolean('addtimer open on startup', fallback=True)) - - self.dockWidgetOptions = optionsDock(self) - self.addDockWidget(Qt.RightDockWidgetArea, self.dockWidgetOptions) - self.dockWidgetOptions.setVisible(config['QOL'].getboolean('settings open on startup', fallback=False)) - - self.dockWidgetGuide = Guide(self) - self.addDockWidget(Qt.RightDockWidgetArea, self.dockWidgetGuide) - self.dockWidgetGuide.setVisible(config['QOL'].getboolean('guide open on startup', fallback=False)) - - self.dockWidgetStaticTimer = staticTimers(self) - self.addDockWidget(Qt.RightDockWidgetArea, self.dockWidgetStaticTimer) - self.dockWidgetStaticTimer.setVisible(config['QOL'].getboolean('static timers open on startup', fallback=False)) - - # So the resize event doesn't spam the save window resolution function (see sizeApplyTimerTimeout() ) - self.windowSizeApplyTimer = QTimer(self) - self.windowSizeApplyTimer.setObjectName('windowSizeApplyTimer') - self.windowSizeApplyTimer.timeout.connect(lambda: self.sizeApplyTimerTimeout()) - - self.resize(config['WINDOW SIZE'].getint('width', fallback=1920), config['WINDOW SIZE'].getint('height', fallback=1080)) - - self.loadSaveData() - - # Checking to see if the client is the latest version - self.dockWidgetOptions.checkUpdate() - - # Checking to see if any static timers went off while the program was shutdown - self.dockWidgetStaticTimer.checkNotify() - - def loadSaveData(self): - data = ConfigParser() - data.read(saveFile_path) - - for timerID in data.sections(): - - # Name - name = data[timerID]['name'] - - # Changing the timer's destination into a datetime type - timeFinished = data[timerID]['time finished'] - timeFinished = datetime.strptime(timeFinished, '%Y-%m-%d %H:%M:%S') - timeFinished = timeFinished - datetime.today().replace(microsecond=0) - - # The original duration (For resetting the timer) and changing into a timedelta type - originalDuration = data[timerID]['time original duration'].split(':') - originalDuration = timedelta(hours= int(originalDuration[0]), minutes= int(originalDuration[1])) - - # Border color (In hexcode format) - borderColor = data[timerID]['border color'] - - # Notes - notes = data[timerID]['notes'] - - - - # Remove old ID - data.remove_section(timerID) - - # Create stopwatch - self.central.addStopWatch(name, timeFinished, name, originalDuration, borderColor, notes, save=False) - - # Update the savefile (Old IDs are removed) - with open(saveFile_path, 'w') as f: - data.write(f) - - # Update the savefile (Saving new IDs created from for loop) - self.saveData() - - def saveData(self): - - data = ConfigParser() - - for qtObject in self.central.findChildren(qtw.QFrame): - - objectName: str = qtObject.objectName() - - if len(objectName) == 13: # Length of a stopwatch's name which is their id() value - - data[objectName] = { - 'name' : qtObject.findChild(qtw.QLabel, "nameLabel").text(), - 'time finished' : qtObject.property("finishedTime"), - 'time original duration' : qtObject.property("originalDuration"), - 'border color' : qtObject.property("border-color"), - 'notes' : qtObject.findChild(qtw.QTextEdit).toPlainText() - } - - with open(saveFile_path, 'w') as f: - data.write(f) - - def sizeApplyTimerTimeout(self): - - self.windowSizeApplyTimer.stop() - - config['WINDOW SIZE'] = {'width' : str(mw.width()), 'height' : str(mw.height())} - - saveConfig() - - - def closeEvent(self, a0: qtg.QCloseEvent) -> None: - - if not config['OPTIONS'].getboolean('shutdown app on close', fallback=False): - # Trigger the "openClose_Pressed" function of the trayMenu (assuming trayMenu is an instance) - - trayMenu.openClose_Pressed() - - a0.ignore() - else: - # Save data before exiting the application - self.saveData() - app.exit() - - - def resizeEvent(self, a0: qtg.QResizeEvent) -> None: - - if not self.windowSizeApplyTimer.isActive(): - # Start the window size apply timer if it is not active - - self.windowSizeApplyTimer.start(1000) - - # Call the base class's resizeEvent - return super().resizeEvent(a0) - -class trayMen(qtw.QMenu): - def __init__(self): - super(trayMen, self).__init__() - - self.setObjectName('System Tray') - # Determine the label for the open/close button based on a configuration option - - openOrClose = 'Close' if config['OPTIONS'].getboolean('show on startup', fallback=True) else 'Open' - # Create the open/close button QAction and connect it to the openClose_Pressed method - - self.openCloseButton = qtw.QAction(openOrClose) - self.openCloseButton.triggered.connect(lambda: self.openClose_Pressed()) - # Create the quit application button QAction and connect it to the shutdownApp method - - self.quitAppButton = qtw.QAction("Shut Down") - self.quitAppButton.triggered.connect(lambda: self.shutdownApp()) - - # Add the actions (buttons) to the menu - self.addAction(self.openCloseButton) - self.addAction(self.quitAppButton) - - def shutdownApp(self): - # Save data before shutting down the application - mw.saveData() - app.quit() - - def openClose_Pressed(self): - - if self.openCloseButton.text() == 'Close': - # If the open/close button text is 'Close' Change the button text to 'Open' - self.openCloseButton.setText('Open') - # Set the main window (mw) to be invisible - - mw.setVisible(False) - else: - # If the open/close button text is not 'Close' Change the button text to 'Close' - self.openCloseButton.setText('Close') - mw.setVisible(True) - - -class UpdateAlert(qtw.QDialog): - def __init__(self, parent) -> None: - super().__init__(parent) - - self.setWindowTitle('Genshin Stopwatch: New Version Update!') - self.setWindowIcon(qtg.QIcon(icon_path)) - - layout = qtw.QVBoxLayout() - - self.message = qtw.QLabel(self, text=f'The latest update {version_check} has been released! Would you like to install it?') - layout.addWidget(self.message) - - self.checkBox = qtw.QCheckBox(self, text='Do not automatically check for updates') - self.checkBox.clicked.connect(lambda: self.checkBoxClicked()) - layout.addWidget(self.checkBox) - - self.buttons = qtw.QDialogButtonBox.Ok | qtw.QDialogButtonBox.No - self.buttonBox = qtw.QDialogButtonBox(self.buttons, self) - self.buttonBox.accepted.connect(lambda: webbrowser.open_new_tab('https://github.com/Wolfmyths/Genshin-Stopwatch/releases/latest')) - self.buttonBox.accepted.connect(self.accept) - self.buttonBox.rejected.connect(self.reject) - layout.addWidget(self.buttonBox) - - self.setLayout(layout) - - def checkBoxClicked(self) -> None: - if self.checkBox.isChecked(): - config['OPTIONS']['checkVersionOnStartup'] = 'False' - else: - config['OPTIONS']['checkVersionOnStartup'] = 'True' - - saveConfig() - +from widgets.mainWindow import window +from widgets.sysTrayIcon import sysTrayIcon +from saveConfig import saveConfig +from style import StyleManager, StyleSheets +from constants import VERSION, ICON, PROGRAM_NAME +import notify if __name__ == '__main__': import sys - ### ENVIRONMENT PATHS AND STYLEMANAGER INIT ### styles = StyleManager() - # Used for debugging, flag to see if program is ran through the main.py script or an exe - # This is used for when dealing with files that are packaged within the exe such as guide.html - isScript: bool = os.path.exists(os.path.join(os.path.dirname(__file__), 'main.py')) - - # Loading paths, using `os.curdir` instead of `os.dirname(__file__)` because the file dir is in a temp dir - root_path = os.path.abspath(os.path.dirname(__file__)) if not isScript else os.curdir - config_path = os.path.join(os.path.abspath(os.curdir), 'config.ini') - saveFile_path = os.path.join(os.path.abspath(os.curdir), 'save.txt') - icon_path = os.path.join(os.path.abspath(os.curdir), 'icon.ico') - - config = ConfigParser() - config.read(config_path) - - def saveConfig(): - with open(config_path, 'w') as f: - config.write(f) - - ### APPLICATION INIT ### + config = saveConfig() # Create a QApplication instance - app: qtw.QApplication = qtw.QApplication(sys.argv) + app = qtw.QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) - app.setStyleSheet(styles.getStyleSheet('app')) - - ### INIT NOTIFICATIONS ### - notify = NotificationPanel() - - ### VERSION DEFINING ### + app.setStyleSheet(styles.getStyleSheet(StyleSheets.app)) - # Client Version - version = '1.6' - - # Getting the latest version - try: - # Parsed to remove the "V" in the tag - version_check: str = requests.get('https://api.github.com/repos/Wolfmyths/Genshin-Stopwatch/releases/latest').json()['tag_name'][1:] - except Exception as e: - - # Has to set version_check to something otherwise it would result in a traceback - # Set to current version so the version check condition in window() would return false - version_check = version - - print(e) - - ### WINDOW & SYSTEM TRAY INIT ### # Create an instance of the 'window' class - mw = window() + mw = window(app) # Initialize window - mw.setWindowTitle(f'Genshin Stopwatch V{version}') - mw.setWindowIcon(qtg.QIcon(icon_path)) - mw.show() if config['OPTIONS'].getboolean('show on startup', fallback=True) else mw.hide() - - tray: qtw.QSystemTrayIcon = qtw.QSystemTrayIcon() - tray.setIcon(qtg.QIcon(icon_path)) - tray.setToolTip('Genshin Stopwatch') - tray.setVisible(True) - - trayMenu: qtw.QMenu = trayMen() + mw.setWindowTitle(f'{PROGRAM_NAME} V{VERSION}') + mw.setWindowIcon(qtg.QIcon(ICON)) + mw.show() if config.getStartUp() else mw.hide() - tray.setContextMenu(trayMenu) + tray = sysTrayIcon(app, mw) - notify.Notify('Settings') + # Checking to see if any static timers and stopwatches went off while the program was shutdown + notify.checkMissedNotify() - app.exec_() + app.exec() diff --git a/src/python/main.spec b/src/python/main.spec index 3f1db08..94935d9 100644 --- a/src/python/main.spec +++ b/src/python/main.spec @@ -5,10 +5,10 @@ block_cipher = None a = Analysis( - ['main.py', 'style.py'], + ['main.py'], pathex=[], binaries=[], - datas=[('guide.html', '.'), ('notify.wav', '.')], + datas=[('guide.html', '.'), ('icon.ico', '.')], hiddenimports=[], hookspath=[], hooksconfig={}, diff --git a/src/python/notify.py b/src/python/notify.py new file mode 100644 index 0000000..d2c05d5 --- /dev/null +++ b/src/python/notify.py @@ -0,0 +1,60 @@ +from datetime import datetime + +import PySide6.QtWidgets as qtw + +from widgets.sysTrayIcon import sysTrayIcon + +from saveConfig import saveConfig +from dataParser import dataParser, StopwatchDataKeys +from constants import TimeFormats + +def Notify(title: str, message: str) -> None: + sysTray: sysTrayIcon = qtw.QApplication.instance().findChild(sysTrayIcon) + + if sysTray.supportsMessages(): + sysTray.showMessage(title, message) + +def checkMissedNotify() -> None: + ''' + Checks for missed notifications + for both stopwatches and static timers + ''' + + notificationsMissed: list[str] = [] + + save = saveConfig() + # If notifications are disabled, don't bother executing the rest + if not save.getDesktopNotifications(): return + stopwatches = dataParser() + + today = datetime.today().replace(microsecond=0) + + # stopwatches + for stopwatch in stopwatches.sections(): + try: + finishedDate = datetime.strptime(stopwatches.get(stopwatch, StopwatchDataKeys.time_finished), TimeFormats.Saved_Date) + except: + continue + + if finishedDate <= today: + notificationsMissed.append(stopwatches.get(stopwatch, StopwatchDataKeys.time_object)) + + # static timers + # daily reset + if save.getDailyReset(): + dailyDeadline = save.getDailyDeadline() + + # If time has passed the static timer's deadline and if the notification settings are set to true + if dailyDeadline <= today: + notificationsMissed.append('Daily Reset') + + # weekly reset + if save.getWeeklyReset(): + weeklyDeadline = save.getWeeklyDeadline() + + # If time has passed the static timer's deadline and if the notification settings are set to true + if weeklyDeadline <= today: + notificationsMissed.append('Weekly Reset') + + if notificationsMissed: + Notify('Notifications were missed!', ', '.join(notificationsMissed)) diff --git a/src/python/notify.wav b/src/python/notify.wav deleted file mode 100644 index 43e61032ef09c15e5c2deb47cb087e79f4ad70dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121388 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
    uo>bLlB@$=G|lu#@V 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 Io!ck=2T9zF>i_@% diff --git a/src/python/saveConfig.py b/src/python/saveConfig.py new file mode 100644 index 0000000..da851f5 --- /dev/null +++ b/src/python/saveConfig.py @@ -0,0 +1,132 @@ +from typing import Self +from enum import StrEnum, auto +from datetime import datetime +import os + +from configparser import ConfigParser + +from constants import CONFIG, TimeFormats +from style import ColorPallets + +class ConfigKeys(StrEnum): + # OPTIONS + show_on_startup = 'show on startup' + dailyresetnotify = auto() + weeklyresetnotify = auto() + checkversiononstartup = auto() + shutdown_app_on_close = 'shutdown app on close' + color_pallet = 'color pallet' + desktop_notifications = 'desktop notifications' + + # QOL + expedition_checkbox = auto() + desiredstamina = auto() + addtimer_open_on_startup = 'addtimer open on startup' + settings_open_on_startup = 'settings open on startup' + guide_open_on_startup = 'guide open on startup' + static_timers_open_on_startup = 'static timers open on startup' + gameserver = auto() + + # WINDOW SIZE + width = auto() + height = auto() + + # STATIC TIMERS + dailydeadline = auto() + weeklydeadline = auto() + +class saveConfig(ConfigParser): + def __init__(self): + super(saveConfig, self).__init__() + + # If file does not exist then create it + if not os.path.exists(CONFIG): + with open(CONFIG, 'w+') as f: + pass + + # Checking if sections exist to prevent traceback if they don't + for section in ('OPTIONS', 'QOL', 'STATIC_TIMERS', 'WINDOW SIZE'): + self.__checkSection(section) + + self.read(CONFIG) + + def __new__(cls) -> Self: + + if not hasattr(cls, 'instance'): + + cls.instance = super(saveConfig, cls).__new__(cls) + + return cls.instance + + def __checkSection(self, section: str): + if not self.has_section(section): + self.add_section(section) + + def setOption(self, section: str, option: str, value: str): + self.__checkSection(section) + + self.set(section, option, value) + + def save(self): + with open(CONFIG, 'w') as f: + self.write(f) + + def getStartUp(self) -> bool: + return self.getboolean('OPTIONS', ConfigKeys.show_on_startup, fallback=True) + + def getDailyReset(self) -> bool: + return self.getboolean('OPTIONS', ConfigKeys.dailyresetnotify, fallback=False) + + def getWeeklyReset(self) -> bool: + return self.getboolean('OPTIONS', ConfigKeys.weeklyresetnotify, fallback=False) + + def getCurrentPallet(self) -> ColorPallets: + return ColorPallets(self.get('OPTIONS', ConfigKeys.color_pallet, fallback=ColorPallets.dark)) + + def getVersionCheck(self) -> bool: + return self.getboolean('OPTIONS', ConfigKeys.checkversiononstartup, fallback=True) + + def getShutdownOnClose(self) -> bool: + return self.getboolean('OPTIONS', ConfigKeys.shutdown_app_on_close, fallback=False) + + def getDesktopNotifications(self) -> bool: + return self.getboolean('OPTIONS', ConfigKeys.desktop_notifications, fallback=True) + + def getExpeditionCheckbox(self) -> bool: + return self.getboolean('QOL', ConfigKeys.expedition_checkbox, fallback=True) + + def getDesiredStamina(self) -> str: + return self.get('QOL', ConfigKeys.desiredstamina, fallback='160') + + def getAddtimerStartup(self) -> bool: + return self.getboolean('QOL', ConfigKeys.addtimer_open_on_startup, fallback=True) + + def getSettingsStartup(self) -> bool: + return self.getboolean('QOL', ConfigKeys.settings_open_on_startup, fallback=False) + + def getGuideStartup(self) -> bool: + return self.getboolean('QOL', ConfigKeys.guide_open_on_startup, fallback=False) + + def getStatictimerStartup(self) -> bool: + return self.getboolean('QOL', ConfigKeys.static_timers_open_on_startup, fallback=False) + + def getServer(self) -> str: + return self.get('QOL', ConfigKeys.gameserver, fallback='NA') + + def getWidth(self) -> int: + return self.getint('WINDOW SIZE', ConfigKeys.width, fallback=1920) + + def getHeight(self) -> int: + return self.getint('WINDOW SIZE', ConfigKeys.height, fallback=1080) + + def getDailyDeadline(self) -> datetime: + try: + return datetime.strptime(self.get('STATIC_TIMERS', ConfigKeys.dailydeadline), TimeFormats.Static_Timer) + except: + return datetime(0,0,0) + + def getWeeklyDeadline(self) -> datetime: + try: + return datetime.strptime(self.get('STATIC_TIMERS', ConfigKeys.weeklydeadline), TimeFormats.Static_Timer) + except: + return datetime(0,0,0) diff --git a/src/python/style.py b/src/python/style.py index 9bb8868..2aecc2e 100644 --- a/src/python/style.py +++ b/src/python/style.py @@ -1,5 +1,8 @@ +from typing import Self from configparser import ConfigParser import os +from enum import StrEnum, auto +import random # COLOR PALLET CREDITS: # Dark by archer: https://lospec.com/palette-list/timeless @@ -12,17 +15,33 @@ # Electro by Interprete-me: https://lospec.com/palette-list/neon-moon-tarot # Geo by namida: https://lospec.com/palette-list/koukasita +class ColorPallets(StrEnum): + dark = auto() + light = auto() + original = auto() + hydro = auto() + dendro = auto() + pyro = auto() + cryo = auto() + anemo = auto() + electro = auto() + geo = auto() + random = auto() + +class StyleSheets(StrEnum): + app = auto() + stopwatch = auto() + class StyleManager: def __init__(self) -> None: config = ConfigParser() config.read(os.path.join(os.path.abspath(os.curdir), 'config.ini')) - # Attribute Definitions # Default color pallet is dark - self.selectedColorPallet: str = config.get('OPTIONS', 'color pallet', fallback='dark') + self.selectedColorPallet: str = config.get('OPTIONS', 'color pallet', fallback=ColorPallets.dark) # COLOR TYPE INDEX (These aren't strict naming conventions there are some exceptions) # 0 : Background @@ -31,31 +50,29 @@ def __init__(self) -> None: # 3 : Text # 4 : Alt Text - self.colorPallets: dict[str:tuple[str]] = { - 'dark' : ('#212124', '#464c54', '#5b8087', '#76add8', '#a3e7f0'), - 'light' : ('#fafafa', '#e4e5f1', '#d2d3db', '#9394a5', '#484b6a'), - 'original' : ('#1A1A1B', '#333F44', '#37AA9C', '#94F3E4', '#FCB3FC'), - 'hydro' : ('#070810', '#18284A', '#52A5DE', '#ACD6F6', '#EBF9FF'), - 'dendro' : ('#0a1a2f', '#04373b', '#1a644e', '#40985e', '#d1cb95'), - 'pyro' : ('#5f2f45', '#a02f40', '#e56f15', '#eda94a', '#f5ddbc'), - 'cryo' : ('#2e364d', '#425d87', '#7d95de', '#ddc8f9', '#fbfef9'), - 'anemo' : ('#2e3b43', '#486970', '#5a9e89', '#9adcae', '#f9fcf1'), - 'electro' : ('#000000', '#5f4886', '#8767bd', '#70b9fb', '#fe0094'), - 'geo' : ('#111111', '#554433', '#aa6622', '#dd9933', '#88aa99'), + self.colorPallets: dict[ColorPallets:tuple[str]] = { + ColorPallets.dark : ('#212124', '#464c54', '#5b8087', '#76add8', '#a3e7f0'), + ColorPallets.light : ('#fafafa', '#e4e5f1', '#d2d3db', '#9394a5', '#484b6a'), + ColorPallets.original : ('#1A1A1B', '#333F44', '#37AA9C', '#94F3E4', '#FCB3FC'), + ColorPallets.hydro : ('#070810', '#18284A', '#52A5DE', '#ACD6F6', '#EBF9FF'), + ColorPallets.dendro : ('#0a1a2f', '#04373b', '#1a644e', '#40985e', '#d1cb95'), + ColorPallets.pyro : ('#5f2f45', '#a02f40', '#e56f15', '#eda94a', '#f5ddbc'), + ColorPallets.cryo : ('#2e364d', '#425d87', '#7d95de', '#ddc8f9', '#fbfef9'), + ColorPallets.anemo : ('#2e3b43', '#486970', '#5a9e89', '#9adcae', '#f9fcf1'), + ColorPallets.electro : ('#000000', '#5f4886', '#8767bd', '#70b9fb', '#fe0094'), + ColorPallets.geo : ('#111111', '#554433', '#aa6622', '#dd9933', '#88aa99'), } - self.stopwatchColorsDict: dict[str:str] = { - - 'random' :'random' , - 'cryo' :'#37AA9C', - 'dendro' :'#32B85C', - 'pyro' :'#AB413F', - 'hydro' :'#3F7EAB', - 'geo' :'#F7A936', - 'electro':'#8156E3', - 'anemo' :'#60FD75' - - } + self.stopwatchColorsDict: dict[ColorPallets:str] = { + ColorPallets.random :'random', + ColorPallets.cryo :'#37AA9C', + ColorPallets.dendro :'#32B85C', + ColorPallets.pyro :'#AB413F', + ColorPallets.hydro :'#3F7EAB', + ColorPallets.geo :'#F7A936', + ColorPallets.electro :'#8156E3', + ColorPallets.anemo :'#60FD75' + } self.stopwatchBorderColor: str = self.stopwatchColorsDict['anemo'] @@ -93,9 +110,14 @@ def __init__(self) -> None: color: {4}; }} - QCheckBox::indictator{{ - width: 40px; - length: 40px; + QCheckBox {{ + color: {3}; + font-size: 18px; + }} + + QCheckBox::indicator{{ + width: 20px; + height: 20px; }} QLineEdit{{ @@ -182,93 +204,66 @@ def __init__(self) -> None: # The background color of QFrame is changed in `main.py` in `addStopWatch()` self.stopwatchStyleSheet: str = ''' - QFrame {{ - border: 3px solid {5}; - border-radius: 10px; - background-color: {1}; - }} - - QLabel#nameLabel{{ - font-size: 60px; - }} - - QLabel#CountDownLabel{{ - font-size: 70px; - }} - - QLabel#CountDownLabel[finished="true"]{{ - color: {4}; - font-size: 70px; - }} - - QLabel {{ - font-size: 30px; - color: {3}; - border: none; - text-align: center; - }} - - QTextEdit {{ - background-color: {0}; - color: {3}; - font-size: 24px; - border: none; - }} - - QPushButton#resetButton{{ - font-size: 25px; - }} - - QPushButton:pressed{{ - background-color: {2}; - }} - - + QFrame {{ + border: 3px solid {5}; + border-radius: 10px; + background-color: {1}; + }} - ''' - self.stopwatchStyleSheet_formatted = self.formatStyleSheet(self.stopwatchStyleSheet, self.selectedColorPallet) + QLabel#nameLabel{{ + font-size: 60px; + }} - self.NotificationPanelStyleSheet = ''' + QLabel#CountDownLabel{{ + font-size: 70px; + }} - QFrame {{ - background-color: {1}; + QLabel#CountDownLabel[finished="true"]{{ + color: {4}; + font-size: 70px; }} - QFrame#centralWidget{{ - border: 0px solid {4}; - border-radius: 0px; - background-color: {1}; + QLabel {{ + font-size: 30px; + color: {3}; + border: none; + text-align: center; }} - QLabel#message {{ - font-size: 20px; + QTextEdit {{ + background-color: {0}; color: {3}; + font-size: 24px; + border: none; }} - QLabel#title {{ + QPushButton {{ font-size: 25px; - color: {3}; }} - - ''' - self.NotificationPanelStyleSheet_formatted = self.formatStyleSheet(self.NotificationPanelStyleSheet, self.selectedColorPallet) - def getStyleSheet(self, styleSheet: str | None = None) -> str | list[str]: - ''' - if styleSheet: - Returns stylesheet specified - else: - Returns a list of the available color pallets - ''' - styleSheets: dict[str:str] = { - 'app' : self.appStyleSheet_formatted, - 'stopwatch': self.stopwatchStyleSheet_formatted, - 'notify' : self.NotificationPanelStyleSheet_formatted } + QPushButton:pressed{{ + background-color: {2}; + }} - if styleSheet: - return styleSheets[styleSheet] - else: - return list(styleSheets.keys()) + ''' + self.stopwatchStyleSheet_formatted = self.formatStyleSheet(self.stopwatchStyleSheet, self.selectedColorPallet) + + def __new__(cls) -> Self: + + if not hasattr(cls, 'instance'): + + cls.instance = super(StyleManager, cls).__new__(cls) + + return cls.instance + + def getStyleSheet(self, styleSheet: StyleSheets) -> str: + ''' Returns style sheet, see the enum class `StyleSheets` for available style sheets ''' + + styleSheets: dict[StyleSheets:str] = { + StyleSheets.app : self.appStyleSheet_formatted, + StyleSheets.stopwatch : self.stopwatchStyleSheet_formatted} + + return styleSheets[styleSheet] def getColorPallets(self) -> list[str]: '''Returns a list of the available color pallets''' @@ -285,18 +280,27 @@ def getCurrentColorPallet(self) -> str: '''Returns the color pallet being used''' return self.selectedColorPallet - def getStopwatchColor(self, color: str) -> str: - '''Returns the hexcode version of a stopwatch border color example: {color:hexcode}''' - try: - return self.stopwatchColorsDict[color.lower()] - except KeyError: + def getStopwatchColor(self, color: ColorPallets | str) -> str: + ''' + Returns the hexcode version of a stopwatch border color example: {color:hexcode} + + If given a regular hexcode return the hexcode + ''' + + hexColor: str | None = self.stopwatchColorsDict.get(color.lower()) + + if color == ColorPallets.random: + return random.choice(list(self.stopwatchColorsDict.values())) + elif hexColor is not None: + return hexColor + else: return color.upper() def getStopwatchColors(self) -> dict[str:str]: '''Returns a dictionary of the stopwatch border colors''' return self.stopwatchColorsDict - def changeStopwatchBorderColor(self, color: str) -> None: + def changeStopwatchBorderColor(self, color: ColorPallets) -> None: '''Changes the stopwatch border color''' self.stopwatchBorderColor = self.getStopwatchColor(color) @@ -313,10 +317,9 @@ def changeColorPallet(self, pallet: str) -> None: # Updating formatted strings (The Style Sheets) self.stopwatchStyleSheet_formatted = self.formatStyleSheet(self.stopwatchStyleSheet, self.selectedColorPallet) self.appStyleSheet_formatted = self.formatStyleSheet(self.appStyleSheet, self.selectedColorPallet) - self.NotificationPanelStyleSheet_formatted = self.formatStyleSheet(self.NotificationPanelStyleSheet, self.selectedColorPallet) - def formatStyleSheet(self, styleSheet: str, colorPallet: str) -> str: - '''Formats the stylesheet with the specified color pallet''' + def formatStyleSheet(self, styleSheet: str, colorPallet: ColorPallets) -> str: + '''Formats a stylesheet from this class with the specified color pallet''' colorPalletValues = self.colorPallets[colorPallet] return styleSheet.format(*colorPalletValues, self.stopwatchBorderColor) diff --git a/src/python/widgets/addTimer.py b/src/python/widgets/addTimer.py new file mode 100644 index 0000000..f30d98d --- /dev/null +++ b/src/python/widgets/addTimer.py @@ -0,0 +1,480 @@ +from __future__ import annotations +from typing import TYPE_CHECKING +from datetime import timedelta + +import PySide6.QtWidgets as qtw +import PySide6.QtGui as qtg +from PySide6.QtCore import Qt, Signal + +from saveConfig import saveConfig, ConfigKeys +from style import StyleManager, ColorPallets + +if TYPE_CHECKING: + from widgets.mainWindow import window + import PySide6.QtGui as qtg + +class addTimer(qtw.QDockWidget): + createStopwatch = Signal(str, timedelta, str, timedelta, str) + + # Realm Currency Level (Hidden by default, see show/hide events) + # {Trust Rank : Realm Currency Storage Limit} + REALM_CURRENCY_MAX_STORAGE_VALUES = { + '1' : 300, + '2' : 300, + '3' : 900, + '4' : 1200, + '5' : 1400, + '6' : 1600, + '7' : 1800, + '8' : 2000, + '9' : 2200, + '10' : 2400 + } + + # Topic data + TOPIC_SELECTION = { + + '': { + 'durations': ('Nothing Selected', ) + }, + + 'Stamina': { + 'durations': 'Stamina' + }, + + 'Parametric Transformer': { + 'durations': ('7 Days', ) + }, + + 'Respawns': { + 'durations': ('12 Hours', '1 Day', '2 Days', '3 Days') + }, + + 'Expedition': { + 'durations': ('4 Hours', '8 Hours', '12 Hours', '20 Hours') + }, + + 'Teapot Gardening/Construction': { + 'durations': ('12 Hours', '14 Hours', '16 Hours', '70 Hours') + }, + + 'Realm Currency': { + 'durations': ('Bare-Bones', 'Humble Abode', 'Cozy', 'Queen-Size', 'Elegant', 'Exquisite', 'Extradordinary', 'Stately', 'Luxury', 'Fit for a king') + }, + + 'Realm Companionship XP': { + 'durations' : ('0 - 2999 (2/hr)', '3000 - 5999 (3/hr)', '6000 - 11999 (4/hr)', '12000+ (5/hr)') + }, + + 'Fishing': { + 'durations': ('1 Day', '3 Days') + }, + + 'Custom': { + 'durations': 'Custom' + } + } + + # Values for when the player has characters that have a 25% time discount + # {'4 Hours': 3} + EXPEDITION_DISCOUNT = {k:v for (k,v) in zip(TOPIC_SELECTION['Expedition']['durations'], ('3 Hours', '6 Hours', '9 Hours', '15 Hours'))} + + # Dict of Realm Statuses and their corresponding income rates + # {status:rate/hr} + REALM_CURRENCY_RATES = {k:v for (k,v) in zip(TOPIC_SELECTION['Realm Currency']['durations'], (4, 8, 12, 16, 20, 22, 24, 26, 28, 30))} + + # Dict of Adeptal Energy thresholds and their corresponding income rates for companionship XP + # {adeptal energy range:rate/hr} + REALM_FRIENDSHIP_POINT_RATES = {k:v for (k,v) in zip(TOPIC_SELECTION['Realm Companionship XP']['durations'], range(2, 6)) } + + def __init__(self, parent: window): + super().__init__(parent) + + self.config = saveConfig() + self.styles = StyleManager() + + self.mw: window = parent + + self.setWindowTitle('Add Timer') + self.setObjectName('addTimerDockWidget') + self.setAllowedAreas(Qt.DockWidgetArea.RightDockWidgetArea) + self.setFeatures(qtw.QDockWidget.DockWidgetFeature.DockWidgetClosable) + + # Central Frame + self.centralFrame = qtw.QFrame(self) + + # Vertical Box Layout + verticalLayout = qtw.QVBoxLayout() + self.centralFrame.setLayout(verticalLayout) + + # Topic Frame + self.topicFrame = qtw.QFrame(self.centralFrame) + verticalLayout.addWidget(self.topicFrame) + + # Form Layout (For Topic Frame) + self.formLayout = qtw.QFormLayout() + self.formLayout.setVerticalSpacing(15) + self.formLayout.setRowWrapPolicy(qtw.QFormLayout.RowWrapPolicy.WrapAllRows) + self.topicFrame.setLayout(self.formLayout) + + # Topic Selection Row + self.topicLabel = qtw.QLabel('Timed Object:') + self.topicDropDown = qtw.QComboBox() + self.topicDropDown.setFocusPolicy(Qt.FocusPolicy.NoFocus) + self.topicDropDown.addItems( [x for x in self.TOPIC_SELECTION.keys()] ) + self.topicDropDown.currentTextChanged.connect(lambda topic : self.dropDownSelected(topic)) + + self.formLayout.addRow(self.topicLabel, self.topicDropDown) + + # Duration Row + self.durationLabel = qtw.QLabel('Duration:') + self.durationDropDown = qtw.QComboBox() + self.durationDropDown.addItem(self.TOPIC_SELECTION['']['durations'][0]) + self.durationDropDown.setFocusPolicy(Qt.FocusPolicy.NoFocus) + + self.formLayout.addRow(self.durationLabel, self.durationDropDown) + + self.expeditionLabel = qtw.QLabel('Using a 25% Time Reduction Character') + self.expeditionCheckBox = qtw.QCheckBox() + self.expeditionCheckBox.setChecked(self.config.getExpeditionCheckbox()) + + self.formLayout.addRow(self.expeditionLabel, self.expeditionCheckBox) + + # Line edit w/ label (Hidden by default, see show/hide events) + + self.lineEditLabel1 = qtw.QLabel() + self.lineEditLabel1.setObjectName('lineEditLabel1') + self.lineEdit1 = qtw.QLineEdit() + self.lineEdit1.setObjectName('lineEdit1') + + self.formLayout.addRow(self.lineEditLabel1, self.lineEdit1) + + # Line edit w/ label (Hidden by default, see show/hide events) + + self.lineEditLabel2 = qtw.QLabel() + self.lineEditLabel2.setObjectName('lineEditLabel2') + self.lineEdit2 = qtw.QLineEdit() + self.lineEdit2.setObjectName('lineEdit2') + + self.formLayout.addRow(self.lineEditLabel2, self.lineEdit2) + + # Line edit w/ label (Hidden by default, see show/hide events) + + self.lineEditLabel3 = qtw.QLabel() + self.lineEditLabel3.setObjectName('lineEditLabel3') + self.lineEdit3 = qtw.QLineEdit() + self.lineEdit3.setObjectName('lineEdit3') + + self.formLayout.addRow(self.lineEditLabel3, self.lineEdit3) + + # Name Row + self.nameLabel = qtw.QLabel('Name:') + self.nameLineEdit = qtw.QLineEdit() + self.nameLineEdit.setPlaceholderText('Optional') + + self.formLayout.addRow(self.nameLabel, self.nameLineEdit) + + self.colorsDict: list[str] = list(self.styles.getStopwatchColors().keys()) + + # Color Row + self.outlineColorLabel = qtw.QLabel('Border Color:') + self.outlineColorDropDown = qtw.QComboBox() + self.outlineColorDropDown.setFocusPolicy(Qt.FocusPolicy.NoFocus) + self.outlineColorDropDown.addItems([x.title() for x in self.colorsDict]) + + self.formLayout.addRow(self.outlineColorLabel, self.outlineColorDropDown) + + # Button + self.startTimerButton = qtw.QPushButton('Start Timer') + self.startTimerButton.clicked.connect(self.startStopWatch) + verticalLayout.addWidget(self.startTimerButton, alignment=Qt.AlignmentFlag.AlignTop) + + self.setWidget(self.centralFrame) + + def hideAll(self): + for widget in (self.lineEdit1, + self.lineEdit2, + self.lineEdit3, + self.lineEditLabel1, + self.lineEditLabel2, + self.lineEditLabel3, + self.durationDropDown, + self.durationLabel, + self.expeditionCheckBox, + self.expeditionLabel): + + widget.hide() + + def showRealmCurrency(self): + # Show realm currency elements + + self.lineEditLabel1.show() + self.lineEdit1.show() + + def showCustom(self): + # Show custom elements + + self.lineEditLabel1.show() + self.lineEdit1.show() + + self.lineEditLabel2.show() + self.lineEdit2.show() + + self.lineEditLabel3.show() + self.lineEdit3.show() + + def showNormalDurations(self): + + self.durationLabel.show() + self.durationDropDown.show() + + def showExpedition(self): + + self.durationLabel.show() + self.durationDropDown.show() + + self.expeditionLabel.show() + self.expeditionCheckBox.show() + + def dropDownSelected(self, topic: str): + selectedTopic = self.TOPIC_SELECTION[topic]['durations'] + + self.durationDropDown.clear() + + #Hiding all elements + self.hideAll() + + # Clearning line edits to prevent text from carrying over + self.lineEdit1.clear(), self.lineEdit2.clear(), self.lineEdit3.clear() + + match topic: + + case 'Expedition': + + self.showExpedition() + self.durationDropDown.addItems(selectedTopic) + + case 'Realm Currency': + + self.showNormalDurations() + self.durationLabel.setText('Realm Status:') + self.durationDropDown.addItems(selectedTopic) + + self.showRealmCurrency() + self.lineEditLabel1.setText('Realm Trust Level (1-10)') + + case 'Realm Companionship XP': + + self.showNormalDurations() + self.durationLabel.setText('Adeptal Energy:') + self.durationDropDown.addItems(selectedTopic) + + self.showRealmCurrency() + self.lineEditLabel1.setText('Realm Trust Level (1-10)') + + case 'Stamina': + + self.lineEditLabel1.setText('Current Stamina (Max 160)') + self.lineEditLabel1.show() + self.lineEdit1.show() + + self.lineEditLabel2.setText('Desired stamina (Max 160)') + self.lineEditLabel2.show() + self.lineEdit2.setText(self.config.getDesiredStamina()) + self.lineEdit2.show() + + case 'Custom': + + self.showCustom() + self.lineEditLabel1.setText('Days:') + self.lineEditLabel2.setText('Hours:') + self.lineEditLabel3.setText('Minutes:') + + case _: + + self.durationLabel.setText('Duration:') + + if self.durationLabel.isHidden(): + + self.showNormalDurations() + + self.durationDropDown.addItems(selectedTopic) + + def calculateExpedition(self) -> timedelta: + duration: str = self.durationDropDown.currentText() + + # If discount is checked, use the expedition discount dict instead + hours = duration if not self.expeditionCheckBox.isChecked() else self.EXPEDITION_DISCOUNT[duration] + + hours = int(hours.split()[0]) + + return timedelta(hours=hours) + + def calculateRealmCurrency(self) -> timedelta: + # Rerieve maximum storage and rate values + maxStorage: int = self.REALM_CURRENCY_MAX_STORAGE_VALUES[self.lineEdit1.text()] + + rate = self.durationDropDown.currentText() + rate = self.REALM_CURRENCY_RATES[rate] + + duration = round(maxStorage / rate, 2) + + return timedelta(hours=duration) + + def calculateFriendShipPoints(self) -> timedelta: + # Rerieve maximum storage and rate values + + maxStorage = int(self.lineEdit1.text()) * 50 + + rate = self.durationDropDown.currentText() + rate = self.REALM_FRIENDSHIP_POINT_RATES[rate] + + duration = round(maxStorage / rate, 2) + + return timedelta(hours=duration) + + def calculateCustom(self) -> timedelta: + # Retrieve input values for days, hours, and minutes + days: str|int = self.lineEdit1.text() if self.lineEdit1.text().isdigit() else 0 + hours: str|int = self.lineEdit2.text() if self.lineEdit2.text().isdigit() else 0 + minutes: str|int = self.lineEdit3.text() if self.lineEdit3.text().isdigit() else 0 + + # Convert values to positive integers + days = abs(int(days)) + hours = abs(int(hours)) + minutes = abs(int(minutes)) + + return timedelta(days=days, hours=hours, minutes=minutes) + + def calculateStamina(self) -> timedelta: + # Get inputs + amountOfStamina: str = self.lineEdit1.text() + desiredStamina: str = self.lineEdit2.text() + + # Program remembers how much stamina the user wants + self.config['QOL'][ConfigKeys.desiredstamina] = desiredStamina + self.config.save() + + # Example: (160 - 20 = 140), user needs 140 stamina until 160, stamina takes 8 minutes to regen 1 stamina + minutes = (int(desiredStamina) - int(amountOfStamina)) * 8 + + return timedelta(minutes=minutes) + + def calculateDefault(self) -> timedelta: + duration: str = self.durationDropDown.currentText() + + duration = duration.split() + + if duration[1] == 'Days' or duration[1] == 'Day': + duration = int(duration[0]) * 24 + else: + duration = int(duration[0]) + + hours = duration + + return timedelta(hours=hours) + + def startStopWatch(self): + ''' + Fired when the addTimer button is pressed. + This gathers then transforms the current data suitable for the stopwatch's + parameters and fires the createStopwatch event + ''' + + # Get the selected time object and outline color + timeObject = self.topicDropDown.currentText() + + # self.colorsDict is excluding index 0 because that would be the 'random' key value in the dict + color: str = self.styles.getStopwatchColor(ColorPallets(self.outlineColorDropDown.currentText().lower())) + + # Create a timedelta object with the calculated duration from the matched selected time object + match timeObject: + + case 'Expedition': + duration = self.calculateExpedition() + + case 'Realm Currency': + # Check if the input is in the valid range + maxStorage = int(self.lineEdit1.text()) + if maxStorage < 1 or maxStorage > 10: + return self.lineEdit1.setText('Error: Invalid Input') + + duration = self.calculateRealmCurrency() + + case 'Realm Companionship XP': + # Check if the input is in the valid range + maxStorage = int(self.lineEdit1.text()) + if maxStorage < 1 or maxStorage > 10: + return self.lineEdit1.setText('Error: Invalid Input') + + duration = self.calculateFriendShipPoints() + + case 'Custom': + duration = self.calculateCustom() + + case 'Stamina': + + # Get inputs + amountOfStamina: str = self.lineEdit1.text() + desiredStamina: str = self.lineEdit2.text() + + # Checking for valid inputs + if amountOfStamina.isdigit() == False or int(amountOfStamina) > 160 or int(amountOfStamina) < 0: + return self.lineEdit1.setText('Error: Invalid Input') + + elif desiredStamina.isdigit() == False or int(desiredStamina) > 160 or int(desiredStamina) < 0: + return self.lineEdit2.setText('Error: Invalid Input') + + duration = self.calculateStamina() + + # Case: default + case _: + + # Check if no duration is selected + if not self.topicDropDown.currentText(): + return + + duration = self.calculateDefault() + + duration += timedelta(days=0, hours=0, minutes=0, seconds=0, microseconds=0, milliseconds=0) + + # Get the text from the nameLineEdit and use it as the name if it is not empty, otherwise use timeObject + nameText = self.nameLineEdit.text() + name = nameText if len(nameText) > 0 else timeObject + + # Call the createStopWatch event with the relevant parameters + self.createStopwatch.emit(timeObject, duration, name, duration, color) + + def hideEvent(self, a0: qtg.QHideEvent) -> None: + # Check if the parent widget is hidden + + if self.mw.isHidden(): + a0.ignore() + + else: + + # Uncheck the addtimerButton + self.mw.toolBar.addTimerButton.setChecked(False) + + if not self.mw.isMinimized(): + + self.config['QOL'][ConfigKeys.addtimer_open_on_startup] = 'False' + self.config.save() + + return super().hideEvent(a0) + + def showEvent(self, a0: qtg.QShowEvent) -> None: + + # Check the addtimerButton + self.mw.toolBar.addTimerButton.setChecked(True) + + self.dropDownSelected(self.topicDropDown.currentText()) + # Check if the parent widget is minimized + if not self.mw.isMinimized(): + + self.config['QOL'][ConfigKeys.addtimer_open_on_startup] = 'True' + + self.config.save() + # Call the base class's showEvent method + + return super().showEvent(a0) diff --git a/src/python/widgets/central.py b/src/python/widgets/central.py new file mode 100644 index 0000000..9bb93c3 --- /dev/null +++ b/src/python/widgets/central.py @@ -0,0 +1,94 @@ +from __future__ import annotations +from typing import TYPE_CHECKING +from datetime import timedelta + +from PySide6 import QtWidgets as qtw +from PySide6.QtCore import Qt + +from saveConfig import saveConfig +from dataParser import dataParser, StopwatchDataKeys +from style import StyleManager +from widgets.stopwatch import Stopwatch, Property + +if TYPE_CHECKING: + from widgets.mainWindow import window + +class centralWidget(qtw.QWidget): + def __init__(self, parent: window = None): + super().__init__(parent) + # Create the layout for the central widget + + self.styles = StyleManager() + + self.dataParser = dataParser() + + self.config = saveConfig() + + self.setObjectName('centralWidget') + self.scrollAreaLayout = qtw.QHBoxLayout(self) + self.scrollAreaLayout.setContentsMargins(0,0,0,0) + self.verticalLayout = qtw.QVBoxLayout() + self.verticalLayout.setSpacing(10) + # Create the scroll area + + self.scrollArea = qtw.QScrollArea() + self.scrollArea.setWidgetResizable(True) + self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.scrollArea.setContentsMargins(0,0,0,0) + # Create the widget to hold the scroll area contents + + self.scrollAreaWidgetContents = qtw.QWidget() + self.scrollAreaWidgetContents.setContentsMargins(5,5,5,5) + self.scrollAreaWidgetContents.setLayout(self.verticalLayout) + # Set the scroll area widget + + self.scrollArea.setWidget(self.scrollAreaWidgetContents) + self.scrollAreaLayout.addWidget(self.scrollArea) + + def addStopWatch(self, timeObject: str, duration: timedelta, name: str, startDuration: timedelta, color: str, notepadContents: str = '', save: bool = True) -> None: + # Create a Stopwatch object + stopwatch = Stopwatch(timeObject, duration, name, startDuration, color, notepadContents, central=self) + + # Add stopwatch to central widget layout + self.verticalLayout.addWidget(stopwatch) + + # Connect events + stopwatch.destroyed.connect(lambda: self.removeStopwatch(stopwatch)) + stopwatch.save.connect(self.saveData) + + # Make stopwatch visible + stopwatch.show() + + if save: + self.saveData() + + def removeStopwatch(self, stopwatch: Stopwatch) -> None: + '''Removes stopwatch from save file''' + + self.dataParser.remove_section(stopwatch.id_) + self.saveData() + + def saveData(self): + + stopwatch: Stopwatch + for stopwatch in self.findChildren(Stopwatch): + + objectName: str = stopwatch.objectName() + nameLabel: qtw.QLabel = stopwatch.findChild(qtw.QLabel, "nameLabel") + notepad: qtw.QTextEdit = stopwatch.findChild(qtw.QTextEdit) + + stopwatchData = { + StopwatchDataKeys.time_object : nameLabel.text(), + StopwatchDataKeys.time_finished : stopwatch.property(Property.FinishedTime), + StopwatchDataKeys.time_original_duration : stopwatch.property(Property.OriginalDuration), + StopwatchDataKeys.border_color : stopwatch.property(Property.BorderColor), + StopwatchDataKeys.notes : notepad.toPlainText() + } + + if not self.dataParser.has_section(objectName): + self.dataParser.add_section(objectName) + + for key, value in stopwatchData.items(): + self.dataParser.set(objectName, key, value) + + self.dataParser.save() diff --git a/src/python/widgets/guide.py b/src/python/widgets/guide.py new file mode 100644 index 0000000..5bedd47 --- /dev/null +++ b/src/python/widgets/guide.py @@ -0,0 +1,95 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +import PySide6.QtWidgets as qtw +from PySide6.QtCore import Qt + +from saveConfig import saveConfig, ConfigKeys +from constants import GUIDE + +if TYPE_CHECKING: + from widgets.mainWindow import window + import PySide6.QtGui as qtg + +class Guide(qtw.QDockWidget): + def __init__(self, parent: window = None): + super().__init__(parent) + + self.app = qtw.QApplication.instance() + + self.mw: window = parent + + self.config = saveConfig() + + self.setWindowTitle('Guide') + self.setObjectName('guideDockWidget') + self.setAllowedAreas(Qt.DockWidgetArea.RightDockWidgetArea) + self.setFeatures(qtw.QDockWidget.DockWidgetFeature.DockWidgetClosable) + + # Central Frame + self.centralFrame: qtw.QFrame = qtw.QFrame(self) + + # Vertical Box Layout + verticalLayout: qtw.QVBoxLayout = qtw.QVBoxLayout() + self.centralFrame.setLayout(verticalLayout) + + self.textFile: qtw.QTextBrowser = qtw.QTextBrowser(self.centralFrame) + self.textFile.setReadOnly(True) + self.textFile.setOpenExternalLinks(True) + + # Reading html guide + # The guide is packaged into the .exe so it is always updated + try: + + with open(GUIDE, 'r') as html: + + self.textFile.setHtml(html.read()) + + except Exception as e: + print(e) + + self.textFile.setHtml( + f''' +

    Error. Could not find Guide:

    +

    + {e} + ''') + + verticalLayout.addWidget(self.textFile) + + self.setWidget(self.centralFrame) + + def hideEvent(self, a0: qtg.QHideEvent) -> None: + # Check if the parent widget is hidden + + if self.mw.isHidden(): + a0.ignore() + + else: + + # Uncheck the guideButton + + self.mw.toolBar.guideButton.setChecked(False) + + if not self.mw.isMinimized(): + + self.config['QOL'][ConfigKeys.guide_open_on_startup] = 'False' + self.config.save() + + return super().hideEvent(a0) + + def showEvent(self, a0: qtg.QShowEvent) -> None: + + # Check the guideButton + + self.mw.toolBar.guideButton.setChecked(True) + + # Check if the parent widget is minimized + if not self.mw.isMinimized(): + + self.config['QOL'][ConfigKeys.guide_open_on_startup] = 'True' + + self.config.save() + # Call the base class's showEvent method + + return super().showEvent(a0) \ No newline at end of file diff --git a/src/python/widgets/mainWindow.py b/src/python/widgets/mainWindow.py new file mode 100644 index 0000000..ca651c5 --- /dev/null +++ b/src/python/widgets/mainWindow.py @@ -0,0 +1,136 @@ +from __future__ import annotations +from typing import TYPE_CHECKING +from datetime import timedelta, datetime + +import PySide6.QtWidgets as qtw +from PySide6.QtCore import QTimer, Qt, Signal + +from widgets.addTimer import addTimer +from widgets.options import optionsDock +from widgets.toolbar import toolbar +from widgets.guide import Guide +from widgets.staticTimers import staticTimers +from widgets.central import centralWidget +from constants import MAIN_WINDOW, TimeFormats +from saveConfig import saveConfig, ConfigKeys +from dataParser import dataParser, StopwatchDataKeys + +if TYPE_CHECKING: + import PySide6.QtGui as qtg + +class window(qtw.QMainWindow): + + updateTrayMenu = Signal() + + def __init__(self, app: qtw.QApplication): + super().__init__() + + self.config = saveConfig() + + self.dataParser = dataParser() + + self.app = app + + self.setObjectName(MAIN_WINDOW) + + self.toolBar = toolbar(parent=self) + self.addToolBar(self.toolBar) + + self.central = centralWidget(self) + self.setCentralWidget(self.central) + + self.dockWidgetAddTimer = addTimer(self) + self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.dockWidgetAddTimer) + self.dockWidgetAddTimer.setVisible(self.config.getAddtimerStartup()) + + self.dockWidgetOptions = optionsDock(self) + self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.dockWidgetOptions) + self.dockWidgetOptions.setVisible(self.config.getSettingsStartup()) + + self.dockWidgetGuide = Guide(self) + self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.dockWidgetGuide) + self.dockWidgetGuide.setVisible(self.config.getGuideStartup()) + + self.dockWidgetStaticTimer = staticTimers(self) + self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.dockWidgetStaticTimer) + self.dockWidgetStaticTimer.setVisible(self.config.getStatictimerStartup()) + + # So the resize event doesn't spam the save window resolution function (see sizeApplyTimerTimeout() ) + self.windowSizeApplyTimer = QTimer(self) + self.windowSizeApplyTimer.setObjectName('windowSizeApplyTimer') + self.windowSizeApplyTimer.timeout.connect(self.sizeApplyTimerTimeout) + + self.resize(self.config.getWidth(), self.config.getHeight()) + + self.loadSaveData() + + # Checking to see if the client is the latest version + self.dockWidgetOptions.checkUpdate() + + self.dockWidgetAddTimer.createStopwatch.connect(lambda a, b, c, d, e: self.central.addStopWatch(a, b, c, d, e)) + + def loadSaveData(self): + + for timerID in self.dataParser.sections(): + + # Name + name = self.dataParser[timerID][StopwatchDataKeys.time_object] + + # Changing the timer's destination into a datetime type + timeFinished = self.dataParser[timerID][StopwatchDataKeys.time_finished] + timeFinished = datetime.strptime(timeFinished, TimeFormats.Saved_Date) + timeFinished = timeFinished - datetime.today().replace(microsecond=0) + + # The original duration (For resetting the timer) and changing into a timedelta type + originalDuration = self.dataParser[timerID][StopwatchDataKeys.time_original_duration].split(':') + originalDuration = timedelta(hours= int(originalDuration[0]), minutes= int(originalDuration[1])) + + # Border color (In hexcode format) + borderColor = self.dataParser[timerID][StopwatchDataKeys.border_color] + + # Notes + notes = self.dataParser[timerID][StopwatchDataKeys.notes] + + # Remove old ID + self.dataParser.remove_section(timerID) + + # Create stopwatch by calling the central widget's function + self.central.addStopWatch(name, timeFinished, name, originalDuration, borderColor, notes, save=False) + + # Update the savefile (Old IDs are removed) + self.dataParser.save() + + # Update the savefile (Saving new IDs created from for loop) + self.central.saveData() + + def sizeApplyTimerTimeout(self): + + self.windowSizeApplyTimer.stop() + + self.config['WINDOW SIZE'] = {ConfigKeys.width : str(self.width()), ConfigKeys.height : str(self.height())} + + self.config.save() + + + def closeEvent(self, a0: qtg.QCloseEvent) -> None: + + if not self.config.getShutdownOnClose(): + # Trigger the "openClose_Pressed" function of the trayMenu (assuming trayMenu is an instance) + + self.updateTrayMenu.emit() + + a0.ignore() + else: + # Save data before exiting the application + self.central.saveData() + self.app.exit() + + + def resizeEvent(self, a0: qtg.QResizeEvent) -> None: + + # Start the window size apply timer if it is not active + if not self.windowSizeApplyTimer.isActive(): + self.windowSizeApplyTimer.start(1000) + + # Call the base class's resizeEvent + return super().resizeEvent(a0) diff --git a/src/python/widgets/options.py b/src/python/widgets/options.py new file mode 100644 index 0000000..2ead032 --- /dev/null +++ b/src/python/widgets/options.py @@ -0,0 +1,235 @@ +from __future__ import annotations +from typing import TYPE_CHECKING +from enum import StrEnum, auto +import webbrowser + +import PySide6.QtWidgets as qtw +from PySide6.QtCore import Qt, QTimer + +from widgets.updateAlert import UpdateAlert +from widgets.stopwatch import Stopwatch, Property + +from saveConfig import saveConfig, ConfigKeys +from style import StyleManager, StyleSheets +from checkVersion import checkUpdate + +if TYPE_CHECKING: + from widgets.mainWindow import window + import PySide6.QtGui as qtg + +class ApplyChangesProperty(StrEnum): + unsavedChanges = auto() + +class optionsDock(qtw.QDockWidget): + def __init__(self, parent: window = None): + super().__init__(parent) + + self.mw = parent + + self.config = saveConfig() + + self.styles = StyleManager() + + self.setWindowTitle('Options') + self.setObjectName('optionsDockWidget') + self.setAllowedAreas(Qt.DockWidgetArea.RightDockWidgetArea) + self.setFeatures(qtw.QDockWidget.DockWidgetFeature.DockWidgetClosable) + + # Central Frame + self.centralFrame = qtw.QFrame(self) + + # Vertical Box Layout + verticalLayout = qtw.QVBoxLayout() + self.centralFrame.setLayout(verticalLayout) + + # Topic Frame + self.topicFrame = qtw.QFrame(self.centralFrame) + verticalLayout.addWidget(self.topicFrame) + + # Vertical Layout (For Topic Frame) + self.vertLayout = qtw.QVBoxLayout() + self.vertLayout.setSpacing(10) + self.topicFrame.setLayout(self.vertLayout) + + # Checkbox + self.appOnCloseCheckbox = qtw.QCheckBox() + self.appOnCloseCheckbox.setText('Desktop Notifications') + self.appOnCloseCheckbox.setChecked(self.config.getShutdownOnClose()) + self.appOnCloseCheckbox.clicked.connect(lambda: self.settingChanged(True)) + self.appOnCloseCheckbox.setToolTip('The app will close and not run in the background.') + + # Checkbox + self.showOnOpenCheckbox = qtw.QCheckBox() + self.showOnOpenCheckbox.setText('Show On App Start') + self.showOnOpenCheckbox.setChecked(self.config.getStartUp()) + self.showOnOpenCheckbox.clicked.connect(lambda: self.settingChanged(True)) + self.showOnOpenCheckbox.setToolTip('Program will automatically show or hide when starting.') + + self.updateCheckBox = qtw.QCheckBox() + self.updateCheckBox.setText('Check For Update On Startup') + self.updateCheckBox.setChecked(self.config.getVersionCheck()) + self.updateCheckBox.clicked.connect(lambda: self.settingChanged(True)) + self.updateCheckBox.setToolTip('When the program starts it will go online to check and see if your client is up-to-date') + + # Checkbox + self.notifyCheckbox = qtw.QCheckBox() + self.notifyCheckbox.setText('Desktop Notifications') + self.notifyCheckbox.setChecked(self.config.getDesktopNotifications()) + self.notifyCheckbox.clicked.connect(lambda: self.settingChanged(True)) + self.notifyCheckbox.setToolTip('A windows desktop notification will appear when a stopwatch finishes.') + + # Checkbox + self.staticDailyNotifyCheckbox = qtw.QCheckBox() + self.staticDailyNotifyCheckbox.setText('Daily Reset Notifications') + self.staticDailyNotifyCheckbox.setChecked(self.config.getDailyReset()) + self.staticDailyNotifyCheckbox.clicked.connect(lambda: self.settingChanged(True)) + self.staticDailyNotifyCheckbox.setToolTip('Will recieve a notification when there is a daily reset.') + + # Checkbox + self.staticWeeklyNotifyCheckbox = qtw.QCheckBox() + self.staticWeeklyNotifyCheckbox.setText('Weekly Reset Notifications') + self.staticWeeklyNotifyCheckbox.setChecked(self.config.getWeeklyReset()) + self.staticWeeklyNotifyCheckbox.clicked.connect(lambda: self.settingChanged(True)) + self.staticWeeklyNotifyCheckbox.setToolTip('Will recieve a notification when there is a daily reset.') + + # Label for color scheme dropdown + self.colorPalletLabel = qtw.QLabel('Color Scheme:') + + # Color scheme dropdown + self.colorPallet = qtw.QComboBox() + self.colorPallet.addItems([x.title() for x in self.styles.getColorPallets()]) + self.colorPallet.setCurrentText(self.config.getCurrentPallet().title()) + self.colorPallet.currentTextChanged.connect(lambda: self.settingChanged(True)) + self.colorPallet.setFocusPolicy(Qt.FocusPolicy.NoFocus) + + # Adding checkboxes to frame + for widget in (self.appOnCloseCheckbox, + self.showOnOpenCheckbox, + self.updateCheckBox, + self.notifyCheckbox, + self.staticDailyNotifyCheckbox, + self.staticWeeklyNotifyCheckbox, + self.colorPalletLabel, + self.colorPallet): + self.vertLayout.addWidget(widget) + + # Apply Settings Button + self.applyButton = qtw.QPushButton('Apply', self) + self.applyButton.setObjectName('applySettingsButton') + self.applyButton.clicked.connect(self.applySettings) + self.applyButton.clicked.connect(lambda: self.settingChanged(False)) + self.applyButton.setProperty(ApplyChangesProperty.unsavedChanges, 'false') + verticalLayout.addWidget(self.applyButton, alignment=Qt.AlignmentFlag.AlignTop) + + # Check for updates button + self.updateButton = qtw.QPushButton('Check for updates', self) + self.updateButton.setObjectName('checkUpdateButton') + self.updateButton.clicked.connect(lambda: self.checkUpdate(button=True)) + verticalLayout.addWidget(self.updateButton, alignment=Qt.AlignmentFlag.AlignTop) + + # Support wolfmyths + self.supportButton = qtw.QPushButton('Support Wolfmyths on Ko-Fi', self) + self.supportButton.setToolTip('Opens a new tab on your web browser to ko-fi.com') + self.supportButton.clicked.connect(self.openKoFiLink) + verticalLayout.addWidget(self.supportButton, alignment=Qt.AlignmentFlag.AlignTop) + + self.setWidget(self.centralFrame) + + def settingChanged(self, bool: bool): + + # Change the property of the applyButton to signal the user a setting's been changed + self.applyButton.setProperty(ApplyChangesProperty.unsavedChanges, str(bool).lower()) + + self.style().polish(self.applyButton) + + def openKoFiLink(self) -> None: + webbrowser.open_new_tab('https://ko-fi.com/C0C4MJZS9') + + def checkUpdate(self) -> None: + def updateFound(latestVersion: str, changelog: str) -> None: + # Will create a dialog window if there's a version update unless specified not to + + updateNotify = UpdateAlert(self, latestVersion) + + updateNotify.exec() + + def upToDate() -> None: + self.updateButton.setText('On latest version ^_^') + QTimer.singleShot(3000, lambda: self.updateButton.setText('Check for updates')) + + update = checkUpdate() + # Checking if the function was started by the "check for update button" + update.upToDate.connect(upToDate) + update.updateDetected.connect(lambda x, y: updateFound(x, y)) + + + def applySettings(self): + # Create a dictionary with updated configuration settings + + updatedConfig = { + ConfigKeys.shutdown_app_on_close : str(self.appOnCloseCheckbox.isChecked()), + ConfigKeys.show_on_startup : str(self.showOnOpenCheckbox.isChecked()), + ConfigKeys.desktop_notifications : str(self.notifyCheckbox.isChecked()), + ConfigKeys.color_pallet : self.colorPallet.currentText().lower(), + ConfigKeys.checkversiononstartup : str(self.updateCheckBox.isChecked()), + ConfigKeys.dailyresetnotify : str(self.staticDailyNotifyCheckbox.isChecked()), + ConfigKeys.weeklyresetnotify : str(self.staticWeeklyNotifyCheckbox.isChecked()) + } + + # Checking to see if the user wants to change the color scheme + if self.colorPallet.currentText().lower() != self.config.getCurrentPallet(): + + self.styles.changeColorPallet(updatedConfig[ConfigKeys.color_pallet]) + + stopwatch: Stopwatch + for stopwatch in self.mw.central.findChildren(Stopwatch): + + # Updating the stylesheet again to change border color + self.styles.changeStopwatchBorderColor(stopwatch.property(Property.BorderColor)) + + # Change stopwatch stylesheet + stopwatch.setStyleSheet(self.styles.getStyleSheet(StyleSheets.stopwatch)) + self.mw.central.style().polish(stopwatch) + + # Change global stylesheet + app: qtw.QApplication = qtw.QApplication.instance() + app.setStyleSheet(self.styles.getStyleSheet(StyleSheets.app)) + app.style().polish(app) + + self.config['OPTIONS'] = updatedConfig + + self.config.save() + self.settingChanged(False) + + def hideEvent(self, a0: qtg.QHideEvent) -> None: + # Check if the parent widget is hidden + + if self.mw.isHidden(): + a0.ignore() + + else: + + self.mw.toolBar.optionsButton.setChecked(False) + + if not self.mw.isMinimized(): + # Update the configuration setting 'settings open on startup' to 'False' + self.config['QOL'][ConfigKeys.settings_open_on_startup] = 'False' + + # Save the updated configuration + self.config.save() + + return super().hideEvent(a0) + + def showEvent(self, a0: qtg.QShowEvent) -> None: + # Get references to the parent widget, toolbar, and optionsButton + + # Set the optionsButton as checked + + self.mw.toolBar.optionsButton.setChecked(True) + + # Update the configuration setting 'settings open on startup' to 'True' + if not self.mw.isMinimized(): + + self.config['QOL'][ConfigKeys.settings_open_on_startup] = 'True' + self.config.save() + return super().showEvent(a0) \ No newline at end of file diff --git a/src/python/widgets/staticTimers.py b/src/python/widgets/staticTimers.py new file mode 100644 index 0000000..c5e0d2d --- /dev/null +++ b/src/python/widgets/staticTimers.py @@ -0,0 +1,220 @@ +from __future__ import annotations +from typing import TYPE_CHECKING +from datetime import datetime, timedelta, date + +import PySide6.QtWidgets as qtw +from PySide6.QtCore import Qt, QTimer + +from saveConfig import saveConfig, ConfigKeys +from constants import ONE, ZERO, TimeFormats +import notify + +if TYPE_CHECKING: + import PySide6.QtGui as qtg + from widgets.mainWindow import window + +class staticTimers(qtw.QDockWidget): + def __init__(self, parent: window = None): + super().__init__(parent) + + self.config = saveConfig() + + self.mw = parent + + self.setWindowTitle('Static Timers') + self.setObjectName('staticTimersDockWidget') + self.setAllowedAreas(Qt.DockWidgetArea.RightDockWidgetArea) + self.setFeatures(qtw.QDockWidget.DockWidgetFeature.DockWidgetClosable) + + # Checking to see if 'STATIC_TIMERS' is a valid section in config.ini + try: + self.config['STATIC_TIMERS'].keys() + except KeyError: + self.config.add_section('STATIC_TIMERS') + self.config.save() + + # Central Frame + self.centralFrame: qtw.QFrame = qtw.QFrame(self) + + # Form Layout + formlayout: qtw.QFormLayout = qtw.QFormLayout() + self.centralFrame.setLayout(formlayout) + + # Daily reset timer + self.dailyReset = qtw.QLabel(self, text='Loading...') + self.dailyReset.setObjectName('dailyResetTimer') + formlayout.addRow(qtw.QLabel(self, text='Daily Reset: '), self.dailyReset) + + # Weekly reset timer + self.weeklyReset = qtw.QLabel(self, text='Loading...') + self.weeklyReset.setObjectName('weeklyResetTimer') + formlayout.addRow(qtw.QLabel(self, text='Weekly Reset: '), self.weeklyReset) + + # Server's UTC offsets + self.serverTimezones: dict[str:int|float] = { + 'Asia' : 8, + 'EU' : 1, + 'NA' : -5 + } + + # Dropdown + self.chooseServerDropDown = qtw.QComboBox(self) + self.chooseServerDropDown.addItems(list(self.serverTimezones.keys())) + self.chooseServerDropDown.setCurrentText(self.config.getServer()) + self.chooseServerDropDown.currentTextChanged.connect(lambda server: self.changeServer(server)) + formlayout.addRow('Server: ', self.chooseServerDropDown) + + self.setWidget(self.centralFrame) + + # Defining variables for timers + + self.dailyQTimer = QTimer(self) + self.dailyQTimer.timeout.connect(self.dailyResetTimer) + self.weeklyQTimer = QTimer(self) + self.weeklyQTimer.timeout.connect(self.weeklyResetTimer) + + self.serverChange = False + + # Start Static Timers + self.staticTimerStart() + + def staticTimerStart(self): + + self.serverChange = False + + # Update perferred server setting + self.selectedServer: str = self.config.getServer() + + # Define today's date and time + self.today = datetime.today().replace(microsecond=0) + + ### Daily Timer + + # Calculating the deadline of the daily reset with their choice of server + self.dailyDeadline = datetime(year=self.today.year, month=self.today.month, day=self.today.day, hour=9) + timedelta(hours=self.serverTimezones[self.selectedServer]) + + # If the program starts after the reset time happened, add a day to the deadline + if self.dailyDeadline < self.today: + + self.dailyDeadline += timedelta(days=1) + + self.config['STATIC_TIMERS'][ConfigKeys.dailydeadline] = datetime.strftime(self.dailyDeadline, TimeFormats.Static_Timer) + + self.difference = self.dailyDeadline - self.today + + ### Weekly Timer + + # Calculating how many days left until Monday + # date.weekday starts at 0 + dayOfTheWeek = date.weekday(self.today) + self.weeklyDayDifference: int = 7 - dayOfTheWeek + + # Calculating the deadline of the weekly reset with their choice of server + # Creating datetime object, hour 9 is the time for UTC+0 for weekly reset + self.weeklyDeadline = datetime(year=self.today.year, month=self.today.month, day=self.today.day, hour=9) + + # Adding UTC Offset depepending on server selected + self.weeklyDeadline += timedelta(hours=self.serverTimezones[self.selectedServer]) + + # Adding days left until upcoming Sunday + self.weeklyDeadline += timedelta(days=self.weeklyDayDifference) + + self.config['STATIC_TIMERS'][ConfigKeys.weeklydeadline] = datetime.strftime(self.weeklyDeadline, TimeFormats.Static_Timer) + + self.weeklyDifference = self.weeklyDeadline - self.today + + self.config.save() + + self.dailyQTimer.start(1000) + self.weeklyQTimer.start(1000) + + # Renabling server selection + self.chooseServerDropDown.setEnabled(True) + + + def dailyResetTimer(self): + + if self.difference > ZERO: + + self.difference -= ONE + + self.dailyReset.setText(str(self.difference)) + + else: + + self.dailyDeadline += timedelta(days=1) + self.config['STATIC_TIMERS'][ConfigKeys.dailydeadline] = datetime.strftime(self.weeklyDeadline, TimeFormats.Static_Timer) + self.config.save() + + self.difference = self.dailyDeadline - datetime.today() + + # 2nd condition is so the daily reset doesn't also go off when the weekly reset notification does + if self.config.getDailyReset() and not all((self.weeklyDayDifference > ZERO, self.config.getWeeklyReset())): + notify.Notify('Daily reset', 'The daily reset has been reached!') + + def weeklyResetTimer(self): + + if self.weeklyDifference > ZERO: + + self.weeklyDifference -= ONE + + # Used formatted string because you only need to see the days since `daily reset` tells you the time + self.weeklyReset.setText(f'{self.weeklyDifference.days} Day(s) left') + + else: + + self.weeklyDeadline += timedelta(days=7) + self.config['STATIC_TIMERS'][ConfigKeys.weeklydeadline] = datetime.strftime(self.weeklyDeadline, TimeFormats.Static_Timer) + self.config.save() + + self.weeklyDifference = self.weeklyDeadline - datetime.today() + + if self.config.getWeeklyReset(): + notify.Notify('Weekly reset', 'The weekly reset has been reached!') + + def changeServer(self, server: str): + + # Prevent the selection from being spammed which would break the timers + self.chooseServerDropDown.setEnabled(False) + + self.config['QOL'][ConfigKeys.gameserver] = server + self.config.save() + + self.serverChange = True + + self.dailyQTimer.stop() + self.weeklyQTimer.stop() + + self.staticTimerStart() + + def hideEvent(self, a0: qtg.QHideEvent) -> None: + # Check if the parent widget is hidden + + if self.mw.isHidden(): + a0.ignore() + + else: + + self.mw.toolBar.staticButton.setChecked(False) + + if not self.mw.isMinimized(): + # Update the configuration setting 'settings open on startup' to 'False' + self.config['QOL'][ConfigKeys.static_timers_open_on_startup] = 'False' + + # Save the updated configuration + self.config.save() + + return super().hideEvent(a0) + + def showEvent(self, a0: qtg.QShowEvent) -> None: + + # Set the optionsButton as checked + + self.mw.toolBar.staticButton.setChecked(True) + + # Update the configuration setting 'settings open on startup' to 'True' + if not self.mw.isMinimized(): + + self.config['QOL'][ConfigKeys.static_timers_open_on_startup] = 'True' + self.config.save() + return super().showEvent(a0) \ No newline at end of file diff --git a/src/python/widgets/stopwatch.py b/src/python/widgets/stopwatch.py new file mode 100644 index 0000000..1af84ea --- /dev/null +++ b/src/python/widgets/stopwatch.py @@ -0,0 +1,156 @@ +from __future__ import annotations +from typing import TYPE_CHECKING +from datetime import timedelta, datetime +from enum import StrEnum + +from PySide6 import QtWidgets as qtw +from PySide6.QtCore import QTimer, Signal + +from constants import ZERO, ONE, TimeFormats +from style import StyleSheets +import notify + +if TYPE_CHECKING: + from widgets.central import centralWidget + +class Property(StrEnum): + '''Property Names for Stopwatch QObject''' + BorderColor = 'border-color' + OriginalDuration = 'originalDuration' + FinishedTime = 'finishedTime' + +class Stopwatch(qtw.QFrame): + + save = Signal() + + def __init__(self, timeObject: str, duration: timedelta, name: str, startDuration: timedelta, color: str, notepadContents: str = '', central: centralWidget = None) -> None: + super().__init__() + + self.timeObject = timeObject + self.duration = duration + self.name = name + self.startDuration = startDuration + self.color = color + self.notepadContents = notepadContents + + self.central = central + self.setParent(self.central.scrollAreaWidgetContents) + + # Set the object name of the frame using its ID + self.id_ = str(id(self)) + + self.central.styles.changeStopwatchBorderColor(color) + color = self.central.styles.getStopwatchColor(color) + + self.setStyleSheet(self.central.styles.getStyleSheet(StyleSheets.stopwatch)) + + self.setObjectName(self.id_) + #self.setMaximumHeight(500) + + frameLayout = qtw.QVBoxLayout() + frameLayout.setContentsMargins(15,15,15,15) + frameLayout.setSpacing(10) + # Create a QLabel for the name + + nameLabel = qtw.QLabel(name, self) + nameLabel.setObjectName('nameLabel') + + # Create a delete button + + deleteButton = qtw.QPushButton('Delete', self) + deleteButton.setMinimumHeight(60) + deleteButton.clicked.connect(self.deleteLater) + # Create a QLabel for the countdown time + + self.countDown = qtw.QLabel('00:00:00', self) + self.countDown.setObjectName('CountDownLabel') + # Create a reset button + + resetButton = qtw.QPushButton('Reset', self) + resetButton.setObjectName('resetButton') + resetButton.setMinimumHeight(60) + resetButton.clicked.connect(self.restartTimer) + + # Create a QLabel for the finished date + finishedDate = datetime.now() + duration + self.finishedDateLabel = qtw.QLabel(f'{finishedDate.strftime(TimeFormats.Finished_Date)}') + self.finishedDateLabel.setObjectName('finishedDateLabel') + + # Create a QTextEdit for notes + notepad = qtw.QTextEdit(self) + notepad.setPlaceholderText('Notes:') + notepad.setMaximumHeight(200) + notepad.setText(notepadContents) + + # Add widgets to layout + for widget in (nameLabel, + self.countDown, + self.finishedDateLabel, + notepad, + resetButton, + deleteButton): + frameLayout.addWidget(widget) + + # Set the layout for the frame + self.setLayout(frameLayout) + + # Finished/Difference time calculations + currentTime = datetime.today() + + finishedTime = currentTime + duration + + # Calculate the days, hours, minutes from time delta object for original duration property + startDurationTotalSeconds = startDuration.total_seconds() + startDurationHours, remainder = divmod(startDurationTotalSeconds, 3600) + startDurationMinutes, seconds = divmod(remainder, 60) + + # Setting properties for save manager + # Original duration property of the frame + self.setProperty(Property.OriginalDuration, '{:02}:{:02}:{:02}'.format(int(startDurationHours), int(startDurationMinutes), int(seconds))) + # Time until the stopwatch is finished + self.setProperty(Property.FinishedTime, datetime.strftime(finishedTime, TimeFormats.Saved_Date)) + # Set the border color property + self.setProperty(Property.BorderColor, color) + + self.difference = finishedTime - currentTime + + self.QTimer = QTimer() + self.QTimer.timeout.connect(self.countDownTimer) + + # Setting the text to the difference so when a timer starts its not starting at 00:00:00 unless that's actually the case + if self.difference > ZERO: + self.countDown.setText(str(self.difference)) + + # Begin timer + self.QTimer.start(1000) + + def restartTimer(self) -> None: + self.QTimer.stop() + + today = datetime.today() + finishedTime = today + self.startDuration + self.difference = finishedTime - today + + self.countDown.setText(str(self.difference)) + self.finishedDateLabel.setText(f'{finishedTime.strftime(TimeFormats.Finished_Date)}') + + self.setProperty(Property.FinishedTime, datetime.strftime(finishedTime, TimeFormats.Saved_Date)) + + self.QTimer.start(1000) + + def countDownTimer(self) -> None: + + self.difference -= ONE + + if self.difference > ZERO: + + # Update the countdown label with the new difference + self.countDown.setText(str(self.difference)) + + else: + self.QTimer.stop() + self.countDown.setText('00:00:00') + self.countDown.setProperty('finished', "true") + self.style().polish(self.countDown) + + notify.Notify('Stopwatch finished', f'{self.timeObject} has finished!') diff --git a/src/python/widgets/sysTrayIcon.py b/src/python/widgets/sysTrayIcon.py new file mode 100644 index 0000000..b86d360 --- /dev/null +++ b/src/python/widgets/sysTrayIcon.py @@ -0,0 +1,24 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +import PySide6.QtWidgets as qtw +import PySide6.QtGui as qtg + +from widgets.trayMenu import trayMen + +from constants import ICON + +if TYPE_CHECKING: + from widgets.mainWindow import window + +class sysTrayIcon(qtw.QSystemTrayIcon): + def __init__(self, parent: qtw.QApplication = None, mw: window = None) -> None: + super().__init__(parent=parent) + + self.setIcon(qtg.QIcon(ICON)) + self.setToolTip('Genshin Stopwatch') + self.setVisible(True) + + trayMenu = trayMen(parent, mw) + + self.setContextMenu(trayMenu) diff --git a/src/python/widgets/toolbar.py b/src/python/widgets/toolbar.py new file mode 100644 index 0000000..8992d56 --- /dev/null +++ b/src/python/widgets/toolbar.py @@ -0,0 +1,70 @@ + +from __future__ import annotations +from typing import TYPE_CHECKING + +import PySide6.QtWidgets as qtw +import PySide6.QtGui as qtg + +from constants import TOOLBAR +from saveConfig import saveConfig + +if TYPE_CHECKING: + from widgets.mainWindow import window + +class toolbar(qtw.QToolBar): + def __init__(self, parent: window = None): + super().__init__(parent) + + self.setObjectName(TOOLBAR) + + self.app = qtw.QApplication.instance() + self.mw: window = parent + + self.config = saveConfig() + + # Set floatable and movable to False + self.setFloatable(False) + self.setMovable(False) + + self.layout().setSpacing(20) + + # Add Timer Button + self.addTimerButton = qtg.QAction('Add Timer', self) + self.addTimerButton.setCheckable(True) + self.addTimerButton.setChecked(self.config.getAddtimerStartup()) + self.addTimerButton.triggered.connect(lambda: self.button_Clicked(self.mw.dockWidgetAddTimer, self.addTimerButton.isChecked() ) ) + self.addAction(self.addTimerButton) + + # Options Button + self.optionsButton = qtg.QAction('Options', self) + self.optionsButton.setCheckable(True) + self.optionsButton.setChecked(self.config.getSettingsStartup()) + self.optionsButton.triggered.connect(lambda: self.button_Clicked(self.mw.dockWidgetOptions, self.optionsButton.isChecked() ) ) + self.addAction(self.optionsButton) + + # Guide Button + self.guideButton = qtg.QAction('Guide', self) + self.guideButton.setCheckable(True) + self.guideButton.setChecked(self.config.getGuideStartup()) + self.guideButton.triggered.connect(lambda: self.button_Clicked(self.mw.dockWidgetGuide, self.guideButton.isChecked())) + self.addAction(self.guideButton) + + # Static Timers Button + self.staticButton = qtg.QAction('Static Timers', self) + self.staticButton.setCheckable(True) + self.staticButton.setChecked(self.config.getStatictimerStartup()) + self.staticButton.triggered.connect(lambda: self.button_Clicked(self.mw.dockWidgetStaticTimer, self.staticButton.isChecked())) + self.addAction(self.staticButton) + + def button_Clicked(self, dockObject: qtw.QDockWidget, buttonisChecked: bool): + # Find the QDockWidget based on the dockObjectName + + if buttonisChecked: + # If the button is checked, show the QDockWidget + + dockObject.show() + + else: + # If the button is not checked, hide the QDockWidget + + dockObject.hide() \ No newline at end of file diff --git a/src/python/widgets/trayMenu.py b/src/python/widgets/trayMenu.py new file mode 100644 index 0000000..5918aba --- /dev/null +++ b/src/python/widgets/trayMenu.py @@ -0,0 +1,57 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +import PySide6.QtWidgets as qtw +import PySide6.QtGui as qtg + +from saveConfig import saveConfig + +from constants import SYS_TRAY + +if TYPE_CHECKING: + from widgets.mainWindow import window + +class trayMen(qtw.QMenu): + def __init__(self, app: qtw.QApplication, main_window: window): + super().__init__() + + self.config = saveConfig() + self.app = app + self.main_window = main_window + + self.main_window.updateTrayMenu.connect(self.openClose_Pressed) + + self.setObjectName(SYS_TRAY) + # Determine the label for the open/close button based on a configuration option + + openOrClose = 'Close' if self.config.getStartUp() else 'Open' + # Create the open/close button QAction and connect it to the openClose_Pressed method + + self.openCloseButton = qtg.QAction(openOrClose) + self.openCloseButton.triggered.connect(self.openClose_Pressed) + # Create the quit application button QAction and connect it to the shutdownApp method + + self.quitAppButton = qtg.QAction("Shut Down") + self.quitAppButton.triggered.connect(self.shutdownApp) + + # Add the actions (buttons) to the menu + self.addAction(self.openCloseButton) + self.addAction(self.quitAppButton) + + def shutdownApp(self): + # Save data before shutting down the application + self.config.save() + self.app.quit() + + def openClose_Pressed(self): + + if self.openCloseButton.text() == 'Close': + # If the open/close button text is 'Close' Change the button text to 'Open' + self.openCloseButton.setText('Open') + # Set the main window (mw) to be invisible + + self.main_window.setVisible(False) + else: + # If the open/close button text is not 'Close' Change the button text to 'Close' + self.openCloseButton.setText('Close') + self.main_window.setVisible(True) diff --git a/src/python/widgets/updateAlert.py b/src/python/widgets/updateAlert.py new file mode 100644 index 0000000..a846940 --- /dev/null +++ b/src/python/widgets/updateAlert.py @@ -0,0 +1,46 @@ +import webbrowser + +import PySide6.QtWidgets as qtw +import PySide6.QtGui as qtg + +from saveConfig import saveConfig, ConfigKeys + +from constants import ICON + +class UpdateAlert(qtw.QDialog): + def __init__(self, latestVersion: str) -> None: + super().__init__() + + self.config = saveConfig() + + self.setWindowTitle('Genshin Stopwatch: New Version Update!') + self.setWindowIcon(qtg.QIcon(ICON)) + + layout = qtw.QVBoxLayout() + + self.message = qtw.QLabel(self, text=f'The latest update {latestVersion} has been released! Would you like to download it?') + layout.addWidget(self.message) + + self.checkBox = qtw.QCheckBox(self, text='Do not automatically check for updates') + self.checkBox.clicked.connect(self.checkBoxClicked) + layout.addWidget(self.checkBox) + + self.buttons = qtw.QDialogButtonBox.StandardButton.Ok | qtw.QDialogButtonBox.StandardButton.No + self.buttonBox = qtw.QDialogButtonBox(self.buttons, self) + self.buttonBox.accepted.connect(self.accept) + self.buttonBox.rejected.connect(self.reject) + layout.addWidget(self.buttonBox) + + self.setLayout(layout) + + def accept(self) -> None: + webbrowser.open_new_tab('https://github.com/Wolfmyths/Genshin-Stopwatch/releases/latest') + return super().accept() + + def checkBoxClicked(self) -> None: + if self.checkBox.isChecked(): + self.config['OPTIONS'][ConfigKeys.checkversiononstartup] = 'False' + else: + self.config['OPTIONS'][ConfigKeys.checkversiononstartup] = 'True' + + self.config.save() From e4d70bad7aa67c25f37e9783acedc3959c53178d Mon Sep 17 00:00:00 2001 From: Wolfmyths Date: Sun, 7 Jan 2024 23:12:06 -0500 Subject: [PATCH 2/4] Fixes, adjustments, additions --- src/python/checkVersion.py | 13 ++++-- src/python/saveConfig.py | 6 +-- src/python/style.py | 23 +++++++-- src/python/widgets/central.py | 26 ++++++++++- src/python/widgets/guide.py | 77 +++++-------------------------- src/python/widgets/mainWindow.py | 7 ++- src/python/widgets/options.py | 13 +++--- src/python/widgets/toolbar.py | 8 ++-- src/python/widgets/updateAlert.py | 10 ++-- 9 files changed, 86 insertions(+), 97 deletions(-) diff --git a/src/python/checkVersion.py b/src/python/checkVersion.py index b6147dd..da68b79 100644 --- a/src/python/checkVersion.py +++ b/src/python/checkVersion.py @@ -15,14 +15,14 @@ class checkUpdate(QObject): `checkUpdate` will delete itself after it's finished. ''' - updateDetected = Signal(str, str) + updateDetected = Signal(Version, str) upToDate = Signal() error = Signal() def __init__(self) -> None: super().__init__() - link = 'https://api.github.com/repos/Wolfmyths/Myth-Mod-Manager/releases' + link = 'https://api.github.com/repos/Wolfmyths/Genshin-Stopwatch/releases/latest' network = QNetworkAccessManager(self) request = QNetworkRequest(QUrl(link)) @@ -47,8 +47,15 @@ def __checkVersion(self) -> None: except Exception as e: self.error.emit() return + + # Removing letters from version string + version: str = data['tag_name'] + + for s in version: + if not s.isdigit() and s != '.': + version = version.replace(s, '') - latestVersion = Version.coerce(data['tag_name']) + latestVersion = Version.coerce(version) if latestVersion > VERSION: self.updateDetected.emit(latestVersion, data['body']) diff --git a/src/python/saveConfig.py b/src/python/saveConfig.py index da851f5..1220bc9 100644 --- a/src/python/saveConfig.py +++ b/src/python/saveConfig.py @@ -23,7 +23,6 @@ class ConfigKeys(StrEnum): desiredstamina = auto() addtimer_open_on_startup = 'addtimer open on startup' settings_open_on_startup = 'settings open on startup' - guide_open_on_startup = 'guide open on startup' static_timers_open_on_startup = 'static timers open on startup' gameserver = auto() @@ -103,10 +102,7 @@ def getAddtimerStartup(self) -> bool: def getSettingsStartup(self) -> bool: return self.getboolean('QOL', ConfigKeys.settings_open_on_startup, fallback=False) - - def getGuideStartup(self) -> bool: - return self.getboolean('QOL', ConfigKeys.guide_open_on_startup, fallback=False) - + def getStatictimerStartup(self) -> bool: return self.getboolean('QOL', ConfigKeys.static_timers_open_on_startup, fallback=False) diff --git a/src/python/style.py b/src/python/style.py index 2aecc2e..6c716e0 100644 --- a/src/python/style.py +++ b/src/python/style.py @@ -1,9 +1,10 @@ from typing import Self from configparser import ConfigParser -import os from enum import StrEnum, auto import random +from constants import CONFIG + # COLOR PALLET CREDITS: # Dark by archer: https://lospec.com/palette-list/timeless # Light by wcburgess: https://www.color-hex.com/color-palette/106748 @@ -37,7 +38,7 @@ class StyleManager: def __init__(self) -> None: config = ConfigParser() - config.read(os.path.join(os.path.abspath(os.curdir), 'config.ini')) + config.read(CONFIG) # Attribute Definitions # Default color pallet is dark @@ -50,7 +51,7 @@ def __init__(self) -> None: # 3 : Text # 4 : Alt Text - self.colorPallets: dict[ColorPallets:tuple[str]] = { + self.colorPallets: dict[ColorPallets:tuple[str, str, str, str, str]] = { ColorPallets.dark : ('#212124', '#464c54', '#5b8087', '#76add8', '#a3e7f0'), ColorPallets.light : ('#fafafa', '#e4e5f1', '#d2d3db', '#9394a5', '#484b6a'), ColorPallets.original : ('#1A1A1B', '#333F44', '#37AA9C', '#94F3E4', '#FCB3FC'), @@ -74,10 +75,14 @@ def __init__(self) -> None: ColorPallets.anemo :'#60FD75' } - self.stopwatchBorderColor: str = self.stopwatchColorsDict['anemo'] + self.stopwatchBorderColor: str = self.stopwatchColorsDict[ColorPallets.anemo] self.appStyleSheet: str = ''' + QWidget {{ + background-color: {0}; + }} + QMainWindow{{ background-color: {0}; }} @@ -87,10 +92,17 @@ def __init__(self) -> None: }} QWidget#centralWidget * {{ - background-color: {0}; border: none; }} + QDockWidget {{ + color: {3}; + font-size: 15px; + }} + + QDockWidget::title {{ + background: {1}; + }} QToolButton, QPushButton{{ background-color: {1}; @@ -239,6 +251,7 @@ def __init__(self) -> None: QPushButton {{ font-size: 25px; + background: {0}; }} QPushButton:pressed{{ diff --git a/src/python/widgets/central.py b/src/python/widgets/central.py index 9bb93c3..d138de9 100644 --- a/src/python/widgets/central.py +++ b/src/python/widgets/central.py @@ -3,7 +3,7 @@ from datetime import timedelta from PySide6 import QtWidgets as qtw -from PySide6.QtCore import Qt +from PySide6.QtCore import Qt, Signal from saveConfig import saveConfig from dataParser import dataParser, StopwatchDataKeys @@ -14,6 +14,9 @@ from widgets.mainWindow import window class centralWidget(qtw.QWidget): + + stopwatchCountChanged = Signal() + def __init__(self, parent: window = None): super().__init__(parent) # Create the layout for the central widget @@ -29,6 +32,13 @@ def __init__(self, parent: window = None): self.scrollAreaLayout.setContentsMargins(0,0,0,0) self.verticalLayout = qtw.QVBoxLayout() self.verticalLayout.setSpacing(10) + + # Helper label for when there's no stopwatch present + self.helperLabel = qtw.QLabel('Use the "Add Timer" button to begin creating a stopwatch\n\nFor additional help press the "Guide" button ') + self.helperLabel.setStyleSheet('font-size: 60px;') + self.helperLabel.setWordWrap(True) + self.helperLabel.setAlignment(Qt.AlignmentFlag.AlignCenter) + # Create the scroll area self.scrollArea = qtw.QScrollArea() @@ -45,6 +55,16 @@ def __init__(self, parent: window = None): self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.scrollAreaLayout.addWidget(self.scrollArea) + self.stopwatchCountChanged.connect(self.showOrHideHelperLabel) + self.showOrHideHelperLabel() + + def showOrHideHelperLabel(self): + + if self.findChild(Stopwatch) is None: + self.verticalLayout.addWidget(self.helperLabel) + else: + self.verticalLayout.removeWidget(self.helperLabel) + def addStopWatch(self, timeObject: str, duration: timedelta, name: str, startDuration: timedelta, color: str, notepadContents: str = '', save: bool = True) -> None: # Create a Stopwatch object stopwatch = Stopwatch(timeObject, duration, name, startDuration, color, notepadContents, central=self) @@ -61,6 +81,8 @@ def addStopWatch(self, timeObject: str, duration: timedelta, name: str, startDur if save: self.saveData() + + self.stopwatchCountChanged.emit() def removeStopwatch(self, stopwatch: Stopwatch) -> None: '''Removes stopwatch from save file''' @@ -68,6 +90,8 @@ def removeStopwatch(self, stopwatch: Stopwatch) -> None: self.dataParser.remove_section(stopwatch.id_) self.saveData() + self.stopwatchCountChanged.emit() + def saveData(self): stopwatch: Stopwatch diff --git a/src/python/widgets/guide.py b/src/python/widgets/guide.py index 5bedd47..5e4beda 100644 --- a/src/python/widgets/guide.py +++ b/src/python/widgets/guide.py @@ -1,39 +1,22 @@ -from __future__ import annotations -from typing import TYPE_CHECKING - import PySide6.QtWidgets as qtw -from PySide6.QtCore import Qt - -from saveConfig import saveConfig, ConfigKeys -from constants import GUIDE - -if TYPE_CHECKING: - from widgets.mainWindow import window - import PySide6.QtGui as qtg - -class Guide(qtw.QDockWidget): - def __init__(self, parent: window = None): - super().__init__(parent) - - self.app = qtw.QApplication.instance() +import PySide6.QtGui as qtg - self.mw: window = parent +from constants import GUIDE, ICON - self.config = saveConfig() +class Guide(qtw.QWidget): + def __init__(self): + super().__init__() - self.setWindowTitle('Guide') - self.setObjectName('guideDockWidget') - self.setAllowedAreas(Qt.DockWidgetArea.RightDockWidgetArea) - self.setFeatures(qtw.QDockWidget.DockWidgetFeature.DockWidgetClosable) + self.setWindowTitle('Genshin Stopwatch: Guide') + self.setWindowIcon(qtg.QIcon(ICON)) - # Central Frame - self.centralFrame: qtw.QFrame = qtw.QFrame(self) + self.setMinimumSize(1000, 1000) # Vertical Box Layout - verticalLayout: qtw.QVBoxLayout = qtw.QVBoxLayout() - self.centralFrame.setLayout(verticalLayout) + verticalLayout = qtw.QVBoxLayout() + self.setLayout(verticalLayout) - self.textFile: qtw.QTextBrowser = qtw.QTextBrowser(self.centralFrame) + self.textFile = qtw.QTextBrowser(self) self.textFile.setReadOnly(True) self.textFile.setOpenExternalLinks(True) @@ -46,7 +29,6 @@ def __init__(self, parent: window = None): self.textFile.setHtml(html.read()) except Exception as e: - print(e) self.textFile.setHtml( f''' @@ -56,40 +38,3 @@ def __init__(self, parent: window = None): ''') verticalLayout.addWidget(self.textFile) - - self.setWidget(self.centralFrame) - - def hideEvent(self, a0: qtg.QHideEvent) -> None: - # Check if the parent widget is hidden - - if self.mw.isHidden(): - a0.ignore() - - else: - - # Uncheck the guideButton - - self.mw.toolBar.guideButton.setChecked(False) - - if not self.mw.isMinimized(): - - self.config['QOL'][ConfigKeys.guide_open_on_startup] = 'False' - self.config.save() - - return super().hideEvent(a0) - - def showEvent(self, a0: qtg.QShowEvent) -> None: - - # Check the guideButton - - self.mw.toolBar.guideButton.setChecked(True) - - # Check if the parent widget is minimized - if not self.mw.isMinimized(): - - self.config['QOL'][ConfigKeys.guide_open_on_startup] = 'True' - - self.config.save() - # Call the base class's showEvent method - - return super().showEvent(a0) \ No newline at end of file diff --git a/src/python/widgets/mainWindow.py b/src/python/widgets/mainWindow.py index ca651c5..e57e8f5 100644 --- a/src/python/widgets/mainWindow.py +++ b/src/python/widgets/mainWindow.py @@ -47,14 +47,13 @@ def __init__(self, app: qtw.QApplication): self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.dockWidgetOptions) self.dockWidgetOptions.setVisible(self.config.getSettingsStartup()) - self.dockWidgetGuide = Guide(self) - self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.dockWidgetGuide) - self.dockWidgetGuide.setVisible(self.config.getGuideStartup()) - self.dockWidgetStaticTimer = staticTimers(self) self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.dockWidgetStaticTimer) self.dockWidgetStaticTimer.setVisible(self.config.getStatictimerStartup()) + # Activated by toolbar + self.guide = Guide() + # So the resize event doesn't spam the save window resolution function (see sizeApplyTimerTimeout() ) self.windowSizeApplyTimer = QTimer(self) self.windowSizeApplyTimer.setObjectName('windowSizeApplyTimer') diff --git a/src/python/widgets/options.py b/src/python/widgets/options.py index 2ead032..29d6e25 100644 --- a/src/python/widgets/options.py +++ b/src/python/widgets/options.py @@ -124,7 +124,7 @@ def __init__(self, parent: window = None): # Check for updates button self.updateButton = qtw.QPushButton('Check for updates', self) self.updateButton.setObjectName('checkUpdateButton') - self.updateButton.clicked.connect(lambda: self.checkUpdate(button=True)) + self.updateButton.clicked.connect(self.checkUpdate) verticalLayout.addWidget(self.updateButton, alignment=Qt.AlignmentFlag.AlignTop) # Support wolfmyths @@ -149,18 +149,17 @@ def checkUpdate(self) -> None: def updateFound(latestVersion: str, changelog: str) -> None: # Will create a dialog window if there's a version update unless specified not to - updateNotify = UpdateAlert(self, latestVersion) + updateNotify = UpdateAlert(latestVersion, changelog) updateNotify.exec() def upToDate() -> None: self.updateButton.setText('On latest version ^_^') QTimer.singleShot(3000, lambda: self.updateButton.setText('Check for updates')) - - update = checkUpdate() - # Checking if the function was started by the "check for update button" - update.upToDate.connect(upToDate) - update.updateDetected.connect(lambda x, y: updateFound(x, y)) + + self.check = checkUpdate() + self.check.upToDate.connect(upToDate) + self.check.updateDetected.connect(lambda x, y: updateFound(x, y)) def applySettings(self): diff --git a/src/python/widgets/toolbar.py b/src/python/widgets/toolbar.py index 8992d56..724bd80 100644 --- a/src/python/widgets/toolbar.py +++ b/src/python/widgets/toolbar.py @@ -7,6 +7,7 @@ from constants import TOOLBAR from saveConfig import saveConfig +from widgets.guide import Guide if TYPE_CHECKING: from widgets.mainWindow import window @@ -44,9 +45,7 @@ def __init__(self, parent: window = None): # Guide Button self.guideButton = qtg.QAction('Guide', self) - self.guideButton.setCheckable(True) - self.guideButton.setChecked(self.config.getGuideStartup()) - self.guideButton.triggered.connect(lambda: self.button_Clicked(self.mw.dockWidgetGuide, self.guideButton.isChecked())) + self.guideButton.triggered.connect(self.openGuide) self.addAction(self.guideButton) # Static Timers Button @@ -55,6 +54,9 @@ def __init__(self, parent: window = None): self.staticButton.setChecked(self.config.getStatictimerStartup()) self.staticButton.triggered.connect(lambda: self.button_Clicked(self.mw.dockWidgetStaticTimer, self.staticButton.isChecked())) self.addAction(self.staticButton) + + def openGuide(self): + self.mw.guide.show() def button_Clicked(self, dockObject: qtw.QDockWidget, buttonisChecked: bool): # Find the QDockWidget based on the dockObjectName diff --git a/src/python/widgets/updateAlert.py b/src/python/widgets/updateAlert.py index a846940..a1c1e6d 100644 --- a/src/python/widgets/updateAlert.py +++ b/src/python/widgets/updateAlert.py @@ -8,23 +8,27 @@ from constants import ICON class UpdateAlert(qtw.QDialog): - def __init__(self, latestVersion: str) -> None: + def __init__(self, latestVersion: str, changelog: str) -> None: super().__init__() self.config = saveConfig() - self.setWindowTitle('Genshin Stopwatch: New Version Update!') + self.setWindowTitle('Genshin Stopwatch: Update Detected!') self.setWindowIcon(qtg.QIcon(ICON)) layout = qtw.QVBoxLayout() - self.message = qtw.QLabel(self, text=f'The latest update {latestVersion} has been released! Would you like to download it?') + self.message = qtw.QLabel(self, text=f'The latest update {latestVersion} has been released!\nWould you like to visit the download page?') layout.addWidget(self.message) self.checkBox = qtw.QCheckBox(self, text='Do not automatically check for updates') self.checkBox.clicked.connect(self.checkBoxClicked) layout.addWidget(self.checkBox) + self.patchnotes = qtw.QTextBrowser(self) + self.patchnotes.setMarkdown(changelog) + layout.addWidget(self.patchnotes) + self.buttons = qtw.QDialogButtonBox.StandardButton.Ok | qtw.QDialogButtonBox.StandardButton.No self.buttonBox = qtw.QDialogButtonBox(self.buttons, self) self.buttonBox.accepted.connect(self.accept) From 89f27821fa49207e4defa5b8144d50260f30c528 Mon Sep 17 00:00:00 2001 From: Wolfmyths Date: Sun, 7 Jan 2024 23:20:35 -0500 Subject: [PATCH 3/4] Adjustments and a fix --- img/stopwatch_demo.PNG | Bin 109860 -> 57619 bytes src/python/saveConfig.py | 2 +- src/python/widgets/central.py | 2 ++ 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/img/stopwatch_demo.PNG b/img/stopwatch_demo.PNG index 9d7de6170296a966bdf09cc7c19495b8b339bf15..671991d55faa70c26540b1bca18c2a6c31b50fe1 100644 GIT binary patch literal 57619 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�&TbdQRVidvd?h-o}HlzPJrLh}J9Nj8i`mIgI%n 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 YEu+|(eQjM)Qogs{e{^5rp7U4#AMO|7XaE2J literal 109860 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

    V=!QWmdcJY!3Q@$o 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

    ^+GUFYu#M9A1l9L5OH=jwaIw8`PqhDr1So@kTy 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 diff --git a/src/python/saveConfig.py b/src/python/saveConfig.py index 1220bc9..c34c04d 100644 --- a/src/python/saveConfig.py +++ b/src/python/saveConfig.py @@ -110,7 +110,7 @@ def getServer(self) -> str: return self.get('QOL', ConfigKeys.gameserver, fallback='NA') def getWidth(self) -> int: - return self.getint('WINDOW SIZE', ConfigKeys.width, fallback=1920) + return self.getint('WINDOW SIZE', ConfigKeys.width, fallback=1300) def getHeight(self) -> int: return self.getint('WINDOW SIZE', ConfigKeys.height, fallback=1080) diff --git a/src/python/widgets/central.py b/src/python/widgets/central.py index d138de9..9ed8fdb 100644 --- a/src/python/widgets/central.py +++ b/src/python/widgets/central.py @@ -62,8 +62,10 @@ def showOrHideHelperLabel(self): if self.findChild(Stopwatch) is None: self.verticalLayout.addWidget(self.helperLabel) + self.helperLabel.show() else: self.verticalLayout.removeWidget(self.helperLabel) + self.helperLabel.hide() def addStopWatch(self, timeObject: str, duration: timedelta, name: str, startDuration: timedelta, color: str, notepadContents: str = '', save: bool = True) -> None: # Create a Stopwatch object From d79991b56479e5bf0b4c430de78b1eb0725c606e Mon Sep 17 00:00:00 2001 From: Wolfmyths Date: Wed, 10 Jan 2024 12:12:04 -0500 Subject: [PATCH 4/4] Final adjustments Fixed some styles Added a function when the user clicks on the notification Added lazy exception handling --- src/python/notify.py | 3 ++- src/python/style.py | 11 ++++++++++- src/python/widgets/sysTrayIcon.py | 12 ++++++++++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/python/notify.py b/src/python/notify.py index d2c05d5..00f24f9 100644 --- a/src/python/notify.py +++ b/src/python/notify.py @@ -33,7 +33,8 @@ def checkMissedNotify() -> None: for stopwatch in stopwatches.sections(): try: finishedDate = datetime.strptime(stopwatches.get(stopwatch, StopwatchDataKeys.time_finished), TimeFormats.Saved_Date) - except: + except Exception as e: + print(f'Something went wrong converting a stopwatch finished date into a datetime object: {e}') continue if finishedDate <= today: diff --git a/src/python/style.py b/src/python/style.py index 6c716e0..016dc02 100644 --- a/src/python/style.py +++ b/src/python/style.py @@ -83,6 +83,15 @@ def __init__(self) -> None: background-color: {0}; }} + QMenu {{ + background-color: {3}; + color: {0}; + }} + + QMenu::item:selected {{ + background-color: {4} + }} + QMainWindow{{ background-color: {0}; }} @@ -118,7 +127,7 @@ def __init__(self) -> None: background-color: {2} }} - QPushButton#applySettingsButton[unsavedChanges="true"]{{ + QPushButton#applySettingsButton[unsavedchanges="true"]{{ color: {4}; }} diff --git a/src/python/widgets/sysTrayIcon.py b/src/python/widgets/sysTrayIcon.py index b86d360..4ebb232 100644 --- a/src/python/widgets/sysTrayIcon.py +++ b/src/python/widgets/sysTrayIcon.py @@ -15,10 +15,18 @@ class sysTrayIcon(qtw.QSystemTrayIcon): def __init__(self, parent: qtw.QApplication = None, mw: window = None) -> None: super().__init__(parent=parent) + self.mw = mw + self.setIcon(qtg.QIcon(ICON)) self.setToolTip('Genshin Stopwatch') self.setVisible(True) - trayMenu = trayMen(parent, mw) + self.trayMenu = trayMen(parent, self.mw) + self.messageClicked.connect(self.notifyClicked) - self.setContextMenu(trayMenu) + self.setContextMenu(self.trayMenu) + + def notifyClicked(self) -> None: + self.mw.setVisible(True) + self.trayMenu.openClose_Pressed() + self.mw.activateWindow()