Weltzeituhr - Erstes Programmierprojekt

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
herminator
User
Beiträge: 1
Registriert: Freitag 27. Juni 2025, 15:31

Hallo zusammen,

vorab: Ich bin Anfänger in Python und generell noch neu in der Welt des Programmierens.

Ich möchte schon seit langem lernen, die Basics einer Programmiersprache zu lernen. Da ich am besten mit einem konkreten Ziel lerne und für ein anderes Hobby eine entsprechende Anwendung dafür hätte, habe ich in den letzten Monaten mit Hilfe von Internettutorials und ChatGPT an einer Weltzeituhr programmiert. Konkret geht es um eine grafische Anzeige mehrerer Uhren nebeneinander, die jeweils unterschiedliche Zeitzonen darstellen. Die Uhr soll später einmal auf einem Raspberry Pi laufen.

Vielleicht hat ja jemand Lust, sich meinen Code anzuschauen und ein Feedback zu geben, insbesondere dazu, ob ich gewisse Dinge zu umständlich oder gar falsch gelöst habe.
Ich habe tkinter als GUI benutzt. Kern ist die TimeZoneClockApp-Klasse mit der create_clocks-Methode. Die einzelnen Uhren sind Instanzen der Clock-Klasse.

Ich bin nämlich schon auf ein Problem gestoßen, bei dem ich unsicher bin, ob es an den eingesetzten Tools liegt oder an meiner Umsetzung: Während die Anzeige auf meinem Windows-Rechner flüssig und synchron läuft, sind die Sekundenzeiger meiner Uhren auf dem Raspberry Pi leider nicht mehr gleichmäßig, sie springen komplett asynchron.

Vielen Dank,

Martin

Code: Alles auswählen

import os
import sys
from datetime import datetime, timezone, timedelta
import math
import json
import copy
import pytz
import pyautogui
import ntplib
from screeninfo import get_monitors
from PIL import Image, ImageTk
import threading
from typing import Optional, List

import tkinter as tk
from tkinter import ttk, messagebox


class TimeZoneClockApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Time Zone Clock")
        self.icon = tk.PhotoImage(file=resource_path("icon.png"))
        self.iconphoto(True, self.icon)
        self.version = "1.9"

        # Default values used in settings.json if file is not found
        self.sync_interval = -1  # in minutes or -1 for single sync
        self.autosync_active = False
        self.write_log = False
        self.start_fullscreen = False
        self.ntp_servers = [
            "0.de.pool.ntp.org",
            "1.de.pool.ntp.org",
            "2.de.pool.ntp.org",
            "3.de.pool.ntp.org",
        ]

        self.clocksettings = [
            {
                "name": "UTC",
                "timezone": "UTC",
                "color_background": "white",
                "color_frame": "grey",
                "color_name": "grey",
                "color_text": "grey",
                "color_clockface": "white",
                "color_clockframe": "grey",
                "color_clockhands": "grey",
                "color_secondhand": "orange",
            },
            {
                "name": "New York",
                "timezone": "America/New_York",
                "color_background": "white",
                "color_frame": "grey",
                "color_name": "grey",
                "color_text": "grey",
                "color_clockface": "white",
                "color_clockframe": "grey",
                "color_clockhands": "grey",
                "color_secondhand": "orange",
            },
            {
                "name": "Berlin",
                "timezone": "Europe/Berlin",
                "color_background": "white",
                "color_frame": "grey",
                "color_name": "grey",
                "color_text": "grey",
                "color_clockface": "white",
                "color_clockframe": "grey",
                "color_clockhands": "grey",
                "color_secondhand": "orange",
            },
            {
                "name": "Tokyo",
                "timezone": "Asia/Tokyo",
                "color_background": "white",
                "color_frame": "grey",
                "color_name": "grey",
                "color_text": "grey",
                "color_clockface": "white",
                "color_clockframe": "grey",
                "color_clockhands": "grey",
                "color_secondhand": "orange",
            },
        ]
        self.color_background = "white"
        self.x = 10
        self.y = 10
        self.last_monitor_setup = None

        self.config_file = "settings.json"
        self.read_settings_from_json()

        self.autosync_timer = None
        self.offset = tk.DoubleVar()
        self.offset.set(0.0)
        self.fullscreen_active = False

        self.ntp_status_text = ""
        self.autosync_status_text = ""

        self.clockwidth = 200
        self.clockheight = 300

        self.clocksdialog: Optional[ClocksDialog] = None
        self.serverdialog: Optional[NTPServerDialog] = None
        self.settingsdialog: Optional[SettingsDialog] = None
        self.about_window: Optional[tk.Toplevel] = None

        self.bind("<Button-3>", self.show_menu_on_rightclick)
        self.bind("<F11>", self.toggle_fullscreen)

        self.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.ensure_window_position()
        self.create_clockframe()
        self.create_clocks()
        self.autosync()
        self.keep_active()

        if self.start_fullscreen:
            self.toggle_fullscreen()

    def read_settings_from_json(self):
        try:
            with open(self.config_file, "r", encoding="utf-8") as file:
                settings = json.load(file)
                self.sync_interval = settings.get("sync_interval", self.sync_interval)
                self.autosync_active = settings.get("autosync_active", self.autosync_active)
                self.write_log = settings.get("write_log", self.write_log)
                self.start_fullscreen = settings.get("fullscreen", self.start_fullscreen)
                self.ntp_servers = settings.get("ntp_servers", self.ntp_servers)
                self.clocksettings = settings.get("clocksettings", self.clocksettings)
                self.color_background = settings.get("color_background", self.color_background)
                self.x = settings.get("x", self.x)
                self.y = settings.get("y", self.y)
                self.last_monitor_setup = settings.get("last_monitor_setup", self.last_monitor_setup)
        except FileNotFoundError:
            pass
        except json.JSONDecodeError:
            pass

    def write_settings_to_json(self):
        settings = {
            "sync_interval": self.sync_interval,
            "autosync_active": self.autosync_active,
            "write_log": self.write_log,
            "fullscreen": self.start_fullscreen,
            "ntp_servers": self.ntp_servers,
            "clocksettings": self.clocksettings,
            "color_background": self.color_background,
            "x": self.x,
            "y": self.y,
            "last_monitor_setup": self.last_monitor_setup,
            "version": self.version,
        }
        with open(self.config_file, "w", encoding="utf-8") as file:
            json.dump(settings, file, indent=4, ensure_ascii=False)

    def update_position(self, event):
        self.x = self.winfo_x()
        self.y = self.winfo_y()

    def ensure_window_position(self):
        new_monitor_setup = str(get_monitors())
        if self.last_monitor_setup != new_monitor_setup:
            self.x = 25
            self.y = 25
            self.last_monitor_setup = new_monitor_setup

    def on_closing(self):
        self.write_settings_to_json()
        if self.autosync_timer is not None:
            self.autosync_timer.cancel()
        self.destroy()

    def create_clockframe(self):
        self.frame = tk.Frame(self)
        self.frame.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
        self.frame.config(bg=self.color_background)

        screen_width = max(self.winfo_screenwidth(), 880)
        clocks_per_row = screen_width // (self.clockwidth + 20)

        self.notification_label = tk.Label(
            self.frame,
            text="",
            bg=self.color_background,
            fg="black",
            font=("Helvetica", 8),
        )
        self.notification_label.grid(
            row=0,
            column=0,
            padx=10,
            pady=(0, 0),
            columnspan=min(len(self.clocksettings), clocks_per_row),
            sticky="w",
        )

        self.status_symbol = tk.Label(self.frame, bg=None)
        self.status_symbol.config(text="●", font=("Helvetica", 12), fg="gray", bg=self.color_background)

        self.status_symbol.bind("<Enter>", self.show_tooltip)
        self.status_symbol.bind("<Leave>", self.hide_tooltip)
        self.status_symbol.bind("<Button-1>", self.toggle_autosync)

        self.status_symbol.grid(
            row=0,
            column=min(len(self.clocksettings), clocks_per_row) - 1,
            padx=10,
            pady=(0, 0),
            sticky="e",
        )

    def create_clocks(self):
        try:
            for widget in self.frame.winfo_children():
                if isinstance(widget, Clock):
                    widget.destroy()
        except Exception:
            pass

        screen_width = max(self.winfo_screenwidth(), 880)
        clocks_per_row = screen_width // (self.clockwidth + 20)

        self.clocks = {}

        for i, clock in enumerate(self.clocksettings):
            clock = Clock(
                self.frame,
                self.clocksettings[i],
                self.offset,
                width=self.clockwidth,
                height=self.clockheight,
            )
            row = i // clocks_per_row + 1
            column = i % clocks_per_row
            clock.grid(row=row, column=column, padx=10, pady=(0, 10))
            self.clocks[self.clocksettings[i]["name"]] = clock

        # resize the window to fit the clocks
        self.update_idletasks()
        self.geometry(
            f"{self.frame.winfo_width()}x{self.frame.winfo_height()}+{self.x}+{self.y}"
        )
        self.bind("<Configure>", self.update_position)

        # adjust columnspan of the notification label
        self.notification_label.grid(columnspan=min(len(self.clocksettings), clocks_per_row))

    def autosync(self):
        """Automatic synchronization"""
        if self.autosync_active:
            # kill the timer if it is still running
            if self.autosync_timer is not None:
                self.autosync_timer.cancel()
                self.autosync_timer = None
            self.autosync_status_text = "Autosync is active."
            self.ntp_sync()
            if self.sync_interval > 0:
                self.autosync_timer = threading.Timer(self.sync_interval * 60, self.autosync)
                self.autosync_timer.start()
        else:
            self.autosync_status_text = "Autosync is stopped."
            self.reset_status_symbol()

    def ntp_sync(self):
        """Compare the system time with an NTP server"""
        ntp_client = ntplib.NTPClient()
        for ntp_server in self.ntp_servers:
            try:
                response = ntp_client.request(ntp_server)
                ntp_server_time = datetime.fromtimestamp(response.tx_time, timezone.utc)
                log_message = f"{datetime.now().astimezone().strftime('%Y-%m-%d %H:%M:%S%z')}: Sync with Server: {ntp_server}, Leap: {ntplib.leap_to_text(response.leap)}, Version: {response.version}, Mode: {ntplib.mode_to_text(response.mode)}, Stratum: {ntplib.stratum_to_text(response.stratum)}, Poll: {response.poll}, Precision: {response.precision}, Root Delay: {response.root_delay}, Root Dispersion: {response.root_dispersion}, Reference ID: {ntplib.ref_id_to_text(response.ref_id, response.stratum)}, Reference Timestamp: {datetime.fromtimestamp(response.ref_time, timezone.utc)}, Originate Timestamp: {datetime.fromtimestamp(response.orig_time, timezone.utc)}, Receive Timestamp: {datetime.fromtimestamp(response.recv_time, timezone.utc)}, Transmit Timestamp: {datetime.fromtimestamp(response.tx_time, timezone.utc)}, Destination Timestamp: {datetime.fromtimestamp(response.dest_time, timezone.utc)}, Offset: {response.offset}, Delay: {response.delay}\n"
                if self.write_log:
                    with open("ntp_sync.log", "a") as file:
                        file.write(log_message)
                self.offset.set(response.offset)
                if self.offset.get() > 0:
                    offset_sign = "slow"
                elif self.offset.get() < 0:
                    offset_sign = "fast"
                else:
                    offset_sign = "correct"

                if abs(self.offset.get()) >= 0 and abs(self.offset.get()) < 1:
                    self.ntp_status_text = f"Last synchronization on {ntp_server_time.strftime('%Y-%m-%d')} at {ntp_server_time.strftime('%H:%M:%S %Z')} using NTP-server '{ntp_server}'. Offset: {self.offset.get():.3f} s (Computer clock is {offset_sign}). Delay: {response.delay:.3f} s."
                    self.notification_label.config(text=self.ntp_status_text, bg=None)
                    self.after(5000, self.clear_notification_label)
                    # show a green dot in the notification area
                    self.status_symbol.config(text="●", font=("Helvetica", 12), fg="green3")
                    if not self.autosync_active:
                        self.after(5000, self.reset_status_symbol)
                elif abs(self.offset.get()) >= 1:
                    self.ntp_status_text = f"System time is off by more than 1 second. Last synchronization on {ntp_server_time.strftime('%Y-%m-%d')} at {ntp_server_time.strftime('%H:%M:%S %Z')} using NTP-server '{ntp_server}'. Offset: {self.offset.get():.3f} s (Computer clock is {offset_sign}). Delay: {response.delay:.3f} s."
                    self.notification_label.config(text=self.ntp_status_text, bg=None)
                    self.after(5000, self.clear_notification_label)
                    # show a yellow dot in the notification area
                    self.status_symbol.config(text="●", font=("Helvetica", 12), fg="yellow")
                break  # Exit the loop if successful
            except Exception as e:
                last_exception = e
                if self.write_log:
                    with open("ntp_sync.log", "a") as file:
                        file.write(
                            f"{datetime.now().astimezone().strftime('%Y-%m-%d %H:%M:%S%z')}: Failed to sync with NTP server {ntp_server}: {str(e)}\n"
                        )
                continue
        else:
            self.ntp_status_text = f"Failed to check system time: {last_exception}"
            self.notification_label.config(text=self.ntp_status_text, fg="red")
            self.after(5000, self.clear_notification_label)
            # show a red dot in the notification area
            self.status_symbol.config(text="●", font=("Helvetica", 12), fg="red")

    def clear_notification_label(self):
        self.notification_label.config(text="", bg=None, fg="black")

    def reset_status_symbol(self):
        self.status_symbol.config(text="●", font=("Helvetica", 12), fg="gray")

    def toggle_autosync(self, event):
        if self.autosync_active:
            self.stop_sync()
        else:
            self.start_sync()

    def show_tooltip(self, event):
        self.tooltip = tk.Toplevel(self)
        self.tooltip.wm_overrideredirect(True)
        self.tooltip.wm_geometry(f"+{event.x_root + 10}+{event.y_root + 10}")
        label = tk.Label(
            self.tooltip,
            text=f"{self.autosync_status_text} {self.ntp_status_text}",
            background=None,
            relief="solid",
            borderwidth=1,
            font=("Helvetica", 8),
        )
        label.pack()

    def hide_tooltip(self, event):
        if self.tooltip:
            self.tooltip.destroy()

    def show_context_menu(self, event):
        self.context_menu = tk.Menu(self, tearoff=0)
        self.context_menu.add_command(label="Clocks", command=self.open_clocks_dialog)
        self.context_menu.add_separator()
        self.context_menu.add_command(label="NTP Server", command=self.open_server_dialog)
        self.context_menu.add_separator()
        self.context_menu.add_command(label="Manual sync", command=self.manual_sync)
        self.context_menu.add_command(label="Stop automatic sync", command=self.stop_sync)
        self.context_menu.add_command(label="Start automatic sync", command=self.start_sync)
        self.context_menu.add_command(label="Show sync status", command=self.show_sync_status)
        self.context_menu.add_command(label="Show log file", command=self.show_log_file)
        self.context_menu.add_separator()
        self.context_menu.add_command(label="Settings", command=self.open_settings)
        self.context_menu.add_separator()
        self.context_menu.add_command(label="Fullscreen", command=self.toggle_fullscreen)
        self.context_menu.add_separator()
        self.context_menu.add_command(label="About", command=self.open_about_window)
        self.context_menu.add_separator()
        self.context_menu.add_command(label="Exit", command=self.on_closing)
        self.context_menu.post(event.x_root, event.y_root)


    def show_menu_on_rightclick(self, event):
            self.show_context_menu(event)


    def open_clocks_dialog(self):
        if self.clocksdialog is not None and self.clocksdialog.winfo_exists():
            self.clocksdialog.lift()
        else:
            self.clocksdialog = ClocksDialog(self, "Change Clocks", self.clocksettings)

    def open_server_dialog(self):
        if self.serverdialog is not None and self.serverdialog.winfo_exists():
            self.serverdialog.lift()
        else:
            self.serverdialog = NTPServerDialog(self, "Change NTP Server", self.ntp_servers)

    def open_settings(self):
        if self.settingsdialog is not None and self.settingsdialog.winfo_exists():
            self.settingsdialog.lift()
        else:
            self.settingsdialog = SettingsDialog(self, "Settings")

    def show_log_file(self):
        try:
            os.startfile("ntp_sync.log")
        except Exception as e:
            messagebox.showerror("Error", f"Failed to open log file: {e}")

    def stop_sync(self):
        """Stop the automatic synchronization"""
        if self.autosync_active:
            if self.autosync_timer is not None:
                self.autosync_timer.cancel()
                self.autosync_timer = None
            self.autosync_active = False
            self.autosync_status_text = "Autosync is stopped."
            self.reset_status_symbol()
            self.notification_label.config(text="Autosync is stopped.", bg=None)
            self.after(5000, self.clear_notification_label)
        else:
            self.notification_label.config(text="Autosync is already stopped.", bg=None)
            self.after(5000, self.clear_notification_label)

    def start_sync(self):
        """Start the automatic synchronization"""
        if not self.autosync_active:
            self.autosync_active = True
            self.notification_label.config(text="Autosync is active.", bg=None)
            self.after(5000, self.clear_notification_label)
            self.autosync()
        else:
            self.notification_label.config(text="Autosync is already active.", bg=None)
            self.after(5000, self.clear_notification_label)

    def manual_sync(self):
        """Manual synchronization"""
        self.ntp_sync()

    def toggle_fullscreen(self, event=None):
        self.fullscreen_active = not self.fullscreen_active
        self.attributes("-fullscreen", self.fullscreen_active)

        if self.fullscreen_active:
            #self.config(menu=tk.Menu(self))  # Remove the menubar
            self.bind("<Escape>", self.toggle_fullscreen)
        else:
            #self.config(menu=self.menubar)  # Restore the menubar
            self.unbind("<Escape>")

    def keep_active(self):
        pyautogui.press("shift")
        self.after(60000, self.keep_active)

    def show_sync_status(self):
        messagebox.showinfo(
            "Sync Status",
            f"{self.autosync_status_text} {self.ntp_status_text}",
        )

    def open_about_window(self):
        if self.about_window is not None and self.about_window.winfo_exists():
            self.about_window.lift()
        else:
            self.about_window = tk.Toplevel(self)
            self.about_window.title("About")
            self.about_window.iconphoto(True, tk.PhotoImage(file=resource_path("icon.png")))    
            self.about_window.geometry("300x200")
            self.about_window.resizable(False, False)
            about_label = tk.Label(
                self.about_window,
                text=f"Time Zone Clock\nVersion: {self.version} \n\n Time Zone Database Version: {pytz.OLSON_VERSION}",
                font=("Helvetica", 12),
            )
            about_label.pack()

class Clock(tk.Canvas):
    def __init__(
        self,
        parent: tk.Frame,
        settings: dict[str, str],
        offset: tk.DoubleVar,
        width: int,
        height: int,
    ):
        super().__init__(parent, width=width, height=height)
        self.city = settings["name"]
        self.timezone = settings["timezone"]
        self.color_background = settings["color_background"]
        self.color_frame = settings["color_frame"]
        self.color_name = settings["color_name"]
        self.color_text = settings["color_text"]
        self.color_clockface = settings["color_clockface"]
        self.color_clockframe = settings["color_clockframe"]
        self.color_clockhands = settings["color_clockhands"]
        self.color_secondhand = settings["color_secondhand"]

        # Offset defined as difference between NTP server time and local system time
        self.offset = offset
        self.width = width
        self.height = height
        self.radius = self.width * 0.45
        self.centercanvas = (self.width // 2, self.height // 2)
        self.centerclock = (self.centercanvas[0], self.centercanvas[1] - 20)
        self.config(
            bg=self.color_background,
            highlightbackground=self.color_frame,
            highlightthickness=1,
        )

        self.city_text = self.create_text(
            self.centerclock[0],
            self.centerclock[1] - self.radius - 20,
            text="",
            font=("Helvetica", 12, "bold"),
            fill=self.color_name,
        )
        self.date_text = self.create_text(
            self.centerclock[0],
            self.centerclock[1] + self.radius + 25,
            text="",
            font=("Helvetica", 11),
            fill=self.color_text,
        )
        self.time_text = self.create_text(
            self.centerclock[0],
            self.centerclock[1] + self.radius + 45,
            text="",
            font=("Helvetica", 11),
            fill=self.color_text,
        )
        self.utc_offset_text = self.create_text(
            self.centerclock[0],
            self.centerclock[1] + self.radius + 65,
            text="",
            font=("Helvetica", 11),
            fill=self.color_text,
        )

        self.draw_clockface()
        self.update_clock()

    def draw_clockface(self):
        self.create_oval(
            self.centerclock[0] - self.radius,
            self.centerclock[1] - self.radius,
            self.centerclock[0] + self.radius,
            self.centerclock[1] + self.radius,
            fill=self.color_clockface,
            outline=self.color_clockframe,
            width=1,
        )

        for i in range(60):  # Draw ticks
            angle = math.radians(i * 6 - 90)
            x_start = self.centerclock[0] + self.radius * 0.95 * math.cos(angle)
            y_start = self.centerclock[1] + self.radius * 0.95 * math.sin(angle)
            if i % 5 == 0:  # Draw hour ticks
                x_end = self.centerclock[0] + self.radius * 0.85 * math.cos(angle)
                y_end = self.centerclock[1] + self.radius * 0.85 * math.sin(angle)
                width = 2
            else:  # Draw minute ticks
                x_end = self.centerclock[0] + self.radius * 0.90 * math.cos(angle)
                y_end = self.centerclock[1] + self.radius * 0.90 * math.sin(angle)
                width = 1
            self.create_line(x_start, y_start, x_end, y_end, width=width, fill=self.color_clockframe)
        for i in range(12):  # Draw numbers
            angle = math.radians(i * 30)
            x_num = self.centerclock[0] + self.radius * 0.72 * math.sin(angle)
            y_num = self.centerclock[1] - self.radius * 0.72 * math.cos(angle)
            self.create_text(
                x_num,
                y_num,
                text=str(i if i != 0 else 12),
                font=("Helvetica", 12),
                fill=self.color_clockframe,
            )

    def update_clock(self):
        # check if timezone is valid
        try:
            pytz.timezone(self.timezone)
        except pytz.exceptions.UnknownTimeZoneError:
            self.itemconfig(self.city_text, text="Invalid timezone")
            self.itemconfig(self.time_text, text="")
            self.itemconfig(self.date_text, text="")
            self.itemconfig(self.utc_offset_text, text="")
            return

        now = datetime.now(pytz.timezone(self.timezone)) + timedelta(seconds=self.offset.get())

        self.delete("hands")
        self.draw_hand(
            now.hour % 12 * 30 + now.minute * 0.5,
            self.radius * 0.6,
            6,
            self.color_clockhands,
        )
        self.draw_hand(now.minute * 6, self.radius * 0.9, 4, self.color_clockhands)
        self.draw_hand(now.second * 6, self.radius * 0.95, 2, self.color_secondhand)

        self.itemconfig(self.city_text, text=self.city)
        self.itemconfig(self.time_text, text=now.strftime("%H:%M:%S"))
        self.itemconfig(self.date_text, text=now.strftime("%a %Y-%m-%d"))
        self.itemconfig(self.utc_offset_text, text=f"UTC{now.strftime("%:z")}")

        # Calculate time until next second
        until_next_second = 1000 - (now.microsecond // 1000)
        # Schedule the next update
        self.after(until_next_second, self.update_clock)

    def draw_hand(self, angle, length, width, color):
        angle_rad = math.radians(angle - 90)
        x = self.centerclock[0] + length * math.cos(angle_rad)
        y = self.centerclock[1] + length * math.sin(angle_rad)
        self.create_line(
            self.centerclock[0],
            self.centerclock[1],
            x,
            y,
            width=width,
            fill=color,
            tags="hands",
        )


class ClocksDialog(tk.Toplevel):
    def __init__(self, parent: TimeZoneClockApp, title: str, clocksettings: list[dict[str, str]]):
        super().__init__(parent)
        self.title(title)
        self.iconphoto(True, tk.PhotoImage(file=resource_path("icon.png")))
        # self.geometry("400x300")
        self.resizable(False, False)
        self.parent = parent
        self.clocksettings = clocksettings
        # Make a copy of the original clocksettings to restore them if the user cancels
        self.original_clocksettings = copy.deepcopy(clocksettings)
        self.selected_clock = None

        self.label = ttk.Label(self, text="List of Clocks")
        self.label.grid(row=0, column=0, padx=5, pady=5, sticky="w", columnspan=2)

        self.listbox = tk.Listbox(self, selectmode=tk.SINGLE, width=40)
        for clock in self.clocksettings:
            self.listbox.insert(tk.END, clock["name"] + ": " + clock["timezone"])
        self.listbox.grid(row=1, column=0, padx=5, pady=5, sticky="ew", columnspan=2)


        self.move_up_button = tk.Button(self, text="Move selected clock up", command=self.move_up, width=20)
        self.move_up_button.grid(row=2, column=0, padx=5, pady=5, sticky="e")

        self.move_down_button = tk.Button(self, text="Move selected clock down", command=self.move_down, width=20)
        self.move_down_button.grid(row=3, column=0, padx=5, pady=5, sticky="e")

        self.remove_button = tk.Button(self, text="Remove selected clock", command=self.remove_clock, width=20)
        self.remove_button.grid(row=4, column=0, padx=5, pady=5, sticky="e")

        self.add_button = tk.Button(self, text="Add clock", command=self.add_clock, width=20)
        self.add_button.grid(row=5, column=0, padx=5, pady=5, sticky="e")

        self.change_button = tk.Button(self, text="Change selected clock", command=self.change_clock, width=20)
        self.change_button.grid(row=2, column=1, padx=5, pady=5, sticky="w")

        self.reset_button = tk.Button(self, text="Change all clocks", command=self.change_all_clocks, width=20)
        self.reset_button.grid(row=3, column=1, padx=5, pady=5, sticky="w")

        self.save_button = ttk.Button(self, text="Save", command=self.ok)
        self.save_button.grid(row=6, column=0, padx=5, pady=5, sticky="w")

        self.cancel_button = ttk.Button(self, text="Cancel", command=self.cancel)
        self.cancel_button.grid(row=6, column=1, padx=5, pady=5, sticky="e")

    def move_up(self):
        selected_index = self.listbox.curselection()
        if selected_index:
            index = selected_index[0]
            if index > 0:
                self.swap_items(index, index - 1)

    def move_down(self):
        selected_index = self.listbox.curselection()
        if selected_index:
            index = selected_index[0]
            if index < self.listbox.size() - 1:
                self.swap_items(index, index + 1)

    def swap_items(self, index1, index2):
        self.clocksettings[index1], self.clocksettings[index2] = (
            self.clocksettings[index2],
            self.clocksettings[index1],
        )
        self.populate_listbox()
        self.listbox.selection_set(index2)

    def populate_listbox(self):
        self.listbox.delete(0, tk.END)
        for item in self.clocksettings:
            self.listbox.insert(tk.END, item["name"] + ": " + item["timezone"])

    def remove_clock(self):
        self.selected_clock = self.listbox.curselection()
        for clock in self.selected_clock:
            del self.clocksettings[clock]
            self.populate_listbox()

    def add_clock(self):
        dialog = AddClockDialog(self, "Add Clock")
        self.wait_window(dialog)
        if dialog.newclocksettings:
            self.clocksettings.append(dialog.newclocksettings)
            self.populate_listbox()

    def change_clock(self):
        self.selected_clock = self.listbox.curselection()
        if self.selected_clock:
            dialog = ChangeClockDialog(self, "Change Clock", self.clocksettings[self.selected_clock[0]])
            self.wait_window(dialog)
            if dialog.changedclocksettings:
                self.clocksettings[self.selected_clock[0]] = dialog.changedclocksettings
                self.populate_listbox()

    def change_all_clocks(self):
        dialog = ChangeAllClocksDialog(self, "Change All Clocks", self.clocksettings)
        self.wait_window(dialog)
        if dialog.changedallclocksettings:
            self.clocksettings = dialog.changedallclocksettings
            self.populate_listbox()

    def ok(self):
        self.parent.clocksettings = self.clocksettings
        self.parent.create_clocks()
        self.destroy()

    def cancel(self):
        self.clocksettings.clear()
        # Restore the original clocksettings
        self.clocksettings.extend(self.original_clocksettings)
        self.parent.clocksettings = self.clocksettings
        self.destroy()


class AddClockDialog(tk.Toplevel):
    def __init__(self, parent: ClocksDialog, title: str):
        super().__init__(parent)
        self.title(title)
        self.iconphoto(True, tk.PhotoImage(file=resource_path("icon.png")))
        self.resizable(False, False)

        self.newclocksettings = {}

        ttk.Label(self, text="Enter city name:").grid(row=0, column=0, padx=5, pady=5)
        self.city_entry = ttk.Entry(self, width=35)
        self.city_entry.grid(row=0, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select timezone:").grid(row=1, column=0, padx=5, pady=5)
        self.timezone_combobox = ttk.Combobox(self, values=pytz.all_timezones, width=35, height=10, state="readonly")
        self.timezone_combobox.grid(row=1, column=1, padx=5, pady=5, sticky="w")
        self.timezone_combobox.current(0)

        ttk.Label(self, text="Select background color:").grid(row=2, column=0, padx=5, pady=5)
        self.color_background = tk.Entry(self, width=35)
        self.color_background.insert(0, "white")
        self.color_background.grid(row=2, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select frame color:").grid(row=3, column=0, padx=5, pady=5)
        self.color_frame = tk.Entry(self, width=35)
        self.color_frame.insert(0, "grey")
        self.color_frame.grid(row=3, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select city color:").grid(row=4, column=0, padx=5, pady=5)
        self.color_name = tk.Entry(self, width=35)
        self.color_name.insert(0, "grey")
        self.color_name.grid(row=4, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select digital date time color:").grid(row=5, column=0, padx=5, pady=5)
        self.color_text = tk.Entry(self, width=35)
        self.color_text.insert(0, "grey")
        self.color_text.grid(row=5, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select clock face color:").grid(row=6, column=0, padx=5, pady=5)
        self.color_clockface = tk.Entry(self, width=35)
        self.color_clockface.insert(0, "white")
        self.color_clockface.grid(row=6, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select clock frame color:").grid(row=7, column=0, padx=5, pady=5)
        self.color_clockframe = tk.Entry(self, width=35)
        self.color_clockframe.insert(0, "grey")
        self.color_clockframe.grid(row=7, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select clock hands color:").grid(row=8, column=0, padx=5, pady=5)
        self.color_clockhands = tk.Entry(self, width=35)
        self.color_clockhands.insert(0, "grey")
        self.color_clockhands.grid(row=8, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select second hand color:").grid(row=9, column=0, padx=5, pady=5)
        self.color_secondhand = tk.Entry(self, width=35)
        self.color_secondhand.insert(0, "orange")
        self.color_secondhand.grid(row=9, column=1, padx=5, pady=5, sticky="w")

        self.add_button = ttk.Button(self, text="Add clock", command=self.add_clock)
        self.add_button.grid(row=10, column=0, columnspan=2, padx=5, pady=5)

    def add_clock(self):
        # Validate colors
        try:
            self.winfo_rgb(self.color_background.get())
            self.winfo_rgb(self.color_frame.get())
            self.winfo_rgb(self.color_name.get())
            self.winfo_rgb(self.color_text.get())
            self.winfo_rgb(self.color_clockface.get())
            self.winfo_rgb(self.color_clockframe.get())
            self.winfo_rgb(self.color_clockhands.get())
            self.winfo_rgb(self.color_secondhand.get())
        except tk.TclError:
            messagebox.showerror("Invalid Color", "Please enter a valid color.")
            self.lift()
            return

        self.newclocksettings = {
            "name": self.city_entry.get(),
            "timezone": self.timezone_combobox.get(),
            "color_background": self.color_background.get(),
            "color_frame": self.color_frame.get(),
            "color_name": self.color_name.get(),
            "color_text": self.color_text.get(),
            "color_clockface": self.color_clockface.get(),
            "color_clockframe": self.color_clockframe.get(),
            "color_clockhands": self.color_clockhands.get(),
            "color_secondhand": self.color_secondhand.get(),
        }
        self.destroy()


# a dialog to change the selected clock
class ChangeClockDialog(tk.Toplevel):
    def __init__(self, parent: ClocksDialog, title: str, clocksettings: dict):
        super().__init__(parent)
        self.title(title)
        self.iconphoto(True, tk.PhotoImage(file=resource_path("icon.png")))
        self.resizable(False, False)

        self.changedclocksettings = copy.deepcopy(clocksettings)

        ttk.Label(self, text="Enter city name:").grid(row=0, column=0, padx=5, pady=5)
        self.city_entry = ttk.Entry(self, width=35)
        self.city_entry.insert(0, self.changedclocksettings["name"])
        self.city_entry.grid(row=0, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select timezone:").grid(row=1, column=0, padx=5, pady=5)
        self.timezone_combobox = ttk.Combobox(self, values=pytz.all_timezones, width=35, height=10, state="readonly")
        self.timezone_combobox.grid(row=1, column=1, padx=5, pady=5, sticky="w")
        self.timezone_combobox.set(self.changedclocksettings["timezone"])

        ttk.Label(self, text="Select background color:").grid(row=2, column=0, padx=5, pady=5)
        self.color_background = tk.Entry(self, width=35)
        self.color_background.insert(0, self.changedclocksettings["color_background"])
        self.color_background.grid(row=2, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select frame color:").grid(row=3, column=0, padx=5, pady=5)
        self.color_frame = tk.Entry(self, width=35)
        self.color_frame.insert(0, self.changedclocksettings["color_frame"])
        self.color_frame.grid(row=3, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select city color:").grid(row=4, column=0, padx=5, pady=5)
        self.color_name = tk.Entry(self, width=35)
        self.color_name.insert(0, self.changedclocksettings["color_name"])
        self.color_name.grid(row=4, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select digital date time color:").grid(row=5, column=0, padx=5, pady=5)
        self.color_text = tk.Entry(self, width=35)
        self.color_text.insert(0, self.changedclocksettings["color_text"])
        self.color_text.grid(row=5, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select clock face color:").grid(row=6, column=0, padx=6, pady=5)
        self.color_clockface = tk.Entry(self, width=35)
        self.color_clockface.insert(0, self.changedclocksettings["color_clockface"])
        self.color_clockface.grid(row=6, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select clock frame color:").grid(row=7, column=0, padx=7, pady=5)
        self.color_clockframe = tk.Entry(self, width=35)
        self.color_clockframe.insert(0, self.changedclocksettings["color_clockframe"])
        self.color_clockframe.grid(row=7, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select clock hands color:").grid(row=8, column=0, padx=5, pady=5)
        self.color_clockhands = tk.Entry(self, width=35)
        self.color_clockhands.insert(0, self.changedclocksettings["color_clockhands"])
        self.color_clockhands.grid(row=8, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select second hand color:").grid(row=9, column=0, padx=5, pady=5)
        self.color_secondhand = tk.Entry(self, width=35)
        self.color_secondhand.insert(0, self.changedclocksettings["color_secondhand"])
        self.color_secondhand.grid(row=9, column=1, padx=5, pady=5, sticky="w")

        self.save_button = ttk.Button(self, text="Change Clock", command=self.change_clock)
        self.save_button.grid(row=10, column=0, columnspan=2, padx=5, pady=5)

        self.reset_button = ttk.Button(self, text="Reset", command=self.reset_clock)
        self.reset_button.grid(row=11, column=0, columnspan=2, padx=5, pady=5)

    def change_clock(self):
        # Validate colors
        try:
            self.winfo_rgb(self.color_background.get())
            self.winfo_rgb(self.color_frame.get())
            self.winfo_rgb(self.color_name.get())
            self.winfo_rgb(self.color_text.get())
            self.winfo_rgb(self.color_clockface.get())
            self.winfo_rgb(self.color_clockframe.get())
            self.winfo_rgb(self.color_clockhands.get())
            self.winfo_rgb(self.color_secondhand.get())

        except tk.TclError:
            messagebox.showerror("Invalid Color", "Please enter a valid color.")
            self.lift()
            return

        self.changedclocksettings["name"] = self.city_entry.get()
        self.changedclocksettings["timezone"] = self.timezone_combobox.get()
        self.changedclocksettings["color_background"] = self.color_background.get()
        self.changedclocksettings["color_frame"] = self.color_frame.get()
        self.changedclocksettings["color_name"] = self.color_name.get()
        self.changedclocksettings["color_text"] = self.color_text.get()
        self.changedclocksettings["color_clockface"] = self.color_clockface.get()
        self.changedclocksettings["color_clockframe"] = self.color_clockframe.get()
        self.changedclocksettings["color_clockhands"] = self.color_clockhands.get()
        self.changedclocksettings["color_secondhand"] = self.color_secondhand.get()
        self.destroy()

    def reset_clock(self):
        # use standard colors
        self.changedclocksettings["color_background"] = "white"
        self.changedclocksettings["color_frame"] = "grey"
        self.changedclocksettings["color_name"] = "grey"
        self.changedclocksettings["color_text"] = "grey"
        self.changedclocksettings["color_clockface"] = "white"
        self.changedclocksettings["color_clockframe"] = "grey"
        self.changedclocksettings["color_clockhands"] = "grey"
        self.changedclocksettings["color_secondhand"] = "orange"
        self.destroy()


class ChangeAllClocksDialog(tk.Toplevel):
    def __init__(self, parent: ClocksDialog, title: str, clocksettings: list[dict]):
        super().__init__(parent)
        self.title(title)
        self.iconphoto(True, tk.PhotoImage(file=resource_path("icon.png")))
        self.resizable(False, False)
        self.parent = parent
        self.changedallclocksettings = copy.deepcopy(clocksettings)
        self.changedclocksettings = copy.deepcopy(clocksettings[0])

        ttk.Label(self, text="Select background color:").grid(row=2, column=0, padx=5, pady=5)
        self.color_background = tk.Entry(self, width=35)
        self.color_background.insert(0, self.changedclocksettings["color_background"])
        self.color_background.grid(row=2, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select frame color:").grid(row=3, column=0, padx=5, pady=5)
        self.color_frame = tk.Entry(self, width=35)
        self.color_frame.insert(0, self.changedclocksettings["color_frame"])
        self.color_frame.grid(row=3, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select city color:").grid(row=4, column=0, padx=5, pady=5)
        self.color_name = tk.Entry(self, width=35)
        self.color_name.insert(0, self.changedclocksettings["color_name"])
        self.color_name.grid(row=4, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select digital date time color:").grid(row=5, column=0, padx=5, pady=5)
        self.color_text = tk.Entry(self, width=35)
        self.color_text.insert(0, self.changedclocksettings["color_text"])
        self.color_text.grid(row=5, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select clock face color:").grid(row=6, column=0, padx=6, pady=5)
        self.color_clockface = tk.Entry(self, width=35)
        self.color_clockface.insert(0, self.changedclocksettings["color_clockface"])
        self.color_clockface.grid(row=6, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select clock frame color:").grid(row=7, column=0, padx=7, pady=5)
        self.color_clockframe = tk.Entry(self, width=35)
        self.color_clockframe.insert(0, self.changedclocksettings["color_clockframe"])
        self.color_clockframe.grid(row=7, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select clock hands color:").grid(row=8, column=0, padx=5, pady=5)
        self.color_clockhands = tk.Entry(self, width=35)
        self.color_clockhands.insert(0, self.changedclocksettings["color_clockhands"])
        self.color_clockhands.grid(row=8, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Select second hand color:").grid(row=9, column=0, padx=5, pady=5)
        self.color_secondhand = tk.Entry(self, width=35)
        self.color_secondhand.insert(0, self.changedclocksettings["color_secondhand"])
        self.color_secondhand.grid(row=9, column=1, padx=5, pady=5, sticky="w")

        self.save_button = ttk.Button(self, text="Change all clocks", command=self.change_clock)
        self.save_button.grid(row=10, column=0, columnspan=2, padx=5, pady=5)

        self.reset_button = ttk.Button(self, text="Reset all clocks", command=self.reset_clock)
        self.reset_button.grid(row=11, column=0, columnspan=2, padx=5, pady=5)

    def change_clock(self):
        # Validate colors
        try:
            self.winfo_rgb(self.color_background.get())
            self.winfo_rgb(self.color_frame.get())
            self.winfo_rgb(self.color_name.get())
            self.winfo_rgb(self.color_text.get())
            self.winfo_rgb(self.color_clockface.get())
            self.winfo_rgb(self.color_clockframe.get())
            self.winfo_rgb(self.color_clockhands.get())
            self.winfo_rgb(self.color_secondhand.get())

        except tk.TclError:
            messagebox.showerror("Invalid Color", "Please enter a valid color.")
            self.lift()
            return

        for clock in self.changedallclocksettings:
            clock["color_background"] = self.color_background.get()
            clock["color_frame"] = self.color_frame.get()
            clock["color_name"] = self.color_name.get()
            clock["color_text"] = self.color_text.get()
            clock["color_clockface"] = self.color_clockface.get()
            clock["color_clockframe"] = self.color_clockframe.get()
            clock["color_clockhands"] = self.color_clockhands.get()
            clock["color_secondhand"] = self.color_secondhand.get()
        self.destroy()

    def reset_clock(self):
        # use standard colors
        for clock in self.changedallclocksettings:
            clock["color_background"] = "white"
            clock["color_frame"] = "grey"
            clock["color_name"] = "grey"
            clock["color_text"] = "grey"
            clock["color_clockface"] = "white"
            clock["color_clockframe"] = "grey"
            clock["color_clockhands"] = "grey"
            clock["color_secondhand"] = "orange"
        self.destroy()


# new ChangeNTPServerDialog based on tk.topLevel
class NTPServerDialog(tk.Toplevel):
    def __init__(self, parent: TimeZoneClockApp, title: str, ntp_servers: list[str]):
        super().__init__(parent)
        self.title(title)
        self.iconphoto(True, tk.PhotoImage(file=resource_path("icon.png")))
        self.resizable(False, False)
        self.parent = parent
        self.ntp_servers = ntp_servers
        # Make a copy of the original ntp-servers
        self.original_ntp_servers = copy.deepcopy(ntp_servers)
        self.selected_ntp_server = None

        self.label = ttk.Label(self, text="List of NTP Servers")
        self.label.grid(row=0, column=0, padx=5, pady=5, sticky="w", columnspan=2)

        self.listbox = tk.Listbox(self, selectmode=tk.SINGLE, width=40)
        for server in self.ntp_servers:
            self.listbox.insert(tk.END, server)
        self.listbox.grid(row=1, column=0, padx=5, pady=5, sticky="ew", columnspan=2)

        self.move_up_button = tk.Button(self, text="Move selected server up", command=self.move_up, width=20)
        self.move_up_button.grid(row=2, column=0, padx=5, pady=5, sticky="e")

        self.move_down_button = tk.Button(self, text="Move selected server down", command=self.move_down, width=20)
        self.move_down_button.grid(row=3, column=0, padx=5, pady=5, sticky="e")

        self.remove_button = tk.Button(
            self,
            text="Remove selected server",
            command=self.remove_ntp_server,
            width=20,
        )
        self.remove_button.grid(row=4, column=0, padx=5, pady=5, sticky="e")

        self.add_button = tk.Button(self, text="Add server", command=self.add_ntp_server, width=20)
        self.add_button.grid(row=5, column=0, padx=5, pady=5, sticky="e")

        self.save_button = ttk.Button(self, text="Save", command=self.ok)
        self.save_button.grid(row=6, column=0, padx=5, pady=5, sticky="w")

        self.cancel_button = ttk.Button(self, text="Cancel", command=self.cancel)
        self.cancel_button.grid(row=6, column=1, padx=5, pady=5, sticky="e")

    def move_up(self):
        selected_index = self.listbox.curselection()
        if selected_index:
            index = selected_index[0]
            if index > 0:
                self.swap_items(index, index - 1)

    def move_down(self):
        selected_index = self.listbox.curselection()
        if selected_index:
            index = selected_index[0]
            if index < self.listbox.size() - 1:
                self.swap_items(index, index + 1)

    def swap_items(self, index1, index2):
        self.ntp_servers[index1], self.ntp_servers[index2] = (
            self.ntp_servers[index2],
            self.ntp_servers[index1],
        )
        self.populate_listbox()
        self.listbox.selection_set(index2)

    def populate_listbox(self):
        self.listbox.delete(0, tk.END)
        for item in self.ntp_servers:
            self.listbox.insert(tk.END, item)

    def remove_ntp_server(self):
        self.selected_ntp_server = self.listbox.curselection()
        for server in self.selected_ntp_server:
            del self.ntp_servers[server]
            self.populate_listbox()

    def add_ntp_server(self):
        dialog = AddNTPServerDialog(self, "Add NTP Server")
        self.wait_window(dialog)
        if dialog.server:
            self.ntp_servers.insert(0, dialog.server)
            self.populate_listbox()

    def ok(self):
        self.parent.ntp_servers = self.ntp_servers
        self.parent.ntp_sync()
        self.destroy()

    def cancel(self):
        self.ntp_servers.clear()
        # Restore the original ntp-servers
        self.ntp_servers.extend(self.original_ntp_servers)
        self.parent.ntp_servers = self.ntp_servers
        self.destroy()


class AddNTPServerDialog(tk.Toplevel):
    def __init__(self, parent: ClocksDialog, title: str):
        super().__init__(parent)
        self.title(title)
        self.iconphoto(True, tk.PhotoImage(file=resource_path("icon.png")))
        self.resizable(False, False)

        self.server = None

        ttk.Label(self, text="Enter NTP Server:").grid(row=0, column=0, padx=5, pady=5)
        self.server_entry = ttk.Entry(self, width=35)
        self.server_entry.grid(row=0, column=1, padx=5, pady=5, sticky="w")

        self.add_button = ttk.Button(self, text="Add server", command=self.add_server)
        self.add_button.grid(row=1, column=0, columnspan=2, padx=5, pady=5)

    def add_server(self):
        self.server = self.server_entry.get()
        self.destroy()


class SettingsDialog(tk.Toplevel):
    def __init__(self, parent: TimeZoneClockApp, title: str):
        super().__init__(parent)
        self.title(title)
        self.iconphoto(True, tk.PhotoImage(file=resource_path("icon.png")))
        self.resizable(False, False)

        self.parent = parent

        self.autosync_active_var = tk.BooleanVar(value=self.parent.autosync_active)
        self.autosync_active = ttk.Checkbutton(self, text="Autosync", variable=self.autosync_active_var)
        self.autosync_active.grid(row=0, column=0, padx=5, pady=5, sticky="w")

        self.write_log_var = tk.BooleanVar(value=self.parent.write_log)
        self.write_log = ttk.Checkbutton(self, text="Write log", variable=self.write_log_var)
        self.write_log.grid(row=1, column=0, padx=5, pady=5, sticky="w")

        self.fullscreen_var = tk.BooleanVar(value=self.parent.start_fullscreen)
        self.fullscreen = ttk.Checkbutton(self, text="Start in fullscreen mode", variable=self.fullscreen_var)
        self.fullscreen.grid(row=0, column=1, padx=5, pady=5, sticky="w")

        ttk.Label(self, text="Sync Interval (minutes):").grid(row=2, column=0, padx=5, pady=(5,0), sticky="w")
        self.sync_interval_var = tk.IntVar(value=self.parent.sync_interval)
        self.sync_interval_entry = ttk.Entry(self, textvariable=self.sync_interval_var, width=10)
        self.sync_interval_entry.grid(row=2, column=1, padx=5, pady=(5,0), sticky="w")
        ttk.Label(self, text="Set to -1 for a single sync at program start").grid(row=3, column=0, columnspan=2, padx=5, pady=(0,5), sticky="w")

        ttk.Label(self, text="Background Color").grid(row=4, column=0, padx=5, pady=5, sticky="w")
        self.background_color_var = tk.StringVar(value=self.parent.color_background)
        self.background_color_entry = tk.Entry(self, textvariable=self.background_color_var, width=10)
        self.background_color_entry.grid(row=4, column=1, padx=5, pady=5, sticky="w")

        self.save_button = ttk.Button(self, text="Save", command=self.save_settings)
        self.save_button.grid(row=5, column=0, padx=5, pady=5, sticky="w")

        self.cancel_button = ttk.Button(self, text="Cancel", command=self.cancel)
        self.cancel_button.grid(row=5, column=1, padx=5, pady=5, sticky="e")

    def save_settings(self):
        # validate colors
        try:
            self.winfo_rgb(self.background_color_var.get())
        except tk.TclError:
            messagebox.showerror("Invalid Color", "Please enter a valid color.")
            self.lift()
            return
        self.parent.autosync_active = self.autosync_active_var.get()
        self.parent.write_log = self.write_log_var.get()
        self.parent.start_fullscreen = self.fullscreen_var.get()
        self.parent.sync_interval = self.sync_interval_var.get()
        self.parent.color_background = self.background_color_var.get()
        self.parent.autosync()
        self.parent.create_clocks()
        self.destroy()

    def cancel(self):
        self.destroy()


def resource_path(relative_path):
    try:
        # PyInstaller creates a temp folder and stores path in _MEIPASS
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")

    return os.path.join(base_path, relative_path)


def main():
    app = TimeZoneClockApp()
    app.mainloop()


if __name__ == "__main__":
    main()

Antworten