Compare commits
80 Commits
2cad7ebbac
...
e5f91e0c0a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5f91e0c0a | ||
|
|
8287822fe4 | ||
|
|
1c2ebf6b8b | ||
|
|
d713121a86 | ||
|
|
de73446f73 | ||
|
|
e7878d63b0 | ||
|
|
9927f0550b | ||
|
|
c4c7df1f21 | ||
|
|
4fa4478d77 | ||
|
|
ccd9466b77 | ||
|
|
a8caafb292 | ||
|
|
7e1070e740 | ||
|
|
01a3fdfce5 | ||
|
|
9d0c276db5 | ||
|
|
fd4ec3aff0 | ||
|
|
0cbde8077e | ||
|
|
7c1620c0f0 | ||
|
|
6cca8f1082 | ||
|
|
a74d05aeda | ||
|
|
31a69f11ed | ||
|
|
726662d8b7 | ||
|
|
1aa9a50e62 | ||
|
|
08ee060f34 | ||
|
|
4bb95b61ff | ||
|
|
17ee226a5a | ||
|
|
a1af2b0b74 | ||
|
|
e5fe0d096c | ||
|
|
f4ad80d17e | ||
|
|
e28b2f1e2e | ||
|
|
7d03615916 | ||
|
|
0184376076 | ||
|
|
17851b86de | ||
|
|
b5d2c13259 | ||
|
|
659172cdd3 | ||
|
|
ec543c6db2 | ||
|
|
a02c05bd69 | ||
|
|
c674c7a53b | ||
|
|
1ab7c5c40f | ||
|
|
64d63bdd24 | ||
|
|
f932b2ac10 | ||
|
|
73568fe48c | ||
|
|
0d35587940 | ||
|
|
ee87f92252 | ||
|
|
a627f76197 | ||
|
|
d7722c044e | ||
|
|
9bf970e5fb | ||
|
|
c4e12c4194 | ||
|
|
92aaac7b5a | ||
|
|
782e61ad9a | ||
|
|
149e6ea1ef | ||
|
|
69bff3f757 | ||
|
|
9b9d0a5bcd | ||
|
|
10607f9c3d | ||
|
|
390082fa50 | ||
|
|
ca1b1b2efd | ||
|
|
4817b6768f | ||
|
|
9c7fc247a1 | ||
|
|
fe7c6f92a8 | ||
|
|
93b7ca8cfc | ||
|
|
151a085a9c | ||
|
|
a241484e24 | ||
|
|
3ac2009737 | ||
|
|
59b310b6df | ||
|
|
fb02cedd8d | ||
|
|
204c56efac | ||
|
|
fc1edefa79 | ||
|
|
a915ce396b | ||
|
|
e4afe9a112 | ||
|
|
d34a8a39d5 | ||
|
|
9aaf1e8f3b | ||
|
|
bcbe10d291 | ||
| 69a612dc55 | |||
| b8b59e7dbc | |||
| 41e2d188b9 | |||
| 107392a900 | |||
| 8ccb3d3d22 | |||
| 694d88a339 | |||
|
|
17964cbd9a | ||
| f26dffa221 | |||
|
|
2f44074400 |
1
.gitignore
vendored
@ -20,6 +20,7 @@ config/reloadalas
|
||||
test.py
|
||||
test/
|
||||
note.md
|
||||
MCE/config.json
|
||||
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
|
||||
|
||||
102
Input Helper.py
Normal file
@ -0,0 +1,102 @@
|
||||
import customtkinter
|
||||
import json
|
||||
from MCE.custom_widgets.ctk_scrollable_dropdown import CTkScrollableDropdown
|
||||
import os
|
||||
from tkinter import END
|
||||
|
||||
class InputHelper(customtkinter.CTk):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.create_tabview()
|
||||
self.create_invite_student_widgets()
|
||||
self.create_lessons_widgets()
|
||||
|
||||
|
||||
def create_tabview(self):
|
||||
self.tabview = customtkinter.CTkTabview(master=self)
|
||||
self.tabview.grid(row=0, column=0)
|
||||
|
||||
self.cafe_tab = self.tabview.add("Cafe") # add tab at the end
|
||||
self.lessons_tab = self.tabview.add("Lessons") # add tab at the end
|
||||
self.tabview.set("Cafe") # set currently visible
|
||||
|
||||
def create_invite_student_widgets(self):
|
||||
self.invitation_label = customtkinter.CTkLabel(master=self.cafe_tab, text="Copy and paste this in AAS Invitation Settings:")
|
||||
self.invitation_label.grid(row=0, column=0, padx=60)
|
||||
|
||||
self.invitation_entry = customtkinter.CTkEntry(master=self.cafe_tab, width=500)
|
||||
self.invitation_entry.grid(row=1, column=0)
|
||||
|
||||
self.invite_copy_button = customtkinter.CTkButton(master=self.cafe_tab, text="Copy", width=40, command=lambda : self.copy_entry(self.invitation_entry, self.invite_copy_button))
|
||||
self.invite_copy_button.grid(row=1, column=3, padx=5)
|
||||
|
||||
self.invite_clear_button = customtkinter.CTkButton(master=self.cafe_tab, text="Clear", width=40, fg_color="crimson", command=lambda : self.invitation_entry.delete(0, END))
|
||||
self.invite_clear_button.grid(row=1, column=4, padx=5)
|
||||
|
||||
self.invite_frame = customtkinter.CTkFrame(master=self.cafe_tab, fg_color="transparent")
|
||||
self.invite_frame.grid(row=2, column=0, padx=20, pady=20)
|
||||
|
||||
self.server_dropdown = customtkinter.CTkOptionMenu(master=self.invite_frame, values=self.find_json_files("MCE/student_list"), command=self.switch_server, width=40)
|
||||
self.server_dropdown.grid(row=0, column=0)
|
||||
|
||||
self.student_entry = customtkinter.CTkComboBox(master=self.invite_frame, width=300)
|
||||
self.student_entry.grid(row=0, column=1, padx=(50,0))
|
||||
|
||||
self.student_dropdown = CTkScrollableDropdown(self.student_entry, width=300, height=550, autocomplete=True, command=lambda choice: self.insert(choice, self.invitation_entry), values=[""])
|
||||
self.server_dropdown.set("EN")
|
||||
self.switch_server("EN")
|
||||
|
||||
def create_lessons_widgets(self):
|
||||
self.lessons_label = customtkinter.CTkLabel(master=self.lessons_tab, text="Copy and paste this in AAS Lessons Settings:")
|
||||
self.lessons_label.grid(row=0, column=0, padx=60)
|
||||
|
||||
self.lessons_entry = customtkinter.CTkEntry(master=self.lessons_tab, width=500)
|
||||
self.lessons_entry.grid(row=1, column=0)
|
||||
|
||||
self.lessons_copy_button = customtkinter.CTkButton(master=self.lessons_tab, text="Copy", width=40, command=lambda : self.copy_entry(self.lessons_entry, self.lessons_copy_button))
|
||||
self.lessons_copy_button.grid(row=1, column=1, padx=5)
|
||||
|
||||
self.lessons_clear_button = customtkinter.CTkButton(master=self.lessons_tab, text="Clear", width=40, fg_color="crimson", command=lambda : self.lessons_entry.delete(0, END))
|
||||
self.lessons_clear_button.grid(row=1, column=2, padx=5)
|
||||
|
||||
self.lessons_buttons_frame = customtkinter.CTkFrame(master=self.lessons_tab, fg_color="transparent")
|
||||
self.lessons_buttons_frame.grid(row=2, column=0, padx=20, pady=20)
|
||||
|
||||
for i in range(9):
|
||||
self.lesson_button = customtkinter.CTkButton(master=self.lessons_buttons_frame, text=str(i+1), command=lambda choice=str(i+1): self.insert(choice, self.lessons_entry), width=40)
|
||||
self.lesson_button.grid(row=0, column=i, padx=5)
|
||||
|
||||
|
||||
def find_json_files(self,folder_path):
|
||||
json_files = []
|
||||
for root, dirs, files in os.walk(folder_path):
|
||||
for file in files:
|
||||
if file.endswith(".json"):
|
||||
json_files.append(os.path.splitext(file)[0])
|
||||
return json_files
|
||||
|
||||
def switch_server(self, server):
|
||||
with open(f"MCE/student_list/{server}.json", "r") as f:
|
||||
student_list = json.load(f)
|
||||
self.student_dropdown.configure(values=student_list)
|
||||
|
||||
def insert(self, value, entry):
|
||||
entry.insert(index=END, string=value + " > ")
|
||||
|
||||
def copy_entry(self, entry, button):
|
||||
text_to_copy = entry.get()
|
||||
|
||||
# Check if there is text to copy
|
||||
if text_to_copy:
|
||||
# Clear the clipboard and set the new text
|
||||
self.clipboard_clear()
|
||||
self.clipboard_append(text_to_copy)
|
||||
self.update() # This is necessary on some systems to update the clipboard
|
||||
button_color = button.cget("fg_color")
|
||||
button.configure(fg_color="green")
|
||||
self.after(2000, lambda : button.configure(fg_color=['#3B8ED0', '#1F6AA5']))
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = InputHelper()
|
||||
app.title("Input Helper")
|
||||
app.mainloop()
|
||||
442
MCE Manager.py
Normal file
@ -0,0 +1,442 @@
|
||||
import customtkinter
|
||||
import tkinter as tk
|
||||
import random
|
||||
import re
|
||||
from MCE.custom_widgets.ctkmessagebox import CTkMessagebox
|
||||
from MCE.custom_widgets.ctk_tooltip import CTkToolTip
|
||||
from MCE.custom_widgets.ctk_timeentry import CTkTimeEntry
|
||||
from MCE.custom_widgets.ctk_integerspinbox import CTkIntegerSpinbox
|
||||
from MCE.custom_widgets.ctk_templatedialog import CTkTemplateDialog
|
||||
from MCE.custom_widgets.ctk_notification import CTkNotification
|
||||
from MCE.custom_widgets.ctk_add_button import CTkAddButton
|
||||
from MCE.utils import Linker, Config
|
||||
from filelock import FileLock, Timeout
|
||||
import threading
|
||||
import time
|
||||
|
||||
class MCE_Manager(customtkinter.CTk):
|
||||
def __init__(self, linker, config, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.linker = linker
|
||||
self.config = config
|
||||
self.create_widgets()
|
||||
# Load Template Data
|
||||
self.load_template_data()
|
||||
# Load queue Data
|
||||
self.load_queue_data()
|
||||
|
||||
def create_widgets(self):
|
||||
|
||||
self.create_mission_commissions_widgets()
|
||||
|
||||
|
||||
def create_mission_commissions_widgets(self):
|
||||
# Create Mission/Commissions/Event Checkbox
|
||||
self.create_mission_commissions_checkbox()
|
||||
|
||||
# Create Reset Daily Widgets
|
||||
self.create_reset_daily_widgets()
|
||||
|
||||
# Create Recharge AP and Event Checkboxes
|
||||
self.create_recharge_and_event_checkboxes()
|
||||
|
||||
# Create Preferred Template Selection
|
||||
self.create_preferred_template_selection()
|
||||
|
||||
# Create Mission Tabview with Template and Queue Tabs
|
||||
self.create_mission_tabview()
|
||||
|
||||
# Create Top-Level Window for Template Editing
|
||||
self.create_template_queue_editor()
|
||||
|
||||
# Create Template Frame and Queue Frame
|
||||
self.create_template_and_queue_frames()
|
||||
|
||||
# Create Lists to Store Frame Widgets
|
||||
self.create_frame_lists()
|
||||
|
||||
# Initialize Preferred Template and Templates List
|
||||
self.initialize_preferred_template()
|
||||
|
||||
# Create OptionMenu for Selecting a Template
|
||||
self.create_template_option_menu()
|
||||
|
||||
# Create Delete Template Button
|
||||
self.create_delete_template_button()
|
||||
|
||||
# Helper method to create Mission/Commissions/Event Checkbox
|
||||
def create_mission_commissions_checkbox(self):
|
||||
self.mission_commissions_checkbox = customtkinter.CTkLabel(self, text="Mission/Commissions/Event", width=60, font=customtkinter.CTkFont(family="Inter", size=20, weight="bold"))
|
||||
self.mission_commissions_checkbox.grid(row=11, column=0, sticky="nw", padx=20, pady=20)
|
||||
self.notification = CTkNotification(master=self, text="Config saved")
|
||||
self.notification.grid(row=11, column=1)
|
||||
|
||||
# Helper method to create Reset Daily Widgets
|
||||
def create_reset_daily_widgets(self):
|
||||
self.reset_daily = customtkinter.CTkCheckBox(self, text="Reset Daily", font=customtkinter.CTkFont(family="Inter", size=16, underline=True), command=lambda x=["ResetDaily"]: self.config.save_to_json(x))
|
||||
self.reset_daily.grid(row=12, column=0, sticky="nw", padx=80)
|
||||
self.reset_daily_tooltip = CTkToolTip(self.reset_daily, wraplength=400,
|
||||
message="If enabled and if current time >= reset time,\
|
||||
the queue will automatically be cleared and repopulated with preferred template stages. Only activated once a day.")
|
||||
|
||||
self.reset_daily_sub_label = customtkinter.CTkLabel(self, text="hh/mm/ss", font=customtkinter.CTkFont(family="Inter", size=12))
|
||||
self.reset_daily_sub_label.grid(row=13, column=0, padx=80)
|
||||
|
||||
self.reset_time = CTkTimeEntry(self)
|
||||
self.reset_time.grid(row=12, column=1)
|
||||
self.reset_time.hour_entry.bind("<KeyRelease>", lambda event, x=["ResetTime"]: self.config.save_to_json(x))
|
||||
self.reset_time.minute_entry.bind("<KeyRelease>", lambda event, x=["ResetTime"]: self.config.save_to_json(x))
|
||||
self.reset_time.second_entry.bind("<KeyRelease>", lambda event, x=["ResetTime"]: self.config.save_to_json(x))
|
||||
|
||||
self.linker.widgets["ResetDaily"] = self.reset_daily
|
||||
self.linker.widgets["ResetTime"] = self.reset_time
|
||||
|
||||
# Helper method to create Recharge AP and Event Checkboxes
|
||||
def create_recharge_and_event_checkboxes(self):
|
||||
self.recharge_checkbox = customtkinter.CTkCheckBox(self, text="Recharge AP", command=lambda x=["RechargeAP"]: self.config.save_to_json(x), font=customtkinter.CTkFont(family="Inter", size=16, underline=True))
|
||||
self.recharge_checkbox.grid(row=14, column=0, sticky="nw", padx=80, pady=20)
|
||||
self.linker.widgets["RechargeAP"] = self.recharge_checkbox
|
||||
self.recharge_tooltip = CTkToolTip(self.recharge_checkbox, wraplength=400,
|
||||
message="When enabled, recharge AP when low via cafe earnings, tasks, club and mailbox if they are enabled in their respective sections.")
|
||||
self.event_checkbox = customtkinter.CTkCheckBox(self, text="Sweep Event Stages", command=lambda x=["Event"]: self.config.save_to_json(x), font=customtkinter.CTkFont(family="Inter", size=16, underline=True))
|
||||
self.event_tooltip = CTkToolTip(self.event_checkbox, wraplength=400, message="When enabled, the script will sweep event stages. Otherwise, it will ignore them.")
|
||||
self.event_checkbox.grid(row=15, column=0, sticky="nw", padx=80)
|
||||
self.linker.widgets["Event"] = self.event_checkbox
|
||||
|
||||
# Helper method to create Preferred Template Selection
|
||||
def create_preferred_template_selection(self):
|
||||
self.templates = self.config.config_data["Templates"]
|
||||
self.templates_list = list(self.templates.keys())
|
||||
|
||||
self.preferred_template_label = customtkinter.CTkLabel(self, text="Preferred Template:", font=customtkinter.CTkFont(family="Inter", size=16, underline=True))
|
||||
self.preferred_template_label.grid(row=16, column=0, pady=20)
|
||||
self.preferred_template_tooltip = CTkToolTip(self.preferred_template_label, wraplength=400,
|
||||
message="The template from which to repopulate the queue when it is empty or reset daily is activated")
|
||||
self.preferred_template_optionmenu = customtkinter.CTkOptionMenu(self, values=self.templates_list, command=lambda x, y=["PreferredTemplate"]: self.config.save_to_json(y))
|
||||
self.preferred_template_optionmenu.grid(row=16, column=1, pady=20)
|
||||
self.linker.widgets["PreferredTemplate"] = self.preferred_template_optionmenu
|
||||
|
||||
# Helper method to create Mission Tabview with Template and Queue Tabs
|
||||
def create_mission_tabview(self):
|
||||
self.mission_tabview = customtkinter.CTkTabview(self)
|
||||
self.mission_tabview.grid(row=17, column=0, columnspan=3, padx=20)
|
||||
|
||||
self.tab_template = self.mission_tabview.add('Template')
|
||||
self.tab_queue = self.mission_tabview.add('Queue')
|
||||
|
||||
# Helper method to create Template Queue Editor
|
||||
def create_template_queue_editor(self):
|
||||
self.queue_buttons = []
|
||||
|
||||
for i in [self.tab_queue, self.tab_template]:
|
||||
queue = True if i == self.tab_queue else False
|
||||
|
||||
self.template_labels = customtkinter.CTkFrame(i)
|
||||
self.template_labels.grid(row=0, column=0, sticky="ew")
|
||||
|
||||
self.mode_label = customtkinter.CTkLabel(self.template_labels, text="Mode:", font=customtkinter.CTkFont(underline=True))
|
||||
self.mode_tooltip = CTkToolTip(self.mode_label, message="N : Mission Normal\nH : Mission Hard\nE : Event Quest\nXP : Commissions EXP\nCR : Commissions Credits\n", justify=tk.LEFT)
|
||||
self.mode_label.grid(row=1, column=0, padx=(130, 0), pady=5)
|
||||
|
||||
self.stage_label = customtkinter.CTkLabel(self.template_labels, text="Stage:", font=customtkinter.CTkFont(underline=True))
|
||||
self.stage_tooltip = CTkToolTip(self.stage_label, message="Valid format\nMission: 1-1, 3-A\nCommissions & Event: 01", justify=tk.LEFT)
|
||||
self.stage_label.grid(row=1, column=1, padx=(40, 20), pady=5)
|
||||
|
||||
self.run_times_label = customtkinter.CTkLabel(self.template_labels, text="Number of Sweeps:", font=customtkinter.CTkFont(underline=True))
|
||||
self.run_times_tooltip = CTkToolTip(self.run_times_label, message="How many times do you want to sweep the stage?")
|
||||
self.run_times_label.grid(row=1, column=2, pady=5)
|
||||
|
||||
self.template_buttons_frame = customtkinter.CTkFrame(i)
|
||||
self.template_buttons_frame.grid(row=3, column=0)
|
||||
|
||||
self.highlight_label = customtkinter.CTkLabel(self.template_buttons_frame, text="*You can double click an entry and press up or down arrow to change its position", font=customtkinter.CTkFont(family="Inter", size=12))
|
||||
self.highlight_label.grid(row=0, column=0, columnspan=3)
|
||||
|
||||
self.add_button = CTkAddButton(master=self.template_buttons_frame)
|
||||
self.add_button.button.configure(command=lambda queue=queue, button=self.add_button.button: self.add_frame(queue=queue, button=button))
|
||||
self.add_button.grid(row=1, column=0, padx=5, pady=5)
|
||||
|
||||
# Clear button to clear all frames
|
||||
self.clear_button = customtkinter.CTkButton(self.template_buttons_frame, text="Clear All", command=lambda queue=queue: self.clear_frames(queue=queue), fg_color="crimson")
|
||||
self.clear_button.grid(row=1, column=1, padx=5, pady=5)
|
||||
|
||||
# Save button to save data
|
||||
self.save_button = customtkinter.CTkButton(self.template_buttons_frame, text="Save", command=lambda queue=queue: self.save_data(queue=queue), fg_color="#DC621D")
|
||||
self.save_button.grid(row=1, column=2, padx=5, pady=5)
|
||||
if queue:
|
||||
self.queue_buttons = [self.add_button, self.clear_button, self.save_button]
|
||||
|
||||
# Helper method to create Template Frame and Queue Frame
|
||||
def create_template_and_queue_frames(self):
|
||||
self.template_frame = customtkinter.CTkScrollableFrame(self.tab_template, width=435, height=350)
|
||||
self.template_frame.grid(row=1, column=0, sticky="nsew")
|
||||
|
||||
self.queue_frame = customtkinter.CTkScrollableFrame(self.tab_queue, width=435, height=350)
|
||||
self.queue_frame.grid(row=1, column=0, sticky="nsew")
|
||||
|
||||
# Helper method to create Lists to Store Frame Widgets
|
||||
def create_frame_lists(self):
|
||||
self.template_frames = []
|
||||
self.queue_frames = []
|
||||
self.highlighted_frame = None
|
||||
|
||||
# Helper method to initialize Preferred Template and Templates List
|
||||
def initialize_preferred_template(self):
|
||||
self.preferred_template = self.config.config_data["PreferredTemplate"]
|
||||
self.templates_list.append("Add New Template")
|
||||
|
||||
# Helper method to create OptionMenu for Selecting a Template
|
||||
def create_template_option_menu(self):
|
||||
self.selected_template = tk.StringVar(self.template_frame)
|
||||
self.selected_template.set(self.preferred_template) # Set the initial value to the preferred template
|
||||
|
||||
self.template_optionmenu = customtkinter.CTkOptionMenu(self.template_labels, values=self.templates_list, variable=self.selected_template, command=lambda *args: self.load_template_data())
|
||||
self.template_optionmenu.grid(row=0, column=0, padx=5, pady=5)
|
||||
|
||||
# Helper method to create Delete Template Button
|
||||
def create_delete_template_button(self):
|
||||
self.delete_template_button = customtkinter.CTkButton(self.template_labels, width=40, text="Delete", command=self.delete_template)
|
||||
self.delete_template_button.grid(row=0, column=1)
|
||||
|
||||
# Helper method to add frames from Configuration Data
|
||||
def load_queue_data(self, state="normal"):
|
||||
for entry in self.config.config_data['Queue']:
|
||||
self.add_frame(entry, queue=True, state=state)
|
||||
|
||||
# Function to load template data into frames
|
||||
def load_template_data(self):
|
||||
selected = self.selected_template.get()
|
||||
if selected == "Add New Template":
|
||||
dialog = CTkTemplateDialog(text="Type in new template name. Template name MUST be different from other templates!", title="Template Name", values=self.templates_list[:-1])
|
||||
template_name, template_import = dialog.get_input()
|
||||
if template_name.replace(" ", "") == "":
|
||||
self.template_optionmenu.set(self.previous_selected)
|
||||
return
|
||||
elif template_name in self.templates_list:
|
||||
CTkMessagebox(title="Error", message="Name is invalid.", icon="MCE\icons\cancel.png")
|
||||
self.template_optionmenu.set(self.previous_selected)
|
||||
return
|
||||
else:
|
||||
if template_import != "":
|
||||
self.templates[template_name] = self.templates[template_import]
|
||||
else:
|
||||
self.templates[template_name] = []
|
||||
self.templates_list[-1] = template_name
|
||||
self.preferred_template_optionmenu.configure(values=self.templates_list)
|
||||
selected = template_name
|
||||
self.template_optionmenu.set(selected)
|
||||
self.templates_list.append("Add New Template")
|
||||
self.template_optionmenu.configure(values=self.templates_list)
|
||||
self.clear_frames()
|
||||
for entry in self.templates[selected]:
|
||||
self.add_frame(entry)
|
||||
self.previous_selected = selected
|
||||
|
||||
def delete_template(self):
|
||||
msg = CTkMessagebox(title="Template Deletetion", message=f"Are you sure you want to delete Template {self.previous_selected}?",
|
||||
icon="MCE\icons\question.png", option_1="No", option_2="Yes")
|
||||
response = msg.get()
|
||||
if response=="Yes":
|
||||
if len(self.templates) != 1:
|
||||
del self.templates[self.previous_selected]
|
||||
self.templates_list = list(self.templates.keys())
|
||||
self.preferred_template_optionmenu.configure(values=self.templates_list)
|
||||
if self.preferred_template == self.previous_selected:
|
||||
self.preferred_template = random.choice(self.templates_list)
|
||||
self.config.config_data["PreferredTemplate"] = self.preferred_template
|
||||
self.selected_template.set(self.preferred_template) # Set the initial value to the preferred template
|
||||
self.preferred_template_optionmenu.set(self.preferred_template)
|
||||
self.load_template_data()
|
||||
self.config.save_file()
|
||||
self.templates_list.append("Add New Template")
|
||||
self.template_optionmenu.configure(values=self.templates_list)
|
||||
self.template_optionmenu.set(self.preferred_template)
|
||||
else:
|
||||
CTkMessagebox(title="Error", message="At least one template must exist!!!", icon="MCE\icons\cancel.png")
|
||||
return
|
||||
|
||||
# Function to add a frame with widgets
|
||||
def add_frame(self, inner_list=None, queue=False, state="normal", button=None):
|
||||
position = button.cget("text") if button else "Add Down"
|
||||
frames = self.queue_frames if queue else self.template_frames
|
||||
parent_frame = self.queue_frame if queue else self.template_frame
|
||||
row_index = len(frames) + 1 # Calculate the row for the new frame
|
||||
# Create a frame
|
||||
frame = tk.Frame(parent_frame, bg="gray17")
|
||||
frame.grid(row=row_index, column=0, columnspan=4, padx=10, pady=10, sticky="w")
|
||||
frames.append(frame) if position == "Add Down" else frames.insert(0, frame)
|
||||
# "Up" button to move the frame up
|
||||
up_button = customtkinter.CTkButton(frame, text="Up", width=5, command=lambda f=frame, queue=queue: self.move_frame_up(f, queue), state=state)
|
||||
up_button.grid(row=0, column=0, padx=5, pady=5, sticky="w")
|
||||
# "Down" button to move the frame down
|
||||
down_button = customtkinter.CTkButton(frame, text="Down", width=5, command=lambda f=frame, queue=queue: self.move_frame_down(f, queue), state=state)
|
||||
down_button.grid(row=0, column=1, padx=5, pady=5, sticky="w")
|
||||
# Dropdown menu for mode
|
||||
mode_optionmenu = customtkinter.CTkOptionMenu(frame, width=60, values=["N", "H", "E", "XP", "CR"], state=state)
|
||||
mode_optionmenu.set(inner_list[0] if inner_list else "N")
|
||||
mode_optionmenu.grid(row=0, column=2, padx=5, pady=5, sticky="w")
|
||||
# Entry widget for stage
|
||||
stage_var = tk.StringVar(value=inner_list[1] if inner_list else "")
|
||||
stage_entry = customtkinter.CTkEntry(frame, width=60, textvariable=stage_var, state=state)
|
||||
stage_entry.grid(row=0, column=3, padx=5, pady=5, sticky="w")
|
||||
mode_optionmenu.configure(command=lambda choice, x=mode_optionmenu, y=stage_entry : self.check_entry(x,y))
|
||||
stage_entry.bind('<KeyRelease>', command=lambda event, x=mode_optionmenu, y=stage_entry : self.check_entry(x,y))
|
||||
self.check_entry(mode_optionmenu, stage_entry)
|
||||
# Entry widget for run times (only accepts numbers)
|
||||
run_times_spinbox = CTkIntegerSpinbox(frame, step_size=1, min_value=1)
|
||||
run_times_spinbox.set(value=inner_list[2] if inner_list else 1)
|
||||
run_times_spinbox.grid(row=0, column=4, padx=5, pady=5, sticky="w")
|
||||
|
||||
# Delete button to delete the frame
|
||||
delete_button = customtkinter.CTkButton(frame, text="Delete", width=5, command=lambda f=frame, queue=queue: self.delete_frame(f, queue), state=state)
|
||||
delete_button.grid(row=0, column=5, padx=5, pady=5, sticky="w")
|
||||
|
||||
frame.bind("<Double-Button-1>", lambda event, f=frame: self.highlight_frame(f))
|
||||
if position == "Add Up":
|
||||
self.update_frame_positions(queue=queue)
|
||||
|
||||
# Function to clear all frames
|
||||
def clear_frames(self, queue=False):
|
||||
frames = self.queue_frames if queue else self.template_frames
|
||||
for frame in frames:
|
||||
frame.destroy()
|
||||
frames.clear()
|
||||
|
||||
# Function to save frames as data
|
||||
def save_data(self, queue=False):
|
||||
entries = []
|
||||
frames = self.queue_frames if queue else self.template_frames
|
||||
name = "Queue" if queue else "Template"
|
||||
for frame in frames:
|
||||
mode_optionmenu = frame.winfo_children()[2]
|
||||
stage_entry = frame.winfo_children()[3]
|
||||
if not self.check_entry(mode_optionmenu, stage_entry):
|
||||
CTkMessagebox(title="Error", message="Configuration not saved. Some entries are incomplete or have incorect input.", icon="MCE\icons\cancel.png")
|
||||
return
|
||||
mode = frame.winfo_children()[2].get()
|
||||
stage = frame.winfo_children()[3].get().strip()
|
||||
run_times = frame.winfo_children()[4].get()
|
||||
entries.append([mode, stage, int(run_times)])
|
||||
if queue:
|
||||
self.config.config_data['Queue'] = entries
|
||||
else:
|
||||
selected = self.selected_template.get()
|
||||
self.templates[selected] = entries
|
||||
self.config.save_file(name)
|
||||
|
||||
def check_entry(self, mode_dropdown, stage_entry):
|
||||
mode = mode_dropdown.get()
|
||||
stage = stage_entry.get()
|
||||
if mode in ["N", "H"]:
|
||||
pattern = r'\d{1,2}-[0-9A-Z]'
|
||||
else:
|
||||
pattern = r"^\d{2}$"
|
||||
if re.match(pattern, stage):
|
||||
stage_entry.configure(border_color=['#979DA2', '#565B5E'])
|
||||
return True
|
||||
else:
|
||||
stage_entry.configure(border_color='crimson')
|
||||
return False
|
||||
|
||||
# Function to move a frame up
|
||||
def move_frame_up(self, frame, queue=False):
|
||||
frames = self.queue_frames if queue else self.template_frames
|
||||
index = frames.index(frame)
|
||||
if index > 0:
|
||||
frames[index], frames[index - 1] = frames[index - 1], frames[index]
|
||||
self.update_frame_positions(queue=queue)
|
||||
|
||||
# Function to move a frame down
|
||||
def move_frame_down(self, frame, queue=False):
|
||||
frames = self.queue_frames if queue else self.template_frames
|
||||
index = frames.index(frame)
|
||||
if index < len(frames) - 1:
|
||||
frames[index], frames[index + 1] = frames[index + 1], frames[index]
|
||||
self.update_frame_positions(queue=queue)
|
||||
|
||||
# Function to update frame positions in the grid
|
||||
def update_frame_positions(self, queue=False):
|
||||
frames = self.queue_frames if queue else self.template_frames
|
||||
for index, frame in enumerate(frames):
|
||||
frame.grid(row=index, column=0, columnspan=4, padx=10, pady=10, sticky="w")
|
||||
|
||||
# Function to delete a frame
|
||||
def delete_frame(self, frame, queue=False):
|
||||
if queue:
|
||||
self.queue_frames.remove(frame)
|
||||
else:
|
||||
self.template_frames.remove(frame)
|
||||
frame.destroy()
|
||||
# Update the positions of remaining frames
|
||||
self.update_frame_positions(queue=queue)
|
||||
|
||||
def highlight_frame(self, frame):
|
||||
try:
|
||||
if self.highlighted_frame is not None:
|
||||
self.highlighted_frame.unbind("<Up>")
|
||||
self.highlighted_frame.unbind("<Down>")
|
||||
self.highlighted_frame.config(bg="gray17")
|
||||
except:
|
||||
pass
|
||||
|
||||
if self.highlighted_frame == frame:
|
||||
self.highlighted_frame = None
|
||||
|
||||
else:
|
||||
up_button = frame.winfo_children()[0]
|
||||
down_button = frame.winfo_children()[1]
|
||||
frame.config(bg="yellow")
|
||||
frame.bind("<Up>", lambda event: up_button.invoke())
|
||||
frame.bind("<Down>", lambda event: down_button.invoke())
|
||||
frame.focus_set()
|
||||
self.highlighted_frame = frame
|
||||
|
||||
def check_lock(self):
|
||||
while 1:
|
||||
try:
|
||||
lock = FileLock("MCE\config.json.lock")
|
||||
lock.acquire(timeout=1)
|
||||
except Timeout:
|
||||
if not self.config.locked:
|
||||
self.after(10, lambda : (self.queue_changed(), self.update_queue(), self.switch_queue_state("disabled")))
|
||||
self.config.locked = True
|
||||
elif self.config.locked and self.queue_changed():
|
||||
self.after(10, lambda : (self.update_queue(), self.switch_queue_state("disabled")))
|
||||
else:
|
||||
lock.release()
|
||||
if self.config.locked:
|
||||
self.after(10, lambda : (self.queue_changed(), self.update_queue(), self.switch_queue_state("normal")))
|
||||
self.config.locked = False
|
||||
finally:
|
||||
time.sleep(2)
|
||||
|
||||
def switch_queue_state(self, state):
|
||||
for button in self.queue_buttons:
|
||||
button.configure(state=state)
|
||||
for frame in self.queue_frames:
|
||||
for widget in frame.winfo_children():
|
||||
widget.configure(state=state)
|
||||
|
||||
def update_queue(self):
|
||||
self.clear_frames(queue=True)
|
||||
for entry in self.config.config_data['Queue']:
|
||||
self.add_frame(entry, queue=True)
|
||||
|
||||
def queue_changed(self):
|
||||
new_config_data = self.config.read()
|
||||
changed = self.config.config_data["Queue"] != new_config_data["Queue"] or self.config.config_data["LastRun"] != new_config_data["LastRun"]
|
||||
if changed:
|
||||
self.config.config_data["LastRun"] = new_config_data["LastRun"]
|
||||
self.config.config_data['Queue'] = new_config_data['Queue']
|
||||
return changed
|
||||
|
||||
if __name__ == "__main__":
|
||||
linker = Linker()
|
||||
config = Config(linker, "MCE\config.json")
|
||||
app = MCE_Manager(linker, config)
|
||||
app.title("MCE Manager")
|
||||
linker.sidebar = app
|
||||
config.load_config()
|
||||
daemon_thread = threading.Thread(target=app.check_lock, daemon=True)
|
||||
daemon_thread.start()
|
||||
app.mainloop()
|
||||
0
MCE/custom_widgets/__init__.py
Normal file
19
MCE/custom_widgets/ctk_add_button.py
Normal file
@ -0,0 +1,19 @@
|
||||
import customtkinter
|
||||
|
||||
class CTkAddButton(customtkinter.CTkFrame):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.button = customtkinter.CTkButton(self, text="Add Down", corner_radius=0, width=120)
|
||||
self.button.grid(row=0, column=0)
|
||||
self.option_menu = customtkinter.CTkOptionMenu(
|
||||
self, values= ["Add Up", "Add Down"], width=10, command=self.set_button, corner_radius=0
|
||||
)
|
||||
self.option_menu.set("")
|
||||
self.option_menu.grid(row=0, column=1)
|
||||
|
||||
def set_button(self, value):
|
||||
self.option_menu.set("")
|
||||
self.button.configure(text=value)
|
||||
|
||||
def configure(self, *args, **kwargs):
|
||||
self.button.configure(*args, **kwargs)
|
||||
95
MCE/custom_widgets/ctk_integerspinbox.py
Normal file
@ -0,0 +1,95 @@
|
||||
import customtkinter
|
||||
import re
|
||||
from typing import Callable
|
||||
|
||||
class CTkIntegerSpinbox(customtkinter.CTkFrame):
|
||||
def __init__(self, *args,
|
||||
width: int = 100,
|
||||
height: int = 32,
|
||||
step_size: int = 1,
|
||||
min_value: int = 0,
|
||||
command: Callable = None,
|
||||
**kwargs):
|
||||
super().__init__(*args, width=width, height=height, **kwargs)
|
||||
|
||||
self.step_size = step_size
|
||||
self.min_value = min_value
|
||||
self.command = command
|
||||
self.after_id = None
|
||||
|
||||
self.grid_columnconfigure((0, 2), weight=0)
|
||||
self.grid_columnconfigure(1, weight=1)
|
||||
|
||||
self.subtract_button = customtkinter.CTkButton(self, text="-", width=height-6, height=height-6)
|
||||
self.subtract_button.grid(row=0, column=0, padx=(3, 0), pady=3)
|
||||
self.subtract_button.bind('<ButtonPress-1>', self.start_decrementing)
|
||||
self.subtract_button.bind('<ButtonRelease-1>', self.stop_decrementing)
|
||||
|
||||
self.entry = customtkinter.CTkEntry(self, width=width-(2*height), height=height-6, border_width=0)
|
||||
self.entry.grid(row=0, column=1, columnspan=1, padx=3, pady=3, sticky="ew")
|
||||
|
||||
self.add_button = customtkinter.CTkButton(self, text="+", width=height-6, height=height-6)
|
||||
self.add_button.grid(row=0, column=2, padx=(0, 3), pady=3)
|
||||
self.add_button.bind('<ButtonPress-1>', self.start_incrementing)
|
||||
self.add_button.bind('<ButtonRelease-1>', self.stop_incrementing)
|
||||
|
||||
self.entry.insert(0, "0")
|
||||
|
||||
# Configure validatecommand to allow only integers
|
||||
vcmd = (self.entry.register(self.validate_input), '%P')
|
||||
self.entry.configure(validate='key', validatecommand=vcmd)
|
||||
|
||||
def start_incrementing(self, event):
|
||||
self.increment()
|
||||
self.after_id = self.after(150, self.start_incrementing, event)
|
||||
|
||||
def stop_incrementing(self, event):
|
||||
if self.after_id:
|
||||
self.after_cancel(self.after_id)
|
||||
self.after_id = None
|
||||
|
||||
def start_decrementing(self, event):
|
||||
self.decrement()
|
||||
self.after_id = self.after(150, self.start_decrementing, event)
|
||||
|
||||
def stop_decrementing(self, event):
|
||||
if self.after_id:
|
||||
self.after_cancel(self.after_id)
|
||||
self.after_id = None
|
||||
|
||||
def increment(self):
|
||||
value = int(self.entry.get()) + self.step_size
|
||||
self.entry.delete(0, "end")
|
||||
self.entry.insert(0, max(self.min_value, value)) # Ensure the value is not less than 1
|
||||
if self.command is not None:
|
||||
self.command()
|
||||
|
||||
def decrement(self):
|
||||
value = int(self.entry.get()) - self.step_size
|
||||
self.entry.delete(0, "end")
|
||||
self.entry.insert(0, max(self.min_value, value)) # Ensure the value is not less than 0
|
||||
if self.command is not None:
|
||||
self.command()
|
||||
|
||||
def validate_input(self, new_value):
|
||||
# Validate that the input is a non-negative integer
|
||||
return re.match(r'^\d*$', new_value) is not None
|
||||
|
||||
def get(self) -> int:
|
||||
try:
|
||||
return int(self.entry.get())
|
||||
except ValueError:
|
||||
return 0
|
||||
|
||||
def set(self, value: int):
|
||||
self.entry.delete(0, "end")
|
||||
self.entry.insert(0, max(self.min_value, value)) # Ensure the value is not less than 0
|
||||
|
||||
def configure(self, **kwargs):
|
||||
state = kwargs.get("state", None)
|
||||
if state is not None:
|
||||
self.subtract_button.configure(state=state)
|
||||
self.add_button.configure(state=state)
|
||||
self.entry.configure(state=state)
|
||||
kwargs.pop("state")
|
||||
super().configure(**kwargs)
|
||||
27
MCE/custom_widgets/ctk_notification.py
Normal file
@ -0,0 +1,27 @@
|
||||
import customtkinter
|
||||
class CTkNotification(customtkinter.CTkFrame):
|
||||
def __init__(self, text, master, **kwargs):
|
||||
self.master_color = master.cget("fg_color")
|
||||
super().__init__(master=master, **kwargs, fg_color=self.master_color)
|
||||
self.label = customtkinter.CTkLabel(self, text=text, text_color=self.master_color, 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", text_color_disabled=self.master_color, command=self.hide, fg_color="transparent", state="disabled")
|
||||
self.close_button.grid(row=0, column=1)
|
||||
self.progress_bar = customtkinter.CTkProgressBar(self, determinate_speed=0.4, fg_color=self.master_color, progress_color=self.master_color)
|
||||
self.progress_bar.grid(row=1, column=0, columnspan=2, sticky="nsew")
|
||||
|
||||
def hide(self):
|
||||
self.configure(fg_color="transparent")
|
||||
self.progress_bar.stop()
|
||||
self.progress_bar.configure(progress_color=self.master_color)
|
||||
self.close_button.configure(state="disabled")
|
||||
self.label.configure(text_color=self.master_color)
|
||||
|
||||
def show(self):
|
||||
self.configure(fg_color="green")
|
||||
self.progress_bar.configure(progress_color="white")
|
||||
self.progress_bar.set(0)
|
||||
self.progress_bar.start()
|
||||
self.close_button.configure(state="normal")
|
||||
self.label.configure(text_color="white")
|
||||
337
MCE/custom_widgets/ctk_scrollable_dropdown.py
Normal file
@ -0,0 +1,337 @@
|
||||
'''
|
||||
Advanced Scrollable Dropdown class for customtkinter widgets
|
||||
Author: Akash Bora
|
||||
'''
|
||||
|
||||
import customtkinter
|
||||
import sys
|
||||
import time
|
||||
import difflib
|
||||
|
||||
class CTkScrollableDropdown(customtkinter.CTkToplevel):
|
||||
|
||||
def __init__(self, attach, x=None, y=None, button_color=None, height: int = 200, width: int = None,
|
||||
fg_color=None, button_height: int = 20, justify="center", scrollbar_button_color=None,
|
||||
scrollbar=True, scrollbar_button_hover_color=None, frame_border_width=2, values=[],
|
||||
command=None, image_values=[], alpha: float = 0.97, frame_corner_radius=20, double_click=False,
|
||||
resize=True, frame_border_color=None, text_color=None, autocomplete=False, **button_kwargs):
|
||||
|
||||
super().__init__(takefocus=1)
|
||||
|
||||
self.focus()
|
||||
self.lift()
|
||||
self.alpha = alpha
|
||||
self.attach = attach
|
||||
self.corner = frame_corner_radius
|
||||
self.padding = 0
|
||||
self.focus_something = False
|
||||
self.disable = True
|
||||
self.update()
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
self.after(100, lambda: self.overrideredirect(True))
|
||||
self.transparent_color = self._apply_appearance_mode(self._fg_color)
|
||||
self.attributes("-transparentcolor", self.transparent_color)
|
||||
elif sys.platform.startswith("darwin"):
|
||||
self.overrideredirect(True)
|
||||
self.transparent_color = 'systemTransparent'
|
||||
self.attributes("-transparent", True)
|
||||
self.focus_something = True
|
||||
else:
|
||||
self.overrideredirect(True)
|
||||
self.transparent_color = '#000001'
|
||||
self.corner = 0
|
||||
self.padding = 18
|
||||
self.withdraw()
|
||||
|
||||
self.hide = True
|
||||
self.attach.bind('<Configure>', lambda e: self._withdraw() if not self.disable else None, add="+")
|
||||
self.attach.winfo_toplevel().bind('<Configure>', lambda e: self._withdraw() if not self.disable else None, add="+")
|
||||
self.attach.winfo_toplevel().bind("<ButtonPress>", lambda e: self._withdraw() if not self.disable else None, add="+")
|
||||
|
||||
|
||||
self.attributes('-alpha', 0)
|
||||
self.disable = False
|
||||
self.fg_color = customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"] if fg_color is None else fg_color
|
||||
self.scroll_button_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_color"] if scrollbar_button_color is None else scrollbar_button_color
|
||||
self.scroll_hover_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_hover_color"] if scrollbar_button_hover_color is None else scrollbar_button_hover_color
|
||||
self.frame_border_color = customtkinter.ThemeManager.theme["CTkFrame"]["border_color"] if frame_border_color is None else frame_border_color
|
||||
self.button_color = customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"] if button_color is None else button_color
|
||||
self.text_color = customtkinter.ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else text_color
|
||||
|
||||
if scrollbar is False:
|
||||
self.scroll_button_color = self.fg_color
|
||||
self.scroll_hover_color = self.fg_color
|
||||
|
||||
self.frame = customtkinter.CTkScrollableFrame(self, bg_color=self.transparent_color, fg_color=self.fg_color,
|
||||
scrollbar_button_hover_color=self.scroll_hover_color,
|
||||
corner_radius=self.corner, border_width=frame_border_width,
|
||||
scrollbar_button_color=self.scroll_button_color,
|
||||
border_color=self.frame_border_color)
|
||||
self.frame._scrollbar.grid_configure(padx=3)
|
||||
self.frame.pack(expand=True, fill="both")
|
||||
self.dummy_entry = customtkinter.CTkEntry(self.frame, fg_color="transparent", border_width=0, height=1, width=1)
|
||||
self.no_match = customtkinter.CTkLabel(self.frame, text="No Match")
|
||||
self.height = height
|
||||
self.height_new = height
|
||||
self.width = width
|
||||
self.command = command
|
||||
self.fade = False
|
||||
self.resize = resize
|
||||
self.autocomplete = autocomplete
|
||||
self.var_update = customtkinter.StringVar()
|
||||
self.appear = False
|
||||
|
||||
if justify.lower()=="left":
|
||||
self.justify = "w"
|
||||
elif justify.lower()=="right":
|
||||
self.justify = "e"
|
||||
else:
|
||||
self.justify = "c"
|
||||
|
||||
self.button_height = button_height
|
||||
self.values = values
|
||||
self.button_num = len(self.values)
|
||||
self.image_values = None if len(image_values)!=len(self.values) else image_values
|
||||
|
||||
self.resizable(width=False, height=False)
|
||||
self.transient(self.master)
|
||||
self._init_buttons(**button_kwargs)
|
||||
|
||||
# Add binding for different ctk widgets
|
||||
if double_click or self.attach.winfo_name().startswith("!ctkentry") or self.attach.winfo_name().startswith("!ctkcombobox"):
|
||||
self.attach.bind('<Double-Button-1>', lambda e: self._iconify(), add="+")
|
||||
else:
|
||||
self.attach.bind('<Button-1>', lambda e: self._iconify(), add="+")
|
||||
|
||||
if self.attach.winfo_name().startswith("!ctkcombobox"):
|
||||
self.attach._canvas.tag_bind("right_parts", "<Button-1>", lambda e: self._iconify())
|
||||
self.attach._canvas.tag_bind("dropdown_arrow", "<Button-1>", lambda e: self._iconify())
|
||||
if self.command is None:
|
||||
self.command = self.attach.set
|
||||
|
||||
if self.attach.winfo_name().startswith("!ctkoptionmenu"):
|
||||
self.attach._canvas.bind("<Button-1>", lambda e: self._iconify())
|
||||
self.attach._text_label.bind("<Button-1>", lambda e: self._iconify())
|
||||
if self.command is None:
|
||||
self.command = self.attach.set
|
||||
|
||||
self.attach.bind("<Destroy>", lambda _: self._destroy(), add="+")
|
||||
|
||||
self.update_idletasks()
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
if self.autocomplete:
|
||||
self.bind_autocomplete()
|
||||
|
||||
self.deiconify()
|
||||
self.withdraw()
|
||||
|
||||
self.attributes("-alpha", self.alpha)
|
||||
|
||||
def _destroy(self):
|
||||
self.after(500, self.destroy_popup)
|
||||
|
||||
def _withdraw(self):
|
||||
if self.winfo_viewable() and self.hide:
|
||||
self.withdraw()
|
||||
|
||||
self.event_generate("<<Closed>>")
|
||||
self.hide = True
|
||||
|
||||
def _update(self, a, b, c):
|
||||
self.live_update(self.attach._entry.get())
|
||||
|
||||
def bind_autocomplete(self, ):
|
||||
def appear(x):
|
||||
self.appear = True
|
||||
|
||||
if self.attach.winfo_name().startswith("!ctkcombobox"):
|
||||
self.attach._entry.configure(textvariable=self.var_update)
|
||||
self.attach._entry.bind("<Key>", appear)
|
||||
self.attach.set(self.values[0])
|
||||
self.var_update.trace_add('write', self._update)
|
||||
|
||||
if self.attach.winfo_name().startswith("!ctkentry"):
|
||||
self.attach.configure(textvariable=self.var_update)
|
||||
self.attach.bind("<Key>", appear)
|
||||
self.var_update.trace_add('write', self._update)
|
||||
|
||||
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/100)
|
||||
|
||||
def fade_in(self):
|
||||
for i in range(0,100,10):
|
||||
if not self.winfo_exists():
|
||||
break
|
||||
self.attributes("-alpha", i/100)
|
||||
self.update()
|
||||
time.sleep(1/100)
|
||||
|
||||
def _init_buttons(self, **button_kwargs):
|
||||
self.i = 0
|
||||
self.widgets = {}
|
||||
for row in self.values:
|
||||
self.widgets[self.i] = customtkinter.CTkButton(self.frame,
|
||||
text=row,
|
||||
height=self.button_height,
|
||||
fg_color=self.button_color,
|
||||
text_color=self.text_color,
|
||||
image=self.image_values[i] if self.image_values is not None else None,
|
||||
anchor=self.justify,
|
||||
command=lambda k=row: self._attach_key_press(k), **button_kwargs)
|
||||
self.widgets[self.i].pack(fill="x", pady=2, padx=(self.padding, 0))
|
||||
self.i+=1
|
||||
|
||||
self.hide = False
|
||||
|
||||
def destroy_popup(self):
|
||||
self.destroy()
|
||||
self.disable = True
|
||||
|
||||
def place_dropdown(self):
|
||||
self.x_pos = self.attach.winfo_rootx() if self.x is None else self.x + self.attach.winfo_rootx()
|
||||
self.y_pos = self.attach.winfo_rooty() + self.attach.winfo_reqheight() + 5 if self.y is None else self.y + self.attach.winfo_rooty()
|
||||
self.width_new = self.attach.winfo_width() if self.width is None else self.width
|
||||
|
||||
if self.resize:
|
||||
if self.button_num<=5:
|
||||
self.height_new = self.button_height * self.button_num + 55
|
||||
else:
|
||||
self.height_new = self.button_height * self.button_num + 35
|
||||
if self.height_new>self.height:
|
||||
self.height_new = self.height
|
||||
|
||||
self.geometry('{}x{}+{}+{}'.format(self.width_new, self.height_new,
|
||||
self.x_pos, self.y_pos))
|
||||
self.fade_in()
|
||||
self.attributes('-alpha', self.alpha)
|
||||
self.attach.focus()
|
||||
|
||||
def _iconify(self):
|
||||
if self.disable: return
|
||||
if self.hide:
|
||||
self.event_generate("<<Opened>>")
|
||||
self._deiconify()
|
||||
self.focus()
|
||||
self.hide = False
|
||||
self.place_dropdown()
|
||||
if self.focus_something:
|
||||
self.dummy_entry.pack()
|
||||
self.dummy_entry.focus_set()
|
||||
self.after(100, self.dummy_entry.pack_forget)
|
||||
else:
|
||||
self.withdraw()
|
||||
self.hide = True
|
||||
|
||||
def _attach_key_press(self, k):
|
||||
self.event_generate("<<Selected>>")
|
||||
self.fade = True
|
||||
if self.command:
|
||||
self.command(k)
|
||||
self.fade = False
|
||||
self.fade_out()
|
||||
self.withdraw()
|
||||
self.hide = True
|
||||
|
||||
def live_update(self, string=None):
|
||||
if not self.appear: return
|
||||
if self.disable: return
|
||||
if self.fade: return
|
||||
if string:
|
||||
string = string.lower()
|
||||
self._deiconify()
|
||||
i=1
|
||||
for key in self.widgets.keys():
|
||||
s = self.widgets[key].cget("text").lower()
|
||||
text_similarity = difflib.SequenceMatcher(None, s[0:len(string)], string).ratio()
|
||||
similar = s.startswith(string) or text_similarity > 0.75
|
||||
if not similar:
|
||||
self.widgets[key].pack_forget()
|
||||
else:
|
||||
self.widgets[key].pack(fill="x", pady=2, padx=(self.padding, 0))
|
||||
i+=1
|
||||
|
||||
if i==1:
|
||||
self.no_match.pack(fill="x", pady=2, padx=(self.padding, 0))
|
||||
else:
|
||||
self.no_match.pack_forget()
|
||||
self.button_num = i
|
||||
self.place_dropdown()
|
||||
|
||||
else:
|
||||
self.no_match.pack_forget()
|
||||
self.button_num = len(self.values)
|
||||
for key in self.widgets.keys():
|
||||
self.widgets[key].destroy()
|
||||
self._init_buttons()
|
||||
self.place_dropdown()
|
||||
|
||||
self.frame._parent_canvas.yview_moveto(0.0)
|
||||
self.appear = False
|
||||
|
||||
def insert(self, value, **kwargs):
|
||||
self.widgets[self.i] = customtkinter.CTkButton(self.frame,
|
||||
text=value,
|
||||
height=self.button_height,
|
||||
fg_color=self.button_color,
|
||||
text_color=self.text_color,
|
||||
anchor=self.justify,
|
||||
command=lambda k=value: self._attach_key_press(k), **kwargs)
|
||||
self.widgets[self.i].pack(fill="x", pady=2, padx=(self.padding, 0))
|
||||
self.i+=1
|
||||
self.values.append(value)
|
||||
|
||||
def _deiconify(self):
|
||||
if len(self.values)>0:
|
||||
self.deiconify()
|
||||
|
||||
def popup(self, x=None, y=None):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.hide = True
|
||||
self._iconify()
|
||||
|
||||
def configure(self, **kwargs):
|
||||
if "height" in kwargs:
|
||||
self.height = kwargs.pop("height")
|
||||
self.height_new = self.height
|
||||
|
||||
if "alpha" in kwargs:
|
||||
self.alpha = kwargs.pop("alpha")
|
||||
|
||||
if "width" in kwargs:
|
||||
self.width = kwargs.pop("width")
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.frame.configure(fg_color=kwargs.pop("fg_color"))
|
||||
|
||||
if "values" in kwargs:
|
||||
self.values = kwargs.pop("values")
|
||||
self.image_values = None
|
||||
self.button_num = len(self.values)
|
||||
for key in self.widgets.keys():
|
||||
self.widgets[key].destroy()
|
||||
self._init_buttons()
|
||||
|
||||
if "image_values" in kwargs:
|
||||
self.image_values = kwargs.pop("image_values")
|
||||
self.image_values = None if len(self.image_values)!=len(self.values) else self.image_values
|
||||
if self.image_values is not None:
|
||||
i=0
|
||||
for key in self.widgets.keys():
|
||||
self.widgets[key].configure(image=self.image_values[i])
|
||||
i+=1
|
||||
|
||||
if "button_color" in kwargs:
|
||||
for key in self.widgets.keys():
|
||||
self.widgets[key].configure(fg_color=kwargs.pop("button_color"))
|
||||
|
||||
for key in self.widgets.keys():
|
||||
self.widgets[key].configure(**kwargs)
|
||||
291
MCE/custom_widgets/ctk_scrollable_dropdown_frame.py
Normal file
@ -0,0 +1,291 @@
|
||||
'''
|
||||
Advanced Scrollable Dropdown Frame class for customtkinter widgets
|
||||
Author: Akash Bora
|
||||
'''
|
||||
|
||||
import customtkinter
|
||||
import sys
|
||||
import difflib
|
||||
|
||||
class CTkScrollableDropdownFrame(customtkinter.CTkFrame):
|
||||
|
||||
def __init__(self, attach, x=None, y=None, button_color=None, height: int = 200, width: int = None,
|
||||
fg_color=None, button_height: int = 20, justify="center", scrollbar_button_color=None,
|
||||
scrollbar=True, scrollbar_button_hover_color=None, frame_border_width=2, values=[],
|
||||
command=None, image_values=[], double_click=False, frame_corner_radius=True, resize=True, frame_border_color=None,
|
||||
text_color=None, autocomplete=False, **button_kwargs):
|
||||
|
||||
super().__init__(master=attach.winfo_toplevel(), bg_color=attach.cget("bg_color"))
|
||||
|
||||
self.attach = attach
|
||||
self.corner = 11 if frame_corner_radius else 0
|
||||
self.padding = 0
|
||||
self.disable = True
|
||||
|
||||
self.hide = True
|
||||
self.attach.bind('<Configure>', lambda e: self._withdraw() if not self.disable else None, add="+")
|
||||
self.attach.winfo_toplevel().bind("<ButtonPress>", lambda e: self._withdraw() if not self.disable else None, add="+")
|
||||
|
||||
self.disable = False
|
||||
self.fg_color = customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"] if fg_color is None else fg_color
|
||||
self.scroll_button_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_color"] if scrollbar_button_color is None else scrollbar_button_color
|
||||
self.scroll_hover_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_hover_color"] if scrollbar_button_hover_color is None else scrollbar_button_hover_color
|
||||
self.frame_border_color = customtkinter.ThemeManager.theme["CTkFrame"]["border_color"] if frame_border_color is None else frame_border_color
|
||||
self.button_color = customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"] if button_color is None else button_color
|
||||
self.text_color = customtkinter.ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else text_color
|
||||
|
||||
if scrollbar is False:
|
||||
self.scroll_button_color = self.fg_color
|
||||
self.scroll_hover_color = self.fg_color
|
||||
|
||||
self.frame = customtkinter.CTkScrollableFrame(self, fg_color=self.fg_color, bg_color=attach.cget("bg_color"),
|
||||
scrollbar_button_hover_color=self.scroll_hover_color,
|
||||
corner_radius=self.corner, border_width=frame_border_width,
|
||||
scrollbar_button_color=self.scroll_button_color,
|
||||
border_color=self.frame_border_color)
|
||||
self.frame._scrollbar.grid_configure(padx=3)
|
||||
self.frame.pack(expand=True, fill="both")
|
||||
|
||||
if self.corner==0:
|
||||
self.corner = 21
|
||||
|
||||
self.dummy_entry = customtkinter.CTkEntry(self.frame, fg_color="transparent", border_width=0, height=1, width=1)
|
||||
self.no_match = customtkinter.CTkLabel(self.frame, text="No Match")
|
||||
self.height = height
|
||||
self.height_new = height
|
||||
self.width = width
|
||||
self.command = command
|
||||
self.fade = False
|
||||
self.resize = resize
|
||||
self.autocomplete = autocomplete
|
||||
self.var_update = customtkinter.StringVar()
|
||||
self.appear = False
|
||||
|
||||
if justify.lower()=="left":
|
||||
self.justify = "w"
|
||||
elif justify.lower()=="right":
|
||||
self.justify = "e"
|
||||
else:
|
||||
self.justify = "c"
|
||||
|
||||
self.button_height = button_height
|
||||
self.values = values
|
||||
self.button_num = len(self.values)
|
||||
self.image_values = None if len(image_values)!=len(self.values) else image_values
|
||||
|
||||
self._init_buttons(**button_kwargs)
|
||||
|
||||
# Add binding for different ctk widgets
|
||||
if double_click or self.attach.winfo_name().startswith("!ctkentry") or self.attach.winfo_name().startswith("!ctkcombobox"):
|
||||
self.attach.bind('<Double-Button-1>', lambda e: self._iconify(), add="+")
|
||||
self.attach._entry.bind('<FocusOut>', lambda e: self._withdraw() if not self.disable else None, add="+")
|
||||
else:
|
||||
self.attach.bind('<Button-1>', lambda e: self._iconify(), add="+")
|
||||
|
||||
if self.attach.winfo_name().startswith("!ctkcombobox"):
|
||||
self.attach._canvas.tag_bind("right_parts", "<Button-1>", lambda e: self._iconify())
|
||||
self.attach._canvas.tag_bind("dropdown_arrow", "<Button-1>", lambda e: self._iconify())
|
||||
|
||||
if self.command is None:
|
||||
self.command = self.attach.set
|
||||
|
||||
if self.attach.winfo_name().startswith("!ctkoptionmenu"):
|
||||
self.attach._canvas.bind("<Button-1>", lambda e: self._iconify())
|
||||
self.attach._text_label.bind("<Button-1>", lambda e: self._iconify())
|
||||
if self.command is None:
|
||||
self.command = self.attach.set
|
||||
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
self.attach.bind("<Destroy>", lambda _: self._destroy(), add="+")
|
||||
|
||||
if self.autocomplete:
|
||||
self.bind_autocomplete()
|
||||
|
||||
def _destroy(self):
|
||||
self.after(500, self.destroy_popup)
|
||||
|
||||
def _withdraw(self):
|
||||
if self.winfo_viewable() and self.hide:
|
||||
self.place_forget()
|
||||
|
||||
self.event_generate("<<Closed>>")
|
||||
self.hide = True
|
||||
|
||||
def _update(self, a, b, c):
|
||||
self.live_update(self.attach._entry.get())
|
||||
|
||||
def bind_autocomplete(self, ):
|
||||
def appear(x):
|
||||
self.appear = True
|
||||
|
||||
if self.attach.winfo_name().startswith("!ctkcombobox"):
|
||||
self.attach._entry.configure(textvariable=self.var_update)
|
||||
self.attach.set(self.values[0])
|
||||
self.attach._entry.bind("<Key>", appear)
|
||||
self.var_update.trace_add('write', self._update)
|
||||
|
||||
if self.attach.winfo_name().startswith("!ctkentry"):
|
||||
self.attach.configure(textvariable=self.var_update)
|
||||
self.attach.bind("<Key>", appear)
|
||||
self.var_update.trace_add('write', self._update)
|
||||
|
||||
def _init_buttons(self, **button_kwargs):
|
||||
self.i = 0
|
||||
self.widgets = {}
|
||||
for row in self.values:
|
||||
self.widgets[self.i] = customtkinter.CTkButton(self.frame,
|
||||
text=row,
|
||||
height=self.button_height,
|
||||
fg_color=self.button_color,
|
||||
text_color=self.text_color,
|
||||
image=self.image_values[i] if self.image_values is not None else None,
|
||||
anchor=self.justify,
|
||||
command=lambda k=row: self._attach_key_press(k), **button_kwargs)
|
||||
self.widgets[self.i].pack(fill="x", pady=2, padx=(self.padding, 0))
|
||||
self.i+=1
|
||||
|
||||
self.hide = False
|
||||
|
||||
def destroy_popup(self):
|
||||
self.destroy()
|
||||
self.disable = True
|
||||
|
||||
def place_dropdown(self):
|
||||
self.x_pos = self.attach.winfo_x() if self.x is None else self.x + self.attach.winfo_rootx()
|
||||
self.y_pos = self.attach.winfo_y() + self.attach.winfo_reqheight() + 5 if self.y is None else self.y + self.attach.winfo_rooty()
|
||||
self.width_new = self.attach.winfo_width()-45+self.corner if self.width is None else self.width
|
||||
|
||||
if self.resize:
|
||||
if self.button_num<=5:
|
||||
self.height_new = self.button_height * self.button_num + 55
|
||||
else:
|
||||
self.height_new = self.button_height * self.button_num + 35
|
||||
if self.height_new>self.height:
|
||||
self.height_new = self.height
|
||||
|
||||
self.frame.configure(width=self.width_new, height=self.height_new)
|
||||
self.place(x=self.x_pos, y=self.y_pos)
|
||||
|
||||
if sys.platform.startswith("darwin"):
|
||||
self.dummy_entry.pack()
|
||||
self.after(100, self.dummy_entry.pack_forget())
|
||||
|
||||
self.lift()
|
||||
self.attach.focus()
|
||||
|
||||
def _iconify(self):
|
||||
if self.disable: return
|
||||
if self.hide:
|
||||
self.event_generate("<<Opened>>")
|
||||
self.hide = False
|
||||
self.place_dropdown()
|
||||
else:
|
||||
self.place_forget()
|
||||
self.hide = True
|
||||
|
||||
def _attach_key_press(self, k):
|
||||
self.event_generate("<<Selected>>")
|
||||
self.fade = True
|
||||
if self.command:
|
||||
self.command(k)
|
||||
self.fade = False
|
||||
self.place_forget()
|
||||
self.hide = True
|
||||
|
||||
def live_update(self, string=None):
|
||||
if not self.appear: return
|
||||
if self.disable: return
|
||||
if self.fade: return
|
||||
if string:
|
||||
string = string.lower()
|
||||
self._deiconify()
|
||||
i=1
|
||||
for key in self.widgets.keys():
|
||||
s = self.widgets[key].cget("text").lower()
|
||||
text_similarity = difflib.SequenceMatcher(None, s[0:len(string)], string).ratio()
|
||||
similar = s.startswith(string) or text_similarity > 0.75
|
||||
if not similar:
|
||||
self.widgets[key].pack_forget()
|
||||
else:
|
||||
self.widgets[key].pack(fill="x", pady=2, padx=(self.padding, 0))
|
||||
i+=1
|
||||
|
||||
if i==1:
|
||||
self.no_match.pack(fill="x", pady=2, padx=(self.padding, 0))
|
||||
else:
|
||||
self.no_match.pack_forget()
|
||||
self.button_num = i
|
||||
self.place_dropdown()
|
||||
|
||||
else:
|
||||
self.no_match.pack_forget()
|
||||
self.button_num = len(self.values)
|
||||
for key in self.widgets.keys():
|
||||
self.widgets[key].destroy()
|
||||
self._init_buttons()
|
||||
self.place_dropdown()
|
||||
|
||||
self.frame._parent_canvas.yview_moveto(0.0)
|
||||
self.appear = False
|
||||
|
||||
def insert(self, value, **kwargs):
|
||||
self.widgets[self.i] = customtkinter.CTkButton(self.frame,
|
||||
text=value,
|
||||
height=self.button_height,
|
||||
fg_color=self.button_color,
|
||||
text_color=self.text_color,
|
||||
anchor=self.justify,
|
||||
command=lambda k=value: self._attach_key_press(k), **kwargs)
|
||||
self.widgets[self.i].pack(fill="x", pady=2, padx=(self.padding, 0))
|
||||
self.i+=1
|
||||
self.values.append(value)
|
||||
|
||||
def _deiconify(self):
|
||||
if len(self.values)>0:
|
||||
self.pack_forget()
|
||||
|
||||
def popup(self, x=None, y=None):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.hide = True
|
||||
self._iconify()
|
||||
|
||||
def configure(self, **kwargs):
|
||||
if "height" in kwargs:
|
||||
self.height = kwargs.pop("height")
|
||||
self.height_new = self.height
|
||||
|
||||
if "alpha" in kwargs:
|
||||
self.alpha = kwargs.pop("alpha")
|
||||
|
||||
if "width" in kwargs:
|
||||
self.width = kwargs.pop("width")
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
self.frame.configure(fg_color=kwargs.pop("fg_color"))
|
||||
|
||||
if "values" in kwargs:
|
||||
self.values = kwargs.pop("values")
|
||||
self.image_values = None
|
||||
self.button_num = len(self.values)
|
||||
for key in self.widgets.keys():
|
||||
self.widgets[key].destroy()
|
||||
self._init_buttons()
|
||||
|
||||
if "image_values" in kwargs:
|
||||
self.image_values = kwargs.pop("image_values")
|
||||
self.image_values = None if len(self.image_values)!=len(self.values) else self.image_values
|
||||
if self.image_values is not None:
|
||||
i=0
|
||||
for key in self.widgets.keys():
|
||||
self.widgets[key].configure(image=self.image_values[i])
|
||||
i+=1
|
||||
|
||||
if "button_color" in kwargs:
|
||||
for key in self.widgets.keys():
|
||||
self.widgets[key].configure(fg_color=kwargs.pop("button_color"))
|
||||
|
||||
for key in self.widgets.keys():
|
||||
self.widgets[key].configure(**kwargs)
|
||||
133
MCE/custom_widgets/ctk_templatedialog.py
Normal file
@ -0,0 +1,133 @@
|
||||
from typing import Union, Tuple, Optional
|
||||
from customtkinter import CTkLabel, CTkEntry, CTkButton, ThemeManager, CTkToplevel, CTkFont, CTkOptionMenu
|
||||
|
||||
|
||||
class CTkTemplateDialog(CTkToplevel):
|
||||
"""
|
||||
Dialog with extra window, message, entry widget, cancel and ok button.
|
||||
For detailed information check out the documentation.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
|
||||
text_color: Optional[Union[str, Tuple[str, str]]] = None,
|
||||
button_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
|
||||
button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
|
||||
button_text_color: Optional[Union[str, Tuple[str, str]]] = None,
|
||||
entry_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
|
||||
entry_border_color: Optional[Union[str, Tuple[str, str]]] = None,
|
||||
entry_text_color: Optional[Union[str, Tuple[str, str]]] = None,
|
||||
|
||||
title: str = "CTkDialog",
|
||||
font: Optional[Union[tuple, CTkFont]] = None,
|
||||
text: str = "CTkDialog",
|
||||
values = []):
|
||||
|
||||
super().__init__(fg_color=fg_color)
|
||||
|
||||
self._fg_color = ThemeManager.theme["CTkToplevel"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
|
||||
self._text_color = ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else self._check_color_type(button_hover_color)
|
||||
self._button_fg_color = ThemeManager.theme["CTkButton"]["fg_color"] if button_fg_color is None else self._check_color_type(button_fg_color)
|
||||
self._button_hover_color = ThemeManager.theme["CTkButton"]["hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color)
|
||||
self._button_text_color = ThemeManager.theme["CTkButton"]["text_color"] if button_text_color is None else self._check_color_type(button_text_color)
|
||||
self._entry_fg_color = ThemeManager.theme["CTkEntry"]["fg_color"] if entry_fg_color is None else self._check_color_type(entry_fg_color)
|
||||
self._entry_border_color = ThemeManager.theme["CTkEntry"]["border_color"] if entry_border_color is None else self._check_color_type(entry_border_color)
|
||||
self._entry_text_color = ThemeManager.theme["CTkEntry"]["text_color"] if entry_text_color is None else self._check_color_type(entry_text_color)
|
||||
|
||||
self._user_input = ("", "")
|
||||
self._running: bool = False
|
||||
self._title = title
|
||||
self._text = text
|
||||
self._font = font
|
||||
self._values = [""] + values
|
||||
|
||||
self.title(self._title)
|
||||
self.lift() # lift window on top
|
||||
self.attributes("-topmost", True) # stay on top
|
||||
self.protocol("WM_DELETE_WINDOW", self._on_closing)
|
||||
self.after(10, self._create_widgets) # create widgets with slight delay, to avoid white flickering of background
|
||||
self.resizable(False, False)
|
||||
self.grab_set() # make other windows not clickable
|
||||
|
||||
def _create_widgets(self):
|
||||
self.grid_columnconfigure((0, 1), weight=1)
|
||||
self.rowconfigure(0, weight=1)
|
||||
|
||||
self._label = CTkLabel(master=self,
|
||||
width=300,
|
||||
wraplength=300,
|
||||
fg_color="transparent",
|
||||
text_color=self._text_color,
|
||||
text=self._text,
|
||||
font=self._font)
|
||||
self._label.grid(row=0, column=0, columnspan=2, padx=20, pady=20, sticky="ew")
|
||||
|
||||
self._entry = CTkEntry(master=self,
|
||||
width=230,
|
||||
fg_color=self._entry_fg_color,
|
||||
border_color=self._entry_border_color,
|
||||
text_color=self._entry_text_color,
|
||||
font=self._font)
|
||||
self._entry.grid(row=1, column=0, columnspan=2, padx=20, pady=(0, 20), sticky="ew")
|
||||
|
||||
self._label2 = CTkLabel(master=self,
|
||||
width=100,
|
||||
wraplength=100,
|
||||
fg_color="transparent",
|
||||
text_color=self._text_color,
|
||||
text="Import stages from: ",
|
||||
font=self._font)
|
||||
self._label2.grid(row=2, column=0, columnspan=1, padx=(20, 10), pady=(0, 20), sticky="ew")
|
||||
|
||||
self._template_optionmenu = CTkOptionMenu(master=self,
|
||||
width=100,
|
||||
fg_color=self._button_fg_color,
|
||||
button_hover_color=self._button_hover_color,
|
||||
text_color=self._button_text_color,
|
||||
font=self._font,
|
||||
values=self._values
|
||||
)
|
||||
self._template_optionmenu.grid(row=2, column=1, columnspan=1, padx=(10, 20), pady=(0, 20), sticky="ew")
|
||||
|
||||
|
||||
self._ok_button = CTkButton(master=self,
|
||||
width=100,
|
||||
border_width=0,
|
||||
fg_color=self._button_fg_color,
|
||||
hover_color=self._button_hover_color,
|
||||
text_color=self._button_text_color,
|
||||
text='Ok',
|
||||
font=self._font,
|
||||
command=self._ok_event)
|
||||
self._ok_button.grid(row=3, column=0, columnspan=1, padx=(20, 10), pady=(0, 20), sticky="ew")
|
||||
|
||||
self._cancel_button = CTkButton(master=self,
|
||||
width=100,
|
||||
border_width=0,
|
||||
fg_color=self._button_fg_color,
|
||||
hover_color=self._button_hover_color,
|
||||
text_color=self._button_text_color,
|
||||
text='Cancel',
|
||||
font=self._font,
|
||||
command=self._cancel_event)
|
||||
self._cancel_button.grid(row=3, column=1, columnspan=1, padx=(10, 20), pady=(0, 20), sticky="ew")
|
||||
|
||||
self.after(150, lambda: self._entry.focus()) # set focus to entry with slight delay, otherwise it won't work
|
||||
self._entry.bind("<Return>", self._ok_event)
|
||||
|
||||
def _ok_event(self, event=None):
|
||||
self._user_input = self._entry.get(), self._template_optionmenu.get()
|
||||
self.grab_release()
|
||||
self.destroy()
|
||||
|
||||
def _on_closing(self):
|
||||
self.grab_release()
|
||||
self.destroy()
|
||||
|
||||
def _cancel_event(self):
|
||||
self.grab_release()
|
||||
self.destroy()
|
||||
|
||||
def get_input(self):
|
||||
self.master.wait_window(self)
|
||||
return self._user_input
|
||||
36
MCE/custom_widgets/ctk_timeentry.py
Normal file
@ -0,0 +1,36 @@
|
||||
import customtkinter
|
||||
import tkinter as tk
|
||||
|
||||
class CTkTimeEntry(customtkinter.CTkFrame):
|
||||
def __init__(self, master=None, **kwargs):
|
||||
super().__init__(master, **kwargs)
|
||||
self.hour = tk.StringVar()
|
||||
self.minute = tk.StringVar()
|
||||
self.second = tk.StringVar()
|
||||
|
||||
self.hour_entry = customtkinter.CTkEntry(self, width=50, textvariable=self.hour, validate="key", validatecommand=(self.register(self.validate_hour), '%P'))
|
||||
self.hour_entry.pack(side=tk.LEFT)
|
||||
|
||||
self.minute_entry = customtkinter.CTkEntry(self,width=50, textvariable=self.minute, validate="key", validatecommand=(self.register(self.validate_min_sec), '%P'))
|
||||
self.minute_entry.pack(side=tk.LEFT)
|
||||
|
||||
self.second_entry = customtkinter.CTkEntry(self, width=50, textvariable=self.second, validate="key", validatecommand=(self.register(self.validate_min_sec), '%P'))
|
||||
self.second_entry.pack(side=tk.LEFT)
|
||||
|
||||
def validate_hour(self, P):
|
||||
return len(P) <= 2 and (P.isdigit() and int(P) <= 23 or P == "")
|
||||
|
||||
def validate_min_sec(self, P):
|
||||
return len(P) <= 2 and (P.isdigit() and int(P) <= 59 or P == "")
|
||||
|
||||
def set(self, time_str):
|
||||
h, m, s = map(str, time_str.split(':'))
|
||||
self.hour.set(h)
|
||||
self.minute.set(m)
|
||||
self.second.set(s)
|
||||
|
||||
def get(self):
|
||||
h = self.hour.get() if self.hour.get() else "00"
|
||||
m = self.minute.get() if self.minute.get() else "00"
|
||||
s = self.second.get() if self.second.get() else "00"
|
||||
return f"{h.zfill(2)}:{m.zfill(2)}:{s.zfill(2)}"
|
||||
212
MCE/custom_widgets/ctk_tooltip.py
Normal 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)
|
||||
448
MCE/custom_widgets/ctkmessagebox.py
Normal 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()
|
||||
BIN
MCE/icons/cancel.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
MCE/icons/check.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
MCE/icons/info.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
MCE/icons/question.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
MCE/icons/warning.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
158
MCE/student_list/EN.json
Normal file
@ -0,0 +1,158 @@
|
||||
[
|
||||
"Airi",
|
||||
"Akane",
|
||||
"Akane (Bunny Girl)",
|
||||
"Akari",
|
||||
"Ako",
|
||||
"Arisu",
|
||||
"Arisu (Maid)",
|
||||
"Aru",
|
||||
"Aru (New Year)",
|
||||
"Asuna",
|
||||
"Asuna (Bunny Girl)",
|
||||
"Atsuko",
|
||||
"Ayane",
|
||||
"Ayane (Swimsuit)",
|
||||
"Azusa",
|
||||
"Azusa (Swimsuit)",
|
||||
"Cherino",
|
||||
"Cherino (Hot Spring)",
|
||||
"Chihiro",
|
||||
"Chinatsu",
|
||||
"Chinatsu (Hot Spring)",
|
||||
"Chise",
|
||||
"Chise (Swimsuit)",
|
||||
"Eimi",
|
||||
"Fubuki",
|
||||
"Fuuka",
|
||||
"Fuuka (New Year)",
|
||||
"Hanae",
|
||||
"Hanae (Christmas)",
|
||||
"Hanako",
|
||||
"Hanako (Swimsuit)",
|
||||
"Hare",
|
||||
"Haruka",
|
||||
"Haruka (New Year)",
|
||||
"Haruna",
|
||||
"Haruna (New Year)",
|
||||
"Haruna (Sportswear)",
|
||||
"Hasumi",
|
||||
"Hasumi (Sportswear)",
|
||||
"Hatsune Miku",
|
||||
"Hibiki",
|
||||
"Hibiki (Cheerleader)",
|
||||
"Hifumi",
|
||||
"Hifumi (Swimsuit)",
|
||||
"Himari",
|
||||
"Hina",
|
||||
"Hina (Swimsuit)",
|
||||
"Hinata",
|
||||
"Hinata (Swimsuit)",
|
||||
"Hiyori",
|
||||
"Hoshino",
|
||||
"Hoshino (Swimsuit)",
|
||||
"Iori",
|
||||
"Iori (Swimsuit)",
|
||||
"Iroha",
|
||||
"Izumi",
|
||||
"Izumi (Swimsuit)",
|
||||
"Izuna",
|
||||
"Izuna (Swimsuit)",
|
||||
"Junko",
|
||||
"Junko (New Year)",
|
||||
"Juri",
|
||||
"Kaede",
|
||||
"Kaho",
|
||||
"Kanna",
|
||||
"Karin",
|
||||
"Karin (Bunny Girl)",
|
||||
"Kayoko",
|
||||
"Kayoko (New Year)",
|
||||
"Kazusa",
|
||||
"Kirino",
|
||||
"Koharu",
|
||||
"Koharu (Swimsuit)",
|
||||
"Kokona",
|
||||
"Kotama",
|
||||
"Kotori",
|
||||
"Kotori (Cheerleader)",
|
||||
"Koyuki",
|
||||
"Maki",
|
||||
"Mari",
|
||||
"Mari (Sportswear)",
|
||||
"Marina",
|
||||
"Mashiro",
|
||||
"Mashiro (Swimsuit)",
|
||||
"Megu",
|
||||
"Meru",
|
||||
"Michiru",
|
||||
"Midori",
|
||||
"Mika",
|
||||
"Mimori",
|
||||
"Mimori (Swimsuit)",
|
||||
"Mina",
|
||||
"Mine",
|
||||
"Minori",
|
||||
"Misaki",
|
||||
"Miyako",
|
||||
"Miyako (Swimsuit)",
|
||||
"Miyu",
|
||||
"Miyu (Swimsuit)",
|
||||
"Moe",
|
||||
"Momiji",
|
||||
"Momoi",
|
||||
"Mutsuki",
|
||||
"Mutsuki (New Year)",
|
||||
"Nagisa",
|
||||
"Natsu",
|
||||
"Neru",
|
||||
"Neru (Bunny Girl)",
|
||||
"Noa",
|
||||
"Nodoka",
|
||||
"Nodoka (Hot Spring)",
|
||||
"Nonomi",
|
||||
"Nonomi (Swimsuit)",
|
||||
"Pina",
|
||||
"Reisa",
|
||||
"Rumi",
|
||||
"Saki",
|
||||
"Saki (Swimsuit)",
|
||||
"Sakurako",
|
||||
"Saori",
|
||||
"Saya",
|
||||
"Saya (Casual)",
|
||||
"Sena",
|
||||
"Serika",
|
||||
"Serika (New Year)",
|
||||
"Serina",
|
||||
"Serina (Christmas)",
|
||||
"Shigure",
|
||||
"Shimiko",
|
||||
"Shiroko",
|
||||
"Shiroko (Riding)",
|
||||
"Shiroko (Swimsuit)",
|
||||
"Shizuko",
|
||||
"Shizuko (Swimsuit)",
|
||||
"Shun",
|
||||
"Shun (Kid)",
|
||||
"Sumire",
|
||||
"Suzumi",
|
||||
"Toki",
|
||||
"Toki (Bunny Girl)",
|
||||
"Tomoe",
|
||||
"Tsubaki",
|
||||
"Tsukuyo",
|
||||
"Tsurugi",
|
||||
"Tsurugi (Swimsuit)",
|
||||
"Ui",
|
||||
"Ui (Swimsuit)",
|
||||
"Utaha",
|
||||
"Utaha (Cheerleader)",
|
||||
"Wakamo",
|
||||
"Wakamo (Swimsuit)",
|
||||
"Yoshimi",
|
||||
"Yuuka",
|
||||
"Yuuka (Sportswear)",
|
||||
"Yuzu",
|
||||
"Yuzu (Maid)"
|
||||
]
|
||||
168
MCE/student_list/JP.json
Normal file
@ -0,0 +1,168 @@
|
||||
[
|
||||
"\u30a2\u30a4\u30ea",
|
||||
"\u30a2\u30ab\u30cd",
|
||||
"\u30a2\u30ab\u30cd\n\uff08\u30d0\u30cb\u30fc\u30ac\u30fc\u30eb\uff09",
|
||||
"\u30a2\u30ab\u30ea",
|
||||
"\u30a2\u30b3",
|
||||
"\u30a2\u30b9\u30ca",
|
||||
"\u30a2\u30b9\u30ca\n\uff08\u30d0\u30cb\u30fc\u30ac\u30fc\u30eb\uff09",
|
||||
"\u30a2\u30ba\u30b5",
|
||||
"\u30a2\u30ba\u30b5\uff08\u6c34\u7740\uff09",
|
||||
"\u30a2\u30c4\u30b3",
|
||||
"\u30a2\u30e4\u30cd",
|
||||
"\u30a2\u30e4\u30cd\uff08\u6c34\u7740\uff09",
|
||||
"\u30a2\u30ea\u30b9",
|
||||
"\u30a2\u30ea\u30b9\uff08\u30e1\u30a4\u30c9\uff09",
|
||||
"\u30a2\u30eb",
|
||||
"\u30a2\u30eb\uff08\u6b63\u6708\uff09",
|
||||
"\u30a4\u30aa\u30ea",
|
||||
"\u30a4\u30aa\u30ea\uff08\u6c34\u7740\uff09",
|
||||
"\u30a4\u30ba\u30ca",
|
||||
"\u30a4\u30ba\u30ca\uff08\u6c34\u7740\uff09",
|
||||
"\u30a4\u30ba\u30df",
|
||||
"\u30a4\u30ba\u30df\uff08\u6c34\u7740\uff09",
|
||||
"\u30a4\u30c1\u30ab",
|
||||
"\u30a4\u30ed\u30cf",
|
||||
"\u30a6\u30a4",
|
||||
"\u30a6\u30a4\uff08\u6c34\u7740\uff09",
|
||||
"\u30a6\u30bf\u30cf",
|
||||
"\u30a6\u30bf\u30cf\uff08\u5fdc\u63f4\u56e3\uff09",
|
||||
"\u30a8\u30a4\u30df",
|
||||
"\u30a8\u30a4\u30df\uff08\u6c34\u7740\uff09",
|
||||
"\u30ab\u30a8\u30c7",
|
||||
"\u30ab\u30b9\u30df",
|
||||
"\u30ab\u30ba\u30b5",
|
||||
"\u30ab\u30db",
|
||||
"\u30ab\u30e8\u30b3",
|
||||
"\u30ab\u30e8\u30b3\uff08\u6b63\u6708\uff09",
|
||||
"\u30ab\u30ea\u30f3",
|
||||
"\u30ab\u30ea\u30f3\n\uff08\u30d0\u30cb\u30fc\u30ac\u30fc\u30eb\uff09",
|
||||
"\u30ab\u30f3\u30ca",
|
||||
"\u30ad\u30ad\u30e7\u30a6",
|
||||
"\u30ad\u30ea\u30ce",
|
||||
"\u30b3\u30b3\u30ca",
|
||||
"\u30b3\u30bf\u30de",
|
||||
"\u30b3\u30c8\u30ea",
|
||||
"\u30b3\u30c8\u30ea\uff08\u5fdc\u63f4\u56e3\uff09",
|
||||
"\u30b3\u30cf\u30eb",
|
||||
"\u30b3\u30cf\u30eb\uff08\u6c34\u7740\uff09",
|
||||
"\u30b3\u30e6\u30ad",
|
||||
"\u30b5\u30aa\u30ea",
|
||||
"\u30b5\u30ad",
|
||||
"\u30b5\u30ad\uff08\u6c34\u7740\uff09",
|
||||
"\u30b5\u30af\u30e9\u30b3",
|
||||
"\u30b5\u30e4",
|
||||
"\u30b5\u30e4\uff08\u79c1\u670d\uff09",
|
||||
"\u30b7\u30b0\u30ec",
|
||||
"\u30b7\u30b0\u30ec\uff08\u6e29\u6cc9\uff09",
|
||||
"\u30b7\u30ba\u30b3",
|
||||
"\u30b7\u30ba\u30b3\uff08\u6c34\u7740\uff09",
|
||||
"\u30b7\u30df\u30b3",
|
||||
"\u30b7\u30e5\u30f3",
|
||||
"\u30b7\u30e5\u30f3\uff08\u5e7c\u5973\uff09",
|
||||
"\u30b7\u30ed\u30b3",
|
||||
"\u30b7\u30ed\u30b3\n\uff08\u30e9\u30a4\u30c7\u30a3\u30f3\u30b0\uff09",
|
||||
"\u30b7\u30ed\u30b3\uff08\u6c34\u7740\uff09",
|
||||
"\u30b8\u30e5\u30ea",
|
||||
"\u30b8\u30e5\u30f3\u30b3",
|
||||
"\u30b8\u30e5\u30f3\u30b3\uff08\u6b63\u6708\uff09",
|
||||
"\u30b9\u30ba\u30df",
|
||||
"\u30b9\u30df\u30ec",
|
||||
"\u30bb\u30ca",
|
||||
"\u30bb\u30ea\u30ab",
|
||||
"\u30bb\u30ea\u30ab\uff08\u6b63\u6708\uff09",
|
||||
"\u30bb\u30ea\u30ca",
|
||||
"\u30bb\u30ea\u30ca\n\uff08\u30af\u30ea\u30b9\u30de\u30b9\uff09",
|
||||
"\u30c1\u30a7\u30ea\u30ce",
|
||||
"\u30c1\u30a7\u30ea\u30ce\uff08\u6e29\u6cc9\uff09",
|
||||
"\u30c1\u30bb",
|
||||
"\u30c1\u30bb\uff08\u6c34\u7740\uff09",
|
||||
"\u30c1\u30ca\u30c4",
|
||||
"\u30c1\u30ca\u30c4\uff08\u6e29\u6cc9\uff09",
|
||||
"\u30c1\u30d2\u30ed",
|
||||
"\u30c4\u30af\u30e8",
|
||||
"\u30c4\u30d0\u30ad",
|
||||
"\u30c4\u30eb\u30ae",
|
||||
"\u30c4\u30eb\u30ae\uff08\u6c34\u7740\uff09",
|
||||
"\u30c8\u30ad",
|
||||
"\u30c8\u30ad\n\uff08\u30d0\u30cb\u30fc\u30ac\u30fc\u30eb\uff09",
|
||||
"\u30c8\u30e2\u30a8",
|
||||
"\u30ca\u30ae\u30b5",
|
||||
"\u30ca\u30c4",
|
||||
"\u30cd\u30eb",
|
||||
"\u30cd\u30eb\n\uff08\u30d0\u30cb\u30fc\u30ac\u30fc\u30eb\uff09",
|
||||
"\u30ce\u30a2",
|
||||
"\u30ce\u30c9\u30ab",
|
||||
"\u30ce\u30c9\u30ab\uff08\u6e29\u6cc9\uff09",
|
||||
"\u30ce\u30ce\u30df",
|
||||
"\u30ce\u30ce\u30df\uff08\u6c34\u7740\uff09",
|
||||
"\u30cf\u30b9\u30df",
|
||||
"\u30cf\u30b9\u30df\uff08\u4f53\u64cd\u670d\uff09",
|
||||
"\u30cf\u30ca\u30a8",
|
||||
"\u30cf\u30ca\u30a8\n\uff08\u30af\u30ea\u30b9\u30de\u30b9\uff09",
|
||||
"\u30cf\u30ca\u30b3",
|
||||
"\u30cf\u30ca\u30b3\uff08\u6c34\u7740\uff09",
|
||||
"\u30cf\u30eb\u30ab",
|
||||
"\u30cf\u30eb\u30ab\uff08\u6b63\u6708\uff09",
|
||||
"\u30cf\u30eb\u30ca",
|
||||
"\u30cf\u30eb\u30ca\uff08\u4f53\u64cd\u670d\uff09",
|
||||
"\u30cf\u30eb\u30ca\uff08\u6b63\u6708\uff09",
|
||||
"\u30cf\u30ec",
|
||||
"\u30d2\u30ca",
|
||||
"\u30d2\u30ca\u30bf",
|
||||
"\u30d2\u30ca\u30bf\uff08\u6c34\u7740\uff09",
|
||||
"\u30d2\u30ca\uff08\u6c34\u7740\uff09",
|
||||
"\u30d2\u30d3\u30ad",
|
||||
"\u30d2\u30d3\u30ad\uff08\u5fdc\u63f4\u56e3\uff09",
|
||||
"\u30d2\u30d5\u30df",
|
||||
"\u30d2\u30d5\u30df\uff08\u6c34\u7740\uff09",
|
||||
"\u30d2\u30de\u30ea",
|
||||
"\u30d2\u30e8\u30ea",
|
||||
"\u30d5\u30a3\u30fc\u30ca",
|
||||
"\u30d5\u30a6\u30ab",
|
||||
"\u30d5\u30a6\u30ab\uff08\u6b63\u6708\uff09",
|
||||
"\u30d5\u30d6\u30ad",
|
||||
"\u30db\u30b7\u30ce",
|
||||
"\u30db\u30b7\u30ce\uff08\u6c34\u7740\uff09",
|
||||
"\u30de\u30ad",
|
||||
"\u30de\u30b7\u30ed",
|
||||
"\u30de\u30b7\u30ed\uff08\u6c34\u7740\uff09",
|
||||
"\u30de\u30ea\u30ca",
|
||||
"\u30de\u30ea\u30fc",
|
||||
"\u30de\u30ea\u30fc\uff08\u4f53\u64cd\u670d\uff09",
|
||||
"\u30df\u30ab",
|
||||
"\u30df\u30b5\u30ad",
|
||||
"\u30df\u30c1\u30eb",
|
||||
"\u30df\u30c9\u30ea",
|
||||
"\u30df\u30ca",
|
||||
"\u30df\u30cd",
|
||||
"\u30df\u30ce\u30ea",
|
||||
"\u30df\u30e2\u30ea",
|
||||
"\u30df\u30e2\u30ea\uff08\u6c34\u7740\uff09",
|
||||
"\u30df\u30e4\u30b3",
|
||||
"\u30df\u30e4\u30b3\uff08\u6c34\u7740\uff09",
|
||||
"\u30df\u30e6",
|
||||
"\u30df\u30e6\uff08\u6c34\u7740\uff09",
|
||||
"\u30e0\u30c4\u30ad",
|
||||
"\u30e0\u30c4\u30ad\uff08\u6b63\u6708\uff09",
|
||||
"\u30e1\u30b0",
|
||||
"\u30e1\u30eb",
|
||||
"\u30e2\u30a8",
|
||||
"\u30e2\u30df\u30b8",
|
||||
"\u30e2\u30e2\u30a4",
|
||||
"\u30e6\u30a6\u30ab",
|
||||
"\u30e6\u30a6\u30ab\uff08\u4f53\u64cd\u670d\uff09",
|
||||
"\u30e6\u30ab\u30ea",
|
||||
"\u30e6\u30ba",
|
||||
"\u30e6\u30ba\uff08\u30e1\u30a4\u30c9\uff09",
|
||||
"\u30e8\u30b7\u30df",
|
||||
"\u30eb\u30df",
|
||||
"\u30ec\u30a4\u30b5",
|
||||
"\u30ec\u30f3\u30b2",
|
||||
"\u30ef\u30ab\u30e2",
|
||||
"\u30ef\u30ab\u30e2\uff08\u6c34\u7740\uff09",
|
||||
"\u4f50\u5929\u6d99\u5b50",
|
||||
"\u521d\u97f3\u30df\u30af",
|
||||
"\u5fa1\u5742\u7f8e\u7434",
|
||||
"\u98df\u8702\u64cd\u7948"
|
||||
]
|
||||
109
MCE/utils.py
Normal file
@ -0,0 +1,109 @@
|
||||
import customtkinter
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
class Config:
|
||||
def __init__(self, linker, config_file):
|
||||
self.default_config = {
|
||||
"ResetDaily": False,
|
||||
"LastRun": "2023-12-24 21:41:55",
|
||||
"ResetTime": "11:21:30",
|
||||
"RechargeAP": False,
|
||||
"PreferredTemplate": "template1",
|
||||
"Queue": [],
|
||||
"Event": False,
|
||||
"Templates": {
|
||||
"template1": []
|
||||
}
|
||||
}
|
||||
self.linker = linker
|
||||
self.config_file = config_file
|
||||
|
||||
if not os.path.exists(self.config_file):
|
||||
with open(self.config_file, "w") as f:
|
||||
json.dump(self.default_config, f, indent=2)
|
||||
|
||||
self.config_data = self.read()
|
||||
self.linker.widgets = self.set_values_to_none(self.config_data)
|
||||
self.locked = False
|
||||
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 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):
|
||||
if self.locked:
|
||||
with open("MCE\config.json", "r") as config_file:
|
||||
new_config = json.load(config_file)
|
||||
self.config_data["Queue"] = new_config["Queue"]
|
||||
self.config_data["LastRun"] = new_config["LastRun"]
|
||||
with open("MCE\config.json", "w") as config_file:
|
||||
json.dump(self.config_data, config_file, indent=2)
|
||||
if name:
|
||||
self.linker.show_notification(name)
|
||||
|
||||
class Linker:
|
||||
def __init__(self):
|
||||
self.capitalise = lambda word: " ".join(x.title() for x in word.split("_"))
|
||||
self.config = None
|
||||
self.widgets = {}
|
||||
self.sidebar = None
|
||||
self.event_id = None
|
||||
|
||||
def show_notification(self, text):
|
||||
if self.event_id:
|
||||
self.sidebar.after_cancel(self.event_id)
|
||||
self.sidebar.notification.show()
|
||||
self.event_id = self.sidebar.after(2500, self.sidebar.notification.hide)
|
||||
21
aas.py
@ -46,11 +46,30 @@ class ArisuAutoSweeper(AzurLaneAutoScript):
|
||||
from tasks.tactical_challenge.tactical_challenge import TacticalChallenge
|
||||
TacticalChallenge(config=self.config, device=self.device).run()
|
||||
|
||||
def task(self):
|
||||
from tasks.task.task import Task
|
||||
Task(config=self.config, device=self.device).run()
|
||||
|
||||
def shop(self):
|
||||
from tasks.shop.shop import Shop
|
||||
Shop(config=self.config, device=self.device).run()
|
||||
|
||||
def momotalk(self):
|
||||
from tasks.momotalk.momotalk import MomoTalk
|
||||
MomoTalk(config=self.config, device=self.device).run()
|
||||
|
||||
def mission(self):
|
||||
from tasks.mission.mission import Mission
|
||||
Mission(config=self.config, device=self.device).run()
|
||||
|
||||
def schedule(self):
|
||||
from tasks.schedule.schedule import Schedule
|
||||
Schedule(config=self.config, device=self.device).run()
|
||||
|
||||
def data_update(self):
|
||||
from tasks.item.data_update import DataUpdate
|
||||
DataUpdate(config=self.config, device=self.device).run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
aas = ArisuAutoSweeper('aas')
|
||||
aas.loop()
|
||||
|
||||
BIN
assets/en/base/page/EVENT_CHECK.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
assets/en/base/page/WORK_GO_TO_EVENT.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 28 KiB |
BIN
assets/en/base/popup/LEVEL_UP.BUTTON.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
assets/en/base/popup/LEVEL_UP.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
assets/en/base/popup/LOCATION_LEVEL_UP.BUTTON.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
assets/en/base/popup/LOCATION_LEVEL_UP.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
assets/en/base/popup/QUIT.BUTTON.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
assets/en/base/popup/QUIT.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
assets/en/login/OCR_YEAR.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
assets/en/mission/CHECK_COMMISSIONS.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
assets/en/mission/CHECK_CR.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/en/mission/CHECK_MISSION_SWEEP.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
assets/en/mission/CHECK_XP.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/en/mission/EVENT_INDEX.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
assets/en/mission/EVENT_ITEM.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
assets/en/mission/EVENT_LIST.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
assets/en/mission/EVENT_STARS.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
assets/en/mission/HARD_OFF.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
assets/en/mission/HARD_ON.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
assets/en/mission/LEFT.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
assets/en/mission/NORMAL_OFF.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
assets/en/mission/NORMAL_ON.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
assets/en/mission/OCR_AREA.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
assets/en/mission/QUEST_OFF.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
assets/en/mission/QUEST_ON.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
assets/en/mission/RIGHT.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
assets/en/mission/SELECT_CR.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/en/mission/SELECT_XP.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/en/momotalk/BEGIN_STORY.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
assets/en/momotalk/CHATTING.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
assets/en/momotalk/CHAT_AREA.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
assets/en/momotalk/CONFIRM_SKIP.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/en/momotalk/CONFIRM_SORT.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
assets/en/momotalk/FIRST_UNREAD.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
assets/en/momotalk/MENU.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
assets/en/momotalk/NOTIFICATION_BADGE.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
assets/en/momotalk/REPLY.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
assets/en/momotalk/SELECT_STUDENT.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
assets/en/momotalk/SKIP.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
assets/en/momotalk/SORT_ASCENDING.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
assets/en/momotalk/SORT_DESCENDING.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
assets/en/momotalk/STORY.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
assets/en/momotalk/SWITCH_MESSAGE.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
assets/en/momotalk/SWITCH_MESSAGE_CHECK.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
assets/en/momotalk/SWITCH_STUDENT.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
assets/en/momotalk/SWITCH_STUDENT_CHECK.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
assets/en/momotalk/UNREAD.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
assets/en/momotalk/UNREAD_OFF.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
assets/en/momotalk/UNREAD_ON.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
assets/en/schedule/CONFIRM.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/en/schedule/FIRST_ITEM.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
assets/en/schedule/LOCATIONS.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/en/schedule/LOCATIONS_POPUP.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
assets/en/schedule/OCR_TICKET.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
assets/en/schedule/START_LESSON.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
assets/en/shop/CONFIRM_PURCHASE.BUTTON.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
assets/en/shop/CONFIRM_PURCHASE.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
assets/en/shop/CONFIRM_REFRESH.BUTTON.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/en/shop/CONFIRM_REFRESH.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
assets/en/shop/ITEM_LIST.png
Normal file
|
After Width: | Height: | Size: 274 KiB |
BIN
assets/en/shop/NORMAL_OFF.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
assets/en/shop/NORMAL_ON.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
assets/en/shop/OCR_REFRESH.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
assets/en/shop/PURCHASE.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
assets/en/shop/REFRESH.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
assets/en/shop/TC_OFF.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
assets/en/shop/TC_ON.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
assets/en/task/CLAIM.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
assets/en/task/CLAIMED.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
assets/en/task/CLAIMED_ALL.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/en/task/CLAIM_ALL.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
assets/en/task/COMPLETE.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
assets/jp/task/CLAIM.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
assets/jp/task/CLAIMED.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
assets/jp/task/CLAIMED_ALL.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/jp/task/CLAIM_ALL.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/zht/base/page/ACCOUNT_INFO_CHECK.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |