mirror of
https://github.com/ApfelTeeSaft/SplitNotes.git
synced 2026-08-26 19:33:39 +00:00
554 lines
16 KiB
Python
554 lines
16 KiB
Python
import tkinter.colorchooser as colorchooser
|
|
from tkinter import messagebox as msgbox
|
|
import tkinter
|
|
import os
|
|
import sys
|
|
|
|
import config
|
|
|
|
|
|
# Cross-platform path handling
|
|
if getattr(sys, 'frozen', False):
|
|
# Running as compiled executable
|
|
application_path = os.path.dirname(sys.executable)
|
|
else:
|
|
# Running as script
|
|
application_path = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
settings_path = os.path.join(
|
|
application_path,
|
|
config.RESOURCE_FOLDER,
|
|
config.SETTINGS_FILE
|
|
)
|
|
|
|
settings_icon_path = os.path.join(
|
|
application_path,
|
|
config.RESOURCE_FOLDER,
|
|
config.ICONS["SETTINGS"]
|
|
)
|
|
|
|
|
|
def load_settings():
|
|
"""
|
|
Tries to load settings from file.
|
|
If no working settings-file exist one is created and the default settings are returned.
|
|
|
|
returns a dictionary with all settings.
|
|
"""
|
|
|
|
# Ensure resources directory exists
|
|
os.makedirs(os.path.dirname(settings_path), exist_ok=True)
|
|
|
|
# try to open default settings file
|
|
try:
|
|
with open(settings_path, "r", encoding='utf-8') as settings_file:
|
|
settings_content = settings_file.readlines()
|
|
settings_content = [line.strip() for line in settings_content]
|
|
print(f"Settings loaded from: {settings_path}")
|
|
except:
|
|
# File not found
|
|
print("Settings file not found, creating default settings")
|
|
settings_content = set_default_settings()
|
|
|
|
settings = format_settings(settings_content)
|
|
|
|
# Check so settings file has all settings
|
|
if not validate_settings(settings):
|
|
print("Settings validation failed, creating default settings")
|
|
settings = format_settings(set_default_settings())
|
|
|
|
return settings
|
|
|
|
|
|
def set_default_settings():
|
|
"""
|
|
Creates a config file with default settings.
|
|
Returns the default config-file content.
|
|
"""
|
|
print(f"Creating default settings at: {settings_path}")
|
|
set_settings_file_content(config.DEFAULT_CONFIG)
|
|
return config.DEFAULT_CONFIG.split("\n")
|
|
|
|
|
|
def format_settings(file_rows):
|
|
"""
|
|
Takes a list of settings (as written in config files) and
|
|
formats it to a dictionary.
|
|
|
|
Returns a dictionary with all settings as keys.
|
|
"""
|
|
SETTING_PART_LENGTH = 2
|
|
|
|
settings = {}
|
|
|
|
for row in file_rows:
|
|
row = row.strip()
|
|
if '=' in row and not row.startswith('#'): # Skip comments
|
|
parts = row.split("=", 1)
|
|
|
|
if len(parts) == SETTING_PART_LENGTH:
|
|
# Strip to remove whitespace at end and beginning
|
|
key = parts[0].strip()
|
|
value = parts[1].strip()
|
|
settings[key] = value
|
|
|
|
return settings
|
|
|
|
|
|
def validate_settings(settings):
|
|
"""
|
|
Checks a settings dictionary so that all the needed settings are present.
|
|
Enhanced with comprehensive bridge settings validation.
|
|
"""
|
|
|
|
for req_setting in config.REQUIRED_SETTINGS:
|
|
if req_setting not in settings:
|
|
print(f"Missing required setting: {req_setting}")
|
|
return False
|
|
|
|
if not validate_font_size(settings["font_size"]):
|
|
print(f"Invalid font size: {settings['font_size']}")
|
|
return False
|
|
|
|
if not validate_server_port(settings["server_port"]):
|
|
print(f"Invalid server port: {settings['server_port']}")
|
|
return False
|
|
|
|
if not validate_color(settings["text_color"]):
|
|
print(f"Invalid text color: {settings['text_color']}")
|
|
return False
|
|
|
|
if not validate_color(settings["background_color"]):
|
|
print(f"Invalid background color: {settings['background_color']}")
|
|
return False
|
|
|
|
if settings["font"] not in config.AVAILABLE_FONTS:
|
|
print(f"Invalid font: {settings['font']}")
|
|
return False
|
|
|
|
if settings["double_layout"].lower() not in ["true", "false"]:
|
|
print(f"Invalid double_layout: {settings['double_layout']}")
|
|
return False
|
|
|
|
if not validate_pixels(settings["width"]):
|
|
print(f"Invalid width: {settings['width']}")
|
|
return False
|
|
|
|
if not validate_pixels(settings["height"]):
|
|
print(f"Invalid height: {settings['height']}")
|
|
return False
|
|
|
|
if not validate_separator(settings["separator"]):
|
|
print(f"Invalid separator: {settings['separator']}")
|
|
return False
|
|
|
|
# Bridge settings validation
|
|
if settings["bridge_enabled"].lower() not in ["true", "false"]:
|
|
print(f"Invalid bridge_enabled: {settings['bridge_enabled']}")
|
|
return False
|
|
|
|
if not validate_bridge_port(settings["bridge_port"]):
|
|
print(f"Invalid bridge port: {settings['bridge_port']}")
|
|
return False
|
|
|
|
print("All settings validated successfully")
|
|
return True
|
|
|
|
|
|
def validate_bridge_port(port):
|
|
"""Returns whether or not given port is a valid bridge server port."""
|
|
try:
|
|
port_num = int(port)
|
|
return 1024 <= port_num <= 65535
|
|
except:
|
|
return False
|
|
|
|
|
|
def set_settings_file_content(content):
|
|
"""
|
|
Saves given content to the config file, config.cfg, in the resources directory.
|
|
"""
|
|
# Ensure directory exists
|
|
os.makedirs(os.path.dirname(settings_path), exist_ok=True)
|
|
|
|
try:
|
|
with open(settings_path, "w", encoding='utf-8') as settings_file:
|
|
settings_file.write(content)
|
|
print(f"Settings content written to: {settings_path}")
|
|
except Exception as e:
|
|
print(f"Error saving settings content: {e}")
|
|
|
|
|
|
def save_settings(settings):
|
|
"""
|
|
Saves given settings to the settings file.
|
|
Enhanced to ensure bridge settings are properly formatted.
|
|
"""
|
|
print("Saving settings...")
|
|
|
|
# Ensure bridge settings are present and properly formatted
|
|
if "bridge_enabled" not in settings:
|
|
settings["bridge_enabled"] = "false"
|
|
if "bridge_port" not in settings:
|
|
settings["bridge_port"] = "16835"
|
|
|
|
# Convert boolean values to lowercase strings for consistency
|
|
if isinstance(settings.get("bridge_enabled"), bool):
|
|
settings["bridge_enabled"] = str(settings["bridge_enabled"]).lower()
|
|
|
|
file_content = ""
|
|
|
|
# Write settings in a specific order for better readability
|
|
setting_order = [
|
|
"notes", "font", "font_size", "text_color", "background_color",
|
|
"double_layout", "server_port", "width", "height", "separator",
|
|
"bridge_enabled", "bridge_port"
|
|
]
|
|
|
|
# Write ordered settings first
|
|
for key in setting_order:
|
|
if key in settings:
|
|
file_content += f"{key}={settings[key]}\n"
|
|
|
|
# Write any additional settings not in the order list
|
|
for key, value in settings.items():
|
|
if key not in setting_order:
|
|
file_content += f"{key}={value}\n"
|
|
|
|
set_settings_file_content(file_content)
|
|
print(f"Settings saved: bridge_enabled={settings.get('bridge_enabled')}, bridge_port={settings.get('bridge_port')}")
|
|
|
|
|
|
def edit_settings(root_wnd, apply_method):
|
|
"""
|
|
Sets up a window for editing settings.
|
|
root_wnd is the main window that settings should be applied to.
|
|
apply_method is the method to be called to apply validated settings.
|
|
"""
|
|
settings_wnd = tkinter.Toplevel(master=root_wnd,
|
|
width=config.SETTINGS_WINDOW["WIDTH"],
|
|
height=config.SETTINGS_WINDOW["HEIGHT"])
|
|
settings_wnd.title(config.SETTINGS_WINDOW["TITLE"])
|
|
|
|
# Try to load settings icon
|
|
try:
|
|
if os.path.exists(settings_icon_path):
|
|
settings_icon = tkinter.PhotoImage(file=settings_icon_path)
|
|
settings_wnd.iconphoto(False, settings_icon)
|
|
except:
|
|
pass # Icon loading failed, continue without icon
|
|
|
|
settings = load_settings()
|
|
|
|
settings_wnd.resizable(0, 0)
|
|
settings_wnd.transient(root_wnd) # Make it a dialog
|
|
settings_wnd.grab_set() # Make it modal
|
|
|
|
# Center the window
|
|
settings_wnd.geometry(f"{config.SETTINGS_WINDOW['WIDTH']}x{config.SETTINGS_WINDOW['HEIGHT']}")
|
|
settings_wnd.update_idletasks()
|
|
x = (settings_wnd.winfo_screenwidth() // 2) - (config.SETTINGS_WINDOW['WIDTH'] // 2)
|
|
y = (settings_wnd.winfo_screenheight() // 2) - (config.SETTINGS_WINDOW['HEIGHT'] // 2)
|
|
settings_wnd.geometry(f"+{x}+{y}")
|
|
|
|
# Create labels
|
|
font_label = tkinter.Label(settings_wnd,
|
|
text=config.SETTINGS_OPTIONS["FONT"],
|
|
font=config.GUI_FONT)
|
|
font_size_label = tkinter.Label(settings_wnd,
|
|
text=config.SETTINGS_OPTIONS["FONT_SIZE"],
|
|
font=config.GUI_FONT)
|
|
text_color_label = tkinter.Label(settings_wnd,
|
|
text=config.SETTINGS_OPTIONS["TEXT_COLOR"],
|
|
font=config.GUI_FONT)
|
|
bg_color_label = tkinter.Label(settings_wnd,
|
|
text=config.SETTINGS_OPTIONS["BG_COLOR"],
|
|
font=config.GUI_FONT)
|
|
layout_label = tkinter.Label(settings_wnd,
|
|
text=config.SETTINGS_OPTIONS["DOUBLE_LAYOUT"],
|
|
font=config.GUI_FONT)
|
|
newline_label = tkinter.Label(settings_wnd,
|
|
text=config.SETTINGS_OPTIONS["NEW_LINE_SEPARATOR"],
|
|
font=config.GUI_FONT)
|
|
separator_label = tkinter.Label(settings_wnd,
|
|
text=config.SETTINGS_OPTIONS["CUSTOM_SEPARATOR"],
|
|
font=config.GUI_FONT)
|
|
port_label = tkinter.Label(settings_wnd,
|
|
text=config.SETTINGS_OPTIONS["SERVER_PORT"],
|
|
font=config.GUI_FONT)
|
|
default_port_label = tkinter.Label(settings_wnd,
|
|
text=config.SETTINGS_OPTIONS["DEFAULT_SERVER_PORT"],
|
|
font=config.GUI_FONT)
|
|
|
|
# Font Selection
|
|
selected_font = tkinter.StringVar(settings_wnd)
|
|
selected_font.set(settings["font"])
|
|
|
|
font_dropdown = tkinter.OptionMenu(settings_wnd,
|
|
selected_font,
|
|
*config.AVAILABLE_FONTS)
|
|
font_dropdown.configure(font=config.GUI_FONT)
|
|
|
|
# Font Size Selection
|
|
font_size_entry = tkinter.Entry(settings_wnd, width=5, font=config.GUI_FONT)
|
|
font_size_entry.insert(0, settings["font_size"])
|
|
|
|
# Text Color Selection
|
|
text_color = tkinter.Button(settings_wnd,
|
|
width=3,
|
|
height=1)
|
|
|
|
if validate_color(settings["text_color"]):
|
|
text_color.configure(background=settings["text_color"])
|
|
else:
|
|
text_color.configure(background="#000000")
|
|
|
|
def text_color_selection():
|
|
chosen_color = colorchooser.askcolor(parent=settings_wnd)
|
|
if chosen_color[1]:
|
|
settings["text_color"] = chosen_color[1]
|
|
text_color.configure(background=settings["text_color"])
|
|
settings_wnd.focus_force()
|
|
|
|
text_color.configure(command=text_color_selection)
|
|
|
|
# Background color Selection
|
|
bg_color = tkinter.Button(settings_wnd,
|
|
width=3,
|
|
height=1)
|
|
|
|
if validate_color(settings["background_color"]):
|
|
bg_color.configure(background=settings["background_color"])
|
|
else:
|
|
bg_color.configure(background="#FFFFFF")
|
|
|
|
def bg_color_selection():
|
|
chosen_color = colorchooser.askcolor(parent=settings_wnd)
|
|
if chosen_color[1]:
|
|
settings["background_color"] = chosen_color[1]
|
|
bg_color.configure(background=settings["background_color"])
|
|
settings_wnd.focus_force()
|
|
|
|
bg_color.configure(command=bg_color_selection)
|
|
|
|
# Server port Selection
|
|
port_entry = tkinter.Entry(settings_wnd, width=8, font=config.GUI_FONT)
|
|
port_entry.insert(0, settings["server_port"])
|
|
|
|
# Double Layout Selection
|
|
double_layout = tkinter.BooleanVar()
|
|
double_layout_btn = tkinter.Checkbutton(settings_wnd, variable=double_layout)
|
|
|
|
if decode_boolean_setting(settings["double_layout"]):
|
|
double_layout_btn.select()
|
|
|
|
# Separator selection
|
|
separator_entry = tkinter.Entry(settings_wnd, width=14, font=config.GUI_FONT)
|
|
|
|
def set_separator_active(active):
|
|
if active:
|
|
separator_entry.configure(state="normal")
|
|
else:
|
|
separator_entry.configure(state="disabled")
|
|
|
|
use_newline = tkinter.BooleanVar()
|
|
newline_btn = tkinter.Checkbutton(settings_wnd,
|
|
variable=use_newline,
|
|
command=lambda: set_separator_active(not use_newline.get()))
|
|
|
|
if settings["separator"] == config.NEWLINE_CONSTANT:
|
|
newline_btn.select()
|
|
set_separator_active(False)
|
|
else:
|
|
separator_entry.insert(0, settings["separator"])
|
|
|
|
# Save and cancel buttons
|
|
def control_and_save():
|
|
errors_found = False
|
|
|
|
settings["font"] = selected_font.get()
|
|
|
|
chosen_font_size = font_size_entry.get()
|
|
chosen_port = port_entry.get()
|
|
|
|
if use_newline.get():
|
|
chosen_separator = config.NEWLINE_CONSTANT
|
|
else:
|
|
chosen_separator = separator_entry.get()
|
|
|
|
settings["double_layout"] = encode_boolean_setting(double_layout.get())
|
|
|
|
if not validate_font_size(chosen_font_size):
|
|
msgbox.showerror(config.ERRORS["FONT_SIZE"][0], config.ERRORS["FONT_SIZE"][1], parent=settings_wnd)
|
|
errors_found = True
|
|
else:
|
|
settings["font_size"] = chosen_font_size
|
|
|
|
if not validate_server_port(chosen_port):
|
|
msgbox.showerror(config.ERRORS["SERVER_PORT"][0], config.ERRORS["SERVER_PORT"][1], parent=settings_wnd)
|
|
errors_found = True
|
|
else:
|
|
settings["server_port"] = chosen_port
|
|
|
|
if not validate_separator(chosen_separator):
|
|
msgbox.showerror(config.ERRORS["SEPARATOR"][0], config.ERRORS["SEPARATOR"][1], parent=settings_wnd)
|
|
errors_found = True
|
|
else:
|
|
settings["separator"] = chosen_separator
|
|
|
|
if not errors_found:
|
|
save_settings(settings)
|
|
apply_method(settings)
|
|
settings_wnd.destroy()
|
|
else:
|
|
settings_wnd.focus_force()
|
|
|
|
save_btn = tkinter.Button(settings_wnd,
|
|
command=control_and_save,
|
|
text=config.SETTINGS_WINDOW["SAVE"],
|
|
font=config.GUI_FONT)
|
|
cancel_btn = tkinter.Button(settings_wnd,
|
|
command=settings_wnd.destroy,
|
|
text=config.SETTINGS_WINDOW["CANCEL"],
|
|
font=config.GUI_FONT)
|
|
|
|
# Place all components with updated positions
|
|
y_offset = 15
|
|
font_label.place(x=15, y=y_offset)
|
|
y_offset += 40
|
|
font_size_label.place(x=15, y=y_offset)
|
|
y_offset += 40
|
|
text_color_label.place(x=15, y=y_offset)
|
|
y_offset += 40
|
|
bg_color_label.place(x=15, y=y_offset)
|
|
y_offset += 40
|
|
layout_label.place(x=15, y=y_offset)
|
|
y_offset += 25
|
|
newline_label.place(x=15, y=y_offset)
|
|
y_offset += 25
|
|
separator_label.place(x=15, y=y_offset)
|
|
y_offset += 40
|
|
port_label.place(x=15, y=y_offset)
|
|
y_offset += 25
|
|
default_port_label.place(x=15, y=y_offset)
|
|
|
|
# Place input controls
|
|
y_offset = 15
|
|
font_dropdown.place(x=208, y=y_offset)
|
|
y_offset += 40
|
|
font_size_entry.place(x=210, y=y_offset)
|
|
y_offset += 40
|
|
text_color.place(x=210, y=y_offset)
|
|
y_offset += 40
|
|
bg_color.place(x=210, y=y_offset)
|
|
y_offset += 40
|
|
double_layout_btn.place(x=210, y=y_offset)
|
|
y_offset += 25
|
|
newline_btn.place(x=210, y=y_offset)
|
|
y_offset += 25
|
|
separator_entry.place(x=210, y=y_offset)
|
|
y_offset += 40
|
|
port_entry.place(x=210, y=y_offset)
|
|
|
|
# Place buttons at bottom
|
|
save_btn.place(x=110, y=450)
|
|
cancel_btn.place(x=190, y=450)
|
|
|
|
# Handle window close
|
|
def on_closing():
|
|
settings_wnd.grab_release()
|
|
settings_wnd.destroy()
|
|
|
|
settings_wnd.protocol("WM_DELETE_WINDOW", on_closing)
|
|
|
|
|
|
def validate_color(color):
|
|
"""
|
|
Returns whether or not given color is valid in the hexadecimal format.
|
|
"""
|
|
if not isinstance(color, str):
|
|
return False
|
|
return len(color) == 7 and color[0] == "#" and all(c in '0123456789ABCDEFabcdef' for c in color[1:])
|
|
|
|
|
|
def validate_font_size(size):
|
|
"""
|
|
Returns whether or not given size is an acceptable font size.
|
|
"""
|
|
try:
|
|
size = int(size)
|
|
except:
|
|
return False
|
|
|
|
return 6 <= size <= 72
|
|
|
|
|
|
def validate_server_port(port):
|
|
"""
|
|
Returns Whether or not given port is a valid server port.
|
|
"""
|
|
try:
|
|
port_num = int(port)
|
|
return 1024 <= port_num <= 65535
|
|
except:
|
|
return False
|
|
|
|
|
|
def decode_boolean_setting(setting):
|
|
"""
|
|
Decodes a boolean string of "True" or "False"
|
|
to the correct boolean value.
|
|
Enhanced to handle multiple formats.
|
|
"""
|
|
return str(setting).lower() in ("true", "1", "yes", "on")
|
|
|
|
|
|
def encode_boolean_setting(value):
|
|
"""
|
|
Encodes a boolean to the string "true" or "false".
|
|
"""
|
|
return "true" if value else "false"
|
|
|
|
|
|
def validate_pixels(pixels):
|
|
"""
|
|
Checks if given string can be used as a pixel value for height or width.
|
|
Height or Width are assumed to never surpass 10000
|
|
"""
|
|
try:
|
|
pixels = int(pixels)
|
|
except:
|
|
return False
|
|
|
|
return 200 <= pixels <= 10000
|
|
|
|
|
|
def validate_separator(separator):
|
|
"""
|
|
Validates the separator string.
|
|
"""
|
|
if separator == config.NEWLINE_CONSTANT:
|
|
return True
|
|
return len(separator.strip()) > 0
|
|
|
|
|
|
def debug_settings():
|
|
"""Debug function to print current settings"""
|
|
print("\n=== Settings Debug ===")
|
|
try:
|
|
settings = load_settings()
|
|
print("Current settings:")
|
|
for key, value in settings.items():
|
|
print(f" {key}: {value}")
|
|
|
|
print(f"\nSettings file location: {settings_path}")
|
|
print(f"Settings file exists: {os.path.exists(settings_path)}")
|
|
|
|
if os.path.exists(settings_path):
|
|
with open(settings_path, 'r') as f:
|
|
content = f.read()
|
|
print(f"\nRaw file content:\n{content}")
|
|
|
|
except Exception as e:
|
|
print(f"Error in debug_settings: {e}")
|
|
print("=== End Settings Debug ===\n") |