Python Forum
[PySide6] Alarms App
Thread Rating:
  • 1 Vote(s) - 4 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PySide6] Alarms App
#1
Hello everyone.

Here is my alarms app. I did it for fun, because programming is a hobby for me, and because my girlfriend asked for something to help her remember taking medicine.

Any feedback will be appreciated! Smile

GitHub repo

main.py
import sys
import datetime as dt

from PySide6 import QtWidgets, QtGui, QtCore

from db import Database
from alarm import Alarm
from main_window import TimerWindow


def check_alarms() -> None:
    """Checks alarms and display notification."""

    now = dt.datetime.now()
    alarms = [Alarm(t[0], t[1], t[2], t[3]) for t in Database().alarms]
    for alarm in alarms:
        hour = alarm.hour
        minutes = alarm.minutes
        description = alarm.description
        if hour == now.hour and minutes == now.minute:
            aviso = QtWidgets.QMessageBox()
            aviso.setIcon(QtWidgets.QMessageBox.Icon.Information)
            aviso.setWindowTitle(f"Alarm {hour:02}:{minutes:02}")
            aviso.setText(description)
            aviso.exec()


def main() -> None:
    """Program start"""

    app = QtWidgets.QApplication(sys.argv)
    app.setQuitOnLastWindowClosed(False)

    # Timer
    timer = QtCore.QTimer()
    timer.start(1000 * 31)  # Timer timeout every 31 seconds
    timer.timeout.connect(check_alarms)

    # Main window
    window = TimerWindow()

    # System tray
    tray = QtWidgets.QSystemTrayIcon()
    icon = QtGui.QIcon("icon.ico")
    tray.setIcon(icon)
    tray.setVisible(True)

    # Tray menu
    menu = QtWidgets.QMenu()
    open_window = QtGui.QAction("Show alarms")
    open_window.triggered.connect(window.show)
    menu.addAction(open_window)
    close_app = QtGui.QAction("Quit")
    close_app.triggered.connect(app.quit)
    menu.addAction(close_app)
    tray.setContextMenu(menu)

    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
main_window.py
import sys
from PySide6 import QtWidgets, QtGui

from alarm import Alarm
from db import Database


class NewTimerDialog(QtWidgets.QDialog):
    def __init__(self) -> None:
        super().__init__()
        self.setWindowTitle("Add alarm")

        buttons = QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
        self.dialog_buttons = QtWidgets.QDialogButtonBox(buttons)
        self.dialog_buttons.accepted.connect(self.accept)
        self.dialog_buttons.rejected.connect(self.reject)

        self.layout = QtWidgets.QVBoxLayout()
        self.label_time = QtWidgets.QLabel("New alarm time:")
        self.time = QtWidgets.QTimeEdit()
        self.label_desc = QtWidgets.QLabel("Alarm description:")
        self.description = QtWidgets.QLineEdit()

        self.layout.addWidget(self.label_time)
        self.layout.addWidget(self.time)
        self.layout.addWidget(self.label_desc)
        self.layout.addWidget(self.description)
        self.layout.addWidget(self.dialog_buttons)
        self.setLayout(self.layout)


class TimerWindow(QtWidgets.QMainWindow):
    def __init__(self) -> None:
        super().__init__()
        self.db = Database()
        self.setWindowTitle("Despertador 0.2.0")
        self.setWindowIcon(QtGui.QIcon("icon.ico"))
        self.setMinimumSize(240, 360)

        self.main_panel = QtWidgets.QWidget()
        self.main_layout = QtWidgets.QGridLayout(self.main_panel)

        self.btn_add = QtWidgets.QPushButton("Add alarm")
        self.btn_add.clicked.connect(self.add_timer)

        self.timers_panel = QtWidgets.QWidget()
        self.timers_layout = QtWidgets.QVBoxLayout(self.timers_panel)
        self.timers_layout.addStretch()
        self.timers_layout.setDirection(self.timers_layout.Direction.BottomToTop)

        self.main_layout.addWidget(self.btn_add, 0, 0)
        self.main_layout.addWidget(self.timers_panel, 1, 0)

        # DATA
        self.timers = []
        self.update_timers()
        for timer in self.timers:
            self.display_timer(timer)

        self.scroll = QtWidgets.QScrollArea()
        self.scroll.setWidgetResizable(True)
        self.scroll.setVerticalScrollBarPolicy(
            self.scroll.verticalScrollBarPolicy().ScrollBarAlwaysOn
        )
        self.scroll.setWidget(self.main_panel)
        self.setCentralWidget(self.scroll)

    def update_timers(self) -> None:
        self.timers = [Alarm(t[0], t[1], t[2], t[3]) for t in self.db.alarms]

    def display_timer(self, timer: Alarm) -> None:
        def remove_from_layout(
            timer, layout: QtWidgets.QLayout, widget: QtWidgets.QWidget
        ) -> None:
            layout.removeWidget(widget)
            widget.deleteLater()
            self.remove_timer(timer)

        widget = QtWidgets.QGroupBox()
        layout = QtWidgets.QGridLayout(widget)

        label_description = QtWidgets.QLabel(f"<b>{timer.description}</b>")
        label_time = QtWidgets.QLabel(f"{timer.hour:02}:{timer.minutes:02}")
        btn_remove = QtWidgets.QPushButton(text="Remove alarm")
        btn_remove.clicked.connect(lambda: remove_from_layout(timer, layout, widget))

        layout.addWidget(label_description, 0, 0)
        layout.addWidget(label_time, 1, 0)
        layout.addWidget(btn_remove, 2, 0)

        self.timers_layout.addWidget(widget)

    def add_timer(self) -> None:
        new_timer = NewTimerDialog()
        if new_timer.exec():
            description = new_timer.description.text()
            if not description:
                description = "New alarm"
            hour = new_timer.time.time().hour()
            minutes = new_timer.time.time().minute()
            id = self.db.add_alarm(description=description, hour=hour, minutes=minutes)
            self.display_timer(Alarm(id, description, hour, minutes))

    def remove_timer(self, timer) -> None:
        self.db.remove_alarme(key=timer.id)
        self.update_timers()
db.py
import pathlib
import sqlite3


DB_TIMERS = pathlib.Path("alarms.db")


class Database:
    """Database controller."""

    def __init__(self, db=DB_TIMERS) -> None:
        self._db = pathlib.Path(db)
        self._query(
            """CREATE TABLE IF NOT EXISTS tb_alarms (id INTEGER NOT NULL PRIMARY KEY, description TEXT, hour INTEGER, minutes INTEGER);"""
        )

    def add_alarm(self, description: str, hour: int, minutes: int) -> None:
        """Adds a new alarm."""
        with sqlite3.connect(self._db) as conn:
            c = conn.cursor()
            c.execute(
                "INSERT INTO tb_alarms (description, hour, minutes) VALUES (?, ?, ?);",
                [description, hour, minutes],
            )
            return c.lastrowid

    def remove_alarme(self, key: int) -> None:
        """Remove a alarm by the primary key."""
        with sqlite3.connect(self._db) as conn:
            c = conn.cursor()
            c.execute("DELETE FROM tb_alarms WHERE id=(?);", (key,))

    def _query(self, query: str) -> None:
        """DEBUG ONLY, directly execute queries."""
        with sqlite3.connect(self._db) as conn:
            c = conn.cursor()
            return c.execute(query)

    @property
    def alarms(self) -> list[tuple]:
        """List of all alarms on the database."""
        with sqlite3.connect(self._db) as conn:
            c = conn.cursor()
            return c.execute("SELECT * FROM tb_alarms;").fetchall()
alarm.py
from dataclasses import dataclass


@dataclass
class Alarm:
    id: int
    description: str
    hour: int
    minutes: int
Reply
#2
You should add a ReadMe with a description in github.
mactron likes this post
Reply
#3
Yes, I agree with Axel at this moment.
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020