diff --git a/CherryTomato/__init__.py b/CherryTomato/__init__.py index 5210c24..d1cb619 100644 --- a/CherryTomato/__init__.py +++ b/CherryTomato/__init__.py @@ -4,6 +4,6 @@ MEDIA_DIR = os.path.join(BASE_DIR, 'media') APP_ICON = os.path.join(MEDIA_DIR, 'icon.png') -VERSION = '0.3.0' -ORGANIZATION_NAME = 'CherryTomato' +VERSION = '0.4.0' +ORGANIZATION_NAME = 'yakimka' APPLICATION_NAME = 'CherryTomato' diff --git a/CherryTomato/about_window.py b/CherryTomato/about_window.py index 15b08b6..2b16915 100644 --- a/CherryTomato/about_window.py +++ b/CherryTomato/about_window.py @@ -1,3 +1,4 @@ +from PyQt5 import Qt from PyQt5 import QtWidgets from PyQt5.QtGui import QIcon @@ -15,3 +16,9 @@ def __init__(self): self.labelTitle.setText(title) self.setWindowIcon(QIcon(APP_ICON)) + + def keyPressEvent(self, event): + if event.key() == Qt.Qt.Key_Escape: + self.close() + else: + super().keyPressEvent(event) diff --git a/CherryTomato/main.py b/CherryTomato/main.py index 4b79ead..5755c38 100755 --- a/CherryTomato/main.py +++ b/CherryTomato/main.py @@ -8,7 +8,7 @@ from CherryTomato import ORGANIZATION_NAME, APPLICATION_NAME from CherryTomato.main_window import CherryTomatoMainWindow -QCoreApplication.setApplicationName(ORGANIZATION_NAME) +QCoreApplication.setOrganizationName(ORGANIZATION_NAME) QCoreApplication.setApplicationName(APPLICATION_NAME) app = Qt.QApplication(sys.argv) diff --git a/CherryTomato/main_ui.py b/CherryTomato/main_ui.py index 60a75b3..f80f9e1 100644 --- a/CherryTomato/main_ui.py +++ b/CherryTomato/main_ui.py @@ -55,6 +55,9 @@ def setupUi(self, MainWindow): self.actionAbout.setObjectName("actionAbout") self.actionSettings = QtWidgets.QAction(MainWindow) self.actionSettings.setObjectName("actionSettings") + self.actionReset = QtWidgets.QAction(MainWindow) + self.actionReset.setObjectName("actionReset") + self.menuFile.addAction(self.actionReset) self.menuFile.addAction(self.actionSettings) self.menuFile.addAction(self.actionAbout) self.menuBar.addAction(self.menuFile.menuAction()) @@ -68,4 +71,5 @@ def retranslateUi(self, MainWindow): self.menuFile.setTitle(_translate("MainWindow", "File")) self.actionAbout.setText(_translate("MainWindow", "About")) self.actionSettings.setText(_translate("MainWindow", "Settings")) + self.actionReset.setText(_translate("MainWindow", "Reset")) from CherryTomato.widget import QRoundProgressBar, QRoundPushbutton diff --git a/CherryTomato/main_ui.ui b/CherryTomato/main_ui.ui index b677000..1f12e17 100644 --- a/CherryTomato/main_ui.ui +++ b/CherryTomato/main_ui.ui @@ -99,6 +99,7 @@ border-radius: 20px; File + @@ -114,6 +115,11 @@ border-radius: 20px; Settings + + + Reset + + diff --git a/CherryTomato/main_window.py b/CherryTomato/main_window.py index 1134aec..cc1f028 100644 --- a/CherryTomato/main_window.py +++ b/CherryTomato/main_window.py @@ -1,7 +1,7 @@ import os from PyQt5 import Qt, QtCore -from PyQt5.QtGui import QBrush, QColor, QPalette, QIcon +from PyQt5.QtGui import QBrush, QColor, QPalette, QIcon, QKeySequence from PyQt5.QtMultimedia import QSound from CherryTomato import about_window, APP_ICON, MEDIA_DIR, settings_window @@ -35,6 +35,9 @@ def __init__(self, parent=None): self.actionSettings.triggered.connect(self.showSettingsWindow) self.settingsWindow.closing.connect(self.update) + self.actionReset.setShortcut(QKeySequence('Ctrl+R')) + self.actionReset.triggered.connect(self.tomatoTimer.reset) + def update(self): self.tomatoTimer.updateState() @@ -80,10 +83,10 @@ def closeEvent(self, e): @Qt.pyqtSlot() def display(self): - TOMATO_SIGN = '˙' + TOMATO_SIGN = '🍅' minutes, seconds = int(self.tomatoTimer.seconds // 60), int(self.tomatoTimer.seconds % 60) - display = f'\n{minutes:02d}:{seconds:02d}\n{TOMATO_SIGN * self.tomatoTimer.tomatoes}' - self.progress.setFormat(display) + self.progress.setFormat(f'{minutes:02d}:{seconds:02d}') + self.progress.setSecondFormat(f'{TOMATO_SIGN}x{self.tomatoTimer.tomatoes}') self.progress.setValue(self.tomatoTimer.progress) self.changeButtonState() @@ -94,7 +97,7 @@ def display(self): @Qt.pyqtSlot() def do_start(self): - # trigger through proxy + # TODO trigger through proxy if not self.tomatoTimer.running: self.tomatoTimer.start() else: @@ -137,12 +140,6 @@ def setGreen(self): green = (60, 187, 111) self.setColor(lightGreen, green) - def keyPressEvent(self, event): - if event.key() == Qt.Qt.Key_Escape: - self.close() - else: - super().keyPressEvent(event) - @Qt.pyqtSlot() def setFocusOnWindowAndPlayNotification(self): if self.settings.interrupt: diff --git a/CherryTomato/settings_window.py b/CherryTomato/settings_window.py index d98859f..2e7ff90 100644 --- a/CherryTomato/settings_window.py +++ b/CherryTomato/settings_window.py @@ -1,4 +1,4 @@ -from PyQt5 import QtWidgets +from PyQt5 import QtWidgets, Qt from PyQt5.QtCore import pyqtSignal from PyQt5.QtGui import QIcon @@ -32,6 +32,12 @@ def __init__(self): self.autoStopBreak.setChecked(self.settings.autoStopBreak) self.switchToTomatoOnAbort.setChecked(self.settings.switchToTomatoOnAbort) + def keyPressEvent(self, event): + if event.key() == Qt.Qt.Key_Escape: + self.close() + else: + super().keyPressEvent(event) + def closeEvent(self, e): e.accept() self.updateSettings() diff --git a/CherryTomato/tomato_timer.py b/CherryTomato/tomato_timer.py index f4f168b..69dca4d 100644 --- a/CherryTomato/tomato_timer.py +++ b/CherryTomato/tomato_timer.py @@ -3,8 +3,6 @@ from CherryTomato.settings import CherryTomatoSettings -settings = CherryTomatoSettings() - class TomatoTimer(QObject): TICK_TIME = 64 # ms @@ -15,7 +13,7 @@ class TomatoTimer(QObject): def __init__(self): super().__init__() - self.settings = settings + self.settings = CherryTomatoSettings() self.tomatoes = 0 self.state = None @@ -47,7 +45,7 @@ def abort(self): if not self.isTomato() and self.settings.switchToTomatoOnAbort: self.changeState() else: - self.reset() + self.resetTime() self.notifyAboutAnyChange() def createTimer(self): @@ -100,10 +98,10 @@ def _isTimeForLongBreak(self): def updateState(self): if not self.running: - self.reset() + self.resetTime() self.notifyAboutAnyChange() - def reset(self): + def resetTime(self): self.seconds = self.state.time def notifyAboutAnyChange(self): @@ -111,3 +109,11 @@ def notifyAboutAnyChange(self): def notifyTimerIsOver(self): self.finished.emit() + + def reset(self): + self.stop() + self.tomatoes = 0 + self.state = None + del self._states + self.changeState() + self.notifyAboutAnyChange() diff --git a/CherryTomato/widget.py b/CherryTomato/widget.py index f4dfb21..814a480 100644 --- a/CherryTomato/widget.py +++ b/CherryTomato/widget.py @@ -1,10 +1,12 @@ import os from PyQt5 import QtCore +from PyQt5.QtCore import QRectF, Qt +from PyQt5.QtGui import QPainter, QFont, QFontMetricsF, QPaintEvent, QImage from PyQt5.QtWidgets import QPushButton +from qroundprogressbar import QRoundProgressBar as QRoundProgressBar_ from CherryTomato import MEDIA_DIR -from qroundprogressbar import QRoundProgressBar as QRoundProgressBar_ class QRoundPushbutton(QPushButton): @@ -39,11 +41,76 @@ def applyStyleSheet(self): class QRoundProgressBar(QRoundProgressBar_): def __init__(self, parent=None): super().__init__(parent) + self.m_second_format = '' + self.setFormat('') self.setValue(0) self.setBarStyle(QRoundProgressBar.BarStyle.LINE) - self.setOutlinePenWidth(6) - self.setDataPenWidth(6) + self.setOutlinePenWidth(4) + self.setDataPenWidth(4) def resizeEvent(self, e): self.setMaximumWidth(self.height()) + + def setSecondFormat(self, val: str): + if val != self.m_second_format: + self.m_second_format = val + self.valueFormatChanged() + + def paintEvent(self, event: QPaintEvent): + outerRadius = min(self.width(), self.height()) + baseRect = QRectF(1, 1, outerRadius - 2, outerRadius - 2) + buffer = QImage(outerRadius, outerRadius, QImage.Format_ARGB32_Premultiplied) + p = QPainter(buffer) + p.setRenderHint(QPainter.Antialiasing) + self.rebuildDataBrushIfNeeded() + self.drawBackground(p, buffer.rect()) + self.drawBase(p, baseRect) + if self.m_value > 0: + delta = (self.m_max - self.m_min) / (self.m_value - self.m_min) + else: + delta = 0 + self.drawValue(p, baseRect, self.m_value, delta) + firstInnerRect, secondInnerRect, innerRadius = self.calculateInnerRect(outerRadius) + self.drawInnerBackground(p, firstInnerRect) + self.drawInnerBackground(p, secondInnerRect) + self.drawTwoTexts(p, firstInnerRect, secondInnerRect, innerRadius, self.m_value) + p.end() + painter = QPainter(self) + painter.fillRect(baseRect, self.palette().window()) + painter.drawImage(0, 0, buffer) + + def calculateInnerRect(self, outerRadius: float): + innerRect, innerRadius = super().calculateInnerRect(outerRadius) + + left, top, width, height = innerRect.getRect() + + firstHeight = height * 0.7 + firstInnerRect = QRectF(left, top, width, firstHeight) + secondInnerRect = QRectF(left, top + firstHeight, width, height - firstHeight) + return firstInnerRect, secondInnerRect, innerRadius + + def drawTwoTexts(self, + p: QPainter, + firstRect: QRectF, + secondRect: QRectF, + innerRadius: float, + value: float): + if not self.m_format: + return + f = QFont(self.font()) + f.setPixelSize(10) + fm = QFontMetricsF(f) + maxWidth = fm.width(self.valueToText(self.m_max)) + delta = innerRadius / maxWidth + timeFontSize = f.pixelSize() * delta * 0.75 + f.setPixelSize(int(timeFontSize)) + p.setFont(f) + timeTextRect = QRectF(firstRect) + tomatoesTextRect = QRectF(secondRect) + p.setPen(self.palette().text().color()) + p.drawText(timeTextRect, Qt.AlignCenter | Qt.AlignBottom, self.valueToText(value)) + tomatoesFontSize = timeFontSize * 0.3 + f.setPixelSize(int(tomatoesFontSize)) + p.setFont(f) + p.drawText(tomatoesTextRect, Qt.AlignHCenter, self.m_second_format) diff --git a/README.md b/README.md index 7cdc99f..b639c7e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CherryTomato -![Screenshot](assets/Screenshot_20191211_004536.png) +![Screenshot](assets/screenshot.png) ## Features diff --git a/assets/Screenshot_20191211_004536.png b/assets/Screenshot_20191211_004536.png deleted file mode 100644 index 7d91e7b..0000000 Binary files a/assets/Screenshot_20191211_004536.png and /dev/null differ diff --git a/assets/screenshot.png b/assets/screenshot.png new file mode 100644 index 0000000..237bb40 Binary files /dev/null and b/assets/screenshot.png differ diff --git a/setup.py b/setup.py index 2296d4b..e1ee4fb 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup(name='CherryTomato', author='yakimka', - version='0.1.0', + version='0.4.0', packages=['CherryTomato'], install_requires=['PyQt5', 'qroundprogressbar'], url='https://github.com/yakimka/CherryTomato', diff --git a/tests/test_tomato_timer.py b/tests/test_tomato_timer.py index 04f94de..1d77b70 100644 --- a/tests/test_tomato_timer.py +++ b/tests/test_tomato_timer.py @@ -64,7 +64,7 @@ def test_stop(mock_qt_timer, tomato): @pytest.fixture def mock_change_reset(mocker): - return mocker.patch('CherryTomato.tomato_timer.TomatoTimer.reset') + return mocker.patch('CherryTomato.tomato_timer.TomatoTimer.resetTime') @pytest.fixture @@ -76,7 +76,7 @@ def test_abort(mock_for_abort, tomato): tomato.abort() tomato.stop.assert_called_once() - tomato.reset.assert_called_once() + tomato.resetTime.assert_called_once() assert tomato.isTomato() @@ -136,9 +136,9 @@ def test_change_state_to_long_break(tomato, tomatoes, const_value, expected): assert tomato.seconds == expected.time -def test_reset(tomato): +def test_reset_time(tomato): tomato.seconds = 1 - tomato.reset() + tomato.resetTime() assert tomato.seconds == 100