Settings Menu

Finished the settings menu and systems for saving and loading settings
from a config file.
This commit is contained in:
Joelnir
2016-02-18 16:28:54 +01:00
parent c3e7c275f7
commit af125173c4
11 changed files with 528 additions and 349 deletions
+3 -1
View File
@@ -1,2 +1,4 @@
*.pyc
TESTING.py
TESTING.py
.idea
__pycache__
Binary file not shown.
Binary file not shown.
Binary file not shown.
+65 -66
View File
@@ -1,103 +1,102 @@
#CONFIG FILE WITH CONSTANTS
# CONFIG FILE WITH CONSTANTS
DEBUG = True
#Livesplit connection
# Livesplit connection
HOST = "localhost"
PORT = 16834
#In network communication, time out after this time. (in seconds)
# In network communication, time out after this time. (in seconds)
COM_TIMEOUT = 0.5
#Possible commands to send to LiveSplit
# Possible commands to send to LiveSplit
LS_COMMANDS = {
"best_possible": "getbestpossibletime\r\n",
"cur_split_index": "getsplitindex\r\n",
"cur_split_name": "getcurrentsplitname\r\n"
}
#Default Window Size
# Default Window Size
DEFAULT_WINDOW = {"WIDTH": 400, "HEIGHT": 300, "TITLE": "SplitNotes"}
#Color Scheme
COLOR_SCHEME = {}
#Default Welcome Message
# Default Welcome Message
DEFAULT_MSG = "Right Click to Open Notes."
#Update time for polling livesplit and other actions (in seconds)
# Update time for polling livesplit and other actions (in seconds)
POLLING_TIME = 0.5
#file names and path for resources
# file names and path for resources
RESOURCE_FOLDER = "resources"
ICONS= {"GREEN": "green.png", "RED": "red.png"}
SETTINGS_FILE ="config.cfg"
ICONS = {"GREEN": "green.png", "RED": "red.png", "SETTINGS": "settings_icon.png"}
SETTINGS_FILE = "config.cfg"
#Popup menu options
# Popup menu options
MENU_OPTIONS = {
"SINGLE": "Set Single Layout",
"DOUBLE": "Set Double Layout",
"LOAD": "Load Notes",
"BIG": "Big Font",
"SMALL": "Small Font",
"SETTINGS": "Settings"
}
"SINGLE": "Set Single Layout",
"DOUBLE": "Set Double Layout",
"LOAD": "Load Notes",
"BIG": "Big Font",
"SMALL": "Small Font",
"SETTINGS": "Settings"
}
#Error messages
ERRORS = {"NOTES_EMPTY": ("Error", "Notes empty or can't be loaded!")}
# Error messages
ERRORS = {"NOTES_EMPTY": ("Error!", "Notes empty or can't be loaded!"),
"FONT_SIZE": ("Error!", "Invalid Font Size!"),
"SERVER_PORT": ("Error!", "Invalid server port!")}
#Max file size for notes
MAX_FILE_SIZE = 1000000000 #1 Giga-Byte
# Max file size for notes
MAX_FILE_SIZE = 1000000000 # 1 Giga-Byte
#TO be added to title to alert user that timer is running
# To be added to title to alert user that timer is running
RUNNING_ALERT = "RUNNING"
#Font for notes/
#TODO FIX THIS
# Font for notes/
# TODO FIX THIS
FONT = {"NAME": "arial", "SMALL": 12, "BIG": 16}
GUI_FONT = ("arial", 12)
#Color scheme
# Color scheme
COLOR = {"TEXT": "black", "TEXT_BG": "ivory"}
#Files tht should be displayed and opened a notes
# Files tht should be displayed and opened a notes
TEXT_FILES = [
("Text Files", ("*.txt", "*.log", "*.asc", "*.conf", "*.cfg")),
('All','*')
]
#Default content of config.cfg file
DEFAULT_CONFIG = "notes=\nfont_size=12\nfont=arial\ntext_color=FFFFFF\nbackground_color=000000"
("Text Files", ("*.txt", "*.log", "*.asc", "*.conf", "*.cfg")),
('All', '*')
]
#Required settings
REQUIRED_SETTINGS =["notes",
"font",
"font_size",
"text_color",
"background_color"]
# Default content of config.cfg file
DEFAULT_CONFIG = "notes=\nfont_size=12\nfont=arial\ntext_color=#FFFFFF\nbackground_color=#000000\nserver_port=16834"
#Settings window options
SETTINGS_WINDOW = {"TITLE": "Settings",
"WIDTH": 300,
"HEIGHT": 400,
"CANCEL": "Cancel",
"SAVE": "Save"}
# Required settings
REQUIRED_SETTINGS = ("notes",
"font",
"font_size",
"text_color",
"background_color",
"server_port"
)
#OPTIONS IN THE SETTINGS WINDOW
SETTINGS_OPTIONS = {"FONT": "Font",
"FONT_SIZE": "Font Size",
"TEXT_COLOR": "Text Color",
"BG_COLOR": "Background Color"}
#Fonts that can be selected
AVAILABLE_FONTS = ("arial",
"courier new",
"comic sans",
"fixedsys",
"ms sans serif",
"ms serif",
"system",
"times new roman",
"verdana")
# Settings window options
SETTINGS_WINDOW = {"TITLE": "Settings",
"WIDTH": 360,
"HEIGHT": 280,
"CANCEL": "Cancel",
"SAVE": "Save"}
# OPTIONS IN THE SETTINGS WINDOW
SETTINGS_OPTIONS = {"FONT": "Font",
"FONT_SIZE": "Font Size",
"TEXT_COLOR": "Text Color",
"BG_COLOR": "Background Color",
"SERVER_PORT": "LiveSplit Server port",
"DEFAULT_SERVER_PORT": "(Default is 16834)"}
# Fonts that can be selected
AVAILABLE_FONTS = ("arial",
"courier new",
"comic sans",
"fixedsys",
"ms sans serif",
"ms serif",
"system",
"times new roman",
"verdana")
+18 -14
View File
@@ -5,18 +5,20 @@ Conversation with livesplit is done through the server component.
import socket
from threading import Thread
import config
import select #used for checking if socket has data pending
import select # used for checking if socket has data pending
def ls_connect(ls_socket, call_func, window):
"""Connects given socket to the livesplit server."""
con_thread = Thread(target=try_connection, args=(ls_socket, call_func, window))
con_thread.start()
def init_socket():
"""Returns a fresh socket"""
return socket.socket()
def try_connection(ls_socket, call_func, window):
"""
Tries to connect given socket to ls.
@@ -28,13 +30,15 @@ def try_connection(ls_socket, call_func, window):
ls_socket.connect((config.HOST, config.PORT))
except:
return False
call_func(window)
def close_socket(com_socket):
"""Closes given socket."""
com_socket.close()
def check_connection(ls_socket):
"""
Check so connection between socket and livesplit
@@ -45,8 +49,8 @@ def check_connection(ls_socket):
return True
else:
return False
def send_to_ls(ls_socket, command):
"""
Sends given command to ls using given socket.
@@ -59,7 +63,7 @@ def send_to_ls(ls_socket, command):
ls_socket.send(str.encode(config.LS_COMMANDS[command]))
except:
return False
socket_ready = select.select([ls_socket], [], [], config.COM_TIMEOUT)
if socket_ready[0]:
try:
@@ -68,7 +72,7 @@ def send_to_ls(ls_socket, command):
return False
else:
return False
def get_split_index(ls_socket):
"""
@@ -79,12 +83,12 @@ def get_split_index(ls_socket):
Returns False on Error
"""
ls_data = send_to_ls(ls_socket, "cur_split_index")
if not isinstance(ls_data, bool):
return int(ls_data)
else:
return False
def get_split_name(ls_socket):
"""
@@ -92,8 +96,8 @@ def get_split_name(ls_socket):
Returns False if no split is active or Error occurs.
"""
ls_data = send_to_ls(ls_socket, "cur_split_name")
if ls_data:
return ls_data
else:
return False
return False
+167 -164
View File
@@ -12,105 +12,104 @@ import note_reader as noter
import setting_handler as settings
runtime_info = {
"ls_connected": False,
"timer_running": False,
"icon_active": False,
"active_split": -1,
"notes": [],
"double_layout": False,
"big_font": False
}
"ls_connected": False,
"timer_running": False,
"icon_active": False,
"active_split": -1,
"notes": [],
"double_layout": False,
"big_font": False
}
root = tkinter.Tk()
red_path= os.path.join(
str(os.path.dirname(os.path.realpath(sys.argv[0]))),
config.RESOURCE_FOLDER,
config.ICONS["RED"]
)
green_path= os.path.join(
str(os.path.dirname(os.path.realpath(sys.argv[0]))),
config.RESOURCE_FOLDER,
config.ICONS["GREEN"]
)
red_path = os.path.join(
str(os.path.dirname(os.path.realpath(sys.argv[0]))),
config.RESOURCE_FOLDER,
config.ICONS["RED"]
)
green_path = os.path.join(
str(os.path.dirname(os.path.realpath(sys.argv[0]))),
config.RESOURCE_FOLDER,
config.ICONS["GREEN"]
)
red_icon = tkinter.Image("photo", file=red_path)
green_icon = tkinter.Image("photo", file=green_path)
def update(window, com_socket, text1, text2):
"""
Function to loop along tkinter mainloop.
"""
if not runtime_info["ls_connected"]:
#try connecting to ls
# try connecting to ls
con.ls_connect(com_socket, server_found, window)
else:
#is_connected
# is_connected
if runtime_info["notes"]:
#notes loaded
#get index of current split
# notes loaded
# get index of current split
new_index = con.get_split_index(com_socket)
if isinstance(new_index, bool):
#Connection error
# Connection error
com_socket = test_connection(com_socket, window, text1, text2)
else:
#index retrieved succesfully
# index retrieved succesfully
if new_index == -1:
#timer not running
# timer not running
if runtime_info["timer_running"]:
runtime_info["timer_running"] = False
runtime_info["active_split"] = new_index
update_GUI(window, com_socket, text1, text2)
else:
#timer is running
# timer is running
if not runtime_info["timer_running"]:
runtime_info["timer_running"] = True
#special case to fix scrolling
# special case to fix scrolling
if runtime_info["active_split"] == 0:
runtime_info["active_split"] = -1
if not runtime_info["active_split"] == new_index:
#new split, need to update
# new split, need to update
runtime_info["active_split"] = new_index
update_GUI(window, com_socket, text1, text2)
update_GUI(window, com_socket, text1, text2)
else:
#notes not yet loaded
# notes not yet loaded
com_socket = test_connection(com_socket, window, text1, text2)
#self looping
window.after(int(config.POLLING_TIME * 1000),
update, window, com_socket, text1, text2)
# self looping
window.after(int(config.POLLING_TIME * 1000),
update, window, com_socket, text1, text2)
def update_GUI(window, com_socket, text1, text2):
def update_GUI(window, com_socket, text1, text2):
"""
Updates all graphics according to current runtime_info.
Sets window title and Text-box content.
Does NOT set window icon.
"""
index = runtime_info["active_split"]
if index == -1:
index = 0
if runtime_info["timer_running"]:
#Does not test connection if it fails
# Does not test connection if it fails
split_name = con.get_split_name(com_socket)
else:
split_name = False
if runtime_info["notes"]:
set_title_notes(window, index, split_name)
update_notes(window, text1, text2, index)
else:
update_title(config.DEFAULT_WINDOW["TITLE"], window)
def test_connection(com_socket, window, text1, text2):
"""
Runs a connection test to ls using given socket.
@@ -132,16 +131,16 @@ def reset_connection(com_socket, window, text1, text2):
if runtime_info["timer_running"]:
runtime_info["timer_running"] = False
runtime_info["active_split"] = -1
runtime_info["ls_connected"] = False
update_icon(False, window)
update_GUI(window, com_socket, text1, text2)
#Close old and return a fresh socket
# Close old and return a fresh socket
con.close_socket(com_socket)
return con.init_socket()
def server_found(window):
"""
@@ -151,7 +150,7 @@ def server_found(window):
runtime_info["ls_connected"] = True
update_icon(True, window)
def update_icon(active, window):
"""Updates icon of window depending on "active" variable"""
if active and not runtime_info["icon_active"]:
@@ -160,13 +159,13 @@ def update_icon(active, window):
elif runtime_info["icon_active"]:
window.tk.call('wm', 'iconphoto', window._w, red_icon)
runtime_info["icon_active"] = False
def update_title(name, window):
"""Sets the title of given window to name."""
window.wm_title(name)
def adjust_content(window, box1, box2):
"""
Adjusts size of box1 and box2 according to
@@ -176,36 +175,36 @@ def adjust_content(window, box1, box2):
set_double_layout(window, box1, box2)
else:
set_single_layout(window, box1, box2)
def set_double_layout(window, box1, box2):
"""
Configures boxes in the window to fit as in double layout.
"""
runtime_info["double_layout"] = True
w_width = window.winfo_width()
w_height = window.winfo_height()
box1.place(height=(w_height // 2), width=w_width)
box2.place(height=(w_height // 2), width=w_width, y=(w_height // 2))
def set_single_layout(window, box1, box2):
"""
Configures boxes in the window to fit as in single layout.
"""
runtime_info["double_layout"] = False
box2.place_forget()
box1.place(height=window.winfo_height(), width=window.winfo_width())
def show_popup(event, menu):
"""Displays given popup menu at cursor position."""
menu.post(event.x_root, event.y_root)
def menu_change_layout(window, box1, box2, popup):
"""Menu option for changing layout selected."""
if runtime_info["double_layout"]:
@@ -215,38 +214,38 @@ def menu_change_layout(window, box1, box2, popup):
set_double_layout(window, box1, box2)
popup.entryconfig(0, label=config.MENU_OPTIONS["SINGLE"])
def menu_load_notes(window, text1, text2, com_socket):
"""Menu selected load notes option."""
load_notes(window, text1, text2, com_socket)
def load_notes(window, text1, text2, com_socket):
"""
Prompts user to select notes and then tries to load these into the UI.
"""
file = noter.select_file()
if file:
if file:
notes = noter.get_notes(file)
if notes:
#Notes loaded correctly
# Notes loaded correctly
runtime_info["notes"] = notes
split_c = len(notes)
show_info(("Notes Loaded",
("Loaded notes with " + str(split_c) + " splits.")))
show_info(("Notes Loaded",
("Loaded notes with " + str(split_c) + " splits.")))
if not runtime_info["timer_running"]:
runtime_info["active_split"] = -1
update_GUI(window, com_socket, text1, text2)
else:
show_info(config.ERRORS["NOTES_EMPTY"], True)
def show_info(info, warning = False):
def show_info(info, warning=False):
"""
Displays an infor popup window.
if warning is True window has a warning triangle.
@@ -255,8 +254,8 @@ def show_info(info, warning = False):
messagebox.showwarning(info[0], info[1])
else:
messagebox.showinfo(info[0], info[1])
def update_notes(window, text1, text2, index):
"""
Displays notes with the given index in given text widgets.
@@ -267,32 +266,32 @@ def update_notes(window, text1, text2, index):
text2 is always given the notes at index (index + 1) if existing
"""
max_index = (len(runtime_info["notes"]) - 1)
if index < 0:
index = 0
text1.config(state=tkinter.NORMAL)
text2.config(state=tkinter.NORMAL)
text1.delete("1.0", tkinter.END)
text2.delete("1.0", tkinter.END)
if index <= max_index:
text1.insert(tkinter.END, runtime_info["notes"][index])
#cand disply notes for index+1
# cand disply notes for index+1
if index < max_index:
text2.insert(tkinter.END, runtime_info["notes"][index + 1])
text1.config(state=tkinter.DISABLED)
text2.config(state=tkinter.DISABLED)
def right_arrow(window, com_socket, text1, text2):
"""Event handler for right arrow key."""
change_preview(window, com_socket, text1, text2, 1)
def left_arrow(window, com_socket, text1, text2):
"""Event handler for left arrow key."""
change_preview(window, com_socket, text1, text2, -1)
@@ -303,71 +302,77 @@ def change_preview(window, com_socket, text1, text2, move):
if runtime_info["notes"] and (not runtime_info["timer_running"]):
max_index = (len(runtime_info["notes"]) - 1)
index = runtime_info["active_split"]
if index < 0:
index = 0
index += move
if index > max_index:
index = max_index
runtime_info["active_split"] = index
update_GUI(window, com_socket, text1, text2)
def set_title_notes(window, index, split_name = False):
def set_title_notes(window, index, split_name=False):
"""
Set window title to fit with displayed notes.
"""
title = config.DEFAULT_WINDOW["TITLE"]
disp_index = str(index + 1) #start at 1
disp_index = str(index + 1) # start at 1
title += " - " + disp_index
if split_name:
title += " - " + split_name
if runtime_info["timer_running"]:
title += " - " + config.RUNNING_ALERT
update_title(title, window)
def menu_font_size(text_font, menu):
def menu_font_size(text_font, menu):
if runtime_info["big_font"]:
runtime_info["big_font"] = False
menu.entryconfig(1, label=config.MENU_OPTIONS["BIG"])
else:
runtime_info["big_font"] = True
menu.entryconfig(1, label=config.MENU_OPTIONS["SMALL"])
update_font_size(text_font)
def menu_open_settings(root_wnd, apply_method, text1, text2):
settings.edit_settings(root_wnd, apply_method, text1, text2)
settings.edit_settings(root_wnd, (lambda settings: apply_settings(settings, text1, text2)))
def apply_settings(config, text1, text2):
#TODO apply all settings to the root window
# TODO apply all settings to the root window
print("Applying settings")
print(config)
def update_font_size(text_font):
if runtime_info["big_font"]:
font_size = config.FONT["BIG"]
else:
font_size = config.FONT["SMALL"]
text_font.config(size=font_size)
def init_UI(root):
"""Draws default UI and creates event bindings."""
#Create communication socket
# Create communication socket
com_socket = con.init_socket()
#Graphical components
# Graphical components
root.geometry(str(config.DEFAULT_WINDOW["WIDTH"]) + "x" + str(config.DEFAULT_WINDOW["HEIGHT"]))
box1 = tkinter.Frame(root)
box2 = tkinter.Frame(root)
@@ -377,80 +382,78 @@ def init_UI(root):
scroll2 = tkinter.Scrollbar(box2)
scroll2.pack(side=tkinter.RIGHT, fill=tkinter.Y)
text1 = tkinter.Text(
box1,
yscrollcommand=scroll1.set,
wrap=tkinter.WORD,
cursor="arrow"
)
box1,
yscrollcommand=scroll1.set,
wrap=tkinter.WORD,
cursor="arrow"
)
text1.insert(tkinter.END, config.DEFAULT_MSG)
text1.config(state=tkinter.DISABLED)
text1.pack(fill=tkinter.BOTH, expand=True)
text2 = tkinter.Text(
box2,
yscrollcommand=scroll2.set,
wrap=tkinter.WORD,
cursor="arrow"
)
box2,
yscrollcommand=scroll2.set,
wrap=tkinter.WORD,
cursor="arrow"
)
text2.insert(tkinter.END, config.DEFAULT_MSG)
text2.config(state=tkinter.DISABLED)
text2.pack(fill=tkinter.BOTH, expand=True)
scroll1.config(command=text1.yview)
scroll2.config(command=text2.yview)
#Set font and color for text
# Set font and color for text
text_font = font.Font(
family=config.FONT["NAME"],
size=config.FONT["SMALL"]
)
family=config.FONT["NAME"],
size=config.FONT["SMALL"]
)
text1.config(font=text_font)
text2.config(font=text_font)
text1.config(fg=config.COLOR["TEXT"], bg=config.COLOR["TEXT_BG"])
text2.config(fg=config.COLOR["TEXT"], bg=config.COLOR["TEXT_BG"])
set_single_layout(root, box1, box2)
#create popup menu
# create popup menu
popup = tkinter.Menu(root, tearoff=0)
popup.add_command(
label=config.MENU_OPTIONS["DOUBLE"],
command=(
lambda: menu_change_layout(root, box1, box2, popup)
)
) #Needs to be at index 0
label=config.MENU_OPTIONS["DOUBLE"],
command=(
lambda: menu_change_layout(root, box1, box2, popup)
)
) # Needs to be at index 0
popup.add_command(
label=config.MENU_OPTIONS["BIG"],
command=(
lambda: menu_font_size(text_font, popup)
)
) #Needs to be at index 1
label=config.MENU_OPTIONS["BIG"],
command=(
lambda: menu_font_size(text_font, popup)
)
) # Needs to be at index 1
popup.add_command(
label=config.MENU_OPTIONS["LOAD"],
command=(lambda: menu_load_notes(root, text1, text2, com_socket))
)
label=config.MENU_OPTIONS["LOAD"],
command=(lambda: menu_load_notes(root, text1, text2, com_socket))
)
popup.add_command(
label=config.MENU_OPTIONS["SETTINGS"],
command=(lambda: menu_open_settings(root, apply_settings, text1, text2))
)
#Set default window icon and title
label=config.MENU_OPTIONS["SETTINGS"],
command=(lambda: menu_open_settings(root, apply_settings, text1, text2))
)
# Set default window icon and title
root.tk.call('wm', 'iconphoto', root._w, red_icon)
update_title(config.DEFAULT_WINDOW["TITLE"], root)
#Event binds
# Event binds
root.bind("<Configure>", (lambda e: adjust_content(root, box1, box2)))
root.bind("<Button-3>", (lambda e: show_popup(e, popup)))
root.bind("<Right>", (lambda e: right_arrow(root, com_socket, text1, text2)))
root.bind("<Left>", (lambda e: left_arrow(root, com_socket, text1, text2)))
#call update loop
# call update loop
update(root, com_socket, text1, text2)
init_UI(root)
root.mainloop()
root.mainloop()
+23 -22
View File
@@ -15,14 +15,15 @@ these can be used for titles.
all other lines of text are added as notes
"""
def get_note_lines(file_path):
"""
Reads file at given path and returns
a list containing all rows of text in given file.
Returns false if file can not be read.
"""
#check so file isn't too big
# check so file isn't too big
if path.getsize(file_path) > config.MAX_FILE_SIZE:
return False
@@ -30,37 +31,37 @@ def get_note_lines(file_path):
notes_file = open(file_path, "r")
except:
return False
#read file line per line
# read file line per line
f_lines = []
keep_reading = True
while keep_reading:
cur_line = notes_file.readline()
if cur_line:
f_lines.append(cur_line)
else:
keep_reading = False
return f_lines
def encode_notes(note_lines):
"""
Takes a list containing strings.
Encodes given strings according to the note formatting.
Returns the list containing the notes for every split.
"""
def is_title(line):
if not line:
return False
return (line[0] == "[") and (line[-1] == "]")
def is_newline(line):
return (line == "\n") or (line == "\r")
def remove_new_line(line):
if (len(line) >= 1) and (is_newline(line[-1])):
return line[:-1]
@@ -69,9 +70,9 @@ def encode_notes(note_lines):
note_list = []
cur_notes = ""
for line in note_lines:
#remove whitespace at beginning and end
# remove whitespace at beginning and end
line = line.strip(" ")
if is_newline(line):
@@ -81,11 +82,11 @@ def encode_notes(note_lines):
else:
line = remove_new_line(line)
if not is_title(line):
cur_notes += line + "\n" #newline
cur_notes += line + "\n" # newline
if cur_notes:
note_list.append(cur_notes)
return note_list
@@ -97,12 +98,12 @@ def get_notes(file_path):
Returns False if file is empty.
"""
note_lines = get_note_lines(file_path)
if not note_lines:
return False
note_list = encode_notes(note_lines)
return note_list
@@ -112,10 +113,10 @@ def select_file():
Returns False upon no file selection.
Otherwise returns absolute path to selected file.
"""
file = file_dia.askopenfilename(filetypes=config.TEXT_FILES)
if file:
return file
else:
return False
return False
+6
View File
@@ -0,0 +1,6 @@
notes=
font=times new roman
font_size=12
server_port=34
background_color=#4d73f4
text_color=#ff8080
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

+246 -82
View File
@@ -1,3 +1,5 @@
import tkinter.colorchooser as colorchooser
from tkinter import messagebox as msgbox
import tkinter
import os
import sys
@@ -5,10 +7,16 @@ import sys
import config
settings_path = os.path.join(
str(os.path.dirname(os.path.realpath(sys.argv[0]))),
config.RESOURCE_FOLDER,
config.SETTINGS_FILE
)
str(os.path.dirname(os.path.realpath(sys.argv[0]))),
config.RESOURCE_FOLDER,
config.SETTINGS_FILE
)
settings_icon_path = os.path.join(
str(os.path.dirname(os.path.realpath(sys.argv[0]))),
config.RESOURCE_FOLDER,
config.ICONS["SETTINGS"]
)
def load_settings():
"""
@@ -18,34 +26,32 @@ def load_settings():
returns a dictionary with all settings.
"""
#try to open default settings file
# try to open default settings file
try:
settings_file = open(settings_path,"r+")
settings_file = open(settings_path, "r+")
settings_content = get_file_lines(settings_file)
except:
#File not found
# File not found
settings_content = set_default_settings();
settings = format_settings(settings_content)
#Check so settings file has all settings
# Check so settings file has all settings
if not validate_settings(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.
"""
#TODO create file
settings_file = open(settings_path, "w")
settings_file.write(config.DEFAULT_CONFIG)
settings_file.close()
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
@@ -60,23 +66,23 @@ def format_settings(file_rows):
for row in file_rows:
row = row.strip("\n")
parts = row.split("=")
if(len(parts) == SETTING_PART_LENGTH):
if len(parts) == SETTING_PART_LENGTH:
settings[parts[0]] = parts[1]
return settings
def get_file_lines(file):
"""
Returns a list containing all the lines of the gicen file.
"""
#read file line per line
# read file line per line
f_lines = []
keep_reading = True
while keep_reading:
cur_line = file.readline()
if cur_line:
f_lines.append(cur_line)
else:
@@ -84,70 +90,228 @@ def get_file_lines(file):
return f_lines
def validate_settings(settings):
"""
Checks a settings dictionary so that all the needed settings are present.
"""
for req_setting in config.REQUIRED_SETTINGS:
if not req_setting in settings:
if not (req_setting in settings):
return False
if not validate_font_size(settings["font_size"]):
return False
if not validate_server_port(settings["server_port"]):
return False
if not validate_color(settings["text_color"]):
return False
if not validate_color(settings["background_color"]):
return False
if not (settings["font"] in config.AVAILABLE_FONTS):
return False
return True
def edit_settings(root_wnd, apply_method, text1, text2):
settings_wnd = tkinter.Toplevel(master=root_wnd,
width=config.SETTINGS_WINDOW["WIDTH"],
def set_settings_file_content(content):
"""
Saves given content to the config file, config.cfg, in the resources directory.
"""
settings_file = open(settings_path, "w")
settings_file.write(content)
settings_file.close()
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"])
settings = load_settings()
settings_wnd.resizable(0,0)
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)
font_label.place(x=15, y=15)
font_size_label.place(x=15, y=45)
text_color_label.place(x=15, y=75)
bg_color_label.place(x=15, y=105)
#Font Selection
selected_font = tkinter.StringVar(settings_wnd)
font_dropdown = tkinter.OptionMenu(settings_wnd, selected_font, "saker")
font_dropdown.place(x=100, y=15)
#TODO finish font selection and rest of gui
def save_settings():
#TODO collect settings
#font = selected_font.get()
settings = []
apply_method(settings, text1, text2)
settings_wnd.destroy()
save_btn = tkinter.Button(settings_wnd,
command=save_settings,
text=config.SETTINGS_WINDOW["SAVE"])
cancel_btn = tkinter.Button(settings_wnd,
command=settings_wnd.destroy,
text=config.SETTINGS_WINDOW["CANCEL"])
save_btn.place(x=10, y=10)
cancel_btn.place(x=10, y=50)
settings_icon = tkinter.Image("photo", file=settings_icon_path)
settings_wnd.tk.call('wm', 'iconphoto', settings_wnd._w, settings_icon)
settings = load_settings()
settings_wnd.resizable(0, 0)
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)
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=2, 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():
choosen_color = colorchooser.askcolor()
if choosen_color[1]:
settings["text_color"] = choosen_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():
choosen_color = colorchooser.askcolor()
if choosen_color[1]:
print(choosen_color)
settings["background_color"] = choosen_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=6, font=config.GUI_FONT)
port_entry.insert(0, settings["server_port"])
# 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 not validate_font_size(chosen_font_size):
msgbox.showerror(config.ERRORS["FONT_SIZE"][0], config.ERRORS["FONT_SIZE"][1])
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])
errors_found = True
else:
settings["server_port"] = chosen_port
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
font_label.place(x=15, y=15)
font_size_label.place(x=15, y=55)
text_color_label.place(x=15, y=95)
bg_color_label.place(x=15, y=135)
port_label.place(x=15, y=175)
default_port_label.place(x=15, y=200)
font_dropdown.place(x=178, y=15)
font_size_entry.place(x=180, y=55)
text_color.place(x=180, y=95)
bg_color.place(x=180, y=135)
port_entry.place(x=180, y=175)
save_btn.place(x=110, y=230)
cancel_btn.place(x=190, y=230)
def validate_color(color):
"""
Returns whether or not given color is valid in the hexadecimal format.
"""
return isinstance(color, str) and len(color) == 7 and color[0] == "#"
def validate_font_size(size):
"""
Returns whether or not given size is an acceptable font size.
"""
try:
size = int(size)
except:
return False
return 0 < size < 70
def validate_server_port(port):
"""
Returns Whether or not gicen port is a valid server port.
"""
try:
port = int(port)
return True
except:
return False
def save_settings(settings):
file_content = ""
for key in settings.keys():
file_content += key + "=" + settings[key] + "\n"
set_settings_file_content(file_content)