diff --git a/Preferences/Date and Time.app/Date and Time b/Preferences/Date and Time.app/Date and Time index 1c18175e..233f7435 100755 --- a/Preferences/Date and Time.app/Date and Time +++ b/Preferences/Date and Time.app/Date and Time @@ -1,25 +1,107 @@ #!/usr/bin/env python3 -# FIXME: The spinner is not shown; we need to fix this by using threading? Is there a better way? -# TODO: Implement manual time zone setting in the time zone tab -# TODO: Implement manual date and time setting in the date and time tab - import subprocess import sys import os import time +import pytz +import geocoder from PyQt5.QtCore import Qt, QDateTime, QTimer -from PyQt5.QtWidgets import (QApplication, QDateTimeEdit, QGridLayout, +from PyQt5.QtWidgets import (QApplication, QDateTimeEdit, QGridLayout, QComboBox, QGroupBox, QLabel, QMainWindow, QMessageBox, - QPushButton, QTabWidget, QVBoxLayout, QWidget) + QPushButton, QTabWidget, QVBoxLayout, QWidget, QSizePolicy) from PyQt5.QtGui import QMovie, QKeyEvent, QPixmap -from PyQt5.QtCore import QEvent +from PyQt5.QtCore import QEvent, QObject, QThread, pyqtSignal +from timezonefinder import TimezoneFinder + +class SetTimeZoneAutoWorker(QObject): + finished = pyqtSignal() + returncode = pyqtSignal(object) + def run(self): + global new_time_zone + try: + time.sleep(1) + # Set the new time zone automatically + g = geocoder.ip('me') + lat, lng = g.latlng + tf = TimezoneFinder() + new_time_zone = tf.timezone_at(lng=lng, lat=lat) + subprocess.run(["sudo", "tzsetup", f"{new_time_zone}"]) + subprocess.run(["sudo", "adjkerntz", "-a", f"{new_time_zone}"]) + self.returncode.emit("OK") + self.finished.emit() + return + except subprocess.CalledProcessError as e: + self.returncode.emit(e.stderr) + self.finished.emit() + +class SetTimeZoneManualWorker(QObject): + global new_time_zone + finished = pyqtSignal() + returncode = pyqtSignal(object) + def run(self): + try: + time.sleep(1) + # Set the new time zone + subprocess.run(["sudo", "tzsetup", f"{new_time_zone}"]) + subprocess.run(["sudo", "adjkerntz", "-a", f"{new_time_zone}"]) + self.returncode.emit("OK") + self.finished.emit() + return + except subprocess.CalledProcessError as e: + self.returncode.emit(e.stderr) + self.finished.emit() + +class SetDateTimeManualWorker(QObject): + global new_date_time + finished = pyqtSignal() + returncode = pyqtSignal(object) + def run(self): + try: + time.sleep(1) + # Set the new date and time + subprocess.run(["sudo", "date", new_date_time], check=True) + self.returncode.emit("OK") + self.finished.emit() + return + except subprocess.CalledProcessError as e: + self.returncode.emit(e.stderr) + self.finished.emit() +class SetDateTimeAutoWorker(QObject): + finished = pyqtSignal() + returncode = pyqtSignal(object) + def run(self): + try: + time.sleep(1) + # Set the date and time automatically using NTP + subprocess.run(["sudo", "ntpdate", "-v", "-b", + "-u", "pool.ntp.org"], check=True) + self.returncode.emit("OK") + self.finished.emit() + return + except subprocess.CalledProcessError as e: + self.returncode.emit(e.stderr) + self.finished.emit() class DateTimeWindow(QMainWindow): def __init__(self): super().__init__() + # Create Date Time spinner + self.dt_spinner = QLabel() + self.dt_spinner.setMovie(QMovie(os.path.dirname(__file__) + "/Resources/spinner.gif")) + self.dt_spinner.movie().start() + self.dt_spinner.setAlignment(Qt.AlignHCenter | Qt.AlignTop) + self.dt_spinner.hide() + + # Create Time Zone spinner + self.tz_spinner = QLabel() + self.tz_spinner.setMovie(QMovie(os.path.dirname(__file__) + "/Resources/spinner.gif")) + self.tz_spinner.movie().start() + self.tz_spinner.setAlignment(Qt.AlignHCenter | Qt.AlignTop) + self.tz_spinner.hide() + # Create tab widget self.tabs = QTabWidget() @@ -38,7 +120,8 @@ class DateTimeWindow(QMainWindow): # Set window properties self.setWindowTitle("Date and Time") - self.setMinimumSize(400, 250) + #self.setMinimumSize(400, 320) + self.setFixedSize(400, 320) # File menu self.file_menu = self.menuBar().addMenu("File") @@ -75,7 +158,7 @@ class DateTimeWindow(QMainWindow): self.edit_menu.addAction("Set Date and Time automatically", self.set_date_time_auto) - self.edit_menu.addAction("Set time zone automatically", + self.edit_menu.addAction("Set Time Zone automatically", self.set_time_zone_auto) # Help menu @@ -83,20 +166,6 @@ class DateTimeWindow(QMainWindow): about_action = self.help_menu.addAction("About", self.show_about) about_action.setShortcut("Ctrl+?") - # Create spinner - self.spinner = QLabel(self) - # Generated using http://ajaxload.info/ - self.spinner.setMovie( - QMovie(os.path.dirname(__file__) + "/Resources/spinner.gif")) - self.spinner.movie().start() - self.spinner.hide() - self.spinner.setAlignment(Qt.AlignCenter) - # self.spinner.setFixedSize(16, 16) - self.spinner.move(0, self.height() - self.spinner.height()) - # Connect to the resizeEvent of the window to update the position of the spinner - self.resizeEvent = lambda event: self.spinner.move( - -20, self.height() - self.spinner.height() - 10) - def send_shortcut(self, key_code, modifier): # Send shortcut to currently focused widget like Ctrl+C, Ctrl+V, etc. widget = self.focusWidget() @@ -127,12 +196,18 @@ class DateTimeWindow(QMainWindow): self.grp_set_date_time.setLayout(self.vbox_set_date_time) self.btn_set_date_time_auto = QPushButton( - "Set Date and Time automatically", self) + "Set Date and Time Automatically", self) self.grp_set_date_time_auto = QGroupBox("Date and Time options", self) self.vbox_set_date_time_auto = QVBoxLayout() self.vbox_set_date_time_auto.addWidget(self.btn_set_date_time_auto) self.grp_set_date_time_auto.setLayout(self.vbox_set_date_time_auto) + + self.grp_dt_spinner = QGroupBox("", self) + self.grp_dt_spinner.setStyleSheet("border : 0;") + self.vbox_dt_spinner = QVBoxLayout() + self.vbox_dt_spinner.addWidget(self.dt_spinner) + self.grp_dt_spinner.setLayout(self.vbox_dt_spinner) # Connect signals to slots self.btn_set_date_time_manual.clicked.connect( @@ -142,10 +217,9 @@ class DateTimeWindow(QMainWindow): # Create grid layout for date/time tab self.grid_layout_date_time = QGridLayout() - self.grid_layout_date_time.addWidget( - self.grp_set_date_time, 2, 0, 1, 2) - self.grid_layout_date_time.addWidget( - self.grp_set_date_time_auto, 3, 0, 1, 2) + self.grid_layout_date_time.addWidget(self.grp_set_date_time, 2, 0, 1, 2) + self.grid_layout_date_time.addWidget(self.grp_set_date_time_auto, 3, 0, 1, 2) + self.grid_layout_date_time.addWidget(self.grp_dt_spinner, 4, 0, 1, 2) self.grid_layout_date_time.setRowStretch(4, 1) # Create date/time tab and set layout @@ -153,22 +227,45 @@ class DateTimeWindow(QMainWindow): self.date_time_tab.setLayout(self.grid_layout_date_time) def create_time_zone_tab(self): + global new_time_zone # Create widgets - self.btn_set_time_zone_auto = QPushButton( - "Set time zone automatically", self) - - self.grp_set_time_zone = QGroupBox("Set time zone", self) + self.tz_set_time_zone_manual = QDateTimeEdit(self) + self.tz_set_time_zone_manual = QComboBox(self) + timezones = sorted(pytz.all_timezones) + new_time_zone = pytz.timezone(subprocess.check_output('cat /var/db/zoneinfo', shell=True).strip().decode()) + self.tz_set_time_zone_manual.addItems(timezones) + self.tz_set_time_zone_manual.setCurrentText(f'{new_time_zone}') + self.tz_set_time_zone_manual.currentTextChanged.connect(self.update_time_zone_value) + + self.btn_set_time_zone_manual = QPushButton("Set Time Zone", self) + self.grp_set_time_zone_manual = QGroupBox("Set Time Zone", self) + self.vbox_set_time_zone_manual = QVBoxLayout() + self.vbox_set_time_zone_manual.addWidget(self.tz_set_time_zone_manual) + self.vbox_set_time_zone_manual.addWidget(self.btn_set_time_zone_manual) + self.grp_set_time_zone_manual.setLayout(self.vbox_set_time_zone_manual) + + self.btn_set_time_zone_auto = QPushButton("Set Time Zone Automatically", self) + self.grp_set_time_zone = QGroupBox("Time Zone Options", self) self.vbox_set_time_zone = QVBoxLayout() self.vbox_set_time_zone.addWidget(self.btn_set_time_zone_auto) self.grp_set_time_zone.setLayout(self.vbox_set_time_zone) + self.grp_tz_spinner = QGroupBox("", self) + self.grp_tz_spinner.setStyleSheet("border : 0;") + self.vbox_tz_spinner = QVBoxLayout() + self.vbox_tz_spinner.addWidget(self.tz_spinner) + self.grp_tz_spinner.setLayout(self.vbox_tz_spinner) + # Connect signals to slots + self.btn_set_time_zone_manual.clicked.connect(self.set_time_zone_manual) self.btn_set_time_zone_auto.clicked.connect(self.set_time_zone_auto) # Create grid layout for time zone tab self.grid_layout_time_zone = QGridLayout() - self.grid_layout_time_zone.addWidget(self.grp_set_time_zone, 1, 0) - self.grid_layout_time_zone.setRowStretch(2, 1) + self.grid_layout_time_zone.addWidget(self.grp_set_time_zone_manual, 1, 0) + self.grid_layout_time_zone.addWidget(self.grp_set_time_zone, 2, 0) + self.grid_layout_time_zone.addWidget(self.grp_tz_spinner, 3, 0) + self.grid_layout_time_zone.setRowStretch(3, 1) # Create time zone tab and set layout self.time_zone_tab = QWidget(self) @@ -181,52 +278,155 @@ class DateTimeWindow(QMainWindow): capture_output=True, text=True, check=True) return QDateTime.fromString(result.stdout.strip(), "yyyy-MM-dd hh:mm:ss") except subprocess.CalledProcessError as e: - self.show_error_dialog("Error getting current Date and Time", + self.show_error_dialog("Error getting current Date and Time!", f"An error occurred while getting the current Date and Time: {e.stderr}") return QDateTime.currentDateTime() + def check_date_time_manual_result(self, ret_code): + if ret_code == "OK": + self.dt_set_date_time_manual.setDateTime(self.get_current_datetime()) + else: + self.show_error_dialog("Error setting Date and Time automatically!", + f"An error occurred while setting the Date and Time automatically: {ret_code}") + + def update_time_zone_combo_value(self): + global new_time_zone + self.tz_set_time_zone_manual.setCurrentText(f'{new_time_zone}') + def set_date_time_manual(self): - self.spinner.show() + global new_date_time # 202304151224.10 = Sa. 15 Apr. 2023 12:24:10 CEST - new_date_time = self.dt_set_date_time_manual.dateTime().toString( - "yyyyMMddhhmm.ss") + new_date_time = self.dt_set_date_time_manual.dateTime().toString("yyyyMMddhhmm.ss") print(new_date_time) - try: - # Set the new date and time - subprocess.run(["sudo", "date", new_date_time], check=True) - self.dt_set_date_time_manual.setDateTime( - self.get_current_datetime()) - except subprocess.CalledProcessError as e: - self.show_error_dialog("Error setting Date and Time", - f"An error occurred while setting the Date and Time: {e.stderr}") - self.spinner.hide() - - def set_date_time_auto(self): - self.spinner.show() - # Wait one second before setting the date and time automatically - time.sleep(1) - try: - # Set the date and time automatically using NTP - subprocess.run(["sudo", "ntpdate", "-v", "-b", - "-u", "pool.ntp.org"], check=True) - self.dt_set_date_time_manual.setDateTime( - self.get_current_datetime()) - except subprocess.CalledProcessError as e: - self.show_error_dialog("Error setting Date and Time automatically", - f"An error occurred while setting the Date and Time automatically: {e.stderr}") + # create a QThread object + self.thread = QThread(parent=self) + # create a worker object + self.worker = SetDateTimeManualWorker() + # move worker to the thread + self.worker.moveToThread(self.thread) + # connect signals and slots + self.thread.started.connect(self.worker.run) + self.worker.finished.connect(self.thread.quit) + # set actions to execute when thread is done + self.worker.returncode.connect(self.hide_dt_spinner) + self.worker.returncode.connect(self.hide_tz_spinner) + self.worker.returncode.connect(self.enable_window_widgets) + self.worker.returncode.connect(self.check_date_time_manual_result) + # disable button and show spinner + self.disable_window_widgets() + self.show_dt_spinner() + # start the thread + self.thread.start() + + def update_time_zone_value(self, timezone): + global new_time_zone + new_time_zone = timezone + + def check_time_zone_manual_result(self, ret_code): + if ret_code != "OK": + self.show_error_dialog("Error setting Time Zone automatically!", + f"An error occurred while setting the Date and Time automatically: {ret_code}") + + def set_time_zone_manual(self): + # create a QThread object + self.thread = QThread(parent=self) + # create a worker object + self.worker = SetTimeZoneManualWorker() + # move worker to the thread + self.worker.moveToThread(self.thread) + # connect signals and slots + self.thread.started.connect(self.worker.run) + self.worker.finished.connect(self.thread.quit) + # set actions to execute when thread is done + self.worker.returncode.connect(self.set_date_time_auto) + #self.worker.returncode.connect(self.hide_tz_spinner) + #self.worker.returncode.connect(self.enable_window_widgets) + self.worker.returncode.connect(self.check_time_zone_manual_result) + # disable button and show spinner + self.disable_window_widgets() + self.show_tz_spinner() + # start the thread + self.thread.start() + + # show date time spinner wrapper + def show_dt_spinner(self): + self.dt_spinner.show() + + # show time zone spinner wrapper + def show_tz_spinner(self): + self.tz_spinner.show() + + # hide date time spinner wrapper + def hide_dt_spinner(self): + self.dt_spinner.hide() + + # hide date time spinner wrapper + def hide_tz_spinner(self): + self.tz_spinner.hide() + + # enable all widgets + def enable_window_widgets(self): + self.setEnabled(True) + + # disable all widgets + def disable_window_widgets(self): + self.setEnabled(False) + + def check_date_time_auto_result(self, ret_code): + if ret_code == "OK": + self.dt_set_date_time_manual.setDateTime(self.get_current_datetime()) + else: + self.show_error_dialog("Error setting Date and Time automatically!", + f"An error occurred while setting the Date and Time automatically: {ret_code}") # Restart the timer if it is not running if not self.timer.isActive(): self.timer.start() - self.spinner.hide() + def check_time_zone_auto_result(self, ret_code): + if ret_code != "OK": + self.show_error_dialog("Error setting Time Zone automatically!", + f"An error occurred while setting the Time Zone automatically: {ret_code}") + + def set_date_time_auto(self): + # create a QThread object + self.thread = QThread(parent=self) + # create a worker object + self.worker = SetDateTimeAutoWorker() + # move worker to the thread + self.worker.moveToThread(self.thread) + # connect signals and slots + self.thread.started.connect(self.worker.run) + self.worker.finished.connect(self.thread.quit) + # set actions to execute when thread is done + self.worker.returncode.connect(self.hide_dt_spinner) + self.worker.returncode.connect(self.hide_tz_spinner) + self.worker.returncode.connect(self.update_time_zone_combo_value) + self.worker.returncode.connect(self.enable_window_widgets) + self.worker.returncode.connect(self.check_date_time_auto_result) + # disable button and show spinner + self.disable_window_widgets() + self.show_dt_spinner() + # start the thread + self.thread.start() + def set_time_zone_auto(self): - try: - # Set the time zone automatically using NTP - subprocess.run(["sudo", "ntpdate", "-v", "-b", - "-u", "pool.ntp.org"], check=True) - except subprocess.CalledProcessError as e: - self.show_error_dialog("Error setting time zone automatically", - f"An error occurred while setting the time zone automatically: {e.stderr}") + # create a QThread object + self.thread = QThread(parent=self) + # create a worker object + self.worker = SetTimeZoneAutoWorker() + # move worker to the thread + self.worker.moveToThread(self.thread) + # connect signals and slots + self.thread.started.connect(self.worker.run) + self.worker.finished.connect(self.thread.quit) + # set actions to execute when thread is done + self.worker.returncode.connect(self.set_date_time_auto) + self.worker.returncode.connect(self.check_time_zone_auto_result) + # disable button and show spinner + self.disable_window_widgets() + self.show_tz_spinner() + # start the thread + self.thread.start() def show_error_dialog(self, title, message): msg_box = QMessageBox() @@ -249,11 +449,13 @@ class DateTimeWindow(QMainWindow): msg.setDetailedText(data) msg.setText("