diff --git a/add.png b/add.png new file mode 100644 index 0000000..066480b Binary files /dev/null and b/add.png differ diff --git a/apply.png b/apply.png new file mode 100644 index 0000000..4416fb9 Binary files /dev/null and b/apply.png differ diff --git a/delete.png b/delete.png new file mode 100644 index 0000000..5768a47 Binary files /dev/null and b/delete.png differ diff --git a/edit.png b/edit.png new file mode 100644 index 0000000..31b2196 Binary files /dev/null and b/edit.png differ diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..f1a753a Binary files /dev/null and b/icon.png differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..2ee5558 --- /dev/null +++ b/main.py @@ -0,0 +1,12 @@ +# this will either work flawlessly, or fail to do anything... +# if you're trying to understand this, sorry for the spaghetti code + +from ui import Ui +from scheduler import Scheduler +import sys + +sc = Scheduler() +ui = Ui(sc.menu_event, sc.update) +sc.set_ui_class(ui) + +ui.run() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9f3888e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +PySide6 +playsound \ No newline at end of file diff --git a/save.png b/save.png new file mode 100644 index 0000000..9a2487b Binary files /dev/null and b/save.png differ diff --git a/scheduler.py b/scheduler.py new file mode 100644 index 0000000..b2ec179 --- /dev/null +++ b/scheduler.py @@ -0,0 +1,135 @@ +import time, json, dataclasses +from enum import Enum +from playsound import playsound +from sound import play + +class SoundType(Enum): + FIRST_BELL = 1 + SECOND_BELL = 2 + BREAK = 3 + CUSTOM = 4 + +BELL_NAMES = { + SoundType.FIRST_BELL: "1-й дзвоник", + SoundType.SECOND_BELL: "2-й дзвоник", + SoundType.BREAK: "Перерва" +} + +@dataclasses.dataclass +class Bell: + sound: SoundType + hour: int + minute: int + played: bool = False + # only used when sound = SoundType.CUSTOM + custom_file: str = "" + custom_name: str = "" + +@dataclasses.dataclass +class CustomSound: + name: str + times: list[list[int]] + sound_file: str + +@dataclasses.dataclass +class Config: + # yes, this is permanently "borrowed" from the internet + # thx to https://stackoverflow.com/questions/53632152/why-cant-dataclasses-have-mutable-defaults-in-their-class-attributes-declaratio + lessons_start: list[int] = dataclasses.field(default_factory=lambda: [8, 0]) + lesson_length: int = dataclasses.field(default_factory=lambda: 40) + break_length: int = dataclasses.field(default_factory=lambda: 5) + first_bell: int = dataclasses.field(default_factory=lambda: 1) + num_lessons: int = dataclasses.field(default_factory=lambda: 12) + workdays: list[bool] = dataclasses.field(default_factory=lambda: [True, True, True, True, True, True, False]) + sound_files: list[str] = dataclasses.field(default_factory=lambda: ["", "", ""]) + first_bell_before_first_lesson: bool = dataclasses.field(default_factory=lambda: True) + custom_sounds: list[CustomSound] = dataclasses.field(default_factory=lambda: []) + +class Scheduler: + def __init__(self) -> None: + self.bells: list[Bell] = [] + self.bells_enabled: bool = True + self.load_config() + + def set_ui_class(self, ui_class) -> None: + self.ui = ui_class + self.generate_bells() + self.ui.set_settings(self.config) + + def apply_config(self) -> None: + self.ui.get_settings(self.config, CustomSound) + self.generate_bells() + + def generate_bells(self) -> None: + self.bells = [] + if not self.config.workdays[time.localtime().tm_wday]: + self.ui.set_schedule(self.get_bells_list()) + return + + day_minute: int = self.config.lessons_start[0] * 60 + self.config.lessons_start[1] - self.config.first_bell + for lesson_number in range(self.config.num_lessons): + if lesson_number != 0 or self.config.first_bell_before_first_lesson: + self.bells.append(Bell(SoundType.FIRST_BELL, day_minute // 60, day_minute % 60)) + day_minute += self.config.first_bell + self.bells.append(Bell(SoundType.SECOND_BELL, day_minute // 60, day_minute % 60)) + day_minute += self.config.lesson_length + self.bells.append(Bell(SoundType.BREAK, day_minute // 60, day_minute % 60)) + day_minute += self.config.break_length - self.config.first_bell + + for sound in self.config.custom_sounds: + for sound_time in sound.times: + self.bells.append(Bell(SoundType.CUSTOM, *sound_time, custom_file=sound.sound_file, custom_name=sound.name)) + + # so that the first bell before the first lesson can't roll time into the negatives + self.bells = [bell for bell in self.bells if bell.hour <= 23] + self.bells.sort(key=lambda sound: sound.hour * 60 + sound.minute) + + self.ui.set_schedule(self.get_bells_list()) + + def get_bells_list(self) -> list[str]: + bells_list: list[str] = [] + for bell in self.bells: + if bell.sound != SoundType.CUSTOM: bell_name: str = BELL_NAMES[bell.sound] + else: bell_name: str = '"' + bell.custom_name + '"' + bells_list.append(f"{bell.hour:02}:{bell.minute:02} - {bell_name}") + return bells_list + + def menu_event(self, button) -> None: + match button: + case 0: self.apply_config() + case 1: self.save_config() + case 2: self.bells_enabled = True + case 3: self.bells_enabled = False + + def update(self) -> None: + if not self.bells_enabled: + return + t = time.localtime() + for bell_n, bell in enumerate(self.bells): + if (not bell.played) and (bell.hour == t.tm_hour) and (bell.minute == t.tm_min): + bell.played = True + self.ui.select_bell(bell_n) + match bell.sound: + case SoundType.FIRST_BELL: play(self.config.sound_files[0]) + case SoundType.SECOND_BELL: play(self.config.sound_files[1]) + case SoundType.BREAK: play(self.config.sound_files[2]) + case SoundType.CUSTOM: play(bell.custom_file) + break + + def load_config(self) -> None: + try: + with open("config.json", "r") as fp: + self.config = Config(**json.load(fp)) + for i in range(len(self.config.custom_sounds)): + self.config.custom_sounds[i] = CustomSound(**self.config.custom_sounds[i]) + except: + # if something goes wrong, load the default values + # nothing should go wrong with the config file, if the user doesn't edit + # it manually. if that's the case - it's their fault, not mine :P + self.config = Config() + + def save_config(self) -> None: + temp_config = Config() + self.ui.get_settings(temp_config, CustomSound) + with open("config.json", "w") as fp: + json.dump(dataclasses.asdict(temp_config), fp) diff --git a/sound.py b/sound.py new file mode 100644 index 0000000..e920217 --- /dev/null +++ b/sound.py @@ -0,0 +1,7 @@ +import playsound + +def play(file_name): + try: + playsound.playsound(file_name, block=False) + except playsound.PlaysoundException: + pass diff --git a/start.png b/start.png new file mode 100644 index 0000000..d6f9585 Binary files /dev/null and b/start.png differ diff --git a/stop.png b/stop.png new file mode 100644 index 0000000..5d13632 Binary files /dev/null and b/stop.png differ diff --git a/ui.py b/ui.py new file mode 100644 index 0000000..18be382 --- /dev/null +++ b/ui.py @@ -0,0 +1,37 @@ +from PySide6 import QtWidgets, QtGui, QtCore +from windows import MainWindow, MenuActions, ToolBar, MenuBar, Tray, SoundEditorWindow + +class Ui: + def __init__(self, menu_callback, periodic_task): + self.app = QtWidgets.QApplication([]) + self.app.setQuitOnLastWindowClosed(False) + self.sound_editor_window = SoundEditorWindow() + self.main_window = MainWindow(self.sound_editor_window.show) + self.menu_actions = MenuActions(self.main_window, menu_callback) + self.menu_bar = MenuBar(self.main_window, self.menu_actions) + self.toolbar = ToolBar(self.main_window, self.menu_actions) + self.tray = Tray(self.main_window) + + self.sound_editor_window.set_add_sound_callback(self.main_window.additional_sounds_box.add_sound) + + # timer for starting bells + self.timer = QtCore.QTimer() + self.timer.timeout.connect(periodic_task) + self.timer.start(1000) + + def run(self): + self.main_window.show() + self.tray.setVisible(True) + self.app.exec() + + def get_settings(self, *args, **kwargs): + return self.main_window.get_settings(*args, **kwargs) + + def set_settings(self, *args, **kwargs): + self.main_window.set_settings(*args, **kwargs) + + def set_schedule(self, *args, **kwargs): + self.main_window.set_schedule(*args, **kwargs) + + def select_bell(self, *args, **kwargs): + self.main_window.select_bell(*args, **kwargs) diff --git a/widgets.py b/widgets.py new file mode 100644 index 0000000..8d475ce --- /dev/null +++ b/widgets.py @@ -0,0 +1,277 @@ +from PySide6 import QtWidgets, QtGui, QtCore +import time + +DAYS_OF_WEEK = "Понеділок Вівторок Середа Четвер П'ятниця Субота Неділя".split() + +class BasicBox(QtWidgets.QScrollArea): + def __init__(self, title): + super().__init__() + self.widget = QtWidgets.QWidget() + self.setWidgetResizable(True) + self.setWidget(self.widget) + self.layout = QtWidgets.QVBoxLayout(self.widget) + font = QtGui.QFont() + font.setPointSize(16) + title = QtWidgets.QLabel(title) + title.setFont(font) + self.layout.addWidget(title) + +class WidgetArray(QtWidgets.QWidget): + def __init__(self, deleted_item_callback, edit_button=False): + super().__init__() + self.deleted_item_callback = deleted_item_callback + self.main_layout = QtWidgets.QVBoxLayout(self) + + self.control_widget = QtWidgets.QWidget() + self.control_layout = QtWidgets.QHBoxLayout(self.control_widget) + self.add_button = QtWidgets.QPushButton() + self.add_button.setIcon(QtGui.QIcon("add.png")) + self.add_button.setFixedSize(22, 22) + self.delete_button = QtWidgets.QPushButton() + self.delete_button.setIcon(QtGui.QIcon("delete.png")) + self.delete_button.setFixedSize(22, 22) + self.delete_button.clicked.connect(self.delete_items) + self.control_layout.addWidget(self.add_button) + self.control_layout.addWidget(self.delete_button) + if edit_button: + self.edit_button = QtWidgets.QPushButton() + self.edit_button.setIcon(QtGui.QIcon("edit.png")) + self.edit_button.setFixedSize(22, 22) + self.control_layout.addWidget(self.edit_button) + self.control_layout.addStretch(1) + + self.widget_list = QtWidgets.QListWidget() + + self.main_layout.addWidget(self.control_widget) + self.main_layout.addWidget(self.widget_list) + + def add_item(self, widget): + item = QtWidgets.QListWidgetItem() + self.widget_list.addItem(item) + self.widget_list.setItemWidget(item, widget) + + def get_items(self): + for i in range(self.widget_list.count()): + yield self.widget_list.itemWidget(self.widget_list.item(i)) + + def delete_items(self): + selected_items = self.widget_list.selectedItems() + if not selected_items: return + # if multiple items are selected, and the parent of this widget list keeps track of the widgets somehow, + # this makes so they are deleted in the correct order + selected_items.sort(reverse=True, key=lambda item: self.widget_list.row(item)) + for item in selected_items: + row = self.widget_list.row(item) + self.widget_list.takeItem(row) + self.deleted_item_callback(row) + +class BellStatusBox(BasicBox): + def __init__(self): + super().__init__("Розклад дзвінків") + self.bells_list = QtWidgets.QListWidget() + self.layout.addWidget(self.bells_list) + +class ScheduleBox(BasicBox): + def __init__(self): + super().__init__("Налаштування розкладу") + self.grid_widget = QtWidgets.QWidget() + self.grid_layout = QtWidgets.QGridLayout(self.grid_widget) + self.grid_widget.setLayout(self.grid_layout) + + self.first_lesson_input = QtWidgets.QTimeEdit() + self.lesson_length_input = QtWidgets.QSpinBox() + self.break_length_input = QtWidgets.QSpinBox() + self.first_bell_input = QtWidgets.QSpinBox() + self.num_lessons_input = QtWidgets.QSpinBox() + + self.lesson_length_input.setRange(20, 90) + self.break_length_input.setRange(2, 59) + self.first_bell_input.setRange(1, 4) + self.num_lessons_input.setRange(1, 20) + + self.break_length_input.valueChanged.connect(self.update_limits) + + self.grid_layout.addWidget(QtWidgets.QLabel("Перший урок о"), 0, 0) + self.grid_layout.addWidget(QtWidgets.QLabel("Тривалість уроку:"), 1, 0) + self.grid_layout.addWidget(QtWidgets.QLabel("Перерва"), 2, 0) + self.grid_layout.addWidget(QtWidgets.QLabel("Перший дзвоник за"), 3, 0) + self.grid_layout.addWidget(QtWidgets.QLabel("Кількість уроків:"), 4, 0) + self.grid_layout.addWidget(QtWidgets.QLabel("хвилин"), 1, 2) + self.grid_layout.addWidget(QtWidgets.QLabel("хвилин"), 2, 2) + self.grid_layout.addWidget(QtWidgets.QLabel("хвилин до уроку"), 3, 2) + self.grid_layout.addWidget(self.first_lesson_input, 0, 1) + self.grid_layout.addWidget(self.lesson_length_input, 1, 1) + self.grid_layout.addWidget(self.break_length_input, 2, 1) + self.grid_layout.addWidget(self.first_bell_input, 3, 1) + self.grid_layout.addWidget(self.num_lessons_input, 4, 1) + + self.first_bell_before_first_lesson_checkbox = QtWidgets.QCheckBox("Перший дзвоник перед першим уроком") + + self.layout.addWidget(self.grid_widget) + self.layout.addWidget(self.first_bell_before_first_lesson_checkbox) + self.layout.addStretch(1) + + def update_limits(self, value): + self.first_bell_input.setRange(1, value - 1) + +class DaysSelectBox(BasicBox): + def __init__(self): + super().__init__("Робочі дні") + self.days_checkboxes = [] + for day_name in DAYS_OF_WEEK: + self.days_checkboxes.append(QtWidgets.QCheckBox(day_name)) + self.layout.addWidget(self.days_checkboxes[-1]) + self.layout.addStretch(1) + +class AdditionalSoundsBox(BasicBox): + def __init__(self, show_sound_diag): + super().__init__("Додаткові звуки") + self.show_sound_diag = show_sound_diag + self.sound_list = WidgetArray(self.deleted_item, True) + self.sound_list.add_button.clicked.connect(lambda: self.show_sound_diag()) + self.sound_list.edit_button.clicked.connect(self.edit_sound) + self.layout.addWidget(self.sound_list) + self.sounds = [] + self.editing_sound = 0 + + def add_sound(self, name, times, sound_file, edit_n=None): + if edit_n is None: + self.sounds.append([name, times, sound_file]) + self.sound_list.add_item(QtWidgets.QLabel(name)) + else: + self.sounds[edit_n] = [name, times, sound_file] + self.sound_list.widget_list.itemWidget(self.sound_list.widget_list.item(edit_n)).setText(name) + + def deleted_item(self, n): + del self.sounds[n] + + def edit_sound(self): + edit_n = self.sound_list.widget_list.currentRow() + sound = self.sounds[edit_n] + self.show_sound_diag(edit_n, *sound) + +class StatusBox(BasicBox): + def __init__(self): + super().__init__("Стан") + self.grid_widget = QtWidgets.QWidget() + self.grid_layout = QtWidgets.QGridLayout(self.grid_widget) + self.grid_widget.setLayout(self.grid_layout) + + self.day_of_week_widget = QtWidgets.QLabel() + self.current_time_widget = QtWidgets.QLabel() + self.uptime_widget = QtWidgets.QLabel() + self.grid_layout.addWidget(QtWidgets.QLabel("День тижня:"), 0, 0) + self.grid_layout.addWidget(self.day_of_week_widget, 0, 1) + self.grid_layout.addWidget(QtWidgets.QLabel("Поточний час:"), 1, 0) + self.grid_layout.addWidget(self.current_time_widget, 1, 1) + self.grid_layout.addWidget(QtWidgets.QLabel("Зі запуску:"), 2, 0) + self.grid_layout.addWidget(self.uptime_widget, 2, 1) + + self.layout.addWidget(self.grid_widget) + self.layout.addStretch(1) + + self.uptime_timer = QtCore.QElapsedTimer() + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.update) + self.uptime_timer.start() + self.timer.start(1000) + self.update() + + def update(self): + t = time.localtime() + self.day_of_week_widget.setText(DAYS_OF_WEEK[t.tm_wday]) + self.current_time_widget.setText(f"{t.tm_hour:02}:{t.tm_min:02}:{t.tm_sec:02}") + seconds = self.uptime_timer.elapsed() / 1000 + minutes = seconds / 60 + hours = minutes / 60 + days = hours / 24 + self.uptime_widget.setText(f"{int(days)} днів, {int(hours % 24):02}:{int(minutes % 60):02}:{int(seconds % 60):02}") + +class SoundFilesBox(BasicBox): + def __init__(self): + super().__init__("Звуки") + self.grid_widget = QtWidgets.QWidget() + self.grid_layout = QtWidgets.QGridLayout(self.grid_widget) + self.grid_widget.setLayout(self.grid_layout) + + self.first_bell_file_text = QtWidgets.QLabel() + self.second_bell_file_text = QtWidgets.QLabel() + self.break_file_text = QtWidgets.QLabel() + self.first_bell_file_button = QtWidgets.QPushButton("Огляд") + self.second_bell_file_button = QtWidgets.QPushButton("Огляд") + self.break_file_button = QtWidgets.QPushButton("Огляд") + + self.first_bell_file_button .clicked.connect(lambda: self.file_select_diag(0)) + self.second_bell_file_button.clicked.connect(lambda: self.file_select_diag(1)) + self.break_file_button .clicked.connect(lambda: self.file_select_diag(2)) + + self.grid_layout.addWidget(QtWidgets.QLabel("Перший дзвоник:"), 0, 0) + self.grid_layout.addWidget(QtWidgets.QLabel("Другий дзвоник:"), 1, 0) + self.grid_layout.addWidget(QtWidgets.QLabel("Перерва:"), 2, 0) + self.grid_layout.addWidget(self.first_bell_file_text, 0, 1) + self.grid_layout.addWidget(self.second_bell_file_text, 1, 1) + self.grid_layout.addWidget(self.break_file_text, 2, 1) + self.grid_layout.addWidget(self.first_bell_file_button, 0, 2) + self.grid_layout.addWidget(self.second_bell_file_button, 1, 2) + self.grid_layout.addWidget(self.break_file_button, 2, 2) + + self.layout.addWidget(self.grid_widget) + self.layout.addStretch(1) + + def file_select_diag(self, sound_type): + file_name, _ = QtWidgets.QFileDialog.getOpenFileName(self.grid_widget, "SBC - Відкрити файл", "", "Підтримувані файли (*.wav *.mp3 *.flac)") + if file_name is None: + return + match sound_type: + case 0: self.first_bell_file_text .setText(file_name) + case 1: self.second_bell_file_text.setText(file_name) + case 2: self.break_file_text .setText(file_name) + +class SoundEditorBox(BasicBox): + def __init__(self, add_sound_callback, quit_callback): + super().__init__("Редактор звуку") + self.add_sound_callback = add_sound_callback + self.quit_callback = quit_callback + self.time_select_widget = WidgetArray(lambda _: None) + self.sound_name_input = QtWidgets.QLineEdit() + self.save_button = QtWidgets.QPushButton("Зберегти") + self.time_select_widget.add_button.clicked.connect(lambda: self.time_select_widget.add_item(QtWidgets.QTimeEdit())) + self.save_button.clicked.connect(self.save) + + self.file_select_widget = QtWidgets.QWidget() + self.file_select_layout = QtWidgets.QHBoxLayout(self.file_select_widget) + self.file_name_select_text = QtWidgets.QLabel() + self.file_name_select_button = QtWidgets.QPushButton("Огляд") + self.file_name_select_button.clicked.connect(self.file_select_diag) + self.file_select_layout.addWidget(self.file_name_select_text) + self.file_select_layout.addWidget(self.file_name_select_button) + self.file_select_layout.addStretch(1) + + self.layout.addWidget(self.sound_name_input) + self.layout.addWidget(self.time_select_widget) + self.layout.addWidget(self.file_select_widget) + self.layout.addStretch(1) + self.layout.addWidget(self.save_button) + self.edit_n = None + + def file_select_diag(self): + file_name, _ = QtWidgets.QFileDialog.getOpenFileName(self.file_select_widget, "SBC - Відкрити файл", "", "Підтримувані файли (*.wav *.mp3 *.flac)") + if file_name is None: + return + self.file_name_select_text.setText(file_name) + + def save(self): + self.add_sound_callback( + self.sound_name_input.text(), + [[item.time().hour(), item.time().minute()] for item in self.time_select_widget.get_items()], + self.file_name_select_text.text(), + self.edit_n + ) + self.clear_edit() + self.quit_callback() + + def clear_edit(self): + self.sound_name_input.setText("") + for i in range(self.time_select_widget.widget_list.count()): + self.time_select_widget.widget_list.takeItem(0) + self.edit_n = None diff --git a/windows.py b/windows.py new file mode 100644 index 0000000..010341d --- /dev/null +++ b/windows.py @@ -0,0 +1,176 @@ +from PySide6 import QtWidgets, QtGui, QtCore +from widgets import BellStatusBox, ScheduleBox, DaysSelectBox, AdditionalSoundsBox, StatusBox, SoundFilesBox, SoundEditorBox +import sys + +VERSION = "0.2.0" + +class MainWindow(QtWidgets.QMainWindow): + def __init__(self, show_sound_diag): + super().__init__() + self.setWindowTitle("SBC - Головне вікно") + self.setWindowIcon(QtGui.QIcon("icon.png")) + self.setMinimumSize(800, 768) + self.main_widget = QtWidgets.QWidget() + self.main_layout = QtWidgets.QGridLayout(self.main_widget) + + self.bell_status_box = BellStatusBox() + self.schedule_box = ScheduleBox() + self.days_select_box = DaysSelectBox() + self.additional_sounds_box = AdditionalSoundsBox(show_sound_diag) + self.status_box = StatusBox() + self.sound_files_box = SoundFilesBox() + + self.main_layout.addWidget(self.bell_status_box, 0, 0, 3, 1) + self.main_layout.addWidget(self.schedule_box, 0, 1, 1, 1) + self.main_layout.addWidget(self.days_select_box, 1, 1, 1, 1) + self.main_layout.addWidget(self.additional_sounds_box, 2, 1, 1, 1) + self.main_layout.addWidget(self.status_box, 0, 2, 1, 1) + self.main_layout.addWidget(self.sound_files_box, 1, 2, 1, 1) + self.setCentralWidget(self.main_widget) + + def get_settings(self, config, CustomSound): + first_lesson_input = self.schedule_box.first_lesson_input.time() + + config.lessons_start = [first_lesson_input.hour(), first_lesson_input.minute()] + config.lesson_length = self.schedule_box.lesson_length_input.value() + config.break_length = self.schedule_box.break_length_input .value() + config.first_bell = self.schedule_box.first_bell_input .value() + config.num_lessons = self.schedule_box.num_lessons_input .value() + config.first_bell_before_first_lesson = self.schedule_box.first_bell_before_first_lesson_checkbox.isChecked() + config.workdays = [checkbox.isChecked() for checkbox in self.days_select_box.days_checkboxes] + config.sound_files = [ + self.sound_files_box.first_bell_file_text .text(), + self.sound_files_box.second_bell_file_text .text(), + self.sound_files_box.break_file_text .text() + ] + config.custom_sounds = [CustomSound(*sound) for sound in self.additional_sounds_box.sounds] + + def set_settings(self, config): + self.schedule_box.first_lesson_input .setTime (QtCore.QTime(*config.lessons_start)) + self.schedule_box.lesson_length_input.setValue(config.lesson_length) + self.schedule_box.break_length_input .setValue(config.break_length) + self.schedule_box.first_bell_input .setValue(config.first_bell) + self.schedule_box.num_lessons_input .setValue(config.num_lessons) + self.schedule_box.first_bell_before_first_lesson_checkbox.setChecked(config.first_bell_before_first_lesson) + for i in range(0, 7): self.days_select_box.days_checkboxes[i].setChecked(config.workdays[i]) + self.sound_files_box.first_bell_file_text .setText(config.sound_files[0]) + self.sound_files_box.second_bell_file_text .setText(config.sound_files[1]) + self.sound_files_box.break_file_text .setText(config.sound_files[2]) + self.additional_sounds_box.sounds = [[sound.name, sound.times, sound.sound_file] for sound in config.custom_sounds] + for sound in config.custom_sounds: self.additional_sounds_box.sound_list.add_item(QtWidgets.QLabel(sound.name)) + + def set_schedule(self, bells): + self.bell_status_box.bells_list.clear() + for bell in bells: + self.bell_status_box.bells_list.addItem(bell) + + def select_bell(self, bell_n): + self.bell_status_box.bells_list.setCurrentItem(self.bell_status_box.bells_list.item(bell_n)) + +class MenuActions: + def __init__(self, window, callback): + self.window = window + self.callback = callback + self.button_apply = QtGui.QAction(QtGui.QIcon("apply.png"), "Застосувати", window) + self.button_save = QtGui.QAction(QtGui.QIcon("save.png"), "Зберегти налаштування", window) + self.button_start = QtGui.QAction(QtGui.QIcon("start.png"), "Запустити дзвоники", window) + self.button_stop = QtGui.QAction(QtGui.QIcon("stop.png"), "Зупинити все", window) + self.button_about = QtGui.QAction( "Про програму", window) + + self.button_start.setEnabled(False) + + self.button_apply.triggered.connect(lambda: self.handle_button(0)) + self.button_save .triggered.connect(lambda: self.handle_button(1)) + self.button_start.triggered.connect(lambda: self.handle_button(2)) + self.button_stop .triggered.connect(lambda: self.handle_button(3)) + self.button_about.triggered.connect(lambda: self.handle_button(4)) + + def handle_button(self, button): + match button: + case 0 | 1 | 2 | 3: + self.callback(button) + match button: + case 0: QtWidgets.QMessageBox.information(self.window, "SBC - Інформація", "Налаштування застосовано!") + case 1: QtWidgets.QMessageBox.information(self.window, "SBC - Інформація", "Налаштування збережено!") + case 2: + self.button_start.setEnabled(False) + self.button_stop .setEnabled(True) + QtWidgets.QMessageBox.information(self.window, "SBC - Інформація", "Дзвінки запущено. Якщо ви налаштували щось не так, самі винні!") + case 3: + self.button_start.setEnabled(True) + self.button_stop .setEnabled(False) + QtWidgets.QMessageBox.information(self.window, "SBC - Інформація", "Дзвінки зупинено. Щось пішло не так, еге ж? Піди і виправи це негайно!") + case 4: QtWidgets.QMessageBox.information(self.window, "SBC - Про програму", \ + f"SBC {VERSION}\nАвтор: 2o\nTelegram: @xfdtw\nDiscord: @2o___\nЯкщо щось не зрозуміло/не працює пишіть туди.") + +class ToolBar(QtWidgets.QToolBar): + def __init__(self, window, menu_actions): + super().__init__("Toolbar") + window.addToolBar(self) + self.setIconSize(QtCore.QSize(16, 16)) + + self.addAction(menu_actions.button_apply) + self.addAction(menu_actions.button_save) + self.addSeparator() + self.addAction(menu_actions.button_start) + self.addAction(menu_actions.button_stop) + +class MenuBar(QtWidgets.QMenuBar): + def __init__(self, window, menu_actions): + super().__init__() + window.setMenuBar(self) + self.settings_menu = self.addMenu("&Налаштування") + self.bells_menu = self.addMenu("&Дзвінки") + self.help_menu = self.addMenu("Д&опомога") + + self.settings_menu.addAction(menu_actions.button_apply) + self.settings_menu.addAction(menu_actions.button_save) + self.bells_menu.addAction(menu_actions.button_start) + self.bells_menu.addAction(menu_actions.button_stop) + self.help_menu.addAction(menu_actions.button_about) + +class Tray(QtWidgets.QSystemTrayIcon): + def __init__(self, window): + super().__init__() + self.window = window + self.setIcon(QtGui.QIcon("icon.png")) + self.setVisible(True) + + self.menu = QtWidgets.QMenu(self.window) + open_window = QtGui.QAction("Показати головне вікно", self) + close_all = QtGui.QAction("Вийти", self) + open_window.triggered.connect(self.window.show) + close_all.triggered.connect(self.close_all_diag) + self.menu.addAction(open_window) + self.menu.addAction(close_all) + self.setContextMenu(self.menu) + + def close_all_diag(self): + if QtWidgets.QMessageBox.question(self.window, "SBC - Вихід", \ + "Ви дійсно хочете вийти з програми? Після цього дітки кричатимуть чого в них уроки по 5 годин...", \ + QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No) == QtWidgets.QMessageBox.StandardButton.Yes: + sys.exit() + +class SoundEditorWindow(QtWidgets.QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("SBC - Редактор звуків") + self.setWindowIcon(QtGui.QIcon("icon.png")) + self.setMinimumSize(256, 384) + + def set_add_sound_callback(self, add_sound_callback): + self.sound_editor_box = SoundEditorBox(add_sound_callback, self.hide) + self.setCentralWidget(self.sound_editor_box) + + def show(self, edit_n=None, name="", times=[], sound_file=""): + self.sound_editor_box.edit_n = edit_n + self.sound_editor_box.sound_name_input.setText(name) + for sound_time in times: + self.sound_editor_box.time_select_widget \ + .add_item(QtWidgets.QTimeEdit(QtCore.QTime(*sound_time))) + self.sound_editor_box.file_name_select_text.setText(sound_file) + super().show() + + def closeEvent(self, *args, **kwargs): + self.sound_editor_box.clear_edit() + super().closeEvent(*args, **kwargs)