1
0
mirror of https://github.com/TheFunny/ArisuAutoSweeper synced 2026-06-24 13:54:52 +00:00

46 Commits

Author SHA1 Message Date
RedDeadDepresso dd74d25f1b Merge 47f87741fd into 2cad7ebbac 2024-01-06 03:51:32 +00:00
RedDeadDepresso 47f87741fd chore: updated gui task queue empty 2024-01-06 01:11:53 +00:00
RedDeadDepresso f7c477e24e fix: exit aas & emulator 2024-01-06 00:15:28 +00:00
RedDeadDepresso 517c0f1033 feat: exit AAS & emulator 2024-01-06 00:11:18 +00:00
RedDeadDepresso 524f15e2bf fix: bluestacks start emulator 2024-01-05 21:40:28 +00:00
RedDeadDepresso 67310938d2 test: exit emulator 2024-01-05 18:07:29 +00:00
RedDeadDepresso 836ea595ca feat: shutdown when task queue empty 2024-01-04 22:04:30 +00:00
RedDeadDepresso f484756e3f feat: add up in MCE manager 2024-01-04 17:15:16 +00:00
RedDeadDepresso 7803994a3e fix: MCE Manager tooltip 2024-01-04 13:58:14 +00:00
RedDeadDepresso dff39db5b6 perf: momotalk 2024-01-03 23:51:36 +00:00
RedDeadDepresso 5e229d7797 chore: updated requirements.txt 2024-01-03 22:58:29 +00:00
RedDeadDepresso ff1314ab88 fix: momotalk 2024-01-03 22:10:17 +00:00
RedDeadDepresso 283ef3e497 fix: lesson classroom order 2024-01-03 19:37:53 +00:00
RedDeadDepresso 4b9e809beb fix: mission AP calculations 2024-01-03 18:27:23 +00:00
RedDeadDepresso c08482f343 fix: mission event 2024-01-03 16:44:17 +00:00
RedDeadDepresso bab2196ab7 fix: shop 2024-01-02 23:21:34 +00:00
RedDeadDepresso 84b1ca880f chore: updated lesson gui section 2024-01-02 21:14:38 +00:00
RedDeadDepresso 74376e8627 feat: 7 hours mission delay 2024-01-02 21:06:04 +00:00
RedDeadDepresso 29e2865d93 fix: mission ocr 2024-01-02 20:57:08 +00:00
RedDeadDepresso 768da4d37f fix: shop 2024-01-01 19:42:32 +00:00
RedDeadDepresso 9216a36e0c fix: shop 2024-01-01 19:32:25 +00:00
RedDeadDepresso 50e9a1a9d9 fix: lesson 2024-01-01 19:27:08 +00:00
RedDeadDepresso c26db5c179 chore: added licenses 2024-01-01 15:17:12 +00:00
RedDeadDepresso eb5d41914c refactor: mission 2024-01-01 14:59:49 +00:00
RedDeadDepresso 668d77363a feat:lesson 2024-01-01 11:04:16 +00:00
RedDeadDepresso 2cad7ebbac feat: input helper
Just a small program for users to quickly find students name.
2023-12-28 01:20:07 +08:00
RedDeadDepresso bb76e0bba4 fix: task 2023-12-28 01:20:07 +08:00
RedDeadDepresso 662b1535e9 fix: sweep event 2023-12-28 01:20:07 +08:00
RedDeadDepresso 85c6c5c127 feat: added assets event 2023-12-28 01:20:07 +08:00
RedDeadDepresso 2f3a357ff7 fix: task for EN 2023-12-28 01:20:07 +08:00
RedDeadDepresso 672bc30edb refactor: MCE Manager
changed queue thread into daemon thread
2023-12-28 01:20:07 +08:00
RedDeadDepresso 9126c58e9c fix: close random popup in main after login 2023-12-28 01:20:07 +08:00
RedDeadDepresso 42a9c964c9 feat: auto-generate MCE/config.json 2023-12-28 01:20:07 +08:00
RedDeadDepresso 6cf05da422 revert changes in task 2023-12-28 01:20:07 +08:00
RedDeadDepresso 3680b22407 Update .gitignore 2023-12-28 01:20:07 +08:00
RedDeadDepresso 908b134e50 fix: updated regex for mission 2023-12-28 01:20:07 +08:00
RedDeadDepresso ca2fc4ac46 fix: tasks 2023-12-28 01:20:07 +08:00
RedDeadDepresso f1b6d4cd09 feat: mission/commissions/event 2023-12-28 01:20:07 +08:00
YoursFunny 0d66a5c424 fix(tc): change refresh time 2023-12-25 13:56:42 +08:00
YoursFunny 37bbfba299 feat: add tasks assets for jp 2023-12-25 13:17:34 +08:00
YoursFunny 34c5323df3 fix(momotalk): correct sort switch 2023-12-23 17:23:40 +08:00
YoursFunny 30d63fa193 fix(momotalk): correct sidebar switch 2023-12-23 17:03:15 +08:00
YoursFunny be3fdb0988 fix(task): check all claimed 2023-12-23 16:21:46 +08:00
YoursFunny ba9472d0b1 lang: add zh for new settings 2023-12-23 16:09:06 +08:00
RedDeadDepresso 1791b4b05c Added Tasks, Shop, MomoTalk (#11)
* feat: tasks

Added module tasks for EN

* refactor: gui

added tree view Farm and Reward.

* feat: shop

* feat: momotalk

---------

Co-authored-by: YoursFunny <admin@yoursfunny.top>
2023-12-22 13:35:33 +08:00
YoursFunny f8d1ba3d4e perf: change tc priority 2023-12-21 17:56:06 +08:00
129 changed files with 6843 additions and 115 deletions
+1
View File
@@ -20,6 +20,7 @@ config/reloadalas
test.py
test/
note.md
MCE/config.json
# Created by .ignore support plugin (hsz.mobi)
+102
View 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
View 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()
View File
+19
View 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
View 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
View 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")
@@ -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)
@@ -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
View 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
View 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
View File
@@ -0,0 +1,212 @@
"""
CTkToolTip Widget
version: 0.8
"""
import time
import sys
import customtkinter
from tkinter import Toplevel, Frame
class CTkToolTip(Toplevel):
"""
Creates a ToolTip (pop-up) widget for customtkinter.
"""
def __init__(
self,
widget: any = None,
message: str = None,
delay: float = 0.2,
follow: bool = True,
x_offset: int = +20,
y_offset: int = +10,
bg_color: str = None,
corner_radius: int = 10,
border_width: int = 0,
border_color: str = None,
alpha: float = 0.95,
padding: tuple = (10, 2),
**message_kwargs):
super().__init__()
self.widget = widget
self.withdraw()
# Disable ToolTip's title bar
self.overrideredirect(True)
if sys.platform.startswith("win"):
self.transparent_color = self.widget._apply_appearance_mode(
customtkinter.ThemeManager.theme["CTkToplevel"]["fg_color"])
self.attributes("-transparentcolor", self.transparent_color)
self.transient()
elif sys.platform.startswith("darwin"):
self.transparent_color = 'systemTransparent'
self.attributes("-transparent", True)
self.transient(self.master)
else:
self.transparent_color = '#000001'
corner_radius = 0
self.transient()
self.resizable(width=True, height=True)
# Make the background transparent
self.config(background=self.transparent_color)
# StringVar instance for msg string
self.messageVar = customtkinter.StringVar()
self.message = message
self.messageVar.set(self.message)
self.delay = delay
self.follow = follow
self.x_offset = x_offset
self.y_offset = y_offset
self.corner_radius = corner_radius
self.alpha = alpha
self.border_width = border_width
self.padding = padding
self.bg_color = customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"] if bg_color is None else bg_color
self.border_color = border_color
self.disable = False
# visibility status of the ToolTip inside|outside|visible
self.status = "outside"
self.last_moved = 0
self.attributes('-alpha', self.alpha)
if sys.platform.startswith("win"):
if self.widget._apply_appearance_mode(self.bg_color) == self.transparent_color:
self.transparent_color = "#000001"
self.config(background=self.transparent_color)
self.attributes("-transparentcolor", self.transparent_color)
# Add the message widget inside the tooltip
self.transparent_frame = Frame(self, bg=self.transparent_color)
self.transparent_frame.pack(padx=0, pady=0, fill="both", expand=True)
self.frame = customtkinter.CTkFrame(self.transparent_frame, bg_color=self.transparent_color,
corner_radius=self.corner_radius,
border_width=self.border_width, fg_color=self.bg_color,
border_color=self.border_color)
self.frame.pack(padx=0, pady=0, fill="both", expand=True)
self.message_label = customtkinter.CTkLabel(self.frame, textvariable=self.messageVar, **message_kwargs)
self.message_label.pack(fill="both", padx=self.padding[0] + self.border_width,
pady=self.padding[1] + self.border_width, expand=True)
if self.widget.winfo_name() != "tk":
if self.frame.cget("fg_color") == self.widget.cget("bg_color"):
if not bg_color:
self._top_fg_color = self.frame._apply_appearance_mode(
customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"])
if self._top_fg_color != self.transparent_color:
self.frame.configure(fg_color=self._top_fg_color)
# Add bindings to the widget without overriding the existing ones
self.widget.bind("<Enter>", self.on_enter, add="+")
self.widget.bind("<Leave>", self.on_leave, add="+")
self.widget.bind("<Motion>", self.on_enter, add="+")
self.widget.bind("<B1-Motion>", self.on_enter, add="+")
self.widget.bind("<Destroy>", lambda _: self.hide(), add="+")
def show(self) -> None:
"""
Enable the widget.
"""
self.disable = False
def on_enter(self, event) -> None:
"""
Processes motion within the widget including entering and moving.
"""
if self.disable:
return
self.last_moved = time.time()
# Set the status as inside for the very first time
if self.status == "outside":
self.status = "inside"
# If the follow flag is not set, motion within the widget will make the ToolTip dissapear
if not self.follow:
self.status = "inside"
self.withdraw()
# Calculate available space on the right side of the widget relative to the screen
root_width = self.winfo_screenwidth()
widget_x = event.x_root
space_on_right = root_width - widget_x
# Calculate the width of the tooltip's text based on the length of the message string
text_width = self.message_label.winfo_reqwidth()
# Calculate the offset based on available space and text width to avoid going off-screen on the right side
offset_x = self.x_offset
if space_on_right < text_width + 20: # Adjust the threshold as needed
offset_x = -text_width - 20 # Negative offset when space is limited on the right side
# Offsets the ToolTip using the coordinates od an event as an origin
self.geometry(f"+{event.x_root + offset_x}+{event.y_root + self.y_offset}")
# Time is in integer: milliseconds
self.after(int(self.delay * 1000), self._show)
def on_leave(self, event=None) -> None:
"""
Hides the ToolTip temporarily.
"""
if self.disable: return
self.status = "outside"
self.withdraw()
def _show(self) -> None:
"""
Displays the ToolTip.
"""
if not self.widget.winfo_exists():
self.hide()
self.destroy()
if self.status == "inside" and time.time() - self.last_moved >= self.delay:
self.status = "visible"
self.deiconify()
def hide(self) -> None:
"""
Disable the widget from appearing.
"""
if not self.winfo_exists():
return
self.withdraw()
self.disable = True
def is_disabled(self) -> None:
"""
Return the window state
"""
return self.disable
def get(self) -> None:
"""
Returns the text on the tooltip.
"""
return self.messageVar.get()
def configure(self, message: str = None, delay: float = None, bg_color: str = None, **kwargs):
"""
Set new message or configure the label parameters.
"""
if delay: self.delay = delay
if bg_color: self.frame.configure(fg_color=bg_color)
self.messageVar.set(message)
self.message_label.configure(**kwargs)
+448
View File
@@ -0,0 +1,448 @@
"""
CustomTkinter Messagebox
Author: Akash Bora
Version: 2.5
"""
import customtkinter
from PIL import Image, ImageTk
import os
import sys
import time
from typing import Literal
class CTkMessagebox(customtkinter.CTkToplevel):
ICONS = {
"check": None,
"cancel": None,
"info": None,
"question": None,
"warning": None
}
ICON_BITMAP = {}
def __init__(self,
master: any = None,
width: int = 400,
height: int = 200,
title: str = "CTkMessagebox",
message: str = "This is a CTkMessagebox!",
option_1: str = "OK",
option_2: str = None,
option_3: str = None,
options: list = [],
border_width: int = 1,
border_color: str = "default",
button_color: str = "default",
bg_color: str = "default",
fg_color: str = "default",
text_color: str = "default",
title_color: str = "default",
button_text_color: str = "default",
button_width: int = None,
button_height: int = None,
cancel_button_color: str = None,
cancel_button: str = None, # types: circle, cross or none
button_hover_color: str = "default",
icon: str = "info",
icon_size: tuple = None,
corner_radius: int = 15,
justify: str = "right",
font: tuple = None,
header: bool = False,
topmost: bool = True,
fade_in_duration: int = 0,
sound: bool = False,
option_focus: Literal[1, 2, 3] = None):
super().__init__()
self.master_window = master
self.width = 250 if width<250 else width
self.height = 150 if height<150 else height
if self.master_window is None:
self.spawn_x = int((self.winfo_screenwidth()-self.width)/2)
self.spawn_y = int((self.winfo_screenheight()-self.height)/2)
else:
self.spawn_x = int(self.master_window.winfo_width() * .5 + self.master_window.winfo_x() - .5 * self.width + 7)
self.spawn_y = int(self.master_window.winfo_height() * .5 + self.master_window.winfo_y() - .5 * self.height + 20)
self.after(10)
self.geometry(f"{self.width}x{self.height}+{self.spawn_x}+{self.spawn_y}")
self.title(title)
self.resizable(width=False, height=False)
self.fade = fade_in_duration
if self.fade:
self.fade = 20 if self.fade<20 else self.fade
self.attributes("-alpha", 0)
if not header:
self.overrideredirect(1)
if topmost:
self.attributes("-topmost", True)
else:
self.transient(self.master_window)
if sys.platform.startswith("win"):
self.transparent_color = self._apply_appearance_mode(self.cget("fg_color"))
self.attributes("-transparentcolor", self.transparent_color)
default_cancel_button = "cross"
elif sys.platform.startswith("darwin"):
self.transparent_color = 'systemTransparent'
self.attributes("-transparent", True)
default_cancel_button = "circle"
else:
self.transparent_color = '#000001'
corner_radius = 0
default_cancel_button = "cross"
self.lift()
self.config(background=self.transparent_color)
self.protocol("WM_DELETE_WINDOW", self.button_event)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self.x = self.winfo_x()
self.y = self.winfo_y()
self._title = title
self.message = message
self.font = font
self.justify = justify
self.sound = sound
self.cancel_button = cancel_button if cancel_button else default_cancel_button
self.round_corners = corner_radius if corner_radius<=30 else 30
self.button_width = button_width if button_width else self.width/4
self.button_height = button_height if button_height else 28
if self.fade: self.attributes("-alpha", 0)
if self.button_height>self.height/4: self.button_height = self.height/4 -20
self.dot_color = cancel_button_color
self.border_width = border_width if border_width<6 else 5
if type(options) is list and len(options)>0:
try:
option_1 = options[-1]
option_2 = options[-2]
option_3 = options[-3]
except IndexError: None
if bg_color=="default":
self.bg_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"])
else:
self.bg_color = bg_color
if fg_color=="default":
self.fg_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"])
else:
self.fg_color = fg_color
default_button_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkButton"]["fg_color"])
if sys.platform.startswith("win"):
if self.bg_color==self.transparent_color or self.fg_color==self.transparent_color:
self.configure(fg_color="#000001")
self.transparent_color = "#000001"
self.attributes("-transparentcolor", self.transparent_color)
if button_color=="default":
self.button_color = (default_button_color, default_button_color, default_button_color)
else:
if type(button_color) is tuple:
if len(button_color)==2:
self.button_color = (button_color[0], button_color[1], default_button_color)
elif len(button_color)==1:
self.button_color = (button_color[0], default_button_color, default_button_color)
else:
self.button_color = button_color
else:
self.button_color = (button_color, button_color, button_color)
if text_color=="default":
self.text_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkLabel"]["text_color"])
else:
self.text_color = text_color
if title_color=="default":
self.title_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkLabel"]["text_color"])
else:
self.title_color = title_color
if button_text_color=="default":
self.bt_text_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkButton"]["text_color"])
else:
self.bt_text_color = button_text_color
if button_hover_color=="default":
self.bt_hv_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkButton"]["hover_color"])
else:
self.bt_hv_color = button_hover_color
if border_color=="default":
self.border_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkFrame"]["border_color"])
else:
self.border_color = border_color
if icon_size:
self.size_height = icon_size[1] if icon_size[1]<=self.height-100 else self.height-100
self.size = (icon_size[0], self.size_height)
else:
self.size = (self.height/4, self.height/4)
self.icon = self.load_icon(icon, icon_size) if icon else None
self.frame_top = customtkinter.CTkFrame(self, corner_radius=self.round_corners, width=self.width, border_width=self.border_width,
bg_color=self.transparent_color, fg_color=self.bg_color, border_color=self.border_color)
self.frame_top.grid(sticky="nswe")
if button_width:
self.frame_top.grid_columnconfigure(0, weight=1)
else:
self.frame_top.grid_columnconfigure((1,2,3), weight=1)
if button_height:
self.frame_top.grid_rowconfigure((0,1,3), weight=1)
else:
self.frame_top.grid_rowconfigure((0,1,2), weight=1)
self.frame_top.bind("<B1-Motion>", self.move_window)
self.frame_top.bind("<ButtonPress-1>", self.oldxyset)
if self.cancel_button=="cross":
self.button_close = customtkinter.CTkButton(self.frame_top, corner_radius=10, width=0, height=0, hover=False, border_width=0,
text_color=self.dot_color if self.dot_color else self.title_color,
text="", fg_color="transparent", command=self.button_event)
self.button_close.grid(row=0, column=5, sticky="ne", padx=5+self.border_width, pady=5+self.border_width)
elif self.cancel_button=="circle":
self.button_close = customtkinter.CTkButton(self.frame_top, corner_radius=10, width=10, height=10, hover=False, border_width=0,
text="", fg_color=self.dot_color if self.dot_color else "#c42b1c", command=self.button_event)
self.button_close.grid(row=0, column=5, sticky="ne", padx=10, pady=10)
self.title_label = customtkinter.CTkLabel(self.frame_top, width=1, text=self._title, text_color=self.title_color, font=self.font)
self.title_label.grid(row=0, column=0, columnspan=6, sticky="nw", padx=(15,30), pady=5)
self.title_label.bind("<B1-Motion>", self.move_window)
self.title_label.bind("<ButtonPress-1>", self.oldxyset)
self.info = customtkinter.CTkButton(self.frame_top, width=1, height=self.height/2, corner_radius=0, text=self.message, font=self.font,
fg_color=self.fg_color, hover=False, text_color=self.text_color, image=self.icon)
self.info._text_label.configure(wraplength=self.width/2, justify="left")
self.info.grid(row=1, column=0, columnspan=6, sticky="nwes", padx=self.border_width)
if self.info._text_label.winfo_reqheight()>self.height/2:
height_offset = int((self.info._text_label.winfo_reqheight())-(self.height/2) + self.height)
self.geometry(f"{self.width}x{height_offset}")
self.option_text_1 = option_1
self.button_1 = customtkinter.CTkButton(self.frame_top, text=self.option_text_1, fg_color=self.button_color[0],
width=self.button_width, font=self.font, text_color=self.bt_text_color,
hover_color=self.bt_hv_color, height=self.button_height,
command=lambda: self.button_event(self.option_text_1))
self.option_text_2 = option_2
if option_2:
self.button_2 = customtkinter.CTkButton(self.frame_top, text=self.option_text_2, fg_color=self.button_color[1],
width=self.button_width, font=self.font, text_color=self.bt_text_color,
hover_color=self.bt_hv_color, height=self.button_height,
command=lambda: self.button_event(self.option_text_2))
self.option_text_3 = option_3
if option_3:
self.button_3 = customtkinter.CTkButton(self.frame_top, text=self.option_text_3, fg_color=self.button_color[2],
width=self.button_width, font=self.font, text_color=self.bt_text_color,
hover_color=self.bt_hv_color, height=self.button_height,
command=lambda: self.button_event(self.option_text_3))
if self.justify=="center":
if button_width:
columns = [4,3,2]
span = 1
else:
columns = [4,2,0]
span = 2
if option_3:
self.frame_top.columnconfigure((0,1,2,3,4,5), weight=1)
self.button_1.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(0,10), pady=10)
self.button_2.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=10, pady=10)
self.button_3.grid(row=2, column=columns[2], columnspan=span, sticky="news", padx=(10,0), pady=10)
elif option_2:
self.frame_top.columnconfigure((0,5), weight=1)
columns = [2,3]
self.button_1.grid(row=2, column=columns[0], sticky="news", padx=(0,5), pady=10)
self.button_2.grid(row=2, column=columns[1], sticky="news", padx=(5,0), pady=10)
else:
if button_width:
self.frame_top.columnconfigure((0,1,2,3,4,5), weight=1)
else:
self.frame_top.columnconfigure((0,2,4), weight=2)
self.button_1.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=(0,10), pady=10)
elif self.justify=="left":
self.frame_top.columnconfigure((0,1,2,3,4,5), weight=1)
if button_width:
columns = [0,1,2]
span = 1
else:
columns = [0,2,4]
span = 2
if option_3:
self.button_1.grid(row=2, column=columns[2], columnspan=span, sticky="news", padx=(0,10), pady=10)
self.button_2.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=10, pady=10)
self.button_3.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(10,0), pady=10)
elif option_2:
self.button_1.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=10, pady=10)
self.button_2.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(10,0), pady=10)
else:
self.button_1.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(10,0), pady=10)
else:
self.frame_top.columnconfigure((0,1,2,3,4,5), weight=1)
if button_width:
columns = [5,4,3]
span = 1
else:
columns = [4,2,0]
span = 2
self.button_1.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(0,10), pady=10)
if option_2:
self.button_2.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=10, pady=10)
if option_3:
self.button_3.grid(row=2, column=columns[2], columnspan=span, sticky="news", padx=(10,0), pady=10)
if header:
self.title_label.configure(text="")
self.title_label.grid_configure(pady=0)
self.button_close.configure(text_color=self.bg_color)
self.frame_top.configure(corner_radius=0)
if self.winfo_exists():
self.grab_set()
if self.sound:
self.bell()
if self.fade:
self.fade_in()
if option_focus:
self.option_focus = option_focus
self.focus_button(self.option_focus)
else:
if not self.option_text_2 and not self.option_text_3:
self.button_1.focus()
self.button_1.bind("<Return>", lambda event: self.button_event(self.option_text_1))
self.bind("<Escape>", lambda e: self.button_event())
def focus_button(self, option_focus):
try:
self.selected_button = getattr(self, "button_"+str(option_focus))
self.selected_button.focus()
self.selected_button.configure(border_color=self.bt_hv_color, border_width=3)
self.selected_option = getattr(self, "option_text_"+str(option_focus))
self.selected_button.bind("<Return>", lambda event: self.button_event(self.selected_option))
except AttributeError:
return
self.bind("<Left>", lambda e: self.change_left())
self.bind("<Right>", lambda e: self.change_right())
def change_left(self):
if self.option_focus==3:
return
self.selected_button.unbind("<Return>")
self.selected_button.configure(border_width=0)
if self.option_focus==1:
if self.option_text_2:
self.option_focus = 2
elif self.option_focus==2:
if self.option_text_3:
self.option_focus = 3
self.focus_button(self.option_focus)
def change_right(self):
if self.option_focus==1:
return
self.selected_button.unbind("<Return>")
self.selected_button.configure(border_width=0)
if self.option_focus==2:
self.option_focus = 1
elif self.option_focus==3:
self.option_focus = 2
self.focus_button(self.option_focus)
def load_icon(self, icon, icon_size):
if icon not in self.ICONS or self.ICONS[icon] is None:
if icon in ["check", "cancel", "info", "question", "warning"]:
image_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'icons', icon + '.png')
else:
image_path = icon
if icon_size:
size_height = icon_size[1] if icon_size[1] <= self.height - 100 else self.height - 100
size = (icon_size[0], size_height)
else:
size = (self.height / 4, self.height / 4)
self.ICONS[icon] = customtkinter.CTkImage(Image.open(image_path), size=size)
self.ICON_BITMAP[icon] = ImageTk.PhotoImage(file=image_path)
self.after(200, lambda: self.iconphoto(False, self.ICON_BITMAP[icon]))
return self.ICONS[icon]
def fade_in(self):
for i in range(0,110,10):
if not self.winfo_exists():
break
self.attributes("-alpha", i/100)
self.update()
time.sleep(1/self.fade)
def fade_out(self):
for i in range(100,0,-10):
if not self.winfo_exists():
break
self.attributes("-alpha", i/100)
self.update()
time.sleep(1/self.fade)
def get(self):
if self.winfo_exists():
self.master.wait_window(self)
return self.event
def oldxyset(self, event):
self.oldx = event.x
self.oldy = event.y
def move_window(self, event):
self.y = event.y_root - self.oldy
self.x = event.x_root - self.oldx
self.geometry(f'+{self.x}+{self.y}')
def button_event(self, event=None):
try:
self.button_1.configure(state="disabled")
self.button_2.configure(state="disabled")
self.button_3.configure(state="disabled")
except AttributeError:
pass
if self.fade:
self.fade_out()
self.grab_release()
self.destroy()
self.event = event
if __name__ == "__main__":
app = CTkMessagebox()
app.mainloop()
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+158
View 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
View 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
View 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)
+20 -1
View File
@@ -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()
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+123 -17
View File
@@ -33,6 +33,22 @@
"ServerUpdate": "04:00"
}
},
"DataUpdate": {
"Scheduler": {
"Enable": true,
"NextRun": "2020-01-01 00:00:00",
"Command": "DataUpdate",
"ServerUpdate": "04:00"
},
"ItemStorage": {
"AP": {},
"Credit": {},
"Pyroxene": {},
"BountyTicket": {},
"ScrimmageTicket": {},
"TacticalChallengeTicket": {}
}
},
"Cafe": {
"Scheduler": {
"Enable": true,
@@ -54,20 +70,86 @@
"Substitute": false
}
},
"Circle": {
"Schedule": {
"Scheduler": {
"Enable": true,
"Enable": false,
"NextRun": "2020-01-01 00:00:00",
"Command": "Circle",
"Command": "Schedule",
"ServerUpdate": "04:00"
},
"Schedule": {
"OnError": "skip"
},
"Choice1": {
"Location": "None",
"Classrooms": null
},
"Choice2": {
"Location": "None",
"Classrooms": null
},
"Choice3": {
"Location": "None",
"Classrooms": null
},
"Choice4": {
"Location": "None",
"Classrooms": null
},
"Choice5": {
"Location": "None",
"Classrooms": null
}
},
"Mail": {
"Shop": {
"Scheduler": {
"Enable": true,
"Enable": false,
"NextRun": "2020-01-01 00:00:00",
"Command": "Mail",
"Command": "Shop",
"ServerUpdate": "04:00"
},
"NormalShop": {
"Enable": false,
"Purchases": 1,
"1": false,
"2": false,
"3": false,
"4": false,
"5": false,
"6": false,
"7": false,
"8": false,
"9": false,
"10": false,
"11": false,
"12": false,
"13": false,
"14": false,
"15": false,
"16": false,
"17": false,
"18": false,
"19": false,
"20": false
},
"TacticalChallengeShop": {
"Enable": false,
"Purchases": 1,
"1": false,
"2": false,
"3": false,
"4": false,
"5": false,
"6": false,
"7": false,
"8": false,
"9": false,
"10": false,
"11": false,
"12": false,
"13": false,
"14": false,
"15": false
}
},
"Bounty": {
@@ -121,26 +203,50 @@
"Enable": true,
"NextRun": "2020-01-01 00:00:00",
"Command": "TacticalChallenge",
"ServerUpdate": "15:00"
"ServerUpdate": "14:00"
},
"TacticalChallenge": {
"PlayerSelect": 0
}
},
"DataUpdate": {
"Mission": {
"Scheduler": {
"Enable": false,
"NextRun": "2020-01-01 00:00:00",
"Command": "Mission",
"ServerUpdate": "04:00"
}
},
"Circle": {
"Scheduler": {
"Enable": true,
"NextRun": "2020-01-01 00:00:00",
"Command": "DataUpdate",
"Command": "Circle",
"ServerUpdate": "04:00"
}
},
"Task": {
"Scheduler": {
"Enable": false,
"NextRun": "2020-01-01 00:00:00",
"Command": "Task",
"ServerUpdate": "04:00"
}
},
"Mail": {
"Scheduler": {
"Enable": true,
"NextRun": "2020-01-01 00:00:00",
"Command": "Mail",
"ServerUpdate": "04:00"
}
},
"Momotalk": {
"Scheduler": {
"Enable": false,
"NextRun": "2020-01-01 00:00:00",
"Command": "Momotalk",
"ServerUpdate": "04:00"
},
"ItemStorage": {
"AP": {},
"Credit": {},
"Pyroxene": {},
"BountyTicket": {},
"ScrimmageTicket": {},
"TacticalChallengeTicket": {}
}
}
}
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-present Electron React Boilerplate
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+121
View File
@@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Akash Bora
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+121
View File
@@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.
+72
View File
@@ -14,6 +14,9 @@ from module.exception import *
from module.logger import logger
from module.notify import handle_notify
from MCE.custom_widgets.ctkmessagebox import CTkMessagebox
import subprocess
import platform
class AzurLaneAutoScript:
stop_event: threading.Event = None
@@ -26,6 +29,7 @@ class AzurLaneAutoScript:
# Failure count of tasks
# Key: str, task name, value: int, failure count
self.failure_record = {}
self.operating_system = platform.system()
@cached_property
def config(self):
@@ -228,6 +232,27 @@ class AzurLaneAutoScript:
if not self.wait_until(task.next_run):
del_cached_property(self, 'config')
continue
elif method == 'exit_aas':
self.config.Optimization_WhenTaskQueueEmpty = 'goto_main'
self.exit_aas()
exit(1)
elif method == 'exit_emulator':
self.config.Optimization_WhenTaskQueueEmpty = 'goto_main'
self.exit_emulator()
exit(1) # stops AAS from restarting emulator
elif method == 'exit_aas_emulator':
self.config.Optimization_WhenTaskQueueEmpty = 'goto_main'
self.exit_emulator()
self.exit_aas()
exit(1)
elif method == 'shutdown':
self.config.Optimization_WhenTaskQueueEmpty = 'goto_main'
self.shutdown()
release_resources()
self.device.release_during_wait()
if not self.wait_until(task.next_run):
del_cached_property(self, 'config')
continue
else:
logger.warning(f'Invalid Optimization_WhenTaskQueueEmpty: {method}, fallback to stay_there')
release_resources()
@@ -308,6 +333,53 @@ class AzurLaneAutoScript:
self.checker.check_now()
continue
def exit_emulator(self):
if self.operating_system != 'Windows':
logger.error("Exiting emulator is only supported on Windows")
return
try:
from module.device.platform.platform_windows import PlatformWindows
PlatformWindows(self.config).emulator_stop()
except:
logger.error("Failed to stop emulator. It may be due to a lack of administrator privileges.")
def exit_aas(self):
if self.operating_system != 'Windows':
logger.error("Exiting AAS is only supported on Windows")
return
try:
from module.device.platform.platform_windows import PlatformWindows
PlatformWindows(self.config).kill_process_by_regex("aas\.exe")
except:
logger.error("Failed to stop AAS. It may be due to a lack of administrator privileges.")
def shutdown(self):
if self.operating_system not in ["Windows", "Linux", "Darwin"]:
logger.error("Shutdown set during wait but operating system not supported")
else:
logger.info('Shutdown during wait')
try:
self.start_shutdown()
msg = CTkMessagebox(title="AAS: Cancel Shutdown?", message="All tasks have been completed: shutting down. Do you want to cancel?",
icon="MCE\icons\question.png", option_1="Cancel")
response = msg.get()
if response=="Cancel":
self.cancel_shutdown()
except:
logger.error("Failed to shutdown. It may be due to a lack of administrator privileges.")
def start_shutdown(self):
logger.info("Running Shutting down")
if self.operating_system == "Windows":
subprocess.run(["shutdown", "-s", "-t", "60"])
elif self.operating_system in ["Linux", "Darwin"]:
subprocess.run(["shutdown", "-h", "+1"])
def cancel_shutdown(self):
if self.operating_system == "Windows":
subprocess.run(["shutdown", "-a"])
elif self.operating_system in ["Linux", "Darwin"]:
subprocess.run(["shutdown", "-c"])
if __name__ == '__main__':
alas = AzurLaneAutoScript()
+496 -60
View File
@@ -128,7 +128,11 @@
"option": [
"stay_there",
"goto_main",
"close_game"
"close_game",
"exit_aas",
"exit_emulator",
"exit_aas_emulator",
"shutdown"
]
}
}
@@ -162,6 +166,85 @@
}
}
},
"DataUpdate": {
"Scheduler": {
"Enable": {
"type": "state",
"value": true,
"option": [
true
],
"option_bold": [
true
]
},
"NextRun": {
"type": "datetime",
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "input",
"value": "DataUpdate",
"display": "hide"
},
"ServerUpdate": {
"type": "input",
"value": "04:00",
"display": "hide"
}
},
"ItemStorage": {
"AP": {
"type": "stored",
"value": {},
"display": "hide",
"stored": "StoredAP",
"order": 1,
"color": "#62ea6e"
},
"Credit": {
"type": "stored",
"value": {},
"display": "hide",
"stored": "StoredInt",
"order": 2,
"color": "#fdec00"
},
"Pyroxene": {
"type": "stored",
"value": {},
"display": "hide",
"stored": "StoredInt",
"order": 3,
"color": "#21befc"
},
"BountyTicket": {
"type": "stored",
"value": {},
"display": "hide",
"stored": "StoredBountyTicket",
"order": 4,
"color": "#94cb44"
},
"ScrimmageTicket": {
"type": "stored",
"value": {},
"display": "hide",
"stored": "StoredScrimmageTicket",
"order": 5,
"color": "#f86c6a"
},
"TacticalChallengeTicket": {
"type": "stored",
"value": {},
"display": "hide",
"stored": "StoredTacticalChallengeTicket",
"order": 6,
"color": "#7ac8e5"
}
}
},
"Cafe": {
"Scheduler": {
"Enable": {
@@ -239,11 +322,11 @@
}
}
},
"Circle": {
"Schedule": {
"Scheduler": {
"Enable": {
"type": "checkbox",
"value": true,
"value": false,
"option": [
true,
false
@@ -256,7 +339,7 @@
},
"Command": {
"type": "input",
"value": "Circle",
"value": "Schedule",
"display": "hide"
},
"ServerUpdate": {
@@ -264,13 +347,138 @@
"value": "04:00",
"display": "hide"
}
},
"Schedule": {
"OnError": {
"type": "select",
"value": "skip",
"option": [
"stop",
"skip"
]
}
},
"Choice1": {
"Location": {
"type": "select",
"value": "None",
"option": [
"None",
0,
1,
2,
3,
4,
5,
6,
7,
8,
9
]
},
"Classrooms": {
"type": "textarea",
"value": null
}
},
"Choice2": {
"Location": {
"type": "select",
"value": "None",
"option": [
"None",
0,
1,
2,
3,
4,
5,
6,
7,
8,
9
]
},
"Classrooms": {
"type": "textarea",
"value": null
}
},
"Choice3": {
"Location": {
"type": "select",
"value": "None",
"option": [
"None",
0,
1,
2,
3,
4,
5,
6,
7,
8,
9
]
},
"Classrooms": {
"type": "textarea",
"value": null
}
},
"Choice4": {
"Location": {
"type": "select",
"value": "None",
"option": [
"None",
0,
1,
2,
3,
4,
5,
6,
7,
8,
9
]
},
"Classrooms": {
"type": "textarea",
"value": null
}
},
"Choice5": {
"Location": {
"type": "select",
"value": "None",
"option": [
"None",
0,
1,
2,
3,
4,
5,
6,
7,
8,
9
]
},
"Classrooms": {
"type": "textarea",
"value": null
}
}
},
"Mail": {
"Shop": {
"Scheduler": {
"Enable": {
"type": "checkbox",
"value": true,
"value": false,
"option": [
true,
false
@@ -283,7 +491,7 @@
},
"Command": {
"type": "input",
"value": "Mail",
"value": "Shop",
"display": "hide"
},
"ServerUpdate": {
@@ -291,6 +499,178 @@
"value": "04:00",
"display": "hide"
}
},
"NormalShop": {
"Enable": {
"type": "checkbox",
"value": false
},
"Purchases": {
"type": "select",
"value": 1,
"option": [
1,
2,
3,
4
]
},
"1": {
"type": "checkbox",
"value": false
},
"2": {
"type": "checkbox",
"value": false
},
"3": {
"type": "checkbox",
"value": false
},
"4": {
"type": "checkbox",
"value": false
},
"5": {
"type": "checkbox",
"value": false
},
"6": {
"type": "checkbox",
"value": false
},
"7": {
"type": "checkbox",
"value": false
},
"8": {
"type": "checkbox",
"value": false
},
"9": {
"type": "checkbox",
"value": false
},
"10": {
"type": "checkbox",
"value": false
},
"11": {
"type": "checkbox",
"value": false
},
"12": {
"type": "checkbox",
"value": false
},
"13": {
"type": "checkbox",
"value": false
},
"14": {
"type": "checkbox",
"value": false
},
"15": {
"type": "checkbox",
"value": false
},
"16": {
"type": "checkbox",
"value": false
},
"17": {
"type": "checkbox",
"value": false
},
"18": {
"type": "checkbox",
"value": false
},
"19": {
"type": "checkbox",
"value": false
},
"20": {
"type": "checkbox",
"value": false
}
},
"TacticalChallengeShop": {
"Enable": {
"type": "checkbox",
"value": false
},
"Purchases": {
"type": "select",
"value": 1,
"option": [
1,
2,
3,
4
]
},
"1": {
"type": "checkbox",
"value": false
},
"2": {
"type": "checkbox",
"value": false
},
"3": {
"type": "checkbox",
"value": false
},
"4": {
"type": "checkbox",
"value": false
},
"5": {
"type": "checkbox",
"value": false
},
"6": {
"type": "checkbox",
"value": false
},
"7": {
"type": "checkbox",
"value": false
},
"8": {
"type": "checkbox",
"value": false
},
"9": {
"type": "checkbox",
"value": false
},
"10": {
"type": "checkbox",
"value": false
},
"11": {
"type": "checkbox",
"value": false
},
"12": {
"type": "checkbox",
"value": false
},
"13": {
"type": "checkbox",
"value": false
},
"14": {
"type": "checkbox",
"value": false
},
"15": {
"type": "checkbox",
"value": false
}
}
},
"Bounty": {
@@ -506,7 +886,7 @@
},
"ServerUpdate": {
"type": "input",
"value": "15:00",
"value": "14:00",
"display": "hide"
}
},
@@ -523,16 +903,14 @@
}
}
},
"DataUpdate": {
"Mission": {
"Scheduler": {
"Enable": {
"type": "state",
"value": true,
"type": "checkbox",
"value": false,
"option": [
true
],
"option_bold": [
true
true,
false
]
},
"NextRun": {
@@ -542,7 +920,7 @@
},
"Command": {
"type": "input",
"value": "DataUpdate",
"value": "Mission",
"display": "hide"
},
"ServerUpdate": {
@@ -550,55 +928,113 @@
"value": "04:00",
"display": "hide"
}
},
"ItemStorage": {
"AP": {
"type": "stored",
"value": {},
"display": "hide",
"stored": "StoredAP",
"order": 1,
"color": "#62ea6e"
}
},
"Circle": {
"Scheduler": {
"Enable": {
"type": "checkbox",
"value": true,
"option": [
true,
false
]
},
"Credit": {
"type": "stored",
"value": {},
"display": "hide",
"stored": "StoredInt",
"order": 2,
"color": "#fdec00"
"NextRun": {
"type": "datetime",
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Pyroxene": {
"type": "stored",
"value": {},
"display": "hide",
"stored": "StoredInt",
"order": 3,
"color": "#21befc"
"Command": {
"type": "input",
"value": "Circle",
"display": "hide"
},
"BountyTicket": {
"type": "stored",
"value": {},
"display": "hide",
"stored": "StoredBountyTicket",
"order": 4,
"color": "#94cb44"
"ServerUpdate": {
"type": "input",
"value": "04:00",
"display": "hide"
}
}
},
"Task": {
"Scheduler": {
"Enable": {
"type": "checkbox",
"value": false,
"option": [
true,
false
]
},
"ScrimmageTicket": {
"type": "stored",
"value": {},
"display": "hide",
"stored": "StoredScrimmageTicket",
"order": 5,
"color": "#f86c6a"
"NextRun": {
"type": "datetime",
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"TacticalChallengeTicket": {
"type": "stored",
"value": {},
"display": "hide",
"stored": "StoredTacticalChallengeTicket",
"order": 6,
"color": "#7ac8e5"
"Command": {
"type": "input",
"value": "Task",
"display": "hide"
},
"ServerUpdate": {
"type": "input",
"value": "04:00",
"display": "hide"
}
}
},
"Mail": {
"Scheduler": {
"Enable": {
"type": "checkbox",
"value": true,
"option": [
true,
false
]
},
"NextRun": {
"type": "datetime",
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "input",
"value": "Mail",
"display": "hide"
},
"ServerUpdate": {
"type": "input",
"value": "04:00",
"display": "hide"
}
}
},
"Momotalk": {
"Scheduler": {
"Enable": {
"type": "checkbox",
"value": false,
"option": [
true,
false
]
},
"NextRun": {
"type": "datetime",
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "input",
"value": "Momotalk",
"display": "hide"
},
"ServerUpdate": {
"type": "input",
"value": "04:00",
"display": "hide"
}
}
}
+89 -1
View File
@@ -71,7 +71,7 @@ Optimization:
CombatScreenshotInterval: 1.0
WhenTaskQueueEmpty:
value: goto_main
option: [ stay_there, goto_main, close_game ]
option: [ stay_there, goto_main, close_game, exit_aas, exit_emulator, exit_aas_emulator, shutdown ]
# ==================== Daily ====================
@@ -93,6 +93,47 @@ Invitation:
type: textarea
Substitute: false
Schedule:
OnError:
value: skip
option: [ stop, skip ]
Choice1:
Location:
value: None
option: [ None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
Classrooms:
value: null
type: textarea
Choice2:
Location:
value: None
option: [ None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
Classrooms:
value: null
type: textarea
Choice3:
Location:
value: None
option: [ None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
Classrooms:
value: null
type: textarea
Choice4:
Location:
value: None
option: [ None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
Classrooms:
value: null
type: textarea
Choice5:
Location:
value: None
option: [ None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
Classrooms:
value: null
type: textarea
Bounty:
OnError:
value: skip
@@ -140,6 +181,53 @@ TacticalChallenge:
value: 0
option: [ 0, 1, 2, 3 ]
NormalShop:
Enable: false
Purchases:
value: 1
option: [ 1, 2, 3, 4 ]
"1": false
"2": false
"3": false
"4": false
"5": false
"6": false
"7": false
"8": false
"9": false
"10": false
"11": false
"12": false
"13": false
"14": false
"15": false
"16": false
"17": false
"18": false
"19": false
"20": false
TacticalChallengeShop:
Enable: false
Purchases:
value: 1
option: [ 1, 2, 3, 4 ]
"1": false
"2": false
"3": false
"4": false
"5": false
"6": false
"7": false
"8": false
"9": false
"10": false
"11": false
"12": false
"13": false
"14": false
"15": false
ItemStorage:
AP:
stored: StoredAP

Some files were not shown because too many files have changed in this diff Show More