added files

This commit is contained in:
RedDeadDepresso
2023-12-10 15:40:33 +00:00
parent dea3bb76d7
commit 3ead078c49
42 changed files with 2540 additions and 0 deletions

View File

View File

@@ -0,0 +1,12 @@
import customtkinter
class CTkNotification(customtkinter.CTkFrame):
def __init__(self, text, master, **kwargs):
super().__init__(master=master, **kwargs)
self.label = customtkinter.CTkLabel(self, text=text, width=200, wraplength=200, font=("Inter", 16))
self.label.grid(row=0, column=0, sticky="nsew")
self.close_button = customtkinter.CTkButton(self, width=40, text="X", command=self.destroy, fg_color="transparent")
self.close_button.grid(row=0, column=1)
self.progress_bar = customtkinter.CTkProgressBar(self, progress_color="white", determinate_speed=0.4)
self.progress_bar.grid(row=1, column=0, columnspan=2, sticky="nsew")
self.progress_bar.set(0)
self.progress_bar.start()

View File

@@ -0,0 +1,212 @@
"""
CTkToolTip Widget
version: 0.8
"""
import time
import sys
import customtkinter
from tkinter import Toplevel, Frame
class CTkToolTip(Toplevel):
"""
Creates a ToolTip (pop-up) widget for customtkinter.
"""
def __init__(
self,
widget: any = None,
message: str = None,
delay: float = 0.2,
follow: bool = True,
x_offset: int = +20,
y_offset: int = +10,
bg_color: str = None,
corner_radius: int = 10,
border_width: int = 0,
border_color: str = None,
alpha: float = 0.95,
padding: tuple = (10, 2),
**message_kwargs):
super().__init__()
self.widget = widget
self.withdraw()
# Disable ToolTip's title bar
self.overrideredirect(True)
if sys.platform.startswith("win"):
self.transparent_color = self.widget._apply_appearance_mode(
customtkinter.ThemeManager.theme["CTkToplevel"]["fg_color"])
self.attributes("-transparentcolor", self.transparent_color)
self.transient()
elif sys.platform.startswith("darwin"):
self.transparent_color = 'systemTransparent'
self.attributes("-transparent", True)
self.transient(self.master)
else:
self.transparent_color = '#000001'
corner_radius = 0
self.transient()
self.resizable(width=True, height=True)
# Make the background transparent
self.config(background=self.transparent_color)
# StringVar instance for msg string
self.messageVar = customtkinter.StringVar()
self.message = message
self.messageVar.set(self.message)
self.delay = delay
self.follow = follow
self.x_offset = x_offset
self.y_offset = y_offset
self.corner_radius = corner_radius
self.alpha = alpha
self.border_width = border_width
self.padding = padding
self.bg_color = customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"] if bg_color is None else bg_color
self.border_color = border_color
self.disable = False
# visibility status of the ToolTip inside|outside|visible
self.status = "outside"
self.last_moved = 0
self.attributes('-alpha', self.alpha)
if sys.platform.startswith("win"):
if self.widget._apply_appearance_mode(self.bg_color) == self.transparent_color:
self.transparent_color = "#000001"
self.config(background=self.transparent_color)
self.attributes("-transparentcolor", self.transparent_color)
# Add the message widget inside the tooltip
self.transparent_frame = Frame(self, bg=self.transparent_color)
self.transparent_frame.pack(padx=0, pady=0, fill="both", expand=True)
self.frame = customtkinter.CTkFrame(self.transparent_frame, bg_color=self.transparent_color,
corner_radius=self.corner_radius,
border_width=self.border_width, fg_color=self.bg_color,
border_color=self.border_color)
self.frame.pack(padx=0, pady=0, fill="both", expand=True)
self.message_label = customtkinter.CTkLabel(self.frame, textvariable=self.messageVar, **message_kwargs)
self.message_label.pack(fill="both", padx=self.padding[0] + self.border_width,
pady=self.padding[1] + self.border_width, expand=True)
if self.widget.winfo_name() != "tk":
if self.frame.cget("fg_color") == self.widget.cget("bg_color"):
if not bg_color:
self._top_fg_color = self.frame._apply_appearance_mode(
customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"])
if self._top_fg_color != self.transparent_color:
self.frame.configure(fg_color=self._top_fg_color)
# Add bindings to the widget without overriding the existing ones
self.widget.bind("<Enter>", self.on_enter, add="+")
self.widget.bind("<Leave>", self.on_leave, add="+")
self.widget.bind("<Motion>", self.on_enter, add="+")
self.widget.bind("<B1-Motion>", self.on_enter, add="+")
self.widget.bind("<Destroy>", lambda _: self.hide(), add="+")
def show(self) -> None:
"""
Enable the widget.
"""
self.disable = False
def on_enter(self, event) -> None:
"""
Processes motion within the widget including entering and moving.
"""
if self.disable:
return
self.last_moved = time.time()
# Set the status as inside for the very first time
if self.status == "outside":
self.status = "inside"
# If the follow flag is not set, motion within the widget will make the ToolTip dissapear
if not self.follow:
self.status = "inside"
self.withdraw()
# Calculate available space on the right side of the widget relative to the screen
root_width = self.winfo_screenwidth()
widget_x = event.x_root
space_on_right = root_width - widget_x
# Calculate the width of the tooltip's text based on the length of the message string
text_width = self.message_label.winfo_reqwidth()
# Calculate the offset based on available space and text width to avoid going off-screen on the right side
offset_x = self.x_offset
if space_on_right < text_width + 20: # Adjust the threshold as needed
offset_x = -text_width - 20 # Negative offset when space is limited on the right side
# Offsets the ToolTip using the coordinates od an event as an origin
self.geometry(f"+{event.x_root + offset_x}+{event.y_root + self.y_offset}")
# Time is in integer: milliseconds
self.after(int(self.delay * 1000), self._show)
def on_leave(self, event=None) -> None:
"""
Hides the ToolTip temporarily.
"""
if self.disable: return
self.status = "outside"
self.withdraw()
def _show(self) -> None:
"""
Displays the ToolTip.
"""
if not self.widget.winfo_exists():
self.hide()
self.destroy()
if self.status == "inside" and time.time() - self.last_moved >= self.delay:
self.status = "visible"
self.deiconify()
def hide(self) -> None:
"""
Disable the widget from appearing.
"""
if not self.winfo_exists():
return
self.withdraw()
self.disable = True
def is_disabled(self) -> None:
"""
Return the window state
"""
return self.disable
def get(self) -> None:
"""
Returns the text on the tooltip.
"""
return self.messageVar.get()
def configure(self, message: str = None, delay: float = None, bg_color: str = None, **kwargs):
"""
Set new message or configure the label parameters.
"""
if delay: self.delay = delay
if bg_color: self.frame.configure(fg_color=bg_color)
self.messageVar.set(message)
self.message_label.configure(**kwargs)

View File

@@ -0,0 +1,448 @@
"""
CustomTkinter Messagebox
Author: Akash Bora
Version: 2.5
"""
import customtkinter
from PIL import Image, ImageTk
import os
import sys
import time
from typing import Literal
class CTkMessagebox(customtkinter.CTkToplevel):
ICONS = {
"check": None,
"cancel": None,
"info": None,
"question": None,
"warning": None
}
ICON_BITMAP = {}
def __init__(self,
master: any = None,
width: int = 400,
height: int = 200,
title: str = "CTkMessagebox",
message: str = "This is a CTkMessagebox!",
option_1: str = "OK",
option_2: str = None,
option_3: str = None,
options: list = [],
border_width: int = 1,
border_color: str = "default",
button_color: str = "default",
bg_color: str = "default",
fg_color: str = "default",
text_color: str = "default",
title_color: str = "default",
button_text_color: str = "default",
button_width: int = None,
button_height: int = None,
cancel_button_color: str = None,
cancel_button: str = None, # types: circle, cross or none
button_hover_color: str = "default",
icon: str = "info",
icon_size: tuple = None,
corner_radius: int = 15,
justify: str = "right",
font: tuple = None,
header: bool = False,
topmost: bool = True,
fade_in_duration: int = 0,
sound: bool = False,
option_focus: Literal[1, 2, 3] = None):
super().__init__()
self.master_window = master
self.width = 250 if width<250 else width
self.height = 150 if height<150 else height
if self.master_window is None:
self.spawn_x = int((self.winfo_screenwidth()-self.width)/2)
self.spawn_y = int((self.winfo_screenheight()-self.height)/2)
else:
self.spawn_x = int(self.master_window.winfo_width() * .5 + self.master_window.winfo_x() - .5 * self.width + 7)
self.spawn_y = int(self.master_window.winfo_height() * .5 + self.master_window.winfo_y() - .5 * self.height + 20)
self.after(10)
self.geometry(f"{self.width}x{self.height}+{self.spawn_x}+{self.spawn_y}")
self.title(title)
self.resizable(width=False, height=False)
self.fade = fade_in_duration
if self.fade:
self.fade = 20 if self.fade<20 else self.fade
self.attributes("-alpha", 0)
if not header:
self.overrideredirect(1)
if topmost:
self.attributes("-topmost", True)
else:
self.transient(self.master_window)
if sys.platform.startswith("win"):
self.transparent_color = self._apply_appearance_mode(self.cget("fg_color"))
self.attributes("-transparentcolor", self.transparent_color)
default_cancel_button = "cross"
elif sys.platform.startswith("darwin"):
self.transparent_color = 'systemTransparent'
self.attributes("-transparent", True)
default_cancel_button = "circle"
else:
self.transparent_color = '#000001'
corner_radius = 0
default_cancel_button = "cross"
self.lift()
self.config(background=self.transparent_color)
self.protocol("WM_DELETE_WINDOW", self.button_event)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self.x = self.winfo_x()
self.y = self.winfo_y()
self._title = title
self.message = message
self.font = font
self.justify = justify
self.sound = sound
self.cancel_button = cancel_button if cancel_button else default_cancel_button
self.round_corners = corner_radius if corner_radius<=30 else 30
self.button_width = button_width if button_width else self.width/4
self.button_height = button_height if button_height else 28
if self.fade: self.attributes("-alpha", 0)
if self.button_height>self.height/4: self.button_height = self.height/4 -20
self.dot_color = cancel_button_color
self.border_width = border_width if border_width<6 else 5
if type(options) is list and len(options)>0:
try:
option_1 = options[-1]
option_2 = options[-2]
option_3 = options[-3]
except IndexError: None
if bg_color=="default":
self.bg_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"])
else:
self.bg_color = bg_color
if fg_color=="default":
self.fg_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"])
else:
self.fg_color = fg_color
default_button_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkButton"]["fg_color"])
if sys.platform.startswith("win"):
if self.bg_color==self.transparent_color or self.fg_color==self.transparent_color:
self.configure(fg_color="#000001")
self.transparent_color = "#000001"
self.attributes("-transparentcolor", self.transparent_color)
if button_color=="default":
self.button_color = (default_button_color, default_button_color, default_button_color)
else:
if type(button_color) is tuple:
if len(button_color)==2:
self.button_color = (button_color[0], button_color[1], default_button_color)
elif len(button_color)==1:
self.button_color = (button_color[0], default_button_color, default_button_color)
else:
self.button_color = button_color
else:
self.button_color = (button_color, button_color, button_color)
if text_color=="default":
self.text_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkLabel"]["text_color"])
else:
self.text_color = text_color
if title_color=="default":
self.title_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkLabel"]["text_color"])
else:
self.title_color = title_color
if button_text_color=="default":
self.bt_text_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkButton"]["text_color"])
else:
self.bt_text_color = button_text_color
if button_hover_color=="default":
self.bt_hv_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkButton"]["hover_color"])
else:
self.bt_hv_color = button_hover_color
if border_color=="default":
self.border_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkFrame"]["border_color"])
else:
self.border_color = border_color
if icon_size:
self.size_height = icon_size[1] if icon_size[1]<=self.height-100 else self.height-100
self.size = (icon_size[0], self.size_height)
else:
self.size = (self.height/4, self.height/4)
self.icon = self.load_icon(icon, icon_size) if icon else None
self.frame_top = customtkinter.CTkFrame(self, corner_radius=self.round_corners, width=self.width, border_width=self.border_width,
bg_color=self.transparent_color, fg_color=self.bg_color, border_color=self.border_color)
self.frame_top.grid(sticky="nswe")
if button_width:
self.frame_top.grid_columnconfigure(0, weight=1)
else:
self.frame_top.grid_columnconfigure((1,2,3), weight=1)
if button_height:
self.frame_top.grid_rowconfigure((0,1,3), weight=1)
else:
self.frame_top.grid_rowconfigure((0,1,2), weight=1)
self.frame_top.bind("<B1-Motion>", self.move_window)
self.frame_top.bind("<ButtonPress-1>", self.oldxyset)
if self.cancel_button=="cross":
self.button_close = customtkinter.CTkButton(self.frame_top, corner_radius=10, width=0, height=0, hover=False, border_width=0,
text_color=self.dot_color if self.dot_color else self.title_color,
text="", fg_color="transparent", command=self.button_event)
self.button_close.grid(row=0, column=5, sticky="ne", padx=5+self.border_width, pady=5+self.border_width)
elif self.cancel_button=="circle":
self.button_close = customtkinter.CTkButton(self.frame_top, corner_radius=10, width=10, height=10, hover=False, border_width=0,
text="", fg_color=self.dot_color if self.dot_color else "#c42b1c", command=self.button_event)
self.button_close.grid(row=0, column=5, sticky="ne", padx=10, pady=10)
self.title_label = customtkinter.CTkLabel(self.frame_top, width=1, text=self._title, text_color=self.title_color, font=self.font)
self.title_label.grid(row=0, column=0, columnspan=6, sticky="nw", padx=(15,30), pady=5)
self.title_label.bind("<B1-Motion>", self.move_window)
self.title_label.bind("<ButtonPress-1>", self.oldxyset)
self.info = customtkinter.CTkButton(self.frame_top, width=1, height=self.height/2, corner_radius=0, text=self.message, font=self.font,
fg_color=self.fg_color, hover=False, text_color=self.text_color, image=self.icon)
self.info._text_label.configure(wraplength=self.width/2, justify="left")
self.info.grid(row=1, column=0, columnspan=6, sticky="nwes", padx=self.border_width)
if self.info._text_label.winfo_reqheight()>self.height/2:
height_offset = int((self.info._text_label.winfo_reqheight())-(self.height/2) + self.height)
self.geometry(f"{self.width}x{height_offset}")
self.option_text_1 = option_1
self.button_1 = customtkinter.CTkButton(self.frame_top, text=self.option_text_1, fg_color=self.button_color[0],
width=self.button_width, font=self.font, text_color=self.bt_text_color,
hover_color=self.bt_hv_color, height=self.button_height,
command=lambda: self.button_event(self.option_text_1))
self.option_text_2 = option_2
if option_2:
self.button_2 = customtkinter.CTkButton(self.frame_top, text=self.option_text_2, fg_color=self.button_color[1],
width=self.button_width, font=self.font, text_color=self.bt_text_color,
hover_color=self.bt_hv_color, height=self.button_height,
command=lambda: self.button_event(self.option_text_2))
self.option_text_3 = option_3
if option_3:
self.button_3 = customtkinter.CTkButton(self.frame_top, text=self.option_text_3, fg_color=self.button_color[2],
width=self.button_width, font=self.font, text_color=self.bt_text_color,
hover_color=self.bt_hv_color, height=self.button_height,
command=lambda: self.button_event(self.option_text_3))
if self.justify=="center":
if button_width:
columns = [4,3,2]
span = 1
else:
columns = [4,2,0]
span = 2
if option_3:
self.frame_top.columnconfigure((0,1,2,3,4,5), weight=1)
self.button_1.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(0,10), pady=10)
self.button_2.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=10, pady=10)
self.button_3.grid(row=2, column=columns[2], columnspan=span, sticky="news", padx=(10,0), pady=10)
elif option_2:
self.frame_top.columnconfigure((0,5), weight=1)
columns = [2,3]
self.button_1.grid(row=2, column=columns[0], sticky="news", padx=(0,5), pady=10)
self.button_2.grid(row=2, column=columns[1], sticky="news", padx=(5,0), pady=10)
else:
if button_width:
self.frame_top.columnconfigure((0,1,2,3,4,5), weight=1)
else:
self.frame_top.columnconfigure((0,2,4), weight=2)
self.button_1.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=(0,10), pady=10)
elif self.justify=="left":
self.frame_top.columnconfigure((0,1,2,3,4,5), weight=1)
if button_width:
columns = [0,1,2]
span = 1
else:
columns = [0,2,4]
span = 2
if option_3:
self.button_1.grid(row=2, column=columns[2], columnspan=span, sticky="news", padx=(0,10), pady=10)
self.button_2.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=10, pady=10)
self.button_3.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(10,0), pady=10)
elif option_2:
self.button_1.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=10, pady=10)
self.button_2.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(10,0), pady=10)
else:
self.button_1.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(10,0), pady=10)
else:
self.frame_top.columnconfigure((0,1,2,3,4,5), weight=1)
if button_width:
columns = [5,4,3]
span = 1
else:
columns = [4,2,0]
span = 2
self.button_1.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(0,10), pady=10)
if option_2:
self.button_2.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=10, pady=10)
if option_3:
self.button_3.grid(row=2, column=columns[2], columnspan=span, sticky="news", padx=(10,0), pady=10)
if header:
self.title_label.configure(text="")
self.title_label.grid_configure(pady=0)
self.button_close.configure(text_color=self.bg_color)
self.frame_top.configure(corner_radius=0)
if self.winfo_exists():
self.grab_set()
if self.sound:
self.bell()
if self.fade:
self.fade_in()
if option_focus:
self.option_focus = option_focus
self.focus_button(self.option_focus)
else:
if not self.option_text_2 and not self.option_text_3:
self.button_1.focus()
self.button_1.bind("<Return>", lambda event: self.button_event(self.option_text_1))
self.bind("<Escape>", lambda e: self.button_event())
def focus_button(self, option_focus):
try:
self.selected_button = getattr(self, "button_"+str(option_focus))
self.selected_button.focus()
self.selected_button.configure(border_color=self.bt_hv_color, border_width=3)
self.selected_option = getattr(self, "option_text_"+str(option_focus))
self.selected_button.bind("<Return>", lambda event: self.button_event(self.selected_option))
except AttributeError:
return
self.bind("<Left>", lambda e: self.change_left())
self.bind("<Right>", lambda e: self.change_right())
def change_left(self):
if self.option_focus==3:
return
self.selected_button.unbind("<Return>")
self.selected_button.configure(border_width=0)
if self.option_focus==1:
if self.option_text_2:
self.option_focus = 2
elif self.option_focus==2:
if self.option_text_3:
self.option_focus = 3
self.focus_button(self.option_focus)
def change_right(self):
if self.option_focus==1:
return
self.selected_button.unbind("<Return>")
self.selected_button.configure(border_width=0)
if self.option_focus==2:
self.option_focus = 1
elif self.option_focus==3:
self.option_focus = 2
self.focus_button(self.option_focus)
def load_icon(self, icon, icon_size):
if icon not in self.ICONS or self.ICONS[icon] is None:
if icon in ["check", "cancel", "info", "question", "warning"]:
image_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'icons', icon + '.png')
else:
image_path = icon
if icon_size:
size_height = icon_size[1] if icon_size[1] <= self.height - 100 else self.height - 100
size = (icon_size[0], size_height)
else:
size = (self.height / 4, self.height / 4)
self.ICONS[icon] = customtkinter.CTkImage(Image.open(image_path), size=size)
self.ICON_BITMAP[icon] = ImageTk.PhotoImage(file=image_path)
self.after(200, lambda: self.iconphoto(False, self.ICON_BITMAP[icon]))
return self.ICONS[icon]
def fade_in(self):
for i in range(0,110,10):
if not self.winfo_exists():
break
self.attributes("-alpha", i/100)
self.update()
time.sleep(1/self.fade)
def fade_out(self):
for i in range(100,0,-10):
if not self.winfo_exists():
break
self.attributes("-alpha", i/100)
self.update()
time.sleep(1/self.fade)
def get(self):
if self.winfo_exists():
self.master.wait_window(self)
return self.event
def oldxyset(self, event):
self.oldx = event.x
self.oldy = event.y
def move_window(self, event):
self.y = event.y_root - self.oldy
self.x = event.x_root - self.oldx
self.geometry(f'+{self.x}+{self.y}')
def button_event(self, event=None):
try:
self.button_1.configure(state="disabled")
self.button_2.configure(state="disabled")
self.button_3.configure(state="disabled")
except AttributeError:
pass
if self.fade:
self.fade_out()
self.grab_release()
self.destroy()
self.event = event
if __name__ == "__main__":
app = CTkMessagebox()
app.mainloop()

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

0
gui/frames/__init__.py Normal file
View File

View File

@@ -0,0 +1,63 @@
import customtkinter
from gui.custom_widgets.ctk_tooltip import CTkToolTip
from tkinter import filedialog, END
class CreateBackupFrame(customtkinter.CTkFrame):
def __init__(self, master, linker, config, **kwargs):
super().__init__(master, **kwargs)
self.linker = linker
self.config = config
self.create_widgets()
self.bind_to_config()
def create_widgets(self):
self.login_settings_label = customtkinter.CTkLabel(self, text="Create Backup Settings", font=customtkinter.CTkFont(family="Inter", size=30, weight="bold"))
self.login_settings_label.grid(row=0, column=0, columnspan =2, sticky="nw", padx=20, pady=20)
self.create_downloadpath_widgets()
self.create_filename_widgets()
self.create_folders_widgets()
self.linker.login = self
def create_downloadpath_widgets(self):
self.downloadpath = customtkinter.CTkLabel(self, text="Output Directory:", font=customtkinter.CTkFont(size=20, underline=True))
self.downloadpath.grid(row=3, column=0, padx=40, pady=(20, 10), sticky="nw")
self.downloadpath_entry = customtkinter.CTkEntry(self, font=customtkinter.CTkFont(family="Inter", size=16))
self.downloadpath_entry.grid(row=4, column=0, columnspan=2, padx=(60,0), pady=(20, 10), sticky="nsew")
self.downloadpath_button = customtkinter.CTkButton(self, width=50, text="Select", command = self.open_folder)
self.downloadpath_button.grid(row=4, column=2, padx=20, pady=(20, 10), sticky="nsew")
def create_filename_widgets(self):
self.filename_label = customtkinter.CTkLabel(self, text="Filename")
self.filename_label.grid(row=5, column=0)
self.filename_entry = customtkinter.CTkEntry(self, font=customtkinter.CTkFont(family="Inter", size=16))
self.filename_entry.grid(row=6, column=0, columnspan=2, padx=(60,0), pady=(20, 10), sticky="nsew")
def create_folders_widgets(self):
self.folders_label = customtkinter.CTkLabel(self, text="Game Folders:")
self.folders_label.grid(row=7, column=0)
self.userdata_checkbox = customtkinter.CTkCheckBox(self, text="Userdata")
self.userdata_checkbox.grid(row=8, column=0)
self.mods_checkbox = customtkinter.CTkCheckBox(self, text="mods")
self.mods_checkbox.grid(row=8, column=1)
self.bepinex_checkbox = customtkinter.CTkCheckBox(self, text="BepInEx")
self.bepinex_checkbox.grid(row=8, column=2)
def bind_to_config(self):
self.config.bind(self.downloadpath_entry, ["CreateBackup", "OutputPath"])
self.config.bind(self.filename_entry, ["CreateBackup", "Filename"])
self.config.bind(self.userdata_checkbox, ["CreateBackup", "GameFolders", "UserData"])
self.config.bind(self.mods_checkbox, ["CreateBackup", "GameFolders", "mods"])
self.config.bind(self.bepinex_checkbox, ["CreateBackup", "GameFolders", "BepInEx"])
def open_folder(self):
folderpath = filedialog.askdirectory()
if folderpath != "":
self.downloadpath_entry.delete(0, END)
self.downloadpath_entry.insert(0, folderpath)
self.config.save_to_json(["CreateBackup", "OutputPath"])

44
gui/frames/fc_kks.py Normal file
View File

@@ -0,0 +1,44 @@
import customtkinter
from gui.custom_widgets.ctk_tooltip import CTkToolTip
from tkinter import filedialog, END
class FilterConvertKKSFrame(customtkinter.CTkFrame):
def __init__(self, master, linker, config, **kwargs):
super().__init__(master, **kwargs)
self.linker = linker
self.config = config
self.create_widgets()
self.bind_to_config()
def create_widgets(self):
self.login_settings_label = customtkinter.CTkLabel(self, text="Filter & Convert KKS Chara Settings", font=customtkinter.CTkFont(family="Inter", size=30, weight="bold"))
self.login_settings_label.grid(row=0, column=0, columnspan=2, sticky="nw", padx=20, pady=20)
self.create_downloadpath_widgets()
self.create_convert_widgets()
self.linker.login = self
def create_downloadpath_widgets(self):
self.downloadpath = customtkinter.CTkLabel(self, text="Input Directory:", font=customtkinter.CTkFont(size=20, underline=True))
self.downloadpath.grid(row=3, column=0, padx=40, pady=(20, 10), sticky="nw")
self.downloadpath_entry = customtkinter.CTkEntry(self, font=customtkinter.CTkFont(family="Inter", size=16))
self.downloadpath_entry.grid(row=4, column=0, columnspan=2, padx=(60,0), pady=(20, 10), sticky="nsew")
self.downloadpath_button = customtkinter.CTkButton(self, width=50, text="Select", command = self.open_folder)
self.downloadpath_button.grid(row=4, column=2, padx=20, pady=(20, 10), sticky="nsew")
def create_convert_widgets(self):
self.convert_checkbox = customtkinter.CTkCheckBox(self, text="Convert KKS to KK Chara")
self.convert_checkbox.grid(row=5, column=0)
def bind_to_config(self):
self.config.bind(self.downloadpath_entry, ["FCKKS", "InputPath"])
self.config.bind(self.convert_checkbox, ["FCKKS", "Convert"])
def open_folder(self):
folderpath = filedialog.askdirectory()
if folderpath != "":
self.downloadpath_entry.delete(0, END)
self.downloadpath_entry.insert(0, folderpath)
self.config.save_to_json(["FCKKS", "InputPath"])

View File

@@ -0,0 +1,56 @@
import customtkinter
from gui.custom_widgets.ctk_tooltip import CTkToolTip
from tkinter import filedialog, END
class InstallCharaFrame(customtkinter.CTkFrame):
def __init__(self, master, linker, config, **kwargs):
super().__init__(master, **kwargs)
self.linker = linker
self.config = config
self.create_widgets()
self.bind_to_config()
def create_widgets(self):
self.login_settings_label = customtkinter.CTkLabel(self, text="Install Chara Settings", font=customtkinter.CTkFont(family="Inter", size=30, weight="bold"))
self.login_settings_label.grid(row=0, column=0, columnspan =2, sticky="nw", padx=20, pady=20)
self.create_downloadpath_widgets()
self.create_conflict_widgets()
self.create_password_widgets()
self.linker.login = self
def create_downloadpath_widgets(self):
self.downloadpath = customtkinter.CTkLabel(self, text="Input Directory:", font=customtkinter.CTkFont(size=20, underline=True))
self.downloadpath.grid(row=3, column=0, padx=40, pady=(20, 10), sticky="nw")
self.downloadpath_entry = customtkinter.CTkEntry(self, font=customtkinter.CTkFont(family="Inter", size=16))
self.downloadpath_entry.grid(row=4, column=0, columnspan=2, padx=(60,0), pady=(20, 10), sticky="nsew")
self.downloadpath_button = customtkinter.CTkButton(self, width=50, text="Select", command = self.open_folder)
self.downloadpath_button.grid(row=4, column=2, padx=20, pady=(20, 10), sticky="nsew")
def create_conflict_widgets(self):
self.conflict_label = customtkinter.CTkLabel(self, text="If file conflicts:", font=customtkinter.CTkFont(size=20))
self.conflict_label.grid(row=6, column=0, padx=20, pady=(20, 10))
self.conflict_dropdown = customtkinter.CTkOptionMenu(self, values=["Skip", "Replace", "Rename"])
self.conflict_dropdown.grid(row=6, column=1, padx=20, pady=(20, 10))
def create_password_widgets(self):
self.password_label = customtkinter.CTkLabel(self, text="If password required for archives:", font=customtkinter.CTkFont(size=20), wraplength=200)
self.password_label.grid(row=7, column=0, padx=20, pady=(20, 10))
self.password_dropdown = customtkinter.CTkOptionMenu(self, values=["Skip", "Request Password"])
self.password_dropdown.grid(row=7, column=1, padx=20, pady=(20, 10))
def bind_to_config(self):
self.config.bind(self.downloadpath_entry, ["InstallChara", "InputPath"])
self.config.bind(self.conflict_dropdown, ["InstallChara", "FileConflicts"])
self.config.bind(self.password_dropdown, ["InstallChara", "ArchivePassword"])
def open_folder(self):
folderpath = filedialog.askdirectory()
if folderpath != "":
self.downloadpath_entry.delete(0, END)
self.downloadpath_entry.insert(0, folderpath)
self.config.save_to_json(["InstallChara", "InputPath"])

39
gui/frames/logger.py Normal file
View File

@@ -0,0 +1,39 @@
import customtkinter
class LoggerTextBox(customtkinter.CTkFrame):
def __init__(self, master, linker, config, **kwargs):
super().__init__(master=master, **kwargs)
self.linker = linker
self.config = config
self.grid_rowconfigure(0, weight=0)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)
self.log_label = customtkinter.CTkLabel(self, text="Log",font=customtkinter.CTkFont(family="Inter", size=30, weight="bold"))
self.log_label.grid(row=0, column=0, sticky="nw", padx=20, pady=(20,0))
# Button to toggle autoscroll
self.toggle_autoscroll_button = customtkinter.CTkButton(self, height=35, text="Autoscroll On", command=self.toggle_autoscroll, font=("Inter", 16))
self.toggle_autoscroll_button.grid(row=0, column=1, padx=20, pady=20, sticky="nsew")
self.autoscroll_enabled = True # Initially, autoscroll is enabled
self.log_textbox = customtkinter.CTkTextbox(self, state="disabled", font=("Inter", 16), wrap="word")
self.log_textbox.grid(row=1, column=0,columnspan=4, padx=20, pady=20, sticky="nsew")
self.log_level_colors = {
"[MSG]": "white",
"[INFO]": "light blue",
"[SUCCESS]": "light green",
"[ERROR]": "red",
"[SKIPPED]": "orange",
"[REPLACED]": "orange",
"[RENAMED]": "orange",
"[REMOVED]": "orange",
}
for level, color in self.log_level_colors.items():
self.log_textbox.tag_config(level, foreground=color)
self.linker.logger = self
def toggle_autoscroll(self):
self.autoscroll_enabled = not self.autoscroll_enabled
if self.autoscroll_enabled:
self.toggle_autoscroll_button.configure(text="Autoscroll On")
else:
self.toggle_autoscroll_button.configure(text="Autoscroll Off")

View File

@@ -0,0 +1,38 @@
import customtkinter
from gui.custom_widgets.ctk_tooltip import CTkToolTip
from tkinter import filedialog, END
class RemoveCharaFrame(customtkinter.CTkFrame):
def __init__(self, master, linker, config, **kwargs):
super().__init__(master, **kwargs)
self.linker = linker
self.config = config
self.create_widgets()
self.bind_to_config()
def create_widgets(self):
self.login_settings_label = customtkinter.CTkLabel(self, text="Remove Chara Settings", font=customtkinter.CTkFont(family="Inter", size=30, weight="bold"))
self.login_settings_label.grid(row=0, column=0, columnspan =2, sticky="nw", padx=20, pady=20)
self.create_downloadpath_widgets()
self.linker.login = self
def create_downloadpath_widgets(self):
self.downloadpath = customtkinter.CTkLabel(self, text="Input Directory:", font=customtkinter.CTkFont(size=20, underline=True))
self.downloadpath.grid(row=3, column=0, padx=40, pady=(20, 10), sticky="nw")
self.downloadpath_entry = customtkinter.CTkEntry(self, font=customtkinter.CTkFont(family="Inter", size=16))
self.downloadpath_entry.grid(row=4, column=0, columnspan=2, padx=(60,0), pady=(20, 10), sticky="nsew")
self.downloadpath_button = customtkinter.CTkButton(self, width=50, text="Select", command = self.open_folder)
self.downloadpath_button.grid(row=4, column=2, padx=20, pady=(20, 10), sticky="nsew")
def bind_to_config(self):
self.config.bind(self.downloadpath_entry, ["RemoveChara", "InputPath"])
def open_folder(self):
folderpath = filedialog.askdirectory()
if folderpath != "":
self.downloadpath_entry.delete(0, END)
self.downloadpath_entry.insert(0, folderpath)
self.config.save_to_json(["RemoveChara", "InputPath"])

128
gui/frames/sidebar.py Normal file
View File

@@ -0,0 +1,128 @@
import customtkinter
from PIL import Image
from gui.frames.install_chara import InstallCharaFrame
from gui.frames.remove_chara import RemoveCharaFrame
from gui.frames.fc_kks import FilterConvertKKSFrame
from gui.frames.create_backup import CreateBackupFrame
from gui.custom_widgets.ctk_tooltip import CTkToolTip
from tkinter import filedialog, END
class Sidebar(customtkinter.CTkFrame):
def __init__(self, master, linker, config, **kwargs):
self.master = master
self.linker = linker
self.config = config
super().__init__(master=self.master, **kwargs)
self.grid_rowconfigure((0, 1, 2), weight=1)
self.grid_columnconfigure(0, weight=1)
karin_logo = customtkinter.CTkImage(light_image=Image.open("gui/icons/karin.png"), size=(152,152))
karin_logo_label = customtkinter.CTkLabel(self, image=karin_logo, text="")
karin_logo_label.grid(row=0, column=0, sticky="nsew")
self.gear_on = customtkinter.CTkImage(Image.open("gui/icons/gear_on.png"), size=(50,38))
self.gear_off = customtkinter.CTkImage(Image.open("gui/icons/gear_off.png"), size=(50,38))
self.create_module_frames()
self.create_all_button_frame()
self.create_gamepath_frame()
self.create_start_button()
self.create_notification_frames()
self.linker.sidebar = self
def create_module_frames(self):
self.checkbox_frame = customtkinter.CTkFrame(self, fg_color="transparent", border_color="white", border_width=2)
self.checkbox_frame.grid(row=1, column=0, columnspan=4, padx=10, pady=10, sticky="w")
self.prettify = {
"InstallChara": "Install Chara",
"RemoveChara": "Remove Chara",
"CreateBackup": "Create Backup",
"FCKKS": "F&C KKS"
}
self.module_list = [["CreateBackup", CreateBackupFrame], ["FCKKS", FilterConvertKKSFrame], ["InstallChara", InstallCharaFrame], ["RemoveChara", RemoveCharaFrame]]
for index, sublist in enumerate(self.module_list):
module = sublist[0]
self.linker.modules_dictionary[module] = {}
self.create_module_checkbox(module, index)
self.create_module_button(module, index)
frame = sublist[1](self.master, self.linker, self.config, fg_color="#262250")
self.linker.modules_dictionary[module]['frame'] = frame
self.linker.modules_dictionary["CreateBackup"]["button"].configure(image=self.gear_on)
self.linker.modules_dictionary["CreateBackup"]["checkbox"].configure(text_color="#53B9E9")
self.current_frame = self.linker.modules_dictionary["CreateBackup"]["frame"] # Update the current frame
self.current_frame.grid(row=0, column=1, padx=20, pady=20, sticky="nsew")
def create_module_checkbox(self, module, i):
self.linker.modules_dictionary[module]['checkbox'] = customtkinter.CTkCheckBox(
self.checkbox_frame, text=self.prettify[module], text_color="#FFFFFF", font=("Inter", 16), command=lambda x=[module, "Enable"]: self.config.save_to_json(x))
self.linker.modules_dictionary[module]['checkbox'].grid(row=i, column=0, columnspan=2,padx=20, pady=(10, 5), sticky="nw")
self.linker.widgets[module]['Enable'] = self.linker.modules_dictionary[module]['checkbox']
def create_module_button(self, module, i):
self.linker.modules_dictionary[module]['button'] = customtkinter.CTkButton(
self.checkbox_frame, width=50, image=self.gear_off, text="", fg_color="transparent", command=lambda x=module: self.display_settings(module))
self.linker.modules_dictionary[module]['button'].grid(row=i, column=1, padx=(40,0), pady=(2,0), sticky="nw")
def create_all_button_frame(self):
self.select_all_button = customtkinter.CTkButton(self.checkbox_frame, width=100, text="Select All", fg_color="#DC621D", font=("Inter",20), command=self.select_all)
self.select_all_button.grid(row=4, column=0, padx=10, pady=(15,20), sticky="w")
self.clear_all_button = customtkinter.CTkButton(self.checkbox_frame, width=100, text="Clear All", fg_color="#DC621D", font=("Inter",20), command=self.clear_all)
self.clear_all_button.grid(row=4, column=1, padx=10, pady=(15,20), sticky="w")
def create_gamepath_frame(self):
self.gamepath_frame = customtkinter.CTkFrame(self, fg_color="transparent")
self.gamepath_frame.grid(row=2, column=0)
self.gamepath_label = customtkinter.CTkLabel(self.gamepath_frame, text="Game Directory", font=customtkinter.CTkFont(size=16, family="Inter", underline=True))
self.gamepath_label.grid(row=0, column=0, padx=(0, 10), sticky="nw")
self.gamepath_entry = customtkinter.CTkEntry(self.gamepath_frame, font=customtkinter.CTkFont(family="Inter", size=16))
self.gamepath_entry.grid(row=1, column=0, columnspan=2)
self.config.bind(self.gamepath_entry, ["Core", "GamePath"])
self.gamepath_button = customtkinter.CTkButton(self.gamepath_frame, width=50, text="Select", command = self.open_folder)
self.gamepath_button.grid(row=1, column=1)
def create_start_button(self):
self.start_button = customtkinter.CTkButton(self, text="Start", width=200, height=40, command=self.linker.start_stop, font=customtkinter.CTkFont(family="Inter", size=16))
self.start_button.grid(row=3, column=0, pady=20, sticky="n")
def create_notification_frames(self):
for index, element in enumerate(["Template", "Queue", "Configuration"]):
frame = customtkinter.CTkFrame(self, fg_color="transparent", height=50)
if index == 0:
top_pady=170
else:
top_pady=0
frame.grid(row=3+index, column=0, sticky="s", pady=(top_pady,0))
self.linker.name_to_sidebar_frame[element] = frame
def select_all(self):
for module in self.linker.modules_dictionary:
self.linker.modules_dictionary[module]["checkbox"].select()
self.config.config_data[module]["Enable"] = True
self.config.save_file("Configuration")
def clear_all(self):
for module in self.linker.modules_dictionary:
self.linker.modules_dictionary[module]["checkbox"].deselect()
self.config.config_data[module]["Enable"] = False
self.config.save_file("Configuration")
def display_settings(self, module):
for key in self.linker.modules_dictionary:
if key == module:
self.linker.modules_dictionary[key]["button"].configure(image=self.gear_on)
self.linker.modules_dictionary[key]["checkbox"].configure(text_color="#53B9E9")
self.current_frame.grid_remove() # Hide the current frame
self.current_frame = self.linker.modules_dictionary[key]["frame"] # Update the current frame
self.current_frame.grid(row=0, column=1, padx=20, pady=20, sticky="nsew")
else:
self.linker.modules_dictionary[key]["button"].configure(image=self.gear_off)
self.linker.modules_dictionary[key]["checkbox"].configure(text_color="#FFFFFF")
def open_folder(self):
folderpath = filedialog.askdirectory()
if folderpath != "":
self.gamepath_entry.delete(0, END)
self.gamepath_entry.insert(0, folderpath)
self.config.save_to_json(["Core", "GamePath"])

BIN
gui/icons/gear_off.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
gui/icons/gear_on.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
gui/icons/karin.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
gui/icons/karin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

85
gui/util/config.py Normal file
View File

@@ -0,0 +1,85 @@
import sys
import json
import customtkinter
class Config:
def __init__(self, linker, config_file):
self.linker = linker
self.config_file = config_file
self.config_data = self.read()
self.linker.widgets = self.set_values_to_none(self.config_data)
linker.config = self
def read(self):
# Read the JSON file
try:
with open(self.config_file, 'r') as json_file:
config_data = json.load(json_file)
return config_data
except FileNotFoundError:
print(f"Config file '{self.config_file}' not found.")
sys.exit(1)
except json.JSONDecodeError:
print(f"Invalid JSON format in '{self.config_file}'.")
sys.exit(1)
def set_values_to_none(self, input_dict):
result = {}
for key, value in input_dict.items():
if isinstance(value, dict):
result[key] = self.set_values_to_none(value)
else:
result[key] = None
return result
def load_config(self, widgets=None, config_data=None):
if widgets == None:
widgets = self.linker.widgets
config_data = self.config_data
for key in widgets:
if isinstance(widgets[key], dict) and isinstance(config_data[key], dict):
self.load_config(widgets[key], config_data[key])
else:
if widgets[key] is not None:
if isinstance(widgets[key], customtkinter.CTkCheckBox):
if config_data[key] == True:
widgets[key].select()
else:
widgets[key].deselect()
elif isinstance(widgets[key], customtkinter.CTkEntry):
widgets[key].insert(0, config_data[key])
else:
widgets[key].set(config_data[key])
def bind(self, widget, list_keys):
if isinstance(widget, customtkinter.CTkEntry):
widget.bind("<KeyRelease>", lambda event, x=list_keys: self.save_to_json(x))
elif isinstance(widget, (customtkinter.CTkCheckBox)):
widget.configure(command=lambda x=list_keys: self.save_to_json(x))
else:
widget.configure(command=lambda x, y=list_keys: self.save_to_json(y))
widgets_dictionary = self.linker.widgets
for key in list_keys[:-1]:
widgets_dictionary = widgets_dictionary[key]
widgets_dictionary[list_keys[-1]] = widget
def save_to_json(self, list_keys):
widget = self.linker.widgets
data = self.config_data
for i in list_keys[:-1]:
widget = widget[i]
data = data[i]
widget = widget[list_keys[-1]]
value = widget.get()
if isinstance(widget, customtkinter.CTkCheckBox):
value = True if value==1 else False
data[list_keys[-1]] = value
self.save_file("Configuration")
def save_file(self, name=None):
with open("config.json", "w") as config_file:
json.dump(self.config_data, config_file, indent=2)
if name:
self.linker.show_notification(name)

67
gui/util/linker.py Normal file
View File

@@ -0,0 +1,67 @@
import subprocess
import threading
from gui.custom_widgets.ctk_notification import CTkNotification
class Linker:
def __init__(self, master):
self.config = None
self.widgets = {}
self.logger = None
self.login = None
# script.py process
self.script = None
self.master = master
self.modules_dictionary = {}
self.name_to_sidebar_frame = {}
def terminate_script(self):
# If process is running, terminate it
self.script.terminate()
self.script = None
self.sidebar.start_button.configure(text="Start", fg_color = ['#3B8ED0', '#1F6AA5'])
def start_stop(self):
if hasattr(self, 'script') and self.script is not None:
self.terminate_script()
else:
# If process is not running, start it
self.script = subprocess.Popen(['python', 'script.py'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
threading.Thread(target=self.read_output).start()
self.sidebar.start_button.configure(text="Stop", fg_color = "crimson")
def read_output(self):
while self.script is not None:
line = self.script.stdout.readline().decode('utf-8')
if line == "":
if hasattr(self, 'script') and self.script is not None:
self.master.after(10, self.terminate_script)
return
# Check if line contains any log level
for level, color in self.logger.log_level_colors.items():
if level in line:
# Display output in text box with color
self.logger.log_textbox.configure(state="normal")
if level == "[MSG]":
self.logger.log_textbox.insert("end", "-" * 87 + "\n", level)
line = line.replace("[MSG]", "")
self.logger.log_textbox.insert("end", line, level)
elif level == "[INFO]" and "Start Task:" in line:
self.logger.log_textbox.insert("end", "*" * 87 + "\n", level)
self.logger.log_textbox.insert("end", line, level)
else:
self.logger.log_textbox.insert("end", line, level)
self.logger.log_textbox.configure(state="disabled")
break
if self.logger.autoscroll_enabled:
self.logger.log_textbox.yview_moveto(1.0)
def show_notification(self, name):
sidebar_frame = self.name_to_sidebar_frame[name]
if self.script:
new_notification = CTkNotification(text= f"{name} was saved but will be read by the script in the next run.", master=sidebar_frame, fg_color="orange")
else:
new_notification = CTkNotification(text= f"{name} was saved successfully.", master=sidebar_frame, fg_color="green")
new_notification.grid(row=0, column=0, sticky="nsew")
self.sidebar.master.after(2500, new_notification.destroy)