From e1e61a62b933e710c68c58554b8f93d575c38bb2 Mon Sep 17 00:00:00 2001
From: yakimka
Date: Sat, 14 Dec 2019 23:19:48 +0200
Subject: [PATCH] Settings (#3)
* Add settings window
* Add tests
---
CherryTomato/__init__.py | 4 +
CherryTomato/about_ui.py | 50 +++---
CherryTomato/about_ui.ui | 6 +-
CherryTomato/{about.py => about_window.py} | 5 +-
CherryTomato/main.py | 9 +-
CherryTomato/main_ui.py | 4 +
CherryTomato/main_ui.ui | 6 +
CherryTomato/main_window.py | 78 ++++++----
CherryTomato/settings.py | 142 +++++++++++++++++
CherryTomato/settings_ui.py | 105 +++++++++++++
CherryTomato/settings_ui.ui | 172 +++++++++++++++++++++
CherryTomato/settings_window.py | 50 ++++++
CherryTomato/tomato_timer.py | 38 ++---
tests/conftest.py | 42 +++--
tests/test_tomato_timer.py | 40 ++---
tests/test_tomato_timer_slots.py | 4 +-
tests/ui/test_main_window.py | 30 ++++
17 files changed, 672 insertions(+), 113 deletions(-)
rename CherryTomato/{about.py => about_window.py} (65%)
create mode 100644 CherryTomato/settings.py
create mode 100644 CherryTomato/settings_ui.py
create mode 100644 CherryTomato/settings_ui.ui
create mode 100644 CherryTomato/settings_window.py
create mode 100644 tests/ui/test_main_window.py
diff --git a/CherryTomato/__init__.py b/CherryTomato/__init__.py
index acdc0a1..a8a8035 100644
--- a/CherryTomato/__init__.py
+++ b/CherryTomato/__init__.py
@@ -3,3 +3,7 @@
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
MEDIA_DIR = os.path.join(BASE_DIR, 'media')
APP_ICON = os.path.join(MEDIA_DIR, 'icon.png')
+
+VERSION = '0.2.0'
+ORGANIZATION_NAME = 'CherryTomato'
+APPLICATION_NAME = 'CherryTomato'
diff --git a/CherryTomato/about_ui.py b/CherryTomato/about_ui.py
index 341b77d..412bd98 100644
--- a/CherryTomato/about_ui.py
+++ b/CherryTomato/about_ui.py
@@ -16,43 +16,43 @@ def setupUi(self, About):
About.resize(402, 222)
self.verticalLayout = QtWidgets.QVBoxLayout(About)
self.verticalLayout.setObjectName("verticalLayout")
- self.label_title = QtWidgets.QLabel(About)
+ self.labelTitle = QtWidgets.QLabel(About)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.label_title.sizePolicy().hasHeightForWidth())
- self.label_title.setSizePolicy(sizePolicy)
+ sizePolicy.setHeightForWidth(self.labelTitle.sizePolicy().hasHeightForWidth())
+ self.labelTitle.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setPointSize(13)
font.setBold(True)
font.setWeight(75)
- self.label_title.setFont(font)
- self.label_title.setText("CherryTomato")
- self.label_title.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop)
- self.label_title.setObjectName("label_title")
- self.verticalLayout.addWidget(self.label_title)
- self.label_text = QtWidgets.QLabel(About)
+ self.labelTitle.setFont(font)
+ self.labelTitle.setText("CherryTomato")
+ self.labelTitle.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop)
+ self.labelTitle.setObjectName("labelTitle")
+ self.verticalLayout.addWidget(self.labelTitle)
+ self.labelText = QtWidgets.QLabel(About)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(1)
- sizePolicy.setHeightForWidth(self.label_text.sizePolicy().hasHeightForWidth())
- self.label_text.setSizePolicy(sizePolicy)
- self.label_text.setTextFormat(QtCore.Qt.RichText)
- self.label_text.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
- self.label_text.setWordWrap(True)
- self.label_text.setOpenExternalLinks(True)
- self.label_text.setObjectName("label_text")
- self.verticalLayout.addWidget(self.label_text)
- self.label_copyright = QtWidgets.QLabel(About)
+ sizePolicy.setHeightForWidth(self.labelText.sizePolicy().hasHeightForWidth())
+ self.labelText.setSizePolicy(sizePolicy)
+ self.labelText.setTextFormat(QtCore.Qt.RichText)
+ self.labelText.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
+ self.labelText.setWordWrap(True)
+ self.labelText.setOpenExternalLinks(True)
+ self.labelText.setObjectName("labelText")
+ self.verticalLayout.addWidget(self.labelText)
+ self.labelCopyright = QtWidgets.QLabel(About)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.label_copyright.sizePolicy().hasHeightForWidth())
- self.label_copyright.setSizePolicy(sizePolicy)
- self.label_copyright.setText("Copyright © 2019 yakimka")
- self.label_copyright.setAlignment(QtCore.Qt.AlignBottom|QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft)
- self.label_copyright.setObjectName("label_copyright")
- self.verticalLayout.addWidget(self.label_copyright)
+ sizePolicy.setHeightForWidth(self.labelCopyright.sizePolicy().hasHeightForWidth())
+ self.labelCopyright.setSizePolicy(sizePolicy)
+ self.labelCopyright.setText("Copyright © 2019 yakimka")
+ self.labelCopyright.setAlignment(QtCore.Qt.AlignBottom|QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft)
+ self.labelCopyright.setObjectName("labelCopyright")
+ self.verticalLayout.addWidget(self.labelCopyright)
self.retranslateUi(About)
QtCore.QMetaObject.connectSlotsByName(About)
@@ -60,4 +60,4 @@ def setupUi(self, About):
def retranslateUi(self, About):
_translate = QtCore.QCoreApplication.translate
About.setWindowTitle(_translate("About", "About CherryTomato"))
- self.label_text.setText(_translate("About", "
CherryTomato is a simple tomato timer app.
It\'s written in Python 3 using PyQt5.
Project page: https://github.com/yakimka/CherryTomato
"))
+ self.labelText.setText(_translate("About", "CherryTomato is a simple tomato timer app.
It\'s written in Python 3 using PyQt5.
Project page: https://github.com/yakimka/CherryTomato
"))
diff --git a/CherryTomato/about_ui.ui b/CherryTomato/about_ui.ui
index 6c569cd..699a2af 100644
--- a/CherryTomato/about_ui.ui
+++ b/CherryTomato/about_ui.ui
@@ -15,7 +15,7 @@
-
-
+
0
@@ -38,7 +38,7 @@
-
-
+
0
@@ -63,7 +63,7 @@
-
-
+
0
diff --git a/CherryTomato/about.py b/CherryTomato/about_window.py
similarity index 65%
rename from CherryTomato/about.py
rename to CherryTomato/about_window.py
index 46886ff..15b08b6 100644
--- a/CherryTomato/about.py
+++ b/CherryTomato/about_window.py
@@ -1,7 +1,7 @@
from PyQt5 import QtWidgets
from PyQt5.QtGui import QIcon
-from CherryTomato import APP_ICON
+from CherryTomato import APP_ICON, VERSION
from .about_ui import Ui_About
@@ -11,4 +11,7 @@ def __init__(self):
self.setupUi(self)
+ title = f'{self.labelTitle.text()} {VERSION}'
+ self.labelTitle.setText(title)
+
self.setWindowIcon(QIcon(APP_ICON))
diff --git a/CherryTomato/main.py b/CherryTomato/main.py
index a78783e..4b79ead 100755
--- a/CherryTomato/main.py
+++ b/CherryTomato/main.py
@@ -3,12 +3,17 @@
import sys
from PyQt5 import Qt
+from PyQt5.QtCore import QCoreApplication
-from CherryTomato.main_window import TomatoTimerWindow
+from CherryTomato import ORGANIZATION_NAME, APPLICATION_NAME
+from CherryTomato.main_window import CherryTomatoMainWindow
+
+QCoreApplication.setApplicationName(ORGANIZATION_NAME)
+QCoreApplication.setApplicationName(APPLICATION_NAME)
app = Qt.QApplication(sys.argv)
-watch = TomatoTimerWindow()
+watch = CherryTomatoMainWindow()
watch.show()
app.exec_()
diff --git a/CherryTomato/main_ui.py b/CherryTomato/main_ui.py
index 099181a..60a75b3 100644
--- a/CherryTomato/main_ui.py
+++ b/CherryTomato/main_ui.py
@@ -53,6 +53,9 @@ def setupUi(self, MainWindow):
MainWindow.setMenuBar(self.menuBar)
self.actionAbout = QtWidgets.QAction(MainWindow)
self.actionAbout.setObjectName("actionAbout")
+ self.actionSettings = QtWidgets.QAction(MainWindow)
+ self.actionSettings.setObjectName("actionSettings")
+ self.menuFile.addAction(self.actionSettings)
self.menuFile.addAction(self.actionAbout)
self.menuBar.addAction(self.menuFile.menuAction())
@@ -64,4 +67,5 @@ def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "CherryTomato"))
self.menuFile.setTitle(_translate("MainWindow", "File"))
self.actionAbout.setText(_translate("MainWindow", "About"))
+ self.actionSettings.setText(_translate("MainWindow", "Settings"))
from CherryTomato.widget import QRoundProgressBar, QRoundPushbutton
diff --git a/CherryTomato/main_ui.ui b/CherryTomato/main_ui.ui
index f1ca5bb..b677000 100644
--- a/CherryTomato/main_ui.ui
+++ b/CherryTomato/main_ui.ui
@@ -99,6 +99,7 @@ border-radius: 20px;
File
+
@@ -108,6 +109,11 @@ border-radius: 20px;
About
+
+
+ Settings
+
+
diff --git a/CherryTomato/main_window.py b/CherryTomato/main_window.py
index 78a4667..1134aec 100644
--- a/CherryTomato/main_window.py
+++ b/CherryTomato/main_window.py
@@ -1,19 +1,16 @@
import os
from PyQt5 import Qt, QtCore
-from PyQt5.QtCore import QSettings, QSize, QPoint
from PyQt5.QtGui import QBrush, QColor, QPalette, QIcon
from PyQt5.QtMultimedia import QSound
-from CherryTomato import about, APP_ICON, MEDIA_DIR
+from CherryTomato import about_window, APP_ICON, MEDIA_DIR, settings_window
from CherryTomato.main_ui import Ui_MainWindow
+from CherryTomato.settings import CherryTomatoSettings
from CherryTomato.tomato_timer import TomatoTimer
-class TomatoTimerWindow(Qt.QMainWindow, Ui_MainWindow):
- APP_TITLE = 'CherryTomato'
- NOTIFICATION_ON = True
-
+class CherryTomatoMainWindow(Qt.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super().__init__(parent)
@@ -21,10 +18,8 @@ def __init__(self, parent=None):
self.setWindowIcon(QIcon(APP_ICON))
- self.settings = QSettings('yakimka', self.APP_TITLE)
- # Initial window size/pos last saved. Use default values for first time
- self.resize(self.settings.value('size', QSize(400, 520)))
- self.move(self.settings.value('pos', QPoint(50, 50)))
+ self.settings = CherryTomatoSettings()
+ self.setWindowSizeAndPosition()
self.tomatoTimer = TomatoTimer()
@@ -34,8 +29,27 @@ def __init__(self, parent=None):
self.display()
- self.aboutWindow = about.About()
+ self.aboutWindow = about_window.About()
self.actionAbout.triggered.connect(self.showAboutWindow)
+ self.settingsWindow = settings_window.Settings()
+ self.actionSettings.triggered.connect(self.showSettingsWindow)
+ self.settingsWindow.closing.connect(self.update)
+
+ def update(self):
+ self.tomatoTimer.updateState()
+
+ def setWindowSizeAndPosition(self):
+ # Initial window size/pos last saved. Use default values for first time
+ while True:
+ try:
+ self.resize(self.settings.size)
+ self.move(self.settings.position)
+ except TypeError:
+ del self.settings.size
+ del self.settings.position
+ continue
+ else:
+ break
def showAboutWindow(self):
centerX, centerY = self.getCenterPoint()
@@ -44,6 +58,13 @@ def showAboutWindow(self):
self.aboutWindow.move(x, y)
self.aboutWindow.show()
+ def showSettingsWindow(self):
+ centerX, centerY = self.getCenterPoint()
+ x = int(centerX - self.settingsWindow.width() / 2)
+ y = int(centerY - self.settingsWindow.height() / 2)
+ self.settingsWindow.move(x, y)
+ self.settingsWindow.show()
+
def getCenterPoint(self):
x = int(self.x() + self.width() / 2)
y = int(self.y() + self.height() / 2)
@@ -52,8 +73,8 @@ def getCenterPoint(self):
def closeEvent(self, e):
# Write window size and position to config file
- self.settings.setValue('size', self.size())
- self.settings.setValue('pos', self.pos())
+ self.settings.size = self.size()
+ self.settings.position = self.pos()
e.accept()
@@ -74,22 +95,16 @@ def display(self):
@Qt.pyqtSlot()
def do_start(self):
# trigger through proxy
- self.tomatoTimer.start()
-
- @Qt.pyqtSlot()
- def do_stop(self):
- # trigger through proxy
- self.tomatoTimer.abort()
+ if not self.tomatoTimer.running:
+ self.tomatoTimer.start()
+ else:
+ self.tomatoTimer.abort()
def changeButtonState(self):
if not self.tomatoTimer.running:
self.button.setImage('play.png')
- self.button.clicked.disconnect()
- self.button.clicked.connect(self.do_start)
else:
self.button.setImage('stop.png')
- self.button.clicked.disconnect()
- self.button.clicked.connect(self.do_stop)
def setRed(self):
# https://coolors.co/eff0f1-d11f2a-8da1b9-95adb6-fad0cf
@@ -130,12 +145,13 @@ def keyPressEvent(self, event):
@Qt.pyqtSlot()
def setFocusOnWindowAndPlayNotification(self):
- self.raise_()
- self.show()
- self.activateWindow()
- if self.windowState() == QtCore.Qt.WindowMinimized:
- # Window is minimised. Restore it.
- self.setWindowState(QtCore.Qt.WindowNoState)
-
- if self.NOTIFICATION_ON:
+ if self.settings.interrupt:
+ self.raise_()
+ self.show()
+ self.activateWindow()
+ if self.windowState() == QtCore.Qt.WindowMinimized:
+ # Window is minimised. Restore it.
+ self.setWindowState(QtCore.Qt.WindowNoState)
+
+ if self.settings.notification:
QSound.play(os.path.join(MEDIA_DIR, 'sound.wav'))
diff --git a/CherryTomato/settings.py b/CherryTomato/settings.py
new file mode 100644
index 0000000..d05ea44
--- /dev/null
+++ b/CherryTomato/settings.py
@@ -0,0 +1,142 @@
+from collections import UserString
+
+from PyQt5.QtCore import QSettings, QSize, QPoint
+
+
+class State(UserString):
+ def __init__(self, seq: object, time: int):
+ self.time = time
+ super().__init__(seq)
+
+
+class CherryTomatoSettings:
+ SIZE = 'size'
+ SIZE_DEFAULT = QSize(400, 520)
+ POSITION = 'position'
+ POSITION_DEFAULT = QPoint(50, 50)
+
+ STATE_TOMATO = 'state_tomato'
+ TOMATO_DEFAULT = 25 * 60
+ STATE_BREAK = 'state_break'
+ BREAK_DEFAULT = 5 * 60
+ STATE_LONG_BREAK = 'state_long_break'
+ LONG_BREAK_DEFAULT = 15 * 60
+
+ NOTIFICATION = 'notification'
+ NOTIFICATION_DEFAULT = True
+ INTERRUPT = 'interrupt'
+ INTERRUPT_DEFAULT = True
+
+ REPEAT = 'repeat'
+ REPEAT_DEFAULT = 4
+ AUTO_STOP_TOMATO = 'auto_stop_tomato'
+ AUTO_STOP_TOMATO_DEFAULT = True
+ AUTO_STOP_BREAK = 'auto_stop_break'
+ AUTO_STOP_BREAK_DEFAULT = False
+ SWITCH_TO_TOMATO_ON_ABORT = 'switch_to_tomato_on_abort'
+ SWITCH_TO_TOMATO_ON_ABORT_DEFAULT = True
+
+ def __init__(self):
+ self.settings = QSettings()
+
+ @property
+ def size(self):
+ return self.settings.value(self.SIZE, self.SIZE_DEFAULT)
+
+ @size.setter
+ def size(self, val):
+ self.settings.setValue(self.SIZE, val)
+
+ @size.deleter
+ def size(self):
+ self.settings.remove(self.SIZE)
+
+ @property
+ def position(self):
+ return self.settings.value(self.POSITION, self.POSITION_DEFAULT)
+
+ @position.setter
+ def position(self, val):
+ self.settings.setValue(self.POSITION, val)
+
+ @position.deleter
+ def position(self):
+ self.settings.remove(self.POSITION)
+
+ @property
+ def notification(self):
+ return self.settings.value(self.NOTIFICATION, self.NOTIFICATION_DEFAULT, type=bool)
+
+ @notification.setter
+ def notification(self, val):
+ self.settings.setValue(self.NOTIFICATION, val)
+
+ @property
+ def interrupt(self):
+ return self.settings.value(self.INTERRUPT, self.INTERRUPT_DEFAULT, type=bool)
+
+ @interrupt.setter
+ def interrupt(self, val):
+ self.settings.setValue(self.INTERRUPT, val)
+
+ @property
+ def repeat(self):
+ return self.settings.value(self.REPEAT, self.REPEAT_DEFAULT, type=int)
+
+ @repeat.setter
+ def repeat(self, val):
+ self.settings.setValue(self.REPEAT, val)
+
+ @property
+ def autoStopTomato(self):
+ return self.settings.value(self.AUTO_STOP_TOMATO, self.AUTO_STOP_TOMATO_DEFAULT, type=bool)
+
+ @autoStopTomato.setter
+ def autoStopTomato(self, val):
+ self.settings.setValue(self.AUTO_STOP_TOMATO, val)
+
+ @property
+ def autoStopBreak(self):
+ return self.settings.value(self.AUTO_STOP_BREAK, self.AUTO_STOP_BREAK_DEFAULT, type=bool)
+
+ @autoStopBreak.setter
+ def autoStopBreak(self, val):
+ self.settings.setValue(self.AUTO_STOP_BREAK, val)
+
+ @property
+ def switchToTomatoOnAbort(self):
+ return self.settings.value(self.SWITCH_TO_TOMATO_ON_ABORT, self.SWITCH_TO_TOMATO_ON_ABORT_DEFAULT, type=bool)
+
+ @switchToTomatoOnAbort.setter
+ def switchToTomatoOnAbort(self, val):
+ self.settings.setValue(self.SWITCH_TO_TOMATO_ON_ABORT, val)
+
+ @property
+ def stateTomato(self):
+ return self.settings.value(self.STATE_TOMATO, State('tomato', self.TOMATO_DEFAULT))
+
+ @stateTomato.setter
+ def stateTomato(self, min):
+ state = self.stateTomato
+ state.time = min
+ self.settings.setValue(self.STATE_TOMATO, state)
+
+ @property
+ def stateBreak(self):
+ return self.settings.value(self.STATE_BREAK, State('break', self.BREAK_DEFAULT))
+
+ @stateBreak.setter
+ def stateBreak(self, min):
+ state = self.stateBreak
+ state.time = min
+ self.settings.setValue(self.STATE_BREAK, state)
+
+ @property
+ def stateLongBreak(self):
+ return self.settings.value(self.STATE_LONG_BREAK, State('long_break', self.LONG_BREAK_DEFAULT))
+
+ @stateLongBreak.setter
+ def stateLongBreak(self, min):
+ state = self.stateLongBreak
+ state.time = min
+ self.settings.setValue(self.STATE_LONG_BREAK, state)
diff --git a/CherryTomato/settings_ui.py b/CherryTomato/settings_ui.py
new file mode 100644
index 0000000..5c45155
--- /dev/null
+++ b/CherryTomato/settings_ui.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'settings_ui.ui'
+#
+# Created by: PyQt5 UI code generator 5.13.1
+#
+# WARNING! All changes made in this file will be lost!
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Settings(object):
+ def setupUi(self, Settings):
+ Settings.setObjectName("Settings")
+ Settings.resize(419, 575)
+ Settings.setLayoutDirection(QtCore.Qt.LeftToRight)
+ self.verticalLayout = QtWidgets.QVBoxLayout(Settings)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.minutes = QtWidgets.QGroupBox(Settings)
+ self.minutes.setTitle("")
+ self.minutes.setObjectName("minutes")
+ self.formLayout = QtWidgets.QFormLayout(self.minutes)
+ self.formLayout.setObjectName("formLayout")
+ self.stateTomatoLabel = QtWidgets.QLabel(self.minutes)
+ self.stateTomatoLabel.setObjectName("stateTomatoLabel")
+ self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.stateTomatoLabel)
+ self.stateTomato = QtWidgets.QSpinBox(self.minutes)
+ self.stateTomato.setMinimum(1)
+ self.stateTomato.setObjectName("stateTomato")
+ self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.stateTomato)
+ self.stateBreakLabel = QtWidgets.QLabel(self.minutes)
+ self.stateBreakLabel.setObjectName("stateBreakLabel")
+ self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.stateBreakLabel)
+ self.stateBreak = QtWidgets.QSpinBox(self.minutes)
+ self.stateBreak.setMinimum(1)
+ self.stateBreak.setObjectName("stateBreak")
+ self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.stateBreak)
+ self.stateLongBreakLabel = QtWidgets.QLabel(self.minutes)
+ self.stateLongBreakLabel.setObjectName("stateLongBreakLabel")
+ self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.stateLongBreakLabel)
+ self.stateLongBreak = QtWidgets.QSpinBox(self.minutes)
+ self.stateLongBreak.setMinimum(1)
+ self.stateLongBreak.setObjectName("stateLongBreak")
+ self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.stateLongBreak)
+ self.repeatLabel = QtWidgets.QLabel(self.minutes)
+ self.repeatLabel.setObjectName("repeatLabel")
+ self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.repeatLabel)
+ self.repeat = QtWidgets.QSpinBox(self.minutes)
+ self.repeat.setMinimum(1)
+ self.repeat.setObjectName("repeat")
+ self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.repeat)
+ self.verticalLayout.addWidget(self.minutes)
+ self.checkboxes = QtWidgets.QGroupBox(Settings)
+ self.checkboxes.setTitle("")
+ self.checkboxes.setObjectName("checkboxes")
+ self.formLayout_2 = QtWidgets.QFormLayout(self.checkboxes)
+ self.formLayout_2.setObjectName("formLayout_2")
+ self.notification = QtWidgets.QCheckBox(self.checkboxes)
+ self.notification.setLayoutDirection(QtCore.Qt.LeftToRight)
+ self.notification.setAutoRepeat(False)
+ self.notification.setAutoExclusive(False)
+ self.notification.setTristate(False)
+ self.notification.setObjectName("notification")
+ self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.notification)
+ self.interrupt = QtWidgets.QCheckBox(self.checkboxes)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.interrupt.sizePolicy().hasHeightForWidth())
+ self.interrupt.setSizePolicy(sizePolicy)
+ self.interrupt.setLayoutDirection(QtCore.Qt.LeftToRight)
+ self.interrupt.setAutoFillBackground(False)
+ self.interrupt.setObjectName("interrupt")
+ self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.interrupt)
+ self.autoStopTomato = QtWidgets.QCheckBox(self.checkboxes)
+ self.autoStopTomato.setLayoutDirection(QtCore.Qt.LeftToRight)
+ self.autoStopTomato.setObjectName("autoStopTomato")
+ self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.autoStopTomato)
+ self.autoStopBreak = QtWidgets.QCheckBox(self.checkboxes)
+ self.autoStopBreak.setLayoutDirection(QtCore.Qt.LeftToRight)
+ self.autoStopBreak.setObjectName("autoStopBreak")
+ self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.autoStopBreak)
+ self.switchToTomatoOnAbort = QtWidgets.QCheckBox(self.checkboxes)
+ self.switchToTomatoOnAbort.setMinimumSize(QtCore.QSize(0, 0))
+ self.switchToTomatoOnAbort.setLayoutDirection(QtCore.Qt.LeftToRight)
+ self.switchToTomatoOnAbort.setObjectName("switchToTomatoOnAbort")
+ self.formLayout_2.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.switchToTomatoOnAbort)
+ self.verticalLayout.addWidget(self.checkboxes)
+
+ self.retranslateUi(Settings)
+ QtCore.QMetaObject.connectSlotsByName(Settings)
+
+ def retranslateUi(self, Settings):
+ _translate = QtCore.QCoreApplication.translate
+ Settings.setWindowTitle(_translate("Settings", "Settings"))
+ self.stateTomatoLabel.setText(_translate("Settings", "Tomato time (min)"))
+ self.stateBreakLabel.setText(_translate("Settings", "Break time (min)"))
+ self.stateLongBreakLabel.setText(_translate("Settings", "Long break time (min)"))
+ self.repeatLabel.setText(_translate("Settings", "Long break after (tomatoes)"))
+ self.notification.setText(_translate("Settings", "Sound notification"))
+ self.interrupt.setText(_translate("Settings", "Interrupt me"))
+ self.autoStopTomato.setText(_translate("Settings", "Auto stop tomato after break"))
+ self.autoStopBreak.setText(_translate("Settings", "Auto stop break after tomato"))
+ self.switchToTomatoOnAbort.setText(_translate("Settings", "Skip break if you press stop button"))
diff --git a/CherryTomato/settings_ui.ui b/CherryTomato/settings_ui.ui
new file mode 100644
index 0000000..8bdba5f
--- /dev/null
+++ b/CherryTomato/settings_ui.ui
@@ -0,0 +1,172 @@
+
+
+ Settings
+
+
+
+ 0
+ 0
+ 419
+ 575
+
+
+
+ Settings
+
+
+ Qt::LeftToRight
+
+
+
-
+
+
+
+
+
+
-
+
+
+ Tomato time (min)
+
+
+
+ -
+
+
+ 1
+
+
+
+ -
+
+
+ Break time (min)
+
+
+
+ -
+
+
+ 1
+
+
+
+ -
+
+
+ Long break time (min)
+
+
+
+ -
+
+
+ 1
+
+
+
+ -
+
+
+ Long break after (tomatoes)
+
+
+
+ -
+
+
+ 1
+
+
+
+
+
+
+ -
+
+
+
+
+
+
-
+
+
+ Qt::LeftToRight
+
+
+ Sound notification
+
+
+ false
+
+
+ false
+
+
+ false
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Qt::LeftToRight
+
+
+ false
+
+
+ Interrupt me
+
+
+
+ -
+
+
+ Qt::LeftToRight
+
+
+ Auto stop tomato after break
+
+
+
+ -
+
+
+ Qt::LeftToRight
+
+
+ Auto stop break after tomato
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Qt::LeftToRight
+
+
+ Skip break if you press stop button
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CherryTomato/settings_window.py b/CherryTomato/settings_window.py
new file mode 100644
index 0000000..d98859f
--- /dev/null
+++ b/CherryTomato/settings_window.py
@@ -0,0 +1,50 @@
+from PyQt5 import QtWidgets
+from PyQt5.QtCore import pyqtSignal
+from PyQt5.QtGui import QIcon
+
+from CherryTomato import APP_ICON
+from CherryTomato.settings import CherryTomatoSettings
+from CherryTomato.settings_ui import Ui_Settings
+
+
+class Settings(QtWidgets.QWidget, Ui_Settings):
+ closing = pyqtSignal()
+
+ def __init__(self):
+ super().__init__()
+
+ self.setupUi(self)
+
+ self.setWindowIcon(QIcon(APP_ICON))
+ self.settings = CherryTomatoSettings()
+
+ state_tomato = self.settings.stateTomato
+ self.stateTomato.setValue(int(state_tomato.time / 60))
+ state_break = self.settings.stateBreak
+ self.stateBreak.setValue(int(state_break.time / 60))
+ state_long_break = self.settings.stateLongBreak
+ self.stateLongBreak.setValue(int(state_long_break.time / 60))
+ self.repeat.setValue(self.settings.repeat)
+
+ self.notification.setChecked(self.settings.notification)
+ self.interrupt.setChecked(self.settings.interrupt)
+ self.autoStopTomato.setChecked(self.settings.autoStopTomato)
+ self.autoStopBreak.setChecked(self.settings.autoStopBreak)
+ self.switchToTomatoOnAbort.setChecked(self.settings.switchToTomatoOnAbort)
+
+ def closeEvent(self, e):
+ e.accept()
+ self.updateSettings()
+ self.closing.emit()
+
+ def updateSettings(self):
+ self.settings.stateTomato = self.stateTomato.value() * 60
+ self.settings.stateBreak = self.stateBreak.value() * 60
+ self.settings.stateLongBreak = self.stateLongBreak.value() * 60
+ self.settings.repeat = self.repeat.value()
+
+ self.settings.notification = self.notification.isChecked()
+ self.settings.interrupt = self.interrupt.isChecked()
+ self.settings.autoStopTomato = self.autoStopTomato.isChecked()
+ self.settings.autoStopBreak = self.autoStopBreak.isChecked()
+ self.settings.switchToTomatoOnAbort = self.switchToTomatoOnAbort.isChecked()
diff --git a/CherryTomato/tomato_timer.py b/CherryTomato/tomato_timer.py
index 5f55bf1..f4f168b 100644
--- a/CherryTomato/tomato_timer.py
+++ b/CherryTomato/tomato_timer.py
@@ -1,25 +1,13 @@
-from collections import UserString
-
from PyQt5 import Qt
from PyQt5.QtCore import QObject, pyqtSignal
+from CherryTomato.settings import CherryTomatoSettings
-class State(UserString):
- def __init__(self, seq: object, time: int):
- self.time = time
- super().__init__(seq)
+settings = CherryTomatoSettings()
class TomatoTimer(QObject):
- TOMATOS_BEFORE_LONG_BREAK = 4
- AUTO_STOP_TOMATO = True
- AUTO_STOP_BREAK = False
- SWITCH_TO_TOMATO_ON_ABORT = True
-
TICK_TIME = 64 # ms
- STATE_TOMATO = State('tomato', 25 * 60)
- STATE_BREAK = State('break', 5 * 60)
- STATE_LONG_BREAK = State('long_break', 20 * 60)
stateChanged = pyqtSignal()
finished = pyqtSignal()
@@ -27,6 +15,8 @@ class TomatoTimer(QObject):
def __init__(self):
super().__init__()
+ self.settings = settings
+
self.tomatoes = 0
self.state = None
@@ -54,7 +44,7 @@ def start(self):
def abort(self):
self.stop()
- if not self.isTomato() and self.SWITCH_TO_TOMATO_ON_ABORT:
+ if not self.isTomato() and self.settings.switchToTomatoOnAbort:
self.changeState()
else:
self.reset()
@@ -76,16 +66,15 @@ def tick(self):
if self._isNeedAutoStop():
self.stop()
self.notifyTimerIsOver()
-
self.notifyAboutAnyChange()
def isTomato(self):
- return self.state == self.STATE_TOMATO
+ return self.state == self.settings.stateTomato
def _isNeedAutoStop(self):
- if self.isTomato() and self.AUTO_STOP_TOMATO:
+ if self.isTomato() and self.settings.autoStopTomato:
return True
- elif not self.isTomato() and self.AUTO_STOP_BREAK:
+ elif not self.isTomato() and self.settings.autoStopBreak:
return True
return False
@@ -101,13 +90,18 @@ def changeState(self):
def _statesGen(self):
while True:
- yield self.STATE_TOMATO
- yield self.STATE_LONG_BREAK if self._isTimeForLongBreak() else self.STATE_BREAK
+ yield self.settings.stateTomato
+ yield self.settings.stateLongBreak if self._isTimeForLongBreak() else self.settings.stateBreak
def _isTimeForLongBreak(self):
if self.tomatoes == 0:
return False
- return self.isTomato() and self.tomatoes % self.TOMATOS_BEFORE_LONG_BREAK == 0
+ return self.isTomato() and self.tomatoes % self.settings.repeat == 0
+
+ def updateState(self):
+ if not self.running:
+ self.reset()
+ self.notifyAboutAnyChange()
def reset(self):
self.seconds = self.state.time
diff --git a/tests/conftest.py b/tests/conftest.py
index b515a66..87fc634 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,20 +1,44 @@
+from unittest.mock import MagicMock
+
import pytest
+from CherryTomato.settings import CherryTomatoSettings
from CherryTomato.tomato_timer import TomatoTimer
+class MockQSettings:
+ def value(self, key, default, type=None):
+ return default
+
+ setValue = MagicMock()
+ remove = MagicMock()
+
+
@pytest.fixture
-def tomato(monkeypatch):
- monkeypatch.setattr(TomatoTimer, 'TICK_TIME', 64)
- monkeypatch.setattr(TomatoTimer.STATE_TOMATO, 'time', 100)
- monkeypatch.setattr(TomatoTimer.STATE_BREAK, 'time', 25)
- monkeypatch.setattr(TomatoTimer.STATE_LONG_BREAK, 'time', 50)
+def mock_qsettings(monkeypatch, mocker):
+ qsettings = mocker.patch('CherryTomato.settings.QSettings', MockQSettings)
+
+ monkeypatch.setattr(CherryTomatoSettings, 'TOMATO_DEFAULT', 100)
+ monkeypatch.setattr(CherryTomatoSettings, 'BREAK_DEFAULT', 25)
+ monkeypatch.setattr(CherryTomatoSettings, 'LONG_BREAK_DEFAULT', 50)
+ monkeypatch.setattr(CherryTomatoSettings, 'REPEAT_DEFAULT', 4)
+ monkeypatch.setattr(CherryTomatoSettings, 'AUTO_STOP_TOMATO_DEFAULT', False)
+ monkeypatch.setattr(CherryTomatoSettings, 'AUTO_STOP_BREAK_DEFAULT', False)
+ monkeypatch.setattr(CherryTomatoSettings, 'SWITCH_TO_TOMATO_ON_ABORT_DEFAULT', True)
- monkeypatch.setattr(TomatoTimer, 'TOMATOS_BEFORE_LONG_BREAK', 4)
- monkeypatch.setattr(TomatoTimer, 'AUTO_STOP_TOMATO', False)
- monkeypatch.setattr(TomatoTimer, 'AUTO_STOP_BREAK', False)
- monkeypatch.setattr(TomatoTimer, 'SWITCH_TO_TOMATO_ON_ABORT', True)
+ return qsettings
+@pytest.fixture
+def settings(mock_qsettings):
+ settings = CherryTomatoSettings()
+
+ return settings
+
+
+@pytest.fixture
+def tomato(settings, monkeypatch, mocker):
+ monkeypatch.setattr(TomatoTimer, 'TICK_TIME', 64)
+ mocker.patch('CherryTomato.tomato_timer.settings', settings)
tomato = TomatoTimer()
return tomato
diff --git a/tests/test_tomato_timer.py b/tests/test_tomato_timer.py
index 94a0de0..04f94de 100644
--- a/tests/test_tomato_timer.py
+++ b/tests/test_tomato_timer.py
@@ -2,7 +2,11 @@
import pytest
-from CherryTomato.tomato_timer import TomatoTimer
+from CherryTomato.settings import State
+
+STATE_TOMATO = State('tomato', 100)
+STATE_BREAK = State('break', 25)
+STATE_LONG_BREAK = State('long_break', 50)
def test_instance(tomato):
@@ -81,11 +85,11 @@ def test_abort(mock_for_abort, tomato):
(False, False)
])
def test_abort_with_switch_to_tomato_flag(mock_for_abort, tomato_on_break, flag_value, changed):
- tomato_on_break.SWITCH_TO_TOMATO_ON_ABORT = flag_value
+ tomato_on_break.settings.SWITCH_TO_TOMATO_ON_ABORT_DEFAULT = flag_value
tomato_on_break.abort()
- assert (tomato_on_break.STATE_BREAK != tomato_on_break.state) is changed
+ assert ('break' != tomato_on_break.state) is changed
def test_create_timer(mock_qt_timer, tomato):
@@ -93,17 +97,17 @@ def test_create_timer(mock_qt_timer, tomato):
@pytest.mark.parametrize('state,expected', [
- (TomatoTimer.STATE_TOMATO, True),
- (TomatoTimer.STATE_BREAK, False),
- (TomatoTimer.STATE_LONG_BREAK, False),
+ (STATE_TOMATO, True),
+ (STATE_BREAK, False),
+ (STATE_LONG_BREAK, False),
])
def test_is_tomato(tomato, state, expected):
tomato.state = state
assert tomato.isTomato() is expected
-def test_change_state(tomato):
- for i, state in enumerate(cycle([tomato.STATE_TOMATO, tomato.STATE_BREAK])):
+def test_change_state(tomato, settings):
+ for i, state in enumerate(cycle([settings.stateTomato, settings.stateBreak])):
assert tomato.state == state
assert tomato.seconds == state.time
tomato.changeState()
@@ -112,18 +116,18 @@ def test_change_state(tomato):
@pytest.mark.parametrize('tomatoes,const_value,expected', [
- (0, 4, TomatoTimer.STATE_BREAK),
- (3, 4, TomatoTimer.STATE_BREAK),
- (4, 4, TomatoTimer.STATE_LONG_BREAK),
- (5, 4, TomatoTimer.STATE_BREAK),
- (12, 1, TomatoTimer.STATE_LONG_BREAK),
- (1, 10, TomatoTimer.STATE_BREAK),
- (20, 10, TomatoTimer.STATE_LONG_BREAK),
- (100, 10, TomatoTimer.STATE_LONG_BREAK),
+ (0, 4, STATE_BREAK),
+ (3, 4, STATE_BREAK),
+ (4, 4, STATE_LONG_BREAK),
+ (5, 4, STATE_BREAK),
+ (12, 1, STATE_LONG_BREAK),
+ (1, 10, STATE_BREAK),
+ (20, 10, STATE_LONG_BREAK),
+ (100, 10, STATE_LONG_BREAK),
])
def test_change_state_to_long_break(tomato, tomatoes, const_value, expected):
- tomato.TOMATOS_BEFORE_LONG_BREAK = const_value
+ tomato.settings.REPEAT_DEFAULT = const_value
tomato.tomatoes = tomatoes
tomato.changeState()
@@ -137,4 +141,4 @@ def test_reset(tomato):
tomato.reset()
- assert tomato.seconds == tomato.STATE_TOMATO.time
+ assert tomato.seconds == 100
diff --git a/tests/test_tomato_timer_slots.py b/tests/test_tomato_timer_slots.py
index da5f617..98c5400 100644
--- a/tests/test_tomato_timer_slots.py
+++ b/tests/test_tomato_timer_slots.py
@@ -39,8 +39,8 @@ def test_tick_tomatos_count_from_break(tomato_on_break, qtbot):
(False, False, False),
])
def test_tick_auto_stop(tomato, qtbot, mock_stop, is_tomato, flag, auto_stop):
- tomato.AUTO_STOP_TOMATO = flag
- tomato.AUTO_STOP_BREAK = flag
+ tomato.settings.AUTO_STOP_TOMATO_DEFAULT = flag
+ tomato.settings.AUTO_STOP_BREAK_DEFAULT = flag
if not is_tomato:
tomato.changeState()
diff --git a/tests/ui/test_main_window.py b/tests/ui/test_main_window.py
new file mode 100644
index 0000000..f9c7a69
--- /dev/null
+++ b/tests/ui/test_main_window.py
@@ -0,0 +1,30 @@
+import pytest
+from PyQt5 import QtCore
+
+from CherryTomato.main_window import CherryTomatoMainWindow
+
+
+@pytest.fixture(params=['tomato', 'break'])
+def mock_main_window(request, mock_qsettings, qtbot, tomato, monkeypatch):
+ window = CherryTomatoMainWindow()
+ if request.param == 'break':
+ tomato.changeState()
+ monkeypatch.setattr(window, 'tomatoTimer', tomato)
+ window.show()
+ qtbot.addWidget(window)
+ return window
+
+
+def test_start_button_with_tomato(mock_main_window, qtbot):
+ qtbot.mouseClick(mock_main_window.button, QtCore.Qt.LeftButton)
+
+ assert mock_main_window.tomatoTimer.running
+
+
+def test_stop_button_with_tomato(mock_main_window, qtbot):
+ qtbot.mouseClick(mock_main_window.button, QtCore.Qt.LeftButton)
+
+ qtbot.mouseClick(mock_main_window.button, QtCore.Qt.LeftButton)
+
+ assert mock_main_window.tomatoTimer.running is False
+